@nghyane/arcane 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +6 -6
- package/src/cli/update-cli.ts +2 -1
- package/src/cli.ts +2 -1
- package/src/config/settings-schema.ts +0 -10
- package/src/debug/system-info.ts +2 -1
- package/src/discovery/helpers.ts +1 -6
- package/src/index.ts +3 -1
- package/src/main.ts +2 -1
- package/src/memories/index.ts +3 -3
- package/src/modes/components/settings-defs.ts +0 -8
- package/src/patch/applicator.ts +12 -4
- package/src/prompts/agents/task.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +2 -14
- package/src/prompts/system/system-prompt.md +12 -14
- package/src/prompts/tools/task.md +49 -178
- package/src/prompts/tools/todo-write.md +4 -4
- package/src/sdk.ts +15 -16
- package/src/session/session-manager.ts +1 -3
- package/src/task/executor.ts +2 -2
- package/src/task/index.ts +5 -5
- package/src/task/types.ts +7 -20
- package/src/tools/index.ts +16 -33
- package/src/tools/subagent-tool.ts +5 -5
- package/src/tools/todo-write.ts +0 -37
- package/src/version.ts +3 -0
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/tools/jtd-to-json-schema.ts +0 -247
- package/src/tools/submit-result.ts +0 -152
package/src/sdk.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
loadSshTool,
|
|
73
73
|
PythonTool,
|
|
74
74
|
ReadTool,
|
|
75
|
+
type SubagentContext,
|
|
75
76
|
setPreferredImageProvider,
|
|
76
77
|
setPreferredSearchProvider,
|
|
77
78
|
type Tool,
|
|
@@ -157,12 +158,8 @@ export interface CreateAgentSessionOptions {
|
|
|
157
158
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
158
159
|
toolNames?: string[];
|
|
159
160
|
|
|
160
|
-
/**
|
|
161
|
-
|
|
162
|
-
/** Whether to include the submit_result tool by default */
|
|
163
|
-
requireSubmitResultTool?: boolean;
|
|
164
|
-
/** Task recursion depth (for subagent sessions). Default: 0 */
|
|
165
|
-
taskDepth?: number;
|
|
161
|
+
/** Whether this is a subagent session. Default: false */
|
|
162
|
+
isSubagent?: boolean;
|
|
166
163
|
/** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
|
|
167
164
|
parentTaskPrefix?: string;
|
|
168
165
|
|
|
@@ -223,6 +220,7 @@ export {
|
|
|
223
220
|
PythonTool,
|
|
224
221
|
ReadTool,
|
|
225
222
|
WriteTool,
|
|
223
|
+
type SubagentContext,
|
|
226
224
|
type ToolSession,
|
|
227
225
|
};
|
|
228
226
|
|
|
@@ -629,8 +627,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
629
627
|
|
|
630
628
|
// For subagent sessions using GitHub Copilot, add X-Initiator header
|
|
631
629
|
// to ensure proper billing (agent-initiated vs user-initiated)
|
|
632
|
-
const
|
|
633
|
-
const forceCopilotAgentInitiator =
|
|
630
|
+
const isSubagent = options.isSubagent ?? false;
|
|
631
|
+
const forceCopilotAgentInitiator = isSubagent;
|
|
634
632
|
if (forceCopilotAgentInitiator && model?.provider === "github-copilot") {
|
|
635
633
|
model = {
|
|
636
634
|
...model,
|
|
@@ -722,9 +720,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
722
720
|
contextFiles,
|
|
723
721
|
skills,
|
|
724
722
|
eventBus,
|
|
725
|
-
|
|
726
|
-
requireSubmitResultTool: options.requireSubmitResultTool,
|
|
727
|
-
taskDepth: options.taskDepth ?? 0,
|
|
723
|
+
isSubagent: options.isSubagent ?? false,
|
|
728
724
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
729
725
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
730
726
|
getSessionSpawns: () => options.spawns ?? "*",
|
|
@@ -736,10 +732,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
736
732
|
if (model) return formatModelString(model);
|
|
737
733
|
return undefined;
|
|
738
734
|
},
|
|
739
|
-
|
|
735
|
+
subagentContext: {
|
|
736
|
+
getCompactContext: () => session.formatCompactContext(),
|
|
737
|
+
authStorage,
|
|
738
|
+
modelRegistry,
|
|
739
|
+
},
|
|
740
740
|
settings,
|
|
741
|
-
authStorage,
|
|
742
|
-
modelRegistry,
|
|
743
741
|
};
|
|
744
742
|
|
|
745
743
|
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://)
|
|
@@ -798,7 +796,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
798
796
|
});
|
|
799
797
|
time("discoverAndLoadMCPTools");
|
|
800
798
|
mcpManager = mcpResult.manager;
|
|
801
|
-
toolSession.
|
|
799
|
+
if (!toolSession.subagentContext) toolSession.subagentContext = {};
|
|
800
|
+
toolSession.subagentContext.mcpManager = mcpManager;
|
|
802
801
|
|
|
803
802
|
// If we extracted Exa API keys from MCP configs and EXA_AARCANE_KEY isn't set, use the first one
|
|
804
803
|
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_AARCANE_KEY) {
|
|
@@ -1273,7 +1272,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1273
1272
|
settings,
|
|
1274
1273
|
modelRegistry,
|
|
1275
1274
|
agentDir,
|
|
1276
|
-
|
|
1275
|
+
isSubagent,
|
|
1277
1276
|
});
|
|
1278
1277
|
|
|
1279
1278
|
return {
|
|
@@ -124,8 +124,6 @@ export interface SessionInitEntry extends SessionEntryBase {
|
|
|
124
124
|
task: string;
|
|
125
125
|
/** Tools available to the agent */
|
|
126
126
|
tools: string[];
|
|
127
|
-
/** Output schema if structured output was requested */
|
|
128
|
-
outputSchema?: unknown;
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
/** Mode change entry - tracks agent mode transitions. */
|
|
@@ -1685,7 +1683,7 @@ export class SessionManager {
|
|
|
1685
1683
|
}
|
|
1686
1684
|
|
|
1687
1685
|
/** Append session init metadata (for subagent debugging/replay). Returns entry id. */
|
|
1688
|
-
appendSessionInit(init: { systemPrompt: string; task: string; tools: string[]
|
|
1686
|
+
appendSessionInit(init: { systemPrompt: string; task: string; tools: string[] }): string {
|
|
1689
1687
|
const entry: SessionInitEntry = {
|
|
1690
1688
|
type: "session_init",
|
|
1691
1689
|
id: generateId(this.#byId),
|
package/src/task/executor.ts
CHANGED
|
@@ -107,7 +107,7 @@ export interface ExecutorOptions {
|
|
|
107
107
|
id: string;
|
|
108
108
|
modelOverride?: string | string[];
|
|
109
109
|
thinkingLevel?: ThinkingLevel;
|
|
110
|
-
|
|
110
|
+
isSubagent?: boolean;
|
|
111
111
|
enableLsp?: boolean;
|
|
112
112
|
signal?: AbortSignal;
|
|
113
113
|
onProgress?: (progress: AgentProgress) => void;
|
|
@@ -682,7 +682,7 @@ export async function runAgent(options: ExecutorOptions): Promise<SingleResult>
|
|
|
682
682
|
sessionManager,
|
|
683
683
|
hasUI: false,
|
|
684
684
|
spawns: "",
|
|
685
|
-
|
|
685
|
+
isSubagent: true,
|
|
686
686
|
parentTaskPrefix: id,
|
|
687
687
|
enableLsp: lspEnabled,
|
|
688
688
|
skipPythonPreflight,
|
package/src/task/index.ts
CHANGED
|
@@ -93,7 +93,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
95
|
await fs.mkdir(effectiveArtifactsDir, { recursive: true });
|
|
96
|
-
const compactContext = this.session.getCompactContext?.();
|
|
96
|
+
const compactContext = this.session.subagentContext?.getCompactContext?.();
|
|
97
97
|
let contextFilePath: string | undefined;
|
|
98
98
|
if (compactContext) {
|
|
99
99
|
contextFilePath = path.join(effectiveArtifactsDir, "context.md");
|
|
@@ -164,7 +164,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
164
164
|
description: rendered.description,
|
|
165
165
|
index: 0,
|
|
166
166
|
id: uniqueId,
|
|
167
|
-
|
|
167
|
+
isSubagent: true,
|
|
168
168
|
modelOverride,
|
|
169
169
|
sessionFile,
|
|
170
170
|
persistArtifacts: !!artifactsDir,
|
|
@@ -177,10 +177,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
177
177
|
progressMap.set(0, { ...structuredClone(progress) });
|
|
178
178
|
emitProgress();
|
|
179
179
|
},
|
|
180
|
-
authStorage: this.session.authStorage,
|
|
181
|
-
modelRegistry: this.session.modelRegistry,
|
|
180
|
+
authStorage: this.session.subagentContext?.authStorage,
|
|
181
|
+
modelRegistry: this.session.subagentContext?.modelRegistry,
|
|
182
182
|
settings: this.session.settings,
|
|
183
|
-
mcpManager: this.session.mcpManager,
|
|
183
|
+
mcpManager: this.session.subagentContext?.mcpManager,
|
|
184
184
|
contextFiles,
|
|
185
185
|
skills: resolvedSkills,
|
|
186
186
|
preloadedSkills,
|
package/src/task/types.ts
CHANGED
|
@@ -24,26 +24,13 @@ export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
|
|
|
24
24
|
/** EventBus channel for aggregated subagent progress */
|
|
25
25
|
export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
|
|
26
26
|
|
|
27
|
-
/** Single task item for
|
|
28
|
-
export
|
|
29
|
-
id:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
description: "Short one-liner for UI display only — not seen by the subagent",
|
|
35
|
-
}),
|
|
36
|
-
assignment: Type.String({
|
|
37
|
-
description:
|
|
38
|
-
"Complete per-task instructions the subagent executes. Must follow the Target/Change/Edge Cases/Acceptance structure. Only include per-task deltas — shared background belongs in `context`.",
|
|
39
|
-
}),
|
|
40
|
-
skills: Type.Optional(
|
|
41
|
-
Type.Array(Type.String(), {
|
|
42
|
-
description: "Skill names to preload into the subagent. Use only where it changes correctness.",
|
|
43
|
-
}),
|
|
44
|
-
),
|
|
45
|
-
});
|
|
46
|
-
export type TaskItem = Static<typeof taskItemSchema>;
|
|
27
|
+
/** Single task item for execution */
|
|
28
|
+
export interface TaskItem {
|
|
29
|
+
id: string;
|
|
30
|
+
description: string;
|
|
31
|
+
assignment: string;
|
|
32
|
+
skills?: string[];
|
|
33
|
+
}
|
|
47
34
|
|
|
48
35
|
/** Task schema — single task with optional context */
|
|
49
36
|
export const taskSchema = Type.Object({
|
package/src/tools/index.ts
CHANGED
|
@@ -31,7 +31,6 @@ import { PythonTool } from "./python";
|
|
|
31
31
|
import { ReadTool } from "./read";
|
|
32
32
|
import { ReviewerTool } from "./reviewer-tool";
|
|
33
33
|
import { loadSshTool } from "./ssh";
|
|
34
|
-
import { SubmitResultTool } from "./submit-result";
|
|
35
34
|
import { TodoWriteTool } from "./todo-write";
|
|
36
35
|
import { UndoEditTool } from "./undo-edit";
|
|
37
36
|
import { WriteTool } from "./write";
|
|
@@ -94,7 +93,6 @@ export { OracleTool } from "./oracle";
|
|
|
94
93
|
export { PythonTool, type PythonToolDetails, type PythonToolOptions } from "./python";
|
|
95
94
|
export { ReadTool, type ReadToolDetails, type ReadToolInput } from "./read";
|
|
96
95
|
export { loadSshTool, type SSHToolDetails, SshTool } from "./ssh";
|
|
97
|
-
export { SubmitResultTool } from "./submit-result";
|
|
98
96
|
export { type TodoItem, TodoWriteTool, type TodoWriteToolDetails } from "./todo-write";
|
|
99
97
|
export { UndoEditTool, type UndoEditToolDetails } from "./undo-edit";
|
|
100
98
|
export { WriteTool, type WriteToolDetails, type WriteToolInput } from "./write";
|
|
@@ -108,6 +106,14 @@ export type ContextFileEntry = {
|
|
|
108
106
|
depth?: number;
|
|
109
107
|
};
|
|
110
108
|
|
|
109
|
+
/** Forwarded context for spawning subagent processes */
|
|
110
|
+
export interface SubagentContext {
|
|
111
|
+
authStorage?: import("../session/auth-storage").AuthStorage;
|
|
112
|
+
modelRegistry?: import("../config/model-registry").ModelRegistry;
|
|
113
|
+
mcpManager?: import("../mcp/manager").MCPManager;
|
|
114
|
+
getCompactContext?: () => string;
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
/** Session context for tool factories */
|
|
112
118
|
export interface ToolSession {
|
|
113
119
|
/** Current working directory */
|
|
@@ -128,12 +134,8 @@ export interface ToolSession {
|
|
|
128
134
|
hasEditTool?: boolean;
|
|
129
135
|
/** Event bus for tool/extension communication */
|
|
130
136
|
eventBus?: EventBus;
|
|
131
|
-
/**
|
|
132
|
-
|
|
133
|
-
/** Whether to include the submit_result tool by default */
|
|
134
|
-
requireSubmitResultTool?: boolean;
|
|
135
|
-
/** Task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
136
|
-
taskDepth?: number;
|
|
137
|
+
/** Whether this session is a subagent (spawned by task tool) */
|
|
138
|
+
isSubagent?: boolean;
|
|
137
139
|
/** Get session file */
|
|
138
140
|
getSessionFile: () => string | null;
|
|
139
141
|
/** Get session ID */
|
|
@@ -148,20 +150,14 @@ export interface ToolSession {
|
|
|
148
150
|
getModelString?: () => string | undefined;
|
|
149
151
|
/** Get the current session model string, regardless of how it was chosen */
|
|
150
152
|
getActiveModelString?: () => string | undefined;
|
|
151
|
-
/**
|
|
152
|
-
|
|
153
|
-
/** Model registry for passing to subagents (avoids re-discovery) */
|
|
154
|
-
modelRegistry?: import("../config/model-registry").ModelRegistry;
|
|
155
|
-
/** MCP manager for proxying MCP calls through parent */
|
|
156
|
-
mcpManager?: import("../mcp/manager").MCPManager;
|
|
153
|
+
/** Context for spawning subagent processes (only used by task/subagent tools) */
|
|
154
|
+
subagentContext?: SubagentContext;
|
|
157
155
|
/** Internal URL router for agent:// and skill:// URLs */
|
|
158
156
|
internalRouter?: InternalUrlRouter;
|
|
159
157
|
/** Agent output manager for unique agent:// IDs across task invocations */
|
|
160
158
|
agentOutputManager?: AgentOutputManager;
|
|
161
159
|
/** Settings instance for passing to subagents */
|
|
162
160
|
settings: Settings;
|
|
163
|
-
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
|
164
|
-
getCompactContext?: () => string;
|
|
165
161
|
}
|
|
166
162
|
|
|
167
163
|
type ToolFactory = (session: ToolSession) => Tool | null | Promise<Tool | null>;
|
|
@@ -191,10 +187,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
191
187
|
write: s => new WriteTool(s),
|
|
192
188
|
};
|
|
193
189
|
|
|
194
|
-
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
|
195
|
-
submit_result: s => new SubmitResultTool(s),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
190
|
export type ToolName = keyof typeof BUILTIN_TOOLS;
|
|
199
191
|
|
|
200
192
|
export type PythonToolMode = "ipy-only" | "bash-only" | "both";
|
|
@@ -232,7 +224,6 @@ function getPythonModeFromEnv(): PythonToolMode | null {
|
|
|
232
224
|
*/
|
|
233
225
|
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
|
|
234
226
|
time("createTools:start");
|
|
235
|
-
const includeSubmitResult = session.requireSubmitResultTool === true;
|
|
236
227
|
const enableLsp = session.enableLsp ?? true;
|
|
237
228
|
const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
|
|
238
229
|
const pythonMode = getPythonModeFromEnv() ?? session.settings.get("python.toolMode");
|
|
@@ -278,12 +269,12 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
278
269
|
) {
|
|
279
270
|
requestedTools.push("bash");
|
|
280
271
|
}
|
|
281
|
-
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS
|
|
272
|
+
const allTools: Record<string, ToolFactory> = { ...BUILTIN_TOOLS };
|
|
282
273
|
const isToolAllowed = (name: string) => {
|
|
283
274
|
if (name === "lsp") return enableLsp;
|
|
284
275
|
if (name === "bash") return allowBash;
|
|
285
276
|
if (name === "python") return allowPython;
|
|
286
|
-
if (name === "todo_write") return
|
|
277
|
+
if (name === "todo_write") return session.settings.get("todo.enabled");
|
|
287
278
|
if (name === "find") return session.settings.get("find.enabled");
|
|
288
279
|
if (name === "grep") return session.settings.get("grep.enabled");
|
|
289
280
|
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
@@ -295,25 +286,17 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
295
286
|
if (name === "librarian") return session.settings.get("librarian.enabled");
|
|
296
287
|
if (name === "oracle") return session.settings.get("oracle.enabled");
|
|
297
288
|
if (name === "task") {
|
|
298
|
-
|
|
299
|
-
const currentDepth = session.taskDepth ?? 0;
|
|
300
|
-
return maxDepth < 0 || currentDepth < maxDepth;
|
|
289
|
+
return !session.isSubagent;
|
|
301
290
|
}
|
|
302
291
|
return true;
|
|
303
292
|
};
|
|
304
|
-
if (includeSubmitResult && requestedTools && !requestedTools.includes("submit_result")) {
|
|
305
|
-
requestedTools.push("submit_result");
|
|
306
|
-
}
|
|
307
293
|
|
|
308
294
|
const filteredRequestedTools = requestedTools?.filter(name => name in allTools && isToolAllowed(name));
|
|
309
295
|
|
|
310
296
|
const entries =
|
|
311
297
|
filteredRequestedTools !== undefined
|
|
312
298
|
? filteredRequestedTools.map(name => [name, allTools[name]] as const)
|
|
313
|
-
: [
|
|
314
|
-
...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name)),
|
|
315
|
-
...(includeSubmitResult ? ([["submit_result", HIDDEN_TOOLS.submit_result]] as const) : []),
|
|
316
|
-
];
|
|
299
|
+
: [...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name))];
|
|
317
300
|
|
|
318
301
|
const results = await Promise.all(
|
|
319
302
|
entries.map(async ([name, factory]) => {
|
|
@@ -111,7 +111,7 @@ export function createSubagentTool<T extends TProperties>(
|
|
|
111
111
|
|
|
112
112
|
let contextFilePath: string | undefined;
|
|
113
113
|
if (passContext) {
|
|
114
|
-
const compactContext = this.session.getCompactContext?.();
|
|
114
|
+
const compactContext = this.session.subagentContext?.getCompactContext?.();
|
|
115
115
|
if (compactContext) {
|
|
116
116
|
contextFilePath = path.join(effectiveArtifactsDir, "context.md");
|
|
117
117
|
await Bun.write(contextFilePath, compactContext);
|
|
@@ -125,7 +125,7 @@ export function createSubagentTool<T extends TProperties>(
|
|
|
125
125
|
description: buildDescription(params),
|
|
126
126
|
index: 0,
|
|
127
127
|
id,
|
|
128
|
-
|
|
128
|
+
isSubagent: true,
|
|
129
129
|
modelOverride,
|
|
130
130
|
sessionFile,
|
|
131
131
|
persistArtifacts: !!artifactsDir,
|
|
@@ -134,13 +134,13 @@ export function createSubagentTool<T extends TProperties>(
|
|
|
134
134
|
enableLsp: false,
|
|
135
135
|
signal,
|
|
136
136
|
onProgress: emitProgress,
|
|
137
|
-
authStorage: this.session.authStorage,
|
|
138
|
-
modelRegistry: this.session.modelRegistry,
|
|
137
|
+
authStorage: this.session.subagentContext?.authStorage,
|
|
138
|
+
modelRegistry: this.session.subagentContext?.modelRegistry,
|
|
139
139
|
settings: this.session.settings,
|
|
140
140
|
contextFiles: this.session.contextFiles,
|
|
141
141
|
skills: this.session.skills,
|
|
142
142
|
promptTemplates: this.session.promptTemplates,
|
|
143
|
-
mcpManager: this.session.mcpManager,
|
|
143
|
+
mcpManager: this.session.subagentContext?.mcpManager,
|
|
144
144
|
});
|
|
145
145
|
|
|
146
146
|
if (tempArtifactsDir) {
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -78,39 +78,6 @@ function normalizeTodos(items: Array<{ id?: string; content?: string; status?: s
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function validateSequentialTodos(todos: TodoItem[]): { valid: boolean; error?: string } {
|
|
82
|
-
if (todos.length === 0) return { valid: true };
|
|
83
|
-
|
|
84
|
-
const firstIncompleteIndex = todos.findIndex(todo => todo.status !== "completed");
|
|
85
|
-
if (firstIncompleteIndex >= 0) {
|
|
86
|
-
for (let i = firstIncompleteIndex + 1; i < todos.length; i++) {
|
|
87
|
-
if (todos[i].status === "completed") {
|
|
88
|
-
return {
|
|
89
|
-
valid: false,
|
|
90
|
-
error: `Error: Cannot complete "${todos[i].content}" before completing "${todos[firstIncompleteIndex].content}". Todos must be completed sequentially.`,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const inProgressIndices = todos.reduce<number[]>((acc, todo, index) => {
|
|
97
|
-
if (todo.status === "in_progress") acc.push(index);
|
|
98
|
-
return acc;
|
|
99
|
-
}, []);
|
|
100
|
-
|
|
101
|
-
for (const idx of inProgressIndices) {
|
|
102
|
-
const hasPriorIncomplete = todos.slice(0, idx).some(t => t.status === "pending");
|
|
103
|
-
if (hasPriorIncomplete) {
|
|
104
|
-
return {
|
|
105
|
-
valid: false,
|
|
106
|
-
error: `Cannot start "${todos[idx].content}" while earlier tasks are still pending.`,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return { valid: true };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
81
|
async function loadTodoFile(filePath: string): Promise<TodoFile | null> {
|
|
115
82
|
const file = Bun.file(filePath);
|
|
116
83
|
if (!(await file.exists())) return null;
|
|
@@ -168,10 +135,6 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
168
135
|
_context?: AgentToolContext,
|
|
169
136
|
): Promise<AgentToolResult<TodoWriteToolDetails>> {
|
|
170
137
|
const todos = normalizeTodos(params.todos ?? []);
|
|
171
|
-
const validation = validateSequentialTodos(todos);
|
|
172
|
-
if (!validation.valid) {
|
|
173
|
-
throw new Error(validation.error ?? "Todos must be completed sequentially.");
|
|
174
|
-
}
|
|
175
138
|
const updatedAt = Date.now();
|
|
176
139
|
|
|
177
140
|
const sessionFile = this.session.getSessionFile();
|
package/src/version.ts
ADDED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
<system-reminder>
|
|
2
|
-
You stopped without calling submit_result. This is reminder {{retryCount}} of {{maxRetries}}.
|
|
3
|
-
|
|
4
|
-
Your only available action now is to call submit_result. Choose one:
|
|
5
|
-
- If task is complete: call submit_result with your result data
|
|
6
|
-
- If task failed or was interrupted: call submit_result with status="aborted" and describe what happened
|
|
7
|
-
|
|
8
|
-
Do NOT choose aborted if you can still complete the task through exploration (using available tools or repo context). If you must abort, include what you tried and the exact blocker.
|
|
9
|
-
|
|
10
|
-
Do NOT output text without a tool call. You must call submit_result to finish.
|
|
11
|
-
</system-reminder>
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convert JSON Type Definition (JTD) to JSON Schema.
|
|
3
|
-
*
|
|
4
|
-
* JTD (RFC 8927) is a simpler schema format. This converter allows users to
|
|
5
|
-
* write schemas in JTD and have them converted to JSON Schema for model APIs.
|
|
6
|
-
*
|
|
7
|
-
* @see https://jsontypedef.com/
|
|
8
|
-
* @see https://datatracker.ietf.org/doc/html/rfc8927
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
type JTDPrimitive =
|
|
12
|
-
| "boolean"
|
|
13
|
-
| "string"
|
|
14
|
-
| "timestamp"
|
|
15
|
-
| "float32"
|
|
16
|
-
| "float64"
|
|
17
|
-
| "int8"
|
|
18
|
-
| "uint8"
|
|
19
|
-
| "int16"
|
|
20
|
-
| "uint16"
|
|
21
|
-
| "int32"
|
|
22
|
-
| "uint32";
|
|
23
|
-
|
|
24
|
-
interface JTDType {
|
|
25
|
-
type: JTDPrimitive;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface JTDEnum {
|
|
29
|
-
enum: string[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface JTDElements {
|
|
33
|
-
elements: JTDSchema;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface JTDValues {
|
|
37
|
-
values: JTDSchema;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface JTDProperties {
|
|
41
|
-
properties?: Record<string, JTDSchema>;
|
|
42
|
-
optionalProperties?: Record<string, JTDSchema>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface JTDDiscriminator {
|
|
46
|
-
discriminator: string;
|
|
47
|
-
mapping: Record<string, JTDProperties>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
interface JTDRef {
|
|
51
|
-
ref: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface JTDEmpty {}
|
|
55
|
-
|
|
56
|
-
type JTDSchema = JTDType | JTDEnum | JTDElements | JTDValues | JTDProperties | JTDDiscriminator | JTDRef | JTDEmpty;
|
|
57
|
-
|
|
58
|
-
const primitiveMap: Record<JTDPrimitive, string> = {
|
|
59
|
-
boolean: "boolean",
|
|
60
|
-
string: "string",
|
|
61
|
-
timestamp: "string", // ISO 8601
|
|
62
|
-
float32: "number",
|
|
63
|
-
float64: "number",
|
|
64
|
-
int8: "integer",
|
|
65
|
-
uint8: "integer",
|
|
66
|
-
int16: "integer",
|
|
67
|
-
uint16: "integer",
|
|
68
|
-
int32: "integer",
|
|
69
|
-
uint32: "integer",
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
function isJTDType(schema: unknown): schema is JTDType {
|
|
73
|
-
return typeof schema === "object" && schema !== null && "type" in schema;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isJTDEnum(schema: unknown): schema is JTDEnum {
|
|
77
|
-
return typeof schema === "object" && schema !== null && "enum" in schema;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isJTDElements(schema: unknown): schema is JTDElements {
|
|
81
|
-
return typeof schema === "object" && schema !== null && "elements" in schema;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function isJTDValues(schema: unknown): schema is JTDValues {
|
|
85
|
-
return typeof schema === "object" && schema !== null && "values" in schema;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function isJTDProperties(schema: unknown): schema is JTDProperties {
|
|
89
|
-
return typeof schema === "object" && schema !== null && ("properties" in schema || "optionalProperties" in schema);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isJTDDiscriminator(schema: unknown): schema is JTDDiscriminator {
|
|
93
|
-
return typeof schema === "object" && schema !== null && "discriminator" in schema;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isJTDRef(schema: unknown): schema is JTDRef {
|
|
97
|
-
return typeof schema === "object" && schema !== null && "ref" in schema;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function convertSchema(schema: unknown): unknown {
|
|
101
|
-
if (schema === null || typeof schema !== "object") {
|
|
102
|
-
return {};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Type form: { type: "string" } → { type: "string" }
|
|
106
|
-
if (isJTDType(schema)) {
|
|
107
|
-
const jsonType = primitiveMap[schema.type as JTDPrimitive];
|
|
108
|
-
if (!jsonType) {
|
|
109
|
-
return { type: schema.type };
|
|
110
|
-
}
|
|
111
|
-
return { type: jsonType };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Enum form: { enum: ["a", "b"] } → { enum: ["a", "b"] }
|
|
115
|
-
if (isJTDEnum(schema)) {
|
|
116
|
-
return { enum: schema.enum };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Elements form: { elements: { type: "string" } } → { type: "array", items: ... }
|
|
120
|
-
if (isJTDElements(schema)) {
|
|
121
|
-
return {
|
|
122
|
-
type: "array",
|
|
123
|
-
items: convertSchema(schema.elements),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Values form: { values: { type: "string" } } → { type: "object", additionalProperties: ... }
|
|
128
|
-
if (isJTDValues(schema)) {
|
|
129
|
-
return {
|
|
130
|
-
type: "object",
|
|
131
|
-
additionalProperties: convertSchema(schema.values),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Properties form: { properties: {...}, optionalProperties: {...} }
|
|
136
|
-
if (isJTDProperties(schema)) {
|
|
137
|
-
const properties: Record<string, unknown> = {};
|
|
138
|
-
const required: string[] = [];
|
|
139
|
-
|
|
140
|
-
// Required properties
|
|
141
|
-
if (schema.properties) {
|
|
142
|
-
for (const [key, value] of Object.entries(schema.properties)) {
|
|
143
|
-
properties[key] = convertSchema(value);
|
|
144
|
-
required.push(key);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Optional properties
|
|
149
|
-
if (schema.optionalProperties) {
|
|
150
|
-
for (const [key, value] of Object.entries(schema.optionalProperties)) {
|
|
151
|
-
properties[key] = convertSchema(value);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const result: Record<string, unknown> = {
|
|
156
|
-
type: "object",
|
|
157
|
-
properties,
|
|
158
|
-
additionalProperties: false,
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
if (required.length > 0) {
|
|
162
|
-
result.required = required;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Discriminator form: { discriminator: "type", mapping: { ... } }
|
|
169
|
-
if (isJTDDiscriminator(schema)) {
|
|
170
|
-
const oneOf: unknown[] = [];
|
|
171
|
-
|
|
172
|
-
for (const [tag, props] of Object.entries(schema.mapping)) {
|
|
173
|
-
const converted = convertSchema(props) as Record<string, unknown>;
|
|
174
|
-
// Add the discriminator property
|
|
175
|
-
const properties = (converted.properties || {}) as Record<string, unknown>;
|
|
176
|
-
properties[schema.discriminator] = { const: tag };
|
|
177
|
-
|
|
178
|
-
const required = ((converted.required as string[]) || []).slice();
|
|
179
|
-
if (!required.includes(schema.discriminator)) {
|
|
180
|
-
required.push(schema.discriminator);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
oneOf.push({
|
|
184
|
-
...converted,
|
|
185
|
-
properties,
|
|
186
|
-
required,
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return { oneOf };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Ref form: { ref: "MyType" } → { $ref: "#/$defs/MyType" }
|
|
194
|
-
if (isJTDRef(schema)) {
|
|
195
|
-
return { $ref: `#/$defs/${schema.ref}` };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Empty form: {} → {} (accepts anything)
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Detect if a schema is JTD format (vs JSON Schema).
|
|
204
|
-
*
|
|
205
|
-
* JTD schemas use: type (primitives), properties, optionalProperties, elements, values, enum, discriminator, ref
|
|
206
|
-
* JSON Schema uses: type: "object", type: "array", items, additionalProperties, etc.
|
|
207
|
-
*/
|
|
208
|
-
export function isJTDSchema(schema: unknown): boolean {
|
|
209
|
-
if (schema === null || typeof schema !== "object") {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const obj = schema as Record<string, unknown>;
|
|
214
|
-
|
|
215
|
-
// JTD-specific keywords
|
|
216
|
-
if ("elements" in obj) return true;
|
|
217
|
-
if ("values" in obj) return true;
|
|
218
|
-
if ("optionalProperties" in obj) return true;
|
|
219
|
-
if ("discriminator" in obj) return true;
|
|
220
|
-
if ("ref" in obj) return true;
|
|
221
|
-
|
|
222
|
-
// JTD type primitives (JSON Schema doesn't have int32, float64, etc.)
|
|
223
|
-
if ("type" in obj) {
|
|
224
|
-
const jtdPrimitives = ["timestamp", "float32", "float64", "int8", "uint8", "int16", "uint16", "int32", "uint32"];
|
|
225
|
-
if (jtdPrimitives.includes(obj.type as string)) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// JTD properties form without type: "object" (JSON Schema requires it)
|
|
231
|
-
if ("properties" in obj && !("type" in obj)) {
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Convert JTD schema to JSON Schema.
|
|
240
|
-
* If already JSON Schema, returns as-is.
|
|
241
|
-
*/
|
|
242
|
-
export function jtdToJsonSchema(schema: unknown): unknown {
|
|
243
|
-
if (!isJTDSchema(schema)) {
|
|
244
|
-
return schema;
|
|
245
|
-
}
|
|
246
|
-
return convertSchema(schema);
|
|
247
|
-
}
|