fix: address review feedback on skills and session compaction
This commit is contained in:
@@ -27,7 +27,9 @@ const DEFAULT_SESSION_COMPACTION_POLICY: SessionCompactionPolicy = {
|
|||||||
maxSessionAgeHours: 72,
|
maxSessionAgeHours: 72,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DISABLED_SESSION_COMPACTION_POLICY: SessionCompactionPolicy = {
|
// Adapters with native context management still participate in session resume,
|
||||||
|
// but Paperclip should not rotate them using threshold-based compaction.
|
||||||
|
const ADAPTER_MANAGED_SESSION_POLICY: SessionCompactionPolicy = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maxSessionRuns: 0,
|
maxSessionRuns: 0,
|
||||||
maxRawInputTokens: 0,
|
maxRawInputTokens: 0,
|
||||||
@@ -47,12 +49,12 @@ export const ADAPTER_SESSION_MANAGEMENT: Record<string, AdapterSessionManagement
|
|||||||
claude_local: {
|
claude_local: {
|
||||||
supportsSessionResume: true,
|
supportsSessionResume: true,
|
||||||
nativeContextManagement: "confirmed",
|
nativeContextManagement: "confirmed",
|
||||||
defaultSessionCompaction: DISABLED_SESSION_COMPACTION_POLICY,
|
defaultSessionCompaction: ADAPTER_MANAGED_SESSION_POLICY,
|
||||||
},
|
},
|
||||||
codex_local: {
|
codex_local: {
|
||||||
supportsSessionResume: true,
|
supportsSessionResume: true,
|
||||||
nativeContextManagement: "confirmed",
|
nativeContextManagement: "confirmed",
|
||||||
defaultSessionCompaction: DISABLED_SESSION_COMPACTION_POLICY,
|
defaultSessionCompaction: ADAPTER_MANAGED_SESSION_POLICY,
|
||||||
},
|
},
|
||||||
cursor: {
|
cursor: {
|
||||||
supportsSessionResume: true,
|
supportsSessionResume: true,
|
||||||
@@ -171,4 +173,3 @@ export function hasSessionCompactionThresholds(policy: Pick<
|
|||||||
>) {
|
>) {
|
||||||
return policy.maxSessionRuns > 0 || policy.maxRawInputTokens > 0 || policy.maxSessionAgeHours > 0;
|
return policy.maxSessionRuns > 0 || policy.maxRawInputTokens > 0 || policy.maxSessionAgeHours > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ export interface MarkdownEditorRef {
|
|||||||
focus: () => void;
|
focus: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeRegExp(value: string): string {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Mention detection helpers ---- */
|
/* ---- Mention detection helpers ---- */
|
||||||
|
|
||||||
interface MentionState {
|
interface MentionState {
|
||||||
@@ -255,9 +259,10 @@ export const MarkdownEditor = forwardRef<MarkdownEditorRef, MarkdownEditorProps>
|
|||||||
// so the cursor isn't stuck right next to the image.
|
// so the cursor isn't stuck right next to the image.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const current = latestValueRef.current;
|
const current = latestValueRef.current;
|
||||||
|
const escapedSrc = escapeRegExp(src);
|
||||||
const updated = current.replace(
|
const updated = current.replace(
|
||||||
/!\[([^\]]*)\]\(([^)]+)\)(?!\n\n)/g,
|
new RegExp(`(!\\[[^\\]]*\\]\\(${escapedSrc}\\))(?!\\n\\n)`, "g"),
|
||||||
"\n\n",
|
"$1\n\n",
|
||||||
);
|
);
|
||||||
if (updated !== current) {
|
if (updated !== current) {
|
||||||
latestValueRef.current = updated;
|
latestValueRef.current = updated;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
import { useCallback, useEffect, useMemo, useState, useRef } from "react";
|
||||||
import { useParams, useNavigate, Link, Navigate, useBeforeUnload } from "@/lib/router";
|
import { useParams, useNavigate, Link, Navigate, useBeforeUnload } from "@/lib/router";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { agentsApi, type AgentKey, type ClaudeLoginResult } from "../api/agents";
|
import { agentsApi, type AgentKey, type ClaudeLoginResult, type AvailableSkill } from "../api/agents";
|
||||||
import { budgetsApi } from "../api/budgets";
|
import { budgetsApi } from "../api/budgets";
|
||||||
import { heartbeatsApi } from "../api/heartbeats";
|
import { heartbeatsApi } from "../api/heartbeats";
|
||||||
import { ApiError } from "../api/client";
|
import { ApiError } from "../api/client";
|
||||||
@@ -30,6 +30,7 @@ import { ScrollToBottom } from "../components/ScrollToBottom";
|
|||||||
import { formatCents, formatDate, relativeTime, formatTokens, visibleRunCostUsd } from "../lib/utils";
|
import { formatCents, formatDate, relativeTime, formatTokens, visibleRunCostUsd } from "../lib/utils";
|
||||||
import { cn } from "../lib/utils";
|
import { cn } from "../lib/utils";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Tabs } from "@/components/ui/tabs";
|
import { Tabs } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
@@ -743,7 +744,6 @@ export function AgentDetail() {
|
|||||||
{activeView === "skills" && (
|
{activeView === "skills" && (
|
||||||
<SkillsTab
|
<SkillsTab
|
||||||
agent={agent}
|
agent={agent}
|
||||||
companyId={resolvedCompanyId ?? undefined}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1213,11 +1213,16 @@ function ConfigurationTab({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkillsTab({ agent }: { agent: Agent; companyId?: string }) {
|
function SkillsTab({ agent }: { agent: Agent }) {
|
||||||
const instructionsPath =
|
const instructionsPath =
|
||||||
typeof agent.adapterConfig?.instructionsFilePath === "string" && agent.adapterConfig.instructionsFilePath.trim().length > 0
|
typeof agent.adapterConfig?.instructionsFilePath === "string" && agent.adapterConfig.instructionsFilePath.trim().length > 0
|
||||||
? agent.adapterConfig.instructionsFilePath
|
? agent.adapterConfig.instructionsFilePath
|
||||||
: null;
|
: null;
|
||||||
|
const { data, isLoading, error } = useQuery({
|
||||||
|
queryKey: queryKeys.skills.available,
|
||||||
|
queryFn: () => agentsApi.availableSkills(),
|
||||||
|
});
|
||||||
|
const skills = data?.skills ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -1225,7 +1230,7 @@ function SkillsTab({ agent }: { agent: Agent; companyId?: string }) {
|
|||||||
<h3 className="text-sm font-medium">Skills</h3>
|
<h3 className="text-sm font-medium">Skills</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Skills are reusable instruction bundles the agent can invoke from its local tool environment.
|
Skills are reusable instruction bundles the agent can invoke from its local tool environment.
|
||||||
This view keeps the tab compile-safe and shows the current instructions file path while the broader skills listing work continues elsewhere in the tree.
|
This view shows the current instructions file and the skills currently visible to the local agent runtime.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Agent: <span className="font-mono">{agent.name}</span>
|
Agent: <span className="font-mono">{agent.name}</span>
|
||||||
@@ -1238,11 +1243,48 @@ function SkillsTab({ agent }: { agent: Agent; companyId?: string }) {
|
|||||||
{instructionsPath ?? "No instructions file configured for this agent."}
|
{instructionsPath ?? "No instructions file configured for this agent."}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||||
|
Available skills
|
||||||
|
</div>
|
||||||
|
{isLoading ? (
|
||||||
|
<p className="text-sm text-muted-foreground">Loading available skills…</p>
|
||||||
|
) : error ? (
|
||||||
|
<p className="text-sm text-destructive">
|
||||||
|
{error instanceof Error ? error.message : "Failed to load available skills."}
|
||||||
|
</p>
|
||||||
|
) : skills.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">No local skills were found.</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{skills.map((skill) => (
|
||||||
|
<SkillRow key={skill.name} skill={skill} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SkillRow({ skill }: { skill: AvailableSkill }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-md border border-border bg-muted/20 px-3 py-2 space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-mono text-sm">{skill.name}</span>
|
||||||
|
<Badge variant={skill.isPaperclipManaged ? "secondary" : "outline"}>
|
||||||
|
{skill.isPaperclipManaged ? "Paperclip" : "Local"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{skill.description || "No description available."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- Runs Tab ---- */
|
/* ---- Runs Tab ---- */
|
||||||
|
|
||||||
function RunListItem({ run, isSelected, agentId }: { run: HeartbeatRun; isSelected: boolean; agentId: string }) {
|
function RunListItem({ run, isSelected, agentId }: { run: HeartbeatRun; isSelected: boolean; agentId: string }) {
|
||||||
|
|||||||
@@ -461,6 +461,9 @@ export function ProjectDetail() {
|
|||||||
if (cachedTab === "configuration") {
|
if (cachedTab === "configuration") {
|
||||||
return <Navigate to={`/projects/${canonicalProjectRef}/configuration`} replace />;
|
return <Navigate to={`/projects/${canonicalProjectRef}/configuration`} replace />;
|
||||||
}
|
}
|
||||||
|
if (cachedTab === "budget") {
|
||||||
|
return <Navigate to={`/projects/${canonicalProjectRef}/budget`} replace />;
|
||||||
|
}
|
||||||
if (isProjectPluginTab(cachedTab)) {
|
if (isProjectPluginTab(cachedTab)) {
|
||||||
return <Navigate to={`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(cachedTab)}`} replace />;
|
return <Navigate to={`/projects/${canonicalProjectRef}?tab=${encodeURIComponent(cachedTab)}`} replace />;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user