demian-cli 1.1.2 → 1.2.1
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/README.md +30 -2
- package/dist/cli.mjs +2047 -517
- package/dist/index.mjs +1810 -379
- package/dist/tui-sidecar/darwin-arm64/demian-tui +0 -0
- package/dist/tui-sidecar/linux-x64/demian-tui +0 -0
- package/dist/tui.mjs +48116 -1393
- package/dist/vscode-worker.mjs +3192 -673
- package/docs/ko/README.md +25 -1
- package/package.json +7 -4
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,9 @@ function normalizeAgent(input2) {
|
|
|
13
13
|
defaultDecision: definition.defaultDecision ?? definition.authority?.defaultDecision,
|
|
14
14
|
mode: definition.mode,
|
|
15
15
|
hidden: definition.hidden,
|
|
16
|
+
defaultProviders: definition.defaultProviders,
|
|
17
|
+
permissionIntent: definition.permissionIntent,
|
|
18
|
+
runtimeOverlays: definition.runtimeOverlays,
|
|
16
19
|
provider: definition.provider,
|
|
17
20
|
delegation: definition.delegation,
|
|
18
21
|
catalog: definition.catalog,
|
|
@@ -38,7 +41,7 @@ function isCoworkableAgent(agent) {
|
|
|
38
41
|
if (agent.hidden) return false;
|
|
39
42
|
const mode = agent.mode ?? "primary";
|
|
40
43
|
if (mode !== "subagent" && mode !== "all") return false;
|
|
41
|
-
return agent.delegation?.coworkable
|
|
44
|
+
return agent.delegation?.coworkable === true;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
// src/agents/build.ts
|
|
@@ -46,10 +49,12 @@ var buildAgent = {
|
|
|
46
49
|
name: "build",
|
|
47
50
|
description: "General coding agent for scoped implementation work.",
|
|
48
51
|
mode: "all",
|
|
49
|
-
tools: ["read_file", "write_file", "edit_file", "bash", "grep", "glob", "web_search"],
|
|
52
|
+
tools: ["read_file", "write_file", "edit_file", "bash", "grep", "glob", "web_search", "update_plan", "report_progress"],
|
|
50
53
|
systemPrompt: [
|
|
51
54
|
"You are demian build, a local coding agent.",
|
|
52
55
|
"Use tools to inspect and modify the workspace. Keep changes scoped to the user's request.",
|
|
56
|
+
"Before tool-heavy implementation work, use update_plan and report_progress so the user can see what you are about to verify or change and why it matters.",
|
|
57
|
+
"During longer work, report meaningful milestones in conversational sentences. Explain the purpose, what the evidence or result means, and what you will do next instead of listing raw tool names or command output.",
|
|
53
58
|
"Match the user's language for all user-facing text, including progress-style narration and final results. If the user writes in Korean, write user-facing text in Korean while keeping code identifiers, paths, commands, and tool names unchanged.",
|
|
54
59
|
"Use web_search when current external information is needed.",
|
|
55
60
|
"Tool names are fixed by the runtime. In multi-agent mode, delegate_agent may also be available for bounded specialist work.",
|
|
@@ -60,6 +65,8 @@ var buildAgent = {
|
|
|
60
65
|
{ tool: "read_file", decision: "allow" },
|
|
61
66
|
{ tool: "grep", decision: "allow" },
|
|
62
67
|
{ tool: "glob", decision: "allow" },
|
|
68
|
+
{ tool: "update_plan", decision: "allow" },
|
|
69
|
+
{ tool: "report_progress", decision: "allow" },
|
|
63
70
|
{ tool: "web_search", decision: "ask" },
|
|
64
71
|
{ tool: "write_file", decision: "ask" },
|
|
65
72
|
{ tool: "edit_file", decision: "ask" },
|
|
@@ -105,6 +112,7 @@ var claudeCodeAgent = {
|
|
|
105
112
|
{ tool: "claudecode.Read", decision: "allow" },
|
|
106
113
|
{ tool: "claudecode.Grep", decision: "allow" },
|
|
107
114
|
{ tool: "claudecode.Glob", decision: "allow" },
|
|
115
|
+
{ tool: "claudecode.LS", decision: "allow" },
|
|
108
116
|
{ tool: "claudecode.Edit", decision: "ask" },
|
|
109
117
|
{ tool: "claudecode.Write", decision: "ask" },
|
|
110
118
|
{ tool: "claudecode.MultiEdit", decision: "ask" },
|
|
@@ -133,144 +141,186 @@ var claudeCodeAgent = {
|
|
|
133
141
|
]
|
|
134
142
|
}
|
|
135
143
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
|
|
145
|
+
// src/agents/cowork.ts
|
|
146
|
+
var readPermissions = [
|
|
147
|
+
{ tool: "read_file", decision: "allow" },
|
|
148
|
+
{ tool: "grep", decision: "allow" },
|
|
149
|
+
{ tool: "glob", decision: "allow" },
|
|
150
|
+
{ tool: "update_plan", decision: "allow" },
|
|
151
|
+
{ tool: "report_progress", decision: "allow" }
|
|
152
|
+
];
|
|
153
|
+
var readProgressTools = ["read_file", "grep", "glob", "update_plan", "report_progress"];
|
|
154
|
+
var writePermissions = [
|
|
155
|
+
...readPermissions,
|
|
156
|
+
{ tool: "edit_file", decision: "ask" },
|
|
157
|
+
{ tool: "write_file", decision: "ask" },
|
|
158
|
+
{ tool: "bash", decision: "ask" },
|
|
159
|
+
{ tool: "*", match: { pathGlob: "**/.env" }, decision: "deny", reason: "secrets" },
|
|
160
|
+
{ tool: "*", match: { pathGlob: "**/.env.*" }, decision: "deny", reason: "secrets" },
|
|
161
|
+
{ tool: "*", match: { pathGlob: "node_modules/**" }, decision: "deny", reason: "vendored" }
|
|
162
|
+
];
|
|
163
|
+
var plannerAgent = coworkAgent({
|
|
164
|
+
name: "planner",
|
|
165
|
+
description: "Provider-agnostic planner for decomposing ambiguous or multi-step engineering work.",
|
|
166
|
+
defaultProviders: ["codex", "anthropic", "openai"],
|
|
167
|
+
permissionIntent: "manage",
|
|
168
|
+
category: "planner",
|
|
169
|
+
cost: "standard",
|
|
170
|
+
tools: readProgressTools,
|
|
171
|
+
permissions: readPermissions,
|
|
172
|
+
defaultDecision: "deny",
|
|
173
|
+
prompt: [
|
|
174
|
+
"You are demian planner, a planning cowork sub agent for Demian.",
|
|
175
|
+
"Inspect the delegated scope read-only and return an implementation plan, sequencing, risks, and verification strategy.",
|
|
176
|
+
"Use update_plan and report_progress to keep Demian's visible progress current when the investigation takes multiple tool calls. Report the planning question you are narrowing, what the evidence implies, and what you will inspect next. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
177
|
+
"Do not edit files. Do not ask the user directly; return blockers or questions for the parent Demian coordinator."
|
|
155
178
|
],
|
|
179
|
+
useWhen: ["A parallel planning perspective may clarify scope, sequencing, risks, or verification."],
|
|
180
|
+
avoidWhen: ["The work is already clear and a bounded writer can proceed."]
|
|
181
|
+
});
|
|
182
|
+
var architectAgent = coworkAgent({
|
|
183
|
+
name: "architect",
|
|
184
|
+
description: "Provider-agnostic architect for structure, design, and long-term maintainability decisions.",
|
|
185
|
+
defaultProviders: ["codex", "anthropic"],
|
|
186
|
+
permissionIntent: "manage",
|
|
187
|
+
category: "planner",
|
|
188
|
+
cost: "standard",
|
|
189
|
+
tools: readProgressTools,
|
|
190
|
+
permissions: readPermissions,
|
|
156
191
|
defaultDecision: "deny",
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
prompt: [
|
|
193
|
+
"You are demian architect, a design cowork sub agent for Demian.",
|
|
194
|
+
"Inspect the repository and proposed work read-only. Focus on architecture, interfaces, coupling, migration risk, and maintainability.",
|
|
195
|
+
"Use update_plan and report_progress to summarize what design question you are answering, what the evidence means for the architecture, and what you will inspect next. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
196
|
+
"Return concrete design recommendations and risks for the parent Demian coordinator."
|
|
197
|
+
],
|
|
198
|
+
useWhen: ["A change needs architectural judgment, interface design, or cross-module risk assessment."],
|
|
199
|
+
avoidWhen: ["The task is a small mechanical edit."]
|
|
200
|
+
});
|
|
201
|
+
var supervisorAgent = coworkAgent({
|
|
202
|
+
name: "supervisor",
|
|
203
|
+
description: "Provider-agnostic supervisory reviewer for overall risk, integration quality, and final review.",
|
|
204
|
+
defaultProviders: ["codex", "anthropic"],
|
|
205
|
+
permissionIntent: "review",
|
|
206
|
+
category: "reviewer",
|
|
207
|
+
cost: "standard",
|
|
208
|
+
tools: readProgressTools,
|
|
209
|
+
permissions: readPermissions,
|
|
210
|
+
defaultDecision: "deny",
|
|
211
|
+
prompt: [
|
|
212
|
+
"You are demian supervisor, a skeptical supervisory cowork sub agent for Demian.",
|
|
213
|
+
"Review plans, diffs, integration risks, missing tests, and final readiness. Do not edit files.",
|
|
214
|
+
"Use update_plan and report_progress to expose review progress when the review requires several reads. Include the review focus, the risk you are checking, and the next evidence you will seek. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
215
|
+
"Return concise findings with severity, file references when useful, and a clear recommendation."
|
|
216
|
+
],
|
|
217
|
+
useWhen: ["A second model should review a design, implementation plan, diff, or test strategy before Demian answers."],
|
|
218
|
+
avoidWhen: ["The task needs direct workspace edits from this member."]
|
|
219
|
+
});
|
|
220
|
+
var researcherAgent = coworkAgent({
|
|
221
|
+
name: "researcher",
|
|
222
|
+
description: "Provider-agnostic researcher for repo inspection, external research, and evidence gathering.",
|
|
223
|
+
defaultProviders: ["codex", "claudecode", "anthropic"],
|
|
224
|
+
permissionIntent: "read-only",
|
|
225
|
+
category: "researcher",
|
|
226
|
+
cost: "standard",
|
|
227
|
+
tools: [...readProgressTools, "web_search"],
|
|
228
|
+
permissions: [...readPermissions, { tool: "web_search", decision: "ask" }],
|
|
229
|
+
defaultDecision: "deny",
|
|
230
|
+
prompt: [
|
|
231
|
+
"You are demian researcher, a read-only cowork sub agent for Demian.",
|
|
232
|
+
"Gather evidence from the repository and, when needed, current external sources. Do not edit files.",
|
|
233
|
+
"Use update_plan and report_progress to report the evidence gathered at meaningful milestones. Include what the evidence suggests and where you will look next, without dumping raw tool names, command output, or file previews unless a specific identifier is essential.",
|
|
234
|
+
"Return findings with enough context for the parent Demian coordinator to act."
|
|
194
235
|
],
|
|
236
|
+
useWhen: ["The task needs repository exploration, usage tracing, documentation lookup, or evidence gathering."],
|
|
237
|
+
avoidWhen: ["The task is already scoped for implementation."]
|
|
238
|
+
});
|
|
239
|
+
var builderAgent = coworkAgent({
|
|
240
|
+
name: "builder",
|
|
241
|
+
description: "Provider-agnostic builder for bounded implementation and module-level development.",
|
|
242
|
+
defaultProviders: ["claudecode", "anthropic", "openai"],
|
|
243
|
+
permissionIntent: "write",
|
|
244
|
+
category: "builder",
|
|
245
|
+
cost: "expensive",
|
|
246
|
+
tools: [...readProgressTools, "edit_file", "write_file", "bash"],
|
|
247
|
+
permissions: writePermissions,
|
|
195
248
|
defaultDecision: "ask",
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
},
|
|
203
|
-
catalog: {
|
|
204
|
-
category: "builder",
|
|
205
|
-
cost: "expensive",
|
|
206
|
-
useWhen: ["Claude Code's local coding-agent runtime should implement a bounded writeScope while Demian coordinates review."],
|
|
207
|
-
avoidWhen: ["The task is read-only, exploratory, or can be handled by a normal Demian tool call."]
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// src/agents/cowork.ts
|
|
212
|
-
var codexReviewerAgent = {
|
|
213
|
-
name: "codex-reviewer",
|
|
214
|
-
description: "Codex-backed reviewer for architecture, risk, and patch review.",
|
|
215
|
-
mode: "subagent",
|
|
216
|
-
provider: { profile: "codex" },
|
|
217
|
-
tools: ["read_file", "grep", "glob"],
|
|
218
|
-
systemPrompt: [
|
|
219
|
-
"You are demian codex-reviewer, a skeptical reviewer working as a cowork sub agent for Demian.",
|
|
220
|
-
"Inspect the delegated context, plans, diffs, risks, and missing tests. Do not edit files.",
|
|
221
|
-
"Return concise findings with severity, file references when useful, and a clear recommendation for the parent Demian coordinator."
|
|
222
|
-
].join("\n"),
|
|
223
|
-
permissions: [
|
|
224
|
-
{ tool: "read_file", decision: "allow" },
|
|
225
|
-
{ tool: "grep", decision: "allow" },
|
|
226
|
-
{ tool: "glob", decision: "allow" }
|
|
249
|
+
prompt: [
|
|
250
|
+
"You are demian builder, a bounded implementation cowork sub agent for Demian.",
|
|
251
|
+
"Implement only the delegated task and only inside the provided writeScope.",
|
|
252
|
+
"Use update_plan and report_progress before writes and after meaningful implementation or verification milestones. Say what you are changing, why it is the next safe step, and how you will verify it. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
253
|
+
"If you need broader scope, credentials, user approval, or product decisions, stop and return a concise blocker for Demian.",
|
|
254
|
+
"Return changed files, verification performed, patch summary, and remaining blockers."
|
|
227
255
|
],
|
|
256
|
+
useWhen: ["A bounded writeScope should be implemented while Demian coordinates review."],
|
|
257
|
+
avoidWhen: ["The task is read-only, exploratory, or can be handled by a normal Demian tool call."]
|
|
258
|
+
});
|
|
259
|
+
var reviewerAgent = coworkAgent({
|
|
260
|
+
name: "reviewer",
|
|
261
|
+
description: "Provider-agnostic detailed reviewer for diff-level code review.",
|
|
262
|
+
defaultProviders: ["claudecode", "anthropic", "codex"],
|
|
263
|
+
permissionIntent: "review",
|
|
264
|
+
category: "reviewer",
|
|
265
|
+
cost: "standard",
|
|
266
|
+
tools: readProgressTools,
|
|
267
|
+
permissions: readPermissions,
|
|
228
268
|
defaultDecision: "deny",
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
invocationDecision: "allow"
|
|
235
|
-
},
|
|
236
|
-
catalog: {
|
|
237
|
-
category: "reviewer",
|
|
238
|
-
cost: "standard",
|
|
239
|
-
useWhen: ["A second model should review a design, implementation plan, diff, or test strategy before Demian answers."],
|
|
240
|
-
avoidWhen: ["The task needs direct workspace edits from this member."]
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
var codexPlannerAgent = {
|
|
244
|
-
name: "codex-planner",
|
|
245
|
-
description: "Codex-backed planner for decomposing ambiguous or multi-step engineering work.",
|
|
246
|
-
mode: "subagent",
|
|
247
|
-
provider: { profile: "codex" },
|
|
248
|
-
tools: ["read_file", "grep", "glob"],
|
|
249
|
-
systemPrompt: [
|
|
250
|
-
"You are demian codex-planner, a planning cowork sub agent for Demian.",
|
|
251
|
-
"Inspect the delegated scope read-only and return an implementation plan, sequencing, risks, and verification strategy.",
|
|
252
|
-
"Do not edit files. Do not ask the user directly; return blockers or questions for the parent Demian coordinator."
|
|
253
|
-
].join("\n"),
|
|
254
|
-
permissions: [
|
|
255
|
-
{ tool: "read_file", decision: "allow" },
|
|
256
|
-
{ tool: "grep", decision: "allow" },
|
|
257
|
-
{ tool: "glob", decision: "allow" }
|
|
269
|
+
prompt: [
|
|
270
|
+
"You are demian reviewer, a detailed code-review cowork sub agent for Demian.",
|
|
271
|
+
"Inspect the delegated context read-only. Focus on correctness, edge cases, tests, consistency, and maintainability.",
|
|
272
|
+
"Use update_plan and report_progress to keep longer review passes observable. Mention the finding area, current confidence, and the next evidence you will inspect. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
273
|
+
"Return findings first, ordered by severity, with file references when useful."
|
|
258
274
|
],
|
|
275
|
+
useWhen: ["A diff or focused implementation needs detailed code review."],
|
|
276
|
+
avoidWhen: ["The task needs broad architecture or direct editing."]
|
|
277
|
+
});
|
|
278
|
+
var specialistAgent = coworkAgent({
|
|
279
|
+
name: "specialist",
|
|
280
|
+
description: "Provider-agnostic specialist for focused domain, module, or technology expertise.",
|
|
281
|
+
defaultProviders: ["claudecode", "anthropic"],
|
|
282
|
+
permissionIntent: "read-only",
|
|
283
|
+
category: "researcher",
|
|
284
|
+
cost: "standard",
|
|
285
|
+
tools: readProgressTools,
|
|
286
|
+
permissions: readPermissions,
|
|
259
287
|
defaultDecision: "deny",
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
288
|
+
prompt: [
|
|
289
|
+
"You are demian specialist, a focused domain cowork sub agent for Demian.",
|
|
290
|
+
"Inspect the requested module, technology, or domain read-only unless explicitly assigned write mode with a narrow writeScope.",
|
|
291
|
+
"Use update_plan and report_progress for multi-step investigation so Demian can show the user what is happening. Explain the domain angle you are checking, what the evidence means, and what you will narrow down next. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
292
|
+
"Return precise, specialist-level findings and recommendations."
|
|
293
|
+
],
|
|
294
|
+
useWhen: ["A specific module, library, language, or subsystem needs focused expert analysis."],
|
|
295
|
+
avoidWhen: ["The task is broad coordination or final synthesis."]
|
|
296
|
+
});
|
|
297
|
+
var coworkAgents = [plannerAgent, architectAgent, supervisorAgent, researcherAgent, builderAgent, reviewerAgent, specialistAgent];
|
|
298
|
+
function coworkAgent(options) {
|
|
299
|
+
return {
|
|
300
|
+
name: options.name,
|
|
301
|
+
description: options.description,
|
|
302
|
+
mode: "subagent",
|
|
303
|
+
tools: options.tools,
|
|
304
|
+
permissions: options.permissions,
|
|
305
|
+
defaultDecision: options.defaultDecision,
|
|
306
|
+
defaultProviders: options.defaultProviders,
|
|
307
|
+
permissionIntent: options.permissionIntent,
|
|
308
|
+
systemPrompt: options.prompt.join("\n"),
|
|
309
|
+
delegation: {
|
|
310
|
+
callable: true,
|
|
311
|
+
coworkable: true,
|
|
312
|
+
canDelegate: false,
|
|
313
|
+
canCowork: false,
|
|
314
|
+
invocationDecision: options.permissionIntent === "write" ? "ask" : "allow"
|
|
315
|
+
},
|
|
316
|
+
catalog: {
|
|
317
|
+
category: options.category,
|
|
318
|
+
cost: options.cost,
|
|
319
|
+
useWhen: options.useWhen,
|
|
320
|
+
avoidWhen: options.avoidWhen
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
274
324
|
|
|
275
325
|
// src/agents/execute.ts
|
|
276
326
|
var executeAgent = {
|
|
@@ -282,8 +332,8 @@ var executeAgent = {
|
|
|
282
332
|
"You are demian execute, a bounded implementation agent.",
|
|
283
333
|
"You receive one task from a coordinator. Treat that task scope as a hard boundary.",
|
|
284
334
|
"Read the relevant files before changing them. Do not refactor unrelated code.",
|
|
285
|
-
"Before changing files, use update_plan to mark your assigned step in_progress and report_progress to say what you are starting.",
|
|
286
|
-
"Use report_progress for meaningful milestones during longer work.",
|
|
335
|
+
"Before changing files, use update_plan to mark your assigned step in_progress and report_progress to say what you are starting, why that step matters, and what you will do next.",
|
|
336
|
+
"Use report_progress for meaningful milestones during longer work. Write conversational updates that explain the purpose and implication of the work, not a raw list of tools, command output, or file previews.",
|
|
287
337
|
"Keep the visible work plan in sync with update_plan when you start, finish, block, skip, or fail your assigned step.",
|
|
288
338
|
"Match the user's language for all user-facing text: update_plan titles, summaries, step titles/details, report_progress messages, and final results. If the user writes in Korean, write those user-facing fields in Korean while keeping code identifiers, paths, commands, and tool names unchanged.",
|
|
289
339
|
"When finished, return changed files, verification results, and any remaining issues.",
|
|
@@ -333,9 +383,10 @@ var generalAgent = {
|
|
|
333
383
|
"For non-trivial work, first think through the request and inspect enough context to avoid a shallow plan. Then call update_plan before writes, shell commands, or execute delegation.",
|
|
334
384
|
"Use update_plan.title as the user's main goal, update_plan.summary for the current strategy, and short stable step ids for the sub-goals. Keep exactly one step in_progress while executing.",
|
|
335
385
|
"Match the user's language for all user-facing text: update_plan titles, summaries, step titles/details, report_progress messages, narration, and final answers. If the user writes in Korean, write those user-facing fields in Korean while keeping code identifiers, paths, commands, and tool names unchanged.",
|
|
386
|
+
"For any tool-using work, keep the user oriented before and during the work. Use report_progress with conversational sentences that say what question you are trying to answer, what the latest evidence means, and what you will do next. Do not list raw tool names, command output, or file previews unless the identifier itself is important to the user's decision. This applies to ordinary single-agent work as well as cowork/delegation.",
|
|
336
387
|
"Use the read-only plan agent for ambiguous, risky, or cross-file design work. Use the execute agent for bounded implementation tasks from the plan. Small obvious edits can be done directly.",
|
|
337
388
|
"When the user asks to use Claude Code, keep Demian as the main coordinator and delegate bounded work to the claudecode sub agent when it is available.",
|
|
338
|
-
"Keep the plan synchronized as steps move from pending to in_progress to completed, blocked, skipped, or
|
|
389
|
+
"Keep the plan synchronized as steps move from pending to in_progress to completed, blocked, skipped, failed, or cancelled. Use report_progress when starting a phase, switching phases, learning something important, or finishing meaningful milestones.",
|
|
339
390
|
"Use web_search when current external information is needed.",
|
|
340
391
|
"Keep changes scoped, preserve user work, and explain the outcome clearly.",
|
|
341
392
|
"Tool names are fixed by the runtime. In multi-agent mode, delegate_agent may also be available for bounded specialist work.",
|
|
@@ -382,7 +433,7 @@ var planAgent = {
|
|
|
382
433
|
"Use update_plan to materialize the visible work plan: title is the main goal, summary is the strategy, and steps are concrete sub-goals with short stable ids.",
|
|
383
434
|
"Match the user's language for all user-facing text: update_plan titles, summaries, step titles/details, report_progress messages, and the final planning response. If the user writes in Korean, write those user-facing fields in Korean while keeping code identifiers, paths, commands, and tool names unchanged.",
|
|
384
435
|
"A planning-only run can leave all steps pending. If you are actively investigating one step, mark only that step in_progress.",
|
|
385
|
-
"Use report_progress for major planning milestones, not for every read.",
|
|
436
|
+
"Use report_progress for major planning milestones, not for every read. Make each update conversational: what question you investigated, what that suggests, and what you will check next. Avoid raw tool names, command output, and file previews unless a specific identifier is essential.",
|
|
386
437
|
"Suggest bounded tasks that can be delegated to execute."
|
|
387
438
|
].join("\n"),
|
|
388
439
|
permissions: [
|
|
@@ -410,7 +461,7 @@ var planAgent = {
|
|
|
410
461
|
var AgentRegistry = class {
|
|
411
462
|
#agents = /* @__PURE__ */ new Map();
|
|
412
463
|
#builtinNames = /* @__PURE__ */ new Set();
|
|
413
|
-
constructor(agents = [generalAgent, buildAgent, planAgent, executeAgent, claudeCodeAgent,
|
|
464
|
+
constructor(agents = [generalAgent, buildAgent, planAgent, executeAgent, claudeCodeAgent, ...coworkAgents]) {
|
|
414
465
|
for (const agent of agents) {
|
|
415
466
|
const normalized = normalizeAgent(agent);
|
|
416
467
|
this.#builtinNames.add(normalized.name);
|
|
@@ -464,7 +515,14 @@ var AgentRegistry = class {
|
|
|
464
515
|
}
|
|
465
516
|
};
|
|
466
517
|
function buildSystemPrompt(agent, envInfo) {
|
|
467
|
-
return [
|
|
518
|
+
return [
|
|
519
|
+
agent.systemPrompt,
|
|
520
|
+
envInfo,
|
|
521
|
+
"Keep the user oriented for every non-trivial task, including /goal, cowork, delegated work, and ordinary single-agent work. Before the first tool-heavy action, briefly say what you are about to understand, verify, or change and why that is the right next step. For multi-step work, create or update the visible plan before substantial tool work and keep it synchronized as steps start, finish, skip, block, fail, or get cancelled.",
|
|
522
|
+
"When you use report_progress, write it like a brief teammate update: what you are checking or changing, what you have learned so far, why that matters, and what you will do next. Keep it conversational. Do not dump raw tool names, command output, file previews, or status fields unless a specific identifier is essential for the user to understand the decision.",
|
|
523
|
+
"When a task is long-running, report progress at meaningful phase changes and after important evidence. Prefer the purpose and implication of the work over mechanical execution details; the UI already exposes raw tool activity separately.",
|
|
524
|
+
"When a tool result reports an error, adjust your next step instead of repeating the same call."
|
|
525
|
+
].filter(Boolean).join("\n\n");
|
|
468
526
|
}
|
|
469
527
|
function validateAgentName(name) {
|
|
470
528
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
@@ -477,7 +535,113 @@ import { readFile as readFile6 } from "node:fs/promises";
|
|
|
477
535
|
import crypto4 from "node:crypto";
|
|
478
536
|
import fs7 from "node:fs";
|
|
479
537
|
import os7 from "node:os";
|
|
480
|
-
import
|
|
538
|
+
import path14 from "node:path";
|
|
539
|
+
|
|
540
|
+
// src/util.ts
|
|
541
|
+
function stableStringify(value) {
|
|
542
|
+
return JSON.stringify(sortValue(value));
|
|
543
|
+
}
|
|
544
|
+
function sortValue(value) {
|
|
545
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
546
|
+
if (!value || typeof value !== "object") return value;
|
|
547
|
+
const object2 = value;
|
|
548
|
+
const out = {};
|
|
549
|
+
for (const key of Object.keys(object2).sort()) out[key] = sortValue(object2[key]);
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
function createSessionId() {
|
|
553
|
+
return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
554
|
+
}
|
|
555
|
+
function createRootSessionId() {
|
|
556
|
+
return `root_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
557
|
+
}
|
|
558
|
+
function createAgentSessionId() {
|
|
559
|
+
return `as_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
560
|
+
}
|
|
561
|
+
function createInvocationId() {
|
|
562
|
+
return `inv_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
563
|
+
}
|
|
564
|
+
function preview(value, max = 500) {
|
|
565
|
+
if (value.length <= max) return value;
|
|
566
|
+
return `${value.slice(0, max)}...`;
|
|
567
|
+
}
|
|
568
|
+
function errorMessage(error) {
|
|
569
|
+
if (!(error instanceof Error)) return String(error);
|
|
570
|
+
const primary = error.message || error.name || "Error";
|
|
571
|
+
const cause = errorCauseSummary(error);
|
|
572
|
+
const hint = networkFailureHint(`${primary}${cause ? ` ${cause}` : ""}`);
|
|
573
|
+
const details = [cause, hint].filter(Boolean);
|
|
574
|
+
return details.length ? `${primary} (${details.join("; ")})` : primary;
|
|
575
|
+
}
|
|
576
|
+
function errorCauseSummary(error) {
|
|
577
|
+
const cause = error.cause;
|
|
578
|
+
const detail = causeDetail(cause, /* @__PURE__ */ new Set());
|
|
579
|
+
return detail ? `cause: ${detail}` : void 0;
|
|
580
|
+
}
|
|
581
|
+
function causeDetail(cause, seen) {
|
|
582
|
+
if (cause === void 0 || cause === null) return void 0;
|
|
583
|
+
if (seen.has(cause)) return void 0;
|
|
584
|
+
if (typeof cause === "object" || typeof cause === "function") seen.add(cause);
|
|
585
|
+
if (cause instanceof AggregateError) {
|
|
586
|
+
const details = cause.errors.map((item) => causeDetail(item, seen)).filter((item) => Boolean(item)).slice(0, 3);
|
|
587
|
+
return details.length ? details.join("; ") : cleanMessage(cause.message);
|
|
588
|
+
}
|
|
589
|
+
if (cause instanceof Error) {
|
|
590
|
+
const message = cleanMessage(cause.message);
|
|
591
|
+
const fields = errorFields(cause);
|
|
592
|
+
const nested = causeDetail(cause.cause, seen);
|
|
593
|
+
return [message, fields, nested ? `cause: ${nested}` : void 0].filter(Boolean).join("; ");
|
|
594
|
+
}
|
|
595
|
+
if (typeof cause === "object") {
|
|
596
|
+
const fields = errorFields(cause);
|
|
597
|
+
return fields || cleanMessage(safeStringify(cause));
|
|
598
|
+
}
|
|
599
|
+
return cleanMessage(String(cause));
|
|
600
|
+
}
|
|
601
|
+
function errorFields(value) {
|
|
602
|
+
if (!value || typeof value !== "object") return void 0;
|
|
603
|
+
const object2 = value;
|
|
604
|
+
const parts = [];
|
|
605
|
+
for (const key of ["code", "errno", "syscall"]) {
|
|
606
|
+
const field = object2[key];
|
|
607
|
+
if (typeof field === "string" || typeof field === "number") parts.push(`${key}=${field}`);
|
|
608
|
+
}
|
|
609
|
+
const host = stringField(object2, "hostname") ?? stringField(object2, "host") ?? stringField(object2, "address");
|
|
610
|
+
const port = stringField(object2, "port");
|
|
611
|
+
if (host) parts.push(`target=${host}${port ? `:${port}` : ""}`);
|
|
612
|
+
return unique(parts).join(", ");
|
|
613
|
+
}
|
|
614
|
+
function networkFailureHint(text) {
|
|
615
|
+
const lower = text.toLowerCase();
|
|
616
|
+
if (!/\b(fetch failed|econnreset|econnrefused|etimedout|enotfound|eai_again|und_err_connect_timeout|network|socket|certificate|cert_|unable_to_verify|self_signed)\b/i.test(text)) return void 0;
|
|
617
|
+
if (lower.includes("enotfound") || lower.includes("eai_again")) return "DNS lookup failed; check the provider baseURL, network, VPN, or proxy settings.";
|
|
618
|
+
if (lower.includes("etimedout") || lower.includes("und_err_connect_timeout") || lower.includes("timeout")) return "Connection timed out before the provider returned a response; check network reachability, VPN/proxy, or the provider status.";
|
|
619
|
+
if (lower.includes("econnrefused")) return "Connection was refused; check the provider endpoint host/port and whether the service is running.";
|
|
620
|
+
if (lower.includes("econnreset") || lower.includes("socket")) return "Connection was reset while contacting the provider; retry, or check VPN/proxy/provider stability.";
|
|
621
|
+
if (lower.includes("certificate") || lower.includes("cert_") || lower.includes("unable_to_verify") || lower.includes("self_signed")) return "TLS certificate validation failed; check corporate proxy or custom CA configuration.";
|
|
622
|
+
if (lower.includes("fetch failed")) return "Network request failed before an HTTP response was received; check network, proxy, DNS, and provider endpoint settings.";
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
function cleanMessage(value) {
|
|
626
|
+
const text = value?.replace(/\s+/g, " ").trim();
|
|
627
|
+
return text || void 0;
|
|
628
|
+
}
|
|
629
|
+
function stringField(object2, key) {
|
|
630
|
+
const value = object2[key];
|
|
631
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
632
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
function unique(values) {
|
|
636
|
+
return [...new Set(values)];
|
|
637
|
+
}
|
|
638
|
+
function safeStringify(value) {
|
|
639
|
+
try {
|
|
640
|
+
return JSON.stringify(value);
|
|
641
|
+
} catch {
|
|
642
|
+
return void 0;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
481
645
|
|
|
482
646
|
// src/providers/retry.ts
|
|
483
647
|
var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -528,8 +692,7 @@ function retryDelay(error, attempt) {
|
|
|
528
692
|
}
|
|
529
693
|
function retryReason(error) {
|
|
530
694
|
if (error instanceof ProviderHttpError) return `HTTP ${error.status}`;
|
|
531
|
-
|
|
532
|
-
return String(error);
|
|
695
|
+
return errorMessage(error);
|
|
533
696
|
}
|
|
534
697
|
function sleep(ms2, signal) {
|
|
535
698
|
return new Promise((resolve, reject) => {
|
|
@@ -3183,7 +3346,8 @@ function buildClaudeCliArgs(config, req, flags, resumeSessionId) {
|
|
|
3183
3346
|
const args = ["-p", "--output-format", config.outputFormat ?? "stream-json", "--input-format", config.inputFormat ?? "text", "--verbose"];
|
|
3184
3347
|
if (flags.includePartialMessages) args.push("--include-partial-messages");
|
|
3185
3348
|
if (req.model) args.push("--model", req.model);
|
|
3186
|
-
|
|
3349
|
+
const maxTurns = req.maxTurns === null ? void 0 : req.maxTurns ?? config.maxTurns;
|
|
3350
|
+
if (maxTurns !== void 0 && flags.maxTurns) args.push("--max-turns", String(maxTurns));
|
|
3187
3351
|
if (config.permissionMode && flags.permissionMode) args.push("--permission-mode", config.permissionMode);
|
|
3188
3352
|
if (config.maxBudgetUsd !== void 0 && flags.maxBudgetUsd) args.push("--max-budget-usd", String(config.maxBudgetUsd));
|
|
3189
3353
|
if (resumeSessionId) args.push("--resume", resumeSessionId);
|
|
@@ -22703,44 +22867,10 @@ function now() {
|
|
|
22703
22867
|
}
|
|
22704
22868
|
|
|
22705
22869
|
// src/permissions/engine.ts
|
|
22706
|
-
import
|
|
22870
|
+
import path12 from "node:path";
|
|
22707
22871
|
|
|
22708
22872
|
// src/permissions/grants.ts
|
|
22709
22873
|
import path9 from "node:path";
|
|
22710
|
-
|
|
22711
|
-
// src/util.ts
|
|
22712
|
-
function stableStringify(value) {
|
|
22713
|
-
return JSON.stringify(sortValue(value));
|
|
22714
|
-
}
|
|
22715
|
-
function sortValue(value) {
|
|
22716
|
-
if (Array.isArray(value)) return value.map(sortValue);
|
|
22717
|
-
if (!value || typeof value !== "object") return value;
|
|
22718
|
-
const object2 = value;
|
|
22719
|
-
const out = {};
|
|
22720
|
-
for (const key of Object.keys(object2).sort()) out[key] = sortValue(object2[key]);
|
|
22721
|
-
return out;
|
|
22722
|
-
}
|
|
22723
|
-
function createSessionId() {
|
|
22724
|
-
return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22725
|
-
}
|
|
22726
|
-
function createRootSessionId() {
|
|
22727
|
-
return `root_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22728
|
-
}
|
|
22729
|
-
function createAgentSessionId() {
|
|
22730
|
-
return `as_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22731
|
-
}
|
|
22732
|
-
function createInvocationId() {
|
|
22733
|
-
return `inv_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22734
|
-
}
|
|
22735
|
-
function preview(value, max = 500) {
|
|
22736
|
-
if (value.length <= max) return value;
|
|
22737
|
-
return `${value.slice(0, max)}...`;
|
|
22738
|
-
}
|
|
22739
|
-
function errorMessage(error) {
|
|
22740
|
-
return error instanceof Error ? error.message : String(error);
|
|
22741
|
-
}
|
|
22742
|
-
|
|
22743
|
-
// src/permissions/grants.ts
|
|
22744
22874
|
var SessionGrants = class {
|
|
22745
22875
|
#keys = /* @__PURE__ */ new Set();
|
|
22746
22876
|
has(key) {
|
|
@@ -22755,6 +22885,9 @@ var SessionGrants = class {
|
|
|
22755
22885
|
};
|
|
22756
22886
|
function grantKeyFor(tool, input2, cwd) {
|
|
22757
22887
|
const object2 = input2 && typeof input2 === "object" && !Array.isArray(input2) ? input2 : {};
|
|
22888
|
+
if (CLAUDE_CODE_WORKSPACE_READ_GRANTS.has(tool)) {
|
|
22889
|
+
return `${tool}:workspace-read`;
|
|
22890
|
+
}
|
|
22758
22891
|
if (tool === "bash") {
|
|
22759
22892
|
const command = typeof object2.command === "string" ? object2.command : "";
|
|
22760
22893
|
return `bash:${firstCommandTokens(command, 2).join(" ")}`;
|
|
@@ -22766,6 +22899,7 @@ function grantKeyFor(tool, input2, cwd) {
|
|
|
22766
22899
|
}
|
|
22767
22900
|
return `${tool}:${stableStringify(input2)}`;
|
|
22768
22901
|
}
|
|
22902
|
+
var CLAUDE_CODE_WORKSPACE_READ_GRANTS = /* @__PURE__ */ new Set(["claudecode.Read", "claudecode.Grep", "claudecode.Glob", "claudecode.LS"]);
|
|
22769
22903
|
function grantKeyForAgentInvocation(agentName, providerProfile) {
|
|
22770
22904
|
return `agent:${agentName}:${providerProfile}`;
|
|
22771
22905
|
}
|
|
@@ -22788,31 +22922,151 @@ function firstCommandTokens(command, count) {
|
|
|
22788
22922
|
return tokens;
|
|
22789
22923
|
}
|
|
22790
22924
|
|
|
22791
|
-
// src/
|
|
22925
|
+
// src/permissions/shell-safety.ts
|
|
22792
22926
|
import path10 from "node:path";
|
|
22927
|
+
function isClearlyReadOnlyShellCommand(command) {
|
|
22928
|
+
const tokens = splitSimpleShellWords(command);
|
|
22929
|
+
if (!tokens?.length) return false;
|
|
22930
|
+
if (tokens.some(isUnsafePathToken)) return false;
|
|
22931
|
+
const executable = path10.basename(tokens[0] ?? "");
|
|
22932
|
+
if (SIMPLE_READ_ONLY_COMMANDS.has(executable)) return true;
|
|
22933
|
+
if (executable === "find") return isReadOnlyFind(tokens);
|
|
22934
|
+
if (executable === "git") return isReadOnlyGit(tokens);
|
|
22935
|
+
return false;
|
|
22936
|
+
}
|
|
22937
|
+
function isPotentiallyReadOnlyShellCommand(command) {
|
|
22938
|
+
const tokens = splitSimpleShellWords(command);
|
|
22939
|
+
if (!tokens?.length) return false;
|
|
22940
|
+
if (tokens.some(isUnsafePathToken)) return false;
|
|
22941
|
+
const executable = path10.basename(tokens[0] ?? "");
|
|
22942
|
+
if (isClearlyReadOnlyShellCommand(command)) return true;
|
|
22943
|
+
if (POTENTIAL_AGENT_JUDGED_COMMANDS.has(executable)) return !hasKnownMutatingToken(executable, tokens);
|
|
22944
|
+
return false;
|
|
22945
|
+
}
|
|
22946
|
+
function splitSimpleShellWords(command) {
|
|
22947
|
+
if (!command.trim() || /[\r\n]/.test(command)) return void 0;
|
|
22948
|
+
const words = [];
|
|
22949
|
+
let current = "";
|
|
22950
|
+
let quote;
|
|
22951
|
+
for (let index = 0; index < command.length; index++) {
|
|
22952
|
+
const char = command[index];
|
|
22953
|
+
if (quote) {
|
|
22954
|
+
if (char === quote) {
|
|
22955
|
+
quote = void 0;
|
|
22956
|
+
continue;
|
|
22957
|
+
}
|
|
22958
|
+
if (quote === '"' && (char === "$" || char === "`")) return void 0;
|
|
22959
|
+
if (char === "\\" && quote === '"' && index + 1 < command.length) {
|
|
22960
|
+
current += command[++index];
|
|
22961
|
+
continue;
|
|
22962
|
+
}
|
|
22963
|
+
current += char;
|
|
22964
|
+
continue;
|
|
22965
|
+
}
|
|
22966
|
+
if (/\s/.test(char)) {
|
|
22967
|
+
if (current) {
|
|
22968
|
+
words.push(current);
|
|
22969
|
+
current = "";
|
|
22970
|
+
}
|
|
22971
|
+
continue;
|
|
22972
|
+
}
|
|
22973
|
+
if (char === "'" || char === '"') {
|
|
22974
|
+
quote = char;
|
|
22975
|
+
continue;
|
|
22976
|
+
}
|
|
22977
|
+
if (SHELL_CONTROL_CHARS.has(char)) return void 0;
|
|
22978
|
+
if (char === "\\" && index + 1 < command.length) {
|
|
22979
|
+
current += command[++index];
|
|
22980
|
+
continue;
|
|
22981
|
+
}
|
|
22982
|
+
current += char;
|
|
22983
|
+
}
|
|
22984
|
+
if (quote) return void 0;
|
|
22985
|
+
if (current) words.push(current);
|
|
22986
|
+
return words;
|
|
22987
|
+
}
|
|
22988
|
+
function isUnsafePathToken(token) {
|
|
22989
|
+
if (token === "." || token === "-") return false;
|
|
22990
|
+
if (token === "~" || token.startsWith("~/") || token.startsWith("/")) return true;
|
|
22991
|
+
if (token === ".." || token.startsWith("../") || token.includes("/../")) return true;
|
|
22992
|
+
const assignmentValue = token.match(/^[A-Za-z_][A-Za-z0-9_-]*=(.+)$/)?.[1];
|
|
22993
|
+
if (assignmentValue) return isUnsafePathToken(assignmentValue);
|
|
22994
|
+
const optionValue = token.match(/^--?[A-Za-z0-9][A-Za-z0-9_-]*=(.+)$/)?.[1];
|
|
22995
|
+
return optionValue ? isUnsafePathToken(optionValue) : false;
|
|
22996
|
+
}
|
|
22997
|
+
function isReadOnlyFind(tokens) {
|
|
22998
|
+
return tokens.slice(1).every((token) => !UNSAFE_FIND_TOKENS.has(token));
|
|
22999
|
+
}
|
|
23000
|
+
function isReadOnlyGit(tokens) {
|
|
23001
|
+
const parsed = gitSubcommand(tokens);
|
|
23002
|
+
if (!parsed) return false;
|
|
23003
|
+
if (!READ_ONLY_GIT_SUBCOMMANDS.has(parsed.command)) return false;
|
|
23004
|
+
if (parsed.command === "branch") return parsed.args.every((arg) => READ_ONLY_GIT_BRANCH_ARGS.has(arg));
|
|
23005
|
+
if (parsed.command === "remote") return parsed.args.length === 0 || parsed.args.every((arg) => arg === "-v" || arg === "--verbose" || arg === "get-url");
|
|
23006
|
+
if (parsed.command === "config") return parsed.args.some((arg) => READ_ONLY_GIT_CONFIG_ARGS.has(arg));
|
|
23007
|
+
return true;
|
|
23008
|
+
}
|
|
23009
|
+
function gitSubcommand(tokens) {
|
|
23010
|
+
let index = 1;
|
|
23011
|
+
while (index < tokens.length) {
|
|
23012
|
+
const token = tokens[index];
|
|
23013
|
+
if (token === "--no-pager") {
|
|
23014
|
+
index++;
|
|
23015
|
+
continue;
|
|
23016
|
+
}
|
|
23017
|
+
if (token === "-C") {
|
|
23018
|
+
index += 2;
|
|
23019
|
+
continue;
|
|
23020
|
+
}
|
|
23021
|
+
if (token?.startsWith("-")) return void 0;
|
|
23022
|
+
return { command: token ?? "", args: tokens.slice(index + 1) };
|
|
23023
|
+
}
|
|
23024
|
+
return void 0;
|
|
23025
|
+
}
|
|
23026
|
+
function hasKnownMutatingToken(executable, tokens) {
|
|
23027
|
+
if (executable === "sed") {
|
|
23028
|
+
return tokens.slice(1).some((token) => token === "-i" || token.startsWith("-i") || /^w(\s|$)/.test(token) || /;\s*w(\s|$)/.test(token));
|
|
23029
|
+
}
|
|
23030
|
+
if (executable === "git") {
|
|
23031
|
+
const parsed = gitSubcommand(tokens);
|
|
23032
|
+
return !parsed || MUTATING_GIT_SUBCOMMANDS.has(parsed.command);
|
|
23033
|
+
}
|
|
23034
|
+
return true;
|
|
23035
|
+
}
|
|
23036
|
+
var SHELL_CONTROL_CHARS = /* @__PURE__ */ new Set([";", "&", "|", ">", "<", "`", "$", "(", ")"]);
|
|
23037
|
+
var SIMPLE_READ_ONLY_COMMANDS = /* @__PURE__ */ new Set(["pwd", "ls", "cat", "head", "tail", "wc", "file", "stat", "du", "tree", "realpath", "basename", "dirname", "rg", "grep", "which", "command", "type", "printf", "echo"]);
|
|
23038
|
+
var POTENTIAL_AGENT_JUDGED_COMMANDS = /* @__PURE__ */ new Set(["sed", "git"]);
|
|
23039
|
+
var UNSAFE_FIND_TOKENS = /* @__PURE__ */ new Set(["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fls", "-fprint", "-fprintf"]);
|
|
23040
|
+
var READ_ONLY_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set(["status", "diff", "log", "show", "ls-files", "grep", "rev-parse", "describe", "blame", "shortlog", "branch", "remote", "config"]);
|
|
23041
|
+
var READ_ONLY_GIT_BRANCH_ARGS = /* @__PURE__ */ new Set(["--show-current", "-a", "--all", "-r", "--remotes", "-v", "-vv", "--verbose", "--color", "--no-color"]);
|
|
23042
|
+
var READ_ONLY_GIT_CONFIG_ARGS = /* @__PURE__ */ new Set(["--get", "--get-all", "--get-regexp", "--list", "-l"]);
|
|
23043
|
+
var MUTATING_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set(["add", "am", "apply", "bisect", "checkout", "cherry-pick", "clean", "clone", "commit", "fetch", "gc", "init", "merge", "mv", "pull", "push", "rebase", "reset", "restore", "revert", "rm", "stash", "switch", "tag", "worktree"]);
|
|
23044
|
+
|
|
23045
|
+
// src/workspace/paths.ts
|
|
23046
|
+
import path11 from "node:path";
|
|
22793
23047
|
function normalizePathForMatch(value) {
|
|
22794
|
-
return value.split(
|
|
23048
|
+
return value.split(path11.sep).join("/");
|
|
22795
23049
|
}
|
|
22796
23050
|
function isInsidePath(root, candidate) {
|
|
22797
|
-
const relative =
|
|
22798
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
23051
|
+
const relative = path11.relative(path11.resolve(root), path11.resolve(candidate));
|
|
23052
|
+
return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
|
|
22799
23053
|
}
|
|
22800
23054
|
function resolveInsideCwd(cwd, inputPath) {
|
|
22801
23055
|
if (typeof inputPath !== "string" || inputPath.length === 0) {
|
|
22802
23056
|
throw new Error("path must be a non-empty string");
|
|
22803
23057
|
}
|
|
22804
|
-
const resolved =
|
|
23058
|
+
const resolved = path11.resolve(cwd, inputPath);
|
|
22805
23059
|
if (!isInsidePath(cwd, resolved)) {
|
|
22806
23060
|
throw new Error(`Path is outside the workspace: ${inputPath}`);
|
|
22807
23061
|
}
|
|
22808
23062
|
return resolved;
|
|
22809
23063
|
}
|
|
22810
23064
|
function relativeToCwd(cwd, absolutePath) {
|
|
22811
|
-
const relative =
|
|
23065
|
+
const relative = path11.relative(cwd, absolutePath);
|
|
22812
23066
|
return normalizePathForMatch(relative || ".");
|
|
22813
23067
|
}
|
|
22814
23068
|
function isEnvFile(filePath) {
|
|
22815
|
-
const base =
|
|
23069
|
+
const base = path11.basename(filePath);
|
|
22816
23070
|
if (base === ".env.example") return false;
|
|
22817
23071
|
return base === ".env" || base.startsWith(".env.");
|
|
22818
23072
|
}
|
|
@@ -22898,7 +23152,7 @@ var PermissionEngine = class {
|
|
|
22898
23152
|
}
|
|
22899
23153
|
if (this.#defaultDecision === "by-category") {
|
|
22900
23154
|
return {
|
|
22901
|
-
decision: defaultDecisionByCategory(ref.toolName),
|
|
23155
|
+
decision: defaultDecisionByCategory(ref.toolName, input2),
|
|
22902
23156
|
grantKey,
|
|
22903
23157
|
source: "category-default"
|
|
22904
23158
|
};
|
|
@@ -22919,7 +23173,7 @@ var PermissionEngine = class {
|
|
|
22919
23173
|
#hardDeny(tool, input2, grantKey) {
|
|
22920
23174
|
const paths = inputPaths(tool, input2);
|
|
22921
23175
|
for (const item of paths) {
|
|
22922
|
-
const resolved =
|
|
23176
|
+
const resolved = path12.resolve(this.#cwd, item);
|
|
22923
23177
|
if (!isInsidePath(this.#cwd, resolved)) {
|
|
22924
23178
|
return {
|
|
22925
23179
|
decision: "deny",
|
|
@@ -22973,7 +23227,7 @@ function matchesRule(rule, tool, input2, cwd) {
|
|
|
22973
23227
|
if (rule.match.pathGlob) {
|
|
22974
23228
|
const paths = inputPaths(tool, input2);
|
|
22975
23229
|
if (paths.length === 0) return false;
|
|
22976
|
-
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd,
|
|
23230
|
+
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path12.resolve(cwd, item))))) {
|
|
22977
23231
|
return false;
|
|
22978
23232
|
}
|
|
22979
23233
|
}
|
|
@@ -23020,7 +23274,7 @@ function rulePriority(rule, ref) {
|
|
|
23020
23274
|
}
|
|
23021
23275
|
function matchesPathRule(pattern, relativePath) {
|
|
23022
23276
|
if (!pattern) return true;
|
|
23023
|
-
if (
|
|
23277
|
+
if (path12.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
|
|
23024
23278
|
return matchGlob(pattern, relativePath);
|
|
23025
23279
|
}
|
|
23026
23280
|
function mostSpecificRule(rules, input2, cwd, ref) {
|
|
@@ -23050,10 +23304,12 @@ function commandInput(input2) {
|
|
|
23050
23304
|
const object2 = input2 && typeof input2 === "object" && !Array.isArray(input2) ? input2 : {};
|
|
23051
23305
|
return typeof object2.command === "string" ? object2.command : "";
|
|
23052
23306
|
}
|
|
23053
|
-
function defaultDecisionByCategory(toolName) {
|
|
23054
|
-
|
|
23307
|
+
function defaultDecisionByCategory(toolName, input2) {
|
|
23308
|
+
if (toolName.toLowerCase() === "bash") return isClearlyReadOnlyShellCommand(commandInput(input2)) ? "allow" : "ask";
|
|
23309
|
+
return READ_ONLY_TOOLS.has(toolName) || ORCHESTRATION_TOOLS.has(toolName) ? "allow" : "ask";
|
|
23055
23310
|
}
|
|
23056
|
-
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "read_file", "glob", "grep"]);
|
|
23311
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "LS", "read_file", "glob", "grep"]);
|
|
23312
|
+
var ORCHESTRATION_TOOLS = /* @__PURE__ */ new Set(["delegate_agent", "cowork", "update_plan", "report_progress", "get_goal"]);
|
|
23057
23313
|
|
|
23058
23314
|
// src/permissions/prompt.ts
|
|
23059
23315
|
import readline2 from "node:readline/promises";
|
|
@@ -23098,13 +23354,13 @@ function permissionLabel(req) {
|
|
|
23098
23354
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
23099
23355
|
return `${req.tool}: ${inputObject.path}`;
|
|
23100
23356
|
}
|
|
23101
|
-
const
|
|
23102
|
-
if (
|
|
23357
|
+
const path30 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
23358
|
+
if (path30) return `${tool}: ${path30}`;
|
|
23103
23359
|
return tool;
|
|
23104
23360
|
}
|
|
23105
23361
|
|
|
23106
23362
|
// src/workspace/write-scope.ts
|
|
23107
|
-
import
|
|
23363
|
+
import path13 from "node:path";
|
|
23108
23364
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
23109
23365
|
var WORKSPACE_SCOPE = "**";
|
|
23110
23366
|
function normalizeWriteScope(cwd, scope) {
|
|
@@ -23126,7 +23382,7 @@ function enforceToolWriteScope(input2) {
|
|
|
23126
23382
|
if (paths.length === 0) return { ok: true, paths: [] };
|
|
23127
23383
|
const checked = [];
|
|
23128
23384
|
for (const target of paths) {
|
|
23129
|
-
const resolved =
|
|
23385
|
+
const resolved = path13.resolve(input2.cwd, target);
|
|
23130
23386
|
const relative = relativeToCwd(input2.cwd, resolved);
|
|
23131
23387
|
checked.push(relative);
|
|
23132
23388
|
const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
|
|
@@ -23150,7 +23406,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
|
|
|
23150
23406
|
return out;
|
|
23151
23407
|
}
|
|
23152
23408
|
function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
|
|
23153
|
-
const resolved =
|
|
23409
|
+
const resolved = path13.resolve(cwd, inputPath);
|
|
23154
23410
|
if (!isInsidePath(cwd, resolved)) {
|
|
23155
23411
|
return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
|
|
23156
23412
|
}
|
|
@@ -23169,9 +23425,9 @@ function normalizeScopePattern(cwd, raw) {
|
|
|
23169
23425
|
if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
|
|
23170
23426
|
if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
|
|
23171
23427
|
const hasGlob2 = /[*?[\]{}]/.test(value);
|
|
23172
|
-
if (
|
|
23428
|
+
if (path13.isAbsolute(value)) {
|
|
23173
23429
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
23174
|
-
const resolved =
|
|
23430
|
+
const resolved = path13.resolve(value);
|
|
23175
23431
|
if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
|
|
23176
23432
|
value = relativeToCwd(cwd, resolved);
|
|
23177
23433
|
}
|
|
@@ -23246,7 +23502,7 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23246
23502
|
return { behavior: "allow", updatedInput: input2, toolUseID: ctx.toolUseID };
|
|
23247
23503
|
}
|
|
23248
23504
|
if (evaluation.decision === "allow") {
|
|
23249
|
-
return
|
|
23505
|
+
return allowResult(input2, ctx.toolUseID, evaluation.source === "grant" ? "user_permanent" : void 0);
|
|
23250
23506
|
}
|
|
23251
23507
|
if (evaluation.decision === "deny") {
|
|
23252
23508
|
emitPermission(options, {
|
|
@@ -23258,15 +23514,7 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23258
23514
|
});
|
|
23259
23515
|
return { behavior: "deny", message: evaluation.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23260
23516
|
}
|
|
23261
|
-
|
|
23262
|
-
type: "permission.requested",
|
|
23263
|
-
callId: ctx.toolUseID,
|
|
23264
|
-
tool: qualifiedName,
|
|
23265
|
-
decision: "ask",
|
|
23266
|
-
targetName: toolName
|
|
23267
|
-
});
|
|
23268
|
-
const prompt = options.permissionPrompt ?? ((req) => askPermission(req, { yes: options.yes }));
|
|
23269
|
-
const answer = await prompt({
|
|
23517
|
+
const permissionRequest = {
|
|
23270
23518
|
tool: qualifiedName,
|
|
23271
23519
|
executor: "claudecode",
|
|
23272
23520
|
qualifiedName,
|
|
@@ -23277,10 +23525,32 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23277
23525
|
proposedDecision: "ask",
|
|
23278
23526
|
reason: ctx.title ?? ctx.decisionReason ?? evaluation.reason,
|
|
23279
23527
|
description: ctx.description ?? ctx.displayName
|
|
23528
|
+
};
|
|
23529
|
+
const automatic = await options.autoPermission?.(permissionRequest);
|
|
23530
|
+
if (automatic?.decision === "allow") return allowResult(input2, ctx.toolUseID);
|
|
23531
|
+
if (automatic?.decision === "deny") {
|
|
23532
|
+
emitPermission(options, {
|
|
23533
|
+
type: "permission.denied",
|
|
23534
|
+
callId: ctx.toolUseID,
|
|
23535
|
+
tool: qualifiedName,
|
|
23536
|
+
reason: automatic.reason,
|
|
23537
|
+
targetName: toolName
|
|
23538
|
+
});
|
|
23539
|
+
return { behavior: "deny", message: automatic.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23540
|
+
}
|
|
23541
|
+
emitPermission(options, {
|
|
23542
|
+
type: "permission.requested",
|
|
23543
|
+
callId: ctx.toolUseID,
|
|
23544
|
+
tool: qualifiedName,
|
|
23545
|
+
decision: "ask",
|
|
23546
|
+
targetName: toolName
|
|
23280
23547
|
});
|
|
23548
|
+
const prompt = options.permissionPrompt ?? ((req) => askPermission(req, { yes: options.yes }));
|
|
23549
|
+
const answer = await prompt(permissionRequest);
|
|
23281
23550
|
if (answer.decision === "allow") {
|
|
23282
|
-
if (answer.always) grants.add(evaluation.grantKey);
|
|
23283
23551
|
if (answer.always) {
|
|
23552
|
+
grants.add(evaluation.grantKey);
|
|
23553
|
+
await options.onAlwaysGrant?.(evaluation.grantKey);
|
|
23284
23554
|
emitPermission(options, {
|
|
23285
23555
|
type: "permission.granted",
|
|
23286
23556
|
callId: ctx.toolUseID,
|
|
@@ -23288,7 +23558,10 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23288
23558
|
targetName: toolName
|
|
23289
23559
|
});
|
|
23290
23560
|
}
|
|
23291
|
-
return {
|
|
23561
|
+
return {
|
|
23562
|
+
...allowResult(input2, ctx.toolUseID, answer.always ? "user_permanent" : "user_temporary"),
|
|
23563
|
+
updatedPermissions: answer.always ? ctx.suggestions : void 0
|
|
23564
|
+
};
|
|
23292
23565
|
}
|
|
23293
23566
|
emitPermission(options, {
|
|
23294
23567
|
type: "permission.denied",
|
|
@@ -23300,6 +23573,14 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23300
23573
|
return { behavior: "deny", message: answer.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23301
23574
|
};
|
|
23302
23575
|
}
|
|
23576
|
+
function allowResult(input2, toolUseID, decisionClassification) {
|
|
23577
|
+
return {
|
|
23578
|
+
behavior: "allow",
|
|
23579
|
+
updatedInput: input2,
|
|
23580
|
+
toolUseID,
|
|
23581
|
+
...decisionClassification ? { decisionClassification } : {}
|
|
23582
|
+
};
|
|
23583
|
+
}
|
|
23303
23584
|
function isImplicitPlanExit(options, toolName, source) {
|
|
23304
23585
|
if (options.permissionMode !== "plan" || toolName !== "ExitPlanMode") return false;
|
|
23305
23586
|
return source === "default" || source === "category-default";
|
|
@@ -23429,7 +23710,7 @@ function buildClaudeCodeSdkOptions(config, req, abortController, resumeSessionId
|
|
|
23429
23710
|
permissionMode,
|
|
23430
23711
|
disallowedTools: uniqueDisallowedTools.length ? uniqueDisallowedTools : void 0,
|
|
23431
23712
|
tools,
|
|
23432
|
-
maxTurns: config.maxTurns,
|
|
23713
|
+
maxTurns: req.maxTurns === null ? void 0 : req.maxTurns ?? config.maxTurns,
|
|
23433
23714
|
maxBudgetUsd: config.maxBudgetUsd,
|
|
23434
23715
|
extraArgs: config.useBareMode ? { bare: null } : void 0,
|
|
23435
23716
|
allowDangerouslySkipPermissions: permissionMode === "bypassPermissions" ? true : void 0,
|
|
@@ -23440,7 +23721,10 @@ function buildClaudeCodeSdkOptions(config, req, abortController, resumeSessionId
|
|
|
23440
23721
|
permissionMode,
|
|
23441
23722
|
defaultDecision: config.defaultDecision,
|
|
23442
23723
|
writeScope: req.writeScope,
|
|
23724
|
+
grants: req.grants,
|
|
23725
|
+
onAlwaysGrant: req.onPermissionGrant,
|
|
23443
23726
|
permissionPrompt: req.permissionPrompt,
|
|
23727
|
+
autoPermission: req.autoPermission,
|
|
23444
23728
|
yes: req.yes,
|
|
23445
23729
|
sessionId: req.sessionId,
|
|
23446
23730
|
emit: req.emit,
|
|
@@ -23489,6 +23773,9 @@ function mapSdkMessage(message) {
|
|
|
23489
23773
|
if (message.type === "assistant") {
|
|
23490
23774
|
return mapAssistantMessage(message);
|
|
23491
23775
|
}
|
|
23776
|
+
if (message.type === "user") {
|
|
23777
|
+
return mapUserMessage(message);
|
|
23778
|
+
}
|
|
23492
23779
|
if (message.type === "stream_event") {
|
|
23493
23780
|
return mapPartialAssistantMessage(message);
|
|
23494
23781
|
}
|
|
@@ -23528,6 +23815,35 @@ function mapAssistantMessage(message) {
|
|
|
23528
23815
|
}
|
|
23529
23816
|
return events;
|
|
23530
23817
|
}
|
|
23818
|
+
function mapUserMessage(message) {
|
|
23819
|
+
const events = [];
|
|
23820
|
+
const content = Array.isArray(message.message.content) ? message.message.content : [];
|
|
23821
|
+
for (const block of content) {
|
|
23822
|
+
if (!block || typeof block !== "object" || block.type !== "tool_result") continue;
|
|
23823
|
+
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : `tool_result_${events.length + 1}`;
|
|
23824
|
+
events.push({
|
|
23825
|
+
type: "tool.completed",
|
|
23826
|
+
callId,
|
|
23827
|
+
name: "unknown",
|
|
23828
|
+
ok: block.is_error !== true,
|
|
23829
|
+
preview: toolResultPreview(block.content),
|
|
23830
|
+
raw: message
|
|
23831
|
+
});
|
|
23832
|
+
}
|
|
23833
|
+
return events;
|
|
23834
|
+
}
|
|
23835
|
+
function toolResultPreview(value) {
|
|
23836
|
+
if (typeof value === "string") return value.slice(0, 800);
|
|
23837
|
+
if (Array.isArray(value)) {
|
|
23838
|
+
return value.map((item) => {
|
|
23839
|
+
if (typeof item === "string") return item;
|
|
23840
|
+
if (item && typeof item === "object" && typeof item.text === "string") return String(item.text);
|
|
23841
|
+
return JSON.stringify(item);
|
|
23842
|
+
}).join("\n").slice(0, 800);
|
|
23843
|
+
}
|
|
23844
|
+
if (value === void 0) return "";
|
|
23845
|
+
return JSON.stringify(value).slice(0, 800);
|
|
23846
|
+
}
|
|
23531
23847
|
function mapPartialAssistantMessage(message) {
|
|
23532
23848
|
const event = message.event;
|
|
23533
23849
|
if (event.type !== "content_block_delta") return [];
|
|
@@ -23548,10 +23864,62 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
|
|
|
23548
23864
|
}
|
|
23549
23865
|
|
|
23550
23866
|
// src/config.ts
|
|
23867
|
+
var COWORK_AGENT_NAMES = ["planner", "architect", "supervisor", "researcher", "builder", "reviewer", "specialist"];
|
|
23868
|
+
var COWORK_AGENT_MIGRATIONS = {
|
|
23869
|
+
"codex-planner": { replacement: "planner", note: "planning and coordination work moved to planner" },
|
|
23870
|
+
"codex-reviewer": { replacement: "supervisor", note: "supervisory risk review moved to supervisor" },
|
|
23871
|
+
"claudecode-explorer": { replacement: "researcher", note: "repository exploration moved to researcher; use reviewer for diff-level review" },
|
|
23872
|
+
"claudecode-builder": { replacement: "builder", note: "bounded implementation moved to builder" },
|
|
23873
|
+
claudecode: { replacement: "builder", note: "Claude Code cowork implementation moved to builder" }
|
|
23874
|
+
};
|
|
23551
23875
|
var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
|
|
23552
23876
|
var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
|
|
23553
23877
|
var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
|
|
23554
23878
|
var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
|
|
23879
|
+
var DEFAULT_COWORK_AGENT_PROVIDERS = {
|
|
23880
|
+
planner: ["codex", "anthropic", "openai"],
|
|
23881
|
+
architect: ["codex", "anthropic"],
|
|
23882
|
+
supervisor: ["codex", "anthropic"],
|
|
23883
|
+
researcher: ["codex", "claudecode", "anthropic"],
|
|
23884
|
+
builder: ["claudecode", "anthropic", "openai"],
|
|
23885
|
+
reviewer: ["claudecode", "anthropic", "codex"],
|
|
23886
|
+
specialist: ["claudecode", "anthropic"]
|
|
23887
|
+
};
|
|
23888
|
+
var DEFAULT_COWORK_PERMISSION_OVERLAY = {
|
|
23889
|
+
providerToolNamespaces: {
|
|
23890
|
+
claudecode: "claudecode"
|
|
23891
|
+
},
|
|
23892
|
+
modeMappings: {
|
|
23893
|
+
"read-only": {
|
|
23894
|
+
demianTools: ["read_file", "grep", "glob", "list_dir"],
|
|
23895
|
+
providerTools: {
|
|
23896
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23897
|
+
},
|
|
23898
|
+
defaultDecision: "allow"
|
|
23899
|
+
},
|
|
23900
|
+
review: {
|
|
23901
|
+
demianTools: ["read_file", "grep", "glob", "git_diff"],
|
|
23902
|
+
providerTools: {
|
|
23903
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23904
|
+
},
|
|
23905
|
+
defaultDecision: "allow"
|
|
23906
|
+
},
|
|
23907
|
+
write: {
|
|
23908
|
+
demianTools: ["read_file", "grep", "glob", "edit_file", "write_file", "bash"],
|
|
23909
|
+
providerTools: {
|
|
23910
|
+
claudecode: ["Read", "Grep", "Glob", "LS", "Edit", "MultiEdit", "Write", "Bash"]
|
|
23911
|
+
},
|
|
23912
|
+
defaultDecision: "ask"
|
|
23913
|
+
},
|
|
23914
|
+
manage: {
|
|
23915
|
+
demianTools: ["read_file", "grep", "glob", "task_control", "git_diff"],
|
|
23916
|
+
providerTools: {
|
|
23917
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23918
|
+
},
|
|
23919
|
+
defaultDecision: "ask"
|
|
23920
|
+
}
|
|
23921
|
+
}
|
|
23922
|
+
};
|
|
23555
23923
|
var defaultConfig = {
|
|
23556
23924
|
agentMode: "single-agent",
|
|
23557
23925
|
defaultAgent: "general",
|
|
@@ -23592,7 +23960,31 @@ var defaultConfig = {
|
|
|
23592
23960
|
defaultMergeStrategy: "synthesize",
|
|
23593
23961
|
defaultInvocationDecision: "ask",
|
|
23594
23962
|
readOnlyParallelism: true,
|
|
23595
|
-
writerIsolation: "off"
|
|
23963
|
+
writerIsolation: "off",
|
|
23964
|
+
providers: ["codex", "claudecode"],
|
|
23965
|
+
preflight: {
|
|
23966
|
+
mode: "session-start",
|
|
23967
|
+
ttlMs: 10 * 60 * 1e3
|
|
23968
|
+
},
|
|
23969
|
+
fallback: {
|
|
23970
|
+
nonWrite: {
|
|
23971
|
+
onConnectionError: "next-provider",
|
|
23972
|
+
onTokenLimit: "next-provider",
|
|
23973
|
+
onAuthFailure: "skip-provider",
|
|
23974
|
+
onAllProvidersFailed: "fail-member"
|
|
23975
|
+
},
|
|
23976
|
+
write: {
|
|
23977
|
+
onFailure: "repair-from-worktree",
|
|
23978
|
+
maxRepairAttempts: 1,
|
|
23979
|
+
readChangedFilesBeforeRepair: true
|
|
23980
|
+
}
|
|
23981
|
+
},
|
|
23982
|
+
routing: {
|
|
23983
|
+
preset: "codex-manager-claudecode-worker",
|
|
23984
|
+
agentProviders: structuredClone(DEFAULT_COWORK_AGENT_PROVIDERS),
|
|
23985
|
+
agentModelPools: {}
|
|
23986
|
+
},
|
|
23987
|
+
permissionOverlay: structuredClone(DEFAULT_COWORK_PERMISSION_OVERLAY)
|
|
23596
23988
|
},
|
|
23597
23989
|
goals: {
|
|
23598
23990
|
enabled: true,
|
|
@@ -23672,8 +24064,13 @@ var defaultConfig = {
|
|
|
23672
24064
|
build: "build",
|
|
23673
24065
|
execute: "build",
|
|
23674
24066
|
claudecode: "build",
|
|
23675
|
-
|
|
23676
|
-
|
|
24067
|
+
planner: "plan",
|
|
24068
|
+
architect: "plan",
|
|
24069
|
+
supervisor: "readonly",
|
|
24070
|
+
researcher: "readonly",
|
|
24071
|
+
builder: "build",
|
|
24072
|
+
reviewer: "readonly",
|
|
24073
|
+
specialist: "readonly"
|
|
23677
24074
|
},
|
|
23678
24075
|
profiles: {
|
|
23679
24076
|
default: {
|
|
@@ -23859,8 +24256,8 @@ var defaultConfig = {
|
|
|
23859
24256
|
};
|
|
23860
24257
|
async function loadConfig(options) {
|
|
23861
24258
|
const configs = [];
|
|
23862
|
-
const userConfigDir =
|
|
23863
|
-
const configPaths = [
|
|
24259
|
+
const userConfigDir = path14.join(os7.homedir(), ".demian");
|
|
24260
|
+
const configPaths = [path14.join(userConfigDir, "config.json"), path14.join(userConfigDir, "config.jsond"), options.configPath].filter(Boolean);
|
|
23864
24261
|
for (const filePath of configPaths) {
|
|
23865
24262
|
if (!fs7.existsSync(filePath)) continue;
|
|
23866
24263
|
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
@@ -24048,14 +24445,7 @@ function mergeConfig(base, overlay) {
|
|
|
24048
24445
|
...base.delegation,
|
|
24049
24446
|
...overlay.delegation
|
|
24050
24447
|
},
|
|
24051
|
-
cowork:
|
|
24052
|
-
...base.cowork,
|
|
24053
|
-
...overlay.cowork,
|
|
24054
|
-
maxConcurrentPerProvider: {
|
|
24055
|
-
...base.cowork.maxConcurrentPerProvider,
|
|
24056
|
-
...overlay.cowork?.maxConcurrentPerProvider
|
|
24057
|
-
}
|
|
24058
|
-
},
|
|
24448
|
+
cowork: mergeCoworkConfig(base.cowork, overlay.cowork),
|
|
24059
24449
|
goals: {
|
|
24060
24450
|
...base.goals,
|
|
24061
24451
|
...overlay.goals,
|
|
@@ -24227,6 +24617,168 @@ function uniqueProviderCandidates(values) {
|
|
|
24227
24617
|
}
|
|
24228
24618
|
return out;
|
|
24229
24619
|
}
|
|
24620
|
+
function mergeCoworkConfig(base, overlay) {
|
|
24621
|
+
if (!overlay) return structuredClone(base);
|
|
24622
|
+
return {
|
|
24623
|
+
...base,
|
|
24624
|
+
...overlay,
|
|
24625
|
+
providers: overlay.providers !== void 0 ? canonicalStringArray(overlay.providers, "cowork.providers") : [...base.providers],
|
|
24626
|
+
maxConcurrentPerProvider: {
|
|
24627
|
+
...base.maxConcurrentPerProvider,
|
|
24628
|
+
...overlay.maxConcurrentPerProvider
|
|
24629
|
+
},
|
|
24630
|
+
preflight: {
|
|
24631
|
+
...base.preflight,
|
|
24632
|
+
...overlay.preflight
|
|
24633
|
+
},
|
|
24634
|
+
fallback: {
|
|
24635
|
+
nonWrite: {
|
|
24636
|
+
...base.fallback.nonWrite,
|
|
24637
|
+
...overlay.fallback?.nonWrite
|
|
24638
|
+
},
|
|
24639
|
+
write: {
|
|
24640
|
+
...base.fallback.write,
|
|
24641
|
+
...overlay.fallback?.write
|
|
24642
|
+
}
|
|
24643
|
+
},
|
|
24644
|
+
routing: {
|
|
24645
|
+
...base.routing,
|
|
24646
|
+
...overlay.routing,
|
|
24647
|
+
agentProviders: mergeCoworkAgentProviders(base.routing.agentProviders, overlay.routing?.agentProviders),
|
|
24648
|
+
agentModelPools: mergeCoworkAgentModelPools(base.routing.agentModelPools, overlay.routing?.agentModelPools)
|
|
24649
|
+
},
|
|
24650
|
+
permissionOverlay: mergeCoworkPermissionOverlay(base.permissionOverlay, overlay.permissionOverlay)
|
|
24651
|
+
};
|
|
24652
|
+
}
|
|
24653
|
+
function mergeCoworkAgentProviders(base, overlay) {
|
|
24654
|
+
const out = { ...cloneAgentProviders(base) };
|
|
24655
|
+
for (const [rawAgent, rawProviders] of Object.entries(overlay ?? {})) {
|
|
24656
|
+
const agent = validateCoworkAgentName(rawAgent, `cowork.routing.agentProviders.${rawAgent}`);
|
|
24657
|
+
out[agent] = canonicalStringArray(rawProviders, `cowork.routing.agentProviders.${agent}`);
|
|
24658
|
+
}
|
|
24659
|
+
return out;
|
|
24660
|
+
}
|
|
24661
|
+
function cloneAgentProviders(input2) {
|
|
24662
|
+
const out = {};
|
|
24663
|
+
for (const [agent, providers] of Object.entries(input2)) {
|
|
24664
|
+
out[agent] = [...providers ?? []];
|
|
24665
|
+
}
|
|
24666
|
+
return out;
|
|
24667
|
+
}
|
|
24668
|
+
function mergeCoworkAgentModelPools(base, overlay) {
|
|
24669
|
+
const out = { ...cloneAgentModelPools(base) };
|
|
24670
|
+
for (const [rawAgent, rawEntries] of Object.entries(overlay ?? {})) {
|
|
24671
|
+
const agent = validateCoworkAgentName(rawAgent, `cowork.routing.agentModelPools.${rawAgent}`);
|
|
24672
|
+
out[agent] = canonicalCoworkModelPool(rawEntries, `cowork.routing.agentModelPools.${agent}`);
|
|
24673
|
+
}
|
|
24674
|
+
return out;
|
|
24675
|
+
}
|
|
24676
|
+
function cloneAgentModelPools(input2) {
|
|
24677
|
+
const out = {};
|
|
24678
|
+
for (const [agent, entries] of Object.entries(input2)) {
|
|
24679
|
+
out[agent] = (entries ?? []).map((entry) => ({ ...entry }));
|
|
24680
|
+
}
|
|
24681
|
+
return out;
|
|
24682
|
+
}
|
|
24683
|
+
function canonicalCoworkModelPool(value, pathName) {
|
|
24684
|
+
if (!Array.isArray(value)) throw new Error(`${pathName} must be an array.`);
|
|
24685
|
+
const out = [];
|
|
24686
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24687
|
+
for (const [index, raw] of value.entries()) {
|
|
24688
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${pathName}[${index}] must be an object.`);
|
|
24689
|
+
const entry = raw;
|
|
24690
|
+
if (typeof entry.provider !== "string" || !entry.provider.trim()) throw new Error(`${pathName}[${index}].provider must be a non-empty string.`);
|
|
24691
|
+
const provider = entry.provider.trim();
|
|
24692
|
+
const model = typeof entry.model === "string" && entry.model.trim() ? entry.model.trim() : void 0;
|
|
24693
|
+
const modelProfileName = typeof entry.modelProfileName === "string" && entry.modelProfileName.trim() ? entry.modelProfileName.trim() : void 0;
|
|
24694
|
+
const key = `${provider}\0${modelProfileName ?? ""}\0${model ?? ""}`;
|
|
24695
|
+
if (seen.has(key)) continue;
|
|
24696
|
+
seen.add(key);
|
|
24697
|
+
out.push({
|
|
24698
|
+
provider,
|
|
24699
|
+
...model ? { model } : {},
|
|
24700
|
+
...modelProfileName ? { modelProfileName } : {}
|
|
24701
|
+
});
|
|
24702
|
+
}
|
|
24703
|
+
return out;
|
|
24704
|
+
}
|
|
24705
|
+
function mergeCoworkPermissionOverlay(base, overlay) {
|
|
24706
|
+
const providerToolNamespaces = {
|
|
24707
|
+
...base.providerToolNamespaces,
|
|
24708
|
+
...stringMap(overlay?.providerToolNamespaces, "cowork.permissionOverlay.providerToolNamespaces")
|
|
24709
|
+
};
|
|
24710
|
+
const modeMappings = {};
|
|
24711
|
+
for (const [mode, mapping] of Object.entries(base.modeMappings)) {
|
|
24712
|
+
if (mapping) modeMappings[mode] = clonePermissionModeOverlay(mapping);
|
|
24713
|
+
}
|
|
24714
|
+
for (const [rawMode, rawMapping] of Object.entries(overlay?.modeMappings ?? {})) {
|
|
24715
|
+
const mode = validateCoworkPermissionIntent(rawMode, `cowork.permissionOverlay.modeMappings.${rawMode}`);
|
|
24716
|
+
const baseMapping = modeMappings[mode];
|
|
24717
|
+
if (!rawMapping || typeof rawMapping !== "object" || Array.isArray(rawMapping)) throw new Error(`cowork.permissionOverlay.modeMappings.${mode} must be an object.`);
|
|
24718
|
+
const mapping = rawMapping;
|
|
24719
|
+
modeMappings[mode] = {
|
|
24720
|
+
demianTools: mapping.demianTools !== void 0 ? canonicalStringArray(mapping.demianTools, `cowork.permissionOverlay.modeMappings.${mode}.demianTools`) : [...baseMapping?.demianTools ?? []],
|
|
24721
|
+
providerTools: {
|
|
24722
|
+
...baseMapping ? cloneProviderTools(baseMapping.providerTools) : {},
|
|
24723
|
+
...providerToolsMap(mapping.providerTools, `cowork.permissionOverlay.modeMappings.${mode}.providerTools`)
|
|
24724
|
+
},
|
|
24725
|
+
defaultDecision: validatePermissionDefault(mapping.defaultDecision ?? baseMapping?.defaultDecision ?? "ask", `cowork.permissionOverlay.modeMappings.${mode}.defaultDecision`)
|
|
24726
|
+
};
|
|
24727
|
+
}
|
|
24728
|
+
return { providerToolNamespaces, modeMappings };
|
|
24729
|
+
}
|
|
24730
|
+
function clonePermissionModeOverlay(mapping) {
|
|
24731
|
+
return {
|
|
24732
|
+
demianTools: [...mapping.demianTools],
|
|
24733
|
+
providerTools: cloneProviderTools(mapping.providerTools),
|
|
24734
|
+
defaultDecision: mapping.defaultDecision
|
|
24735
|
+
};
|
|
24736
|
+
}
|
|
24737
|
+
function cloneProviderTools(input2) {
|
|
24738
|
+
return Object.fromEntries(Object.entries(input2).map(([provider, tools]) => [provider, [...tools]]));
|
|
24739
|
+
}
|
|
24740
|
+
function providerToolsMap(value, pathName) {
|
|
24741
|
+
if (value === void 0) return {};
|
|
24742
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${pathName} must be an object.`);
|
|
24743
|
+
const out = {};
|
|
24744
|
+
for (const [provider, tools] of Object.entries(value)) {
|
|
24745
|
+
out[provider] = canonicalStringArray(tools, `${pathName}.${provider}`);
|
|
24746
|
+
}
|
|
24747
|
+
return out;
|
|
24748
|
+
}
|
|
24749
|
+
function stringMap(value, pathName) {
|
|
24750
|
+
if (value === void 0) return {};
|
|
24751
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${pathName} must be an object.`);
|
|
24752
|
+
const out = {};
|
|
24753
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
24754
|
+
if (typeof raw !== "string" || !raw.trim()) throw new Error(`${pathName}.${key} must be a non-empty string.`);
|
|
24755
|
+
out[key] = raw.trim();
|
|
24756
|
+
}
|
|
24757
|
+
return out;
|
|
24758
|
+
}
|
|
24759
|
+
function canonicalStringArray(value, pathName) {
|
|
24760
|
+
if (!Array.isArray(value)) throw new Error(`${pathName} must be an array of strings.`);
|
|
24761
|
+
const out = [];
|
|
24762
|
+
for (const [index, item] of value.entries()) {
|
|
24763
|
+
if (typeof item !== "string" || !item.trim()) throw new Error(`${pathName}[${index}] must be a non-empty string.`);
|
|
24764
|
+
out.push(item.trim());
|
|
24765
|
+
}
|
|
24766
|
+
return [...new Set(out)];
|
|
24767
|
+
}
|
|
24768
|
+
function validatePermissionDefault(value, pathName) {
|
|
24769
|
+
if (value === "allow" || value === "ask" || value === "deny") return value;
|
|
24770
|
+
throw new Error(`${pathName} must be allow, ask, or deny.`);
|
|
24771
|
+
}
|
|
24772
|
+
function validateCoworkAgentName(value, pathName = "cowork agent") {
|
|
24773
|
+
if (COWORK_AGENT_NAMES.includes(value)) return value;
|
|
24774
|
+
const migration = COWORK_AGENT_MIGRATIONS[value];
|
|
24775
|
+
if (migration) throw new Error(`${pathName} uses removed cowork agent "${value}". Use "${migration.replacement}" instead (${migration.note}).`);
|
|
24776
|
+
throw new Error(`${pathName} must be one of: ${COWORK_AGENT_NAMES.join(", ")}.`);
|
|
24777
|
+
}
|
|
24778
|
+
function validateCoworkPermissionIntent(value, pathName) {
|
|
24779
|
+
if (value === "read-only" || value === "review" || value === "write" || value === "manage") return value;
|
|
24780
|
+
throw new Error(`${pathName} must be read-only, review, write, or manage.`);
|
|
24781
|
+
}
|
|
24230
24782
|
function mergeModelProfile(providerConfig, profile) {
|
|
24231
24783
|
const merged = {
|
|
24232
24784
|
...providerConfig,
|
|
@@ -24651,7 +25203,7 @@ function safeJson(value) {
|
|
|
24651
25203
|
}
|
|
24652
25204
|
|
|
24653
25205
|
// src/hooks/dispatcher.ts
|
|
24654
|
-
import
|
|
25206
|
+
import path16 from "node:path";
|
|
24655
25207
|
|
|
24656
25208
|
// src/hooks/command.ts
|
|
24657
25209
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -24744,7 +25296,7 @@ var blockDangerousBashHook = {
|
|
|
24744
25296
|
};
|
|
24745
25297
|
|
|
24746
25298
|
// src/hooks/builtin/protect-env-files.ts
|
|
24747
|
-
import
|
|
25299
|
+
import path15 from "node:path";
|
|
24748
25300
|
var protectEnvFilesHook = {
|
|
24749
25301
|
name: "protect-env-files",
|
|
24750
25302
|
event: "PreToolUse",
|
|
@@ -24752,7 +25304,7 @@ var protectEnvFilesHook = {
|
|
|
24752
25304
|
run(ctx) {
|
|
24753
25305
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
24754
25306
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
24755
|
-
const filePath = typeof input2.path === "string" ?
|
|
25307
|
+
const filePath = typeof input2.path === "string" ? path15.resolve(ctx.cwd, input2.path) : "";
|
|
24756
25308
|
if (filePath && isEnvFile(filePath)) {
|
|
24757
25309
|
return {
|
|
24758
25310
|
decision: "block",
|
|
@@ -24867,7 +25419,7 @@ function matchesHook(match, ctx) {
|
|
|
24867
25419
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
24868
25420
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
24869
25421
|
if (!filePath) return false;
|
|
24870
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
25422
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path16.resolve(ctx.cwd, filePath)))) return false;
|
|
24871
25423
|
}
|
|
24872
25424
|
return true;
|
|
24873
25425
|
}
|
|
@@ -24876,7 +25428,7 @@ function matchesHook(match, ctx) {
|
|
|
24876
25428
|
import crypto5 from "node:crypto";
|
|
24877
25429
|
import { mkdir as mkdir6, readFile as readFile7, rename as rename4, writeFile as writeFile5 } from "node:fs/promises";
|
|
24878
25430
|
import os9 from "node:os";
|
|
24879
|
-
import
|
|
25431
|
+
import path17 from "node:path";
|
|
24880
25432
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
24881
25433
|
var PING_TIMEOUT_MS = 1500;
|
|
24882
25434
|
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
@@ -25188,7 +25740,7 @@ async function fetchJson(url, options) {
|
|
|
25188
25740
|
return text ? JSON.parse(text) : {};
|
|
25189
25741
|
}
|
|
25190
25742
|
function cachePath(cacheKey) {
|
|
25191
|
-
return
|
|
25743
|
+
return path17.join(os9.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
25192
25744
|
}
|
|
25193
25745
|
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
25194
25746
|
if (!allow) return void 0;
|
|
@@ -25203,7 +25755,7 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
|
25203
25755
|
}
|
|
25204
25756
|
async function writeCatalogCache(cacheKey, result) {
|
|
25205
25757
|
const filePath = cachePath(cacheKey);
|
|
25206
|
-
await mkdir6(
|
|
25758
|
+
await mkdir6(path17.dirname(filePath), { recursive: true, mode: 448 });
|
|
25207
25759
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
25208
25760
|
await writeFile5(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
25209
25761
|
await rename4(temp, filePath);
|
|
@@ -25286,7 +25838,7 @@ function withTimeout(promise, timeoutMs) {
|
|
|
25286
25838
|
|
|
25287
25839
|
// src/multimodal.ts
|
|
25288
25840
|
import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
|
|
25289
|
-
import
|
|
25841
|
+
import path18 from "node:path";
|
|
25290
25842
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
25291
25843
|
async function buildUserContent(prompt, images = [], options) {
|
|
25292
25844
|
if (images.length === 0) return prompt;
|
|
@@ -25314,7 +25866,7 @@ async function imageToUrl(input2, options) {
|
|
|
25314
25866
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
25315
25867
|
}
|
|
25316
25868
|
function mimeFromPath(filePath) {
|
|
25317
|
-
switch (
|
|
25869
|
+
switch (path18.extname(filePath).toLowerCase()) {
|
|
25318
25870
|
case ".jpg":
|
|
25319
25871
|
case ".jpeg":
|
|
25320
25872
|
return "image/jpeg";
|
|
@@ -25325,7 +25877,7 @@ function mimeFromPath(filePath) {
|
|
|
25325
25877
|
case ".webp":
|
|
25326
25878
|
return "image/webp";
|
|
25327
25879
|
default:
|
|
25328
|
-
throw new Error(`Unsupported image extension: ${
|
|
25880
|
+
throw new Error(`Unsupported image extension: ${path18.extname(filePath) || "(none)"}`);
|
|
25329
25881
|
}
|
|
25330
25882
|
}
|
|
25331
25883
|
|
|
@@ -25333,7 +25885,7 @@ function mimeFromPath(filePath) {
|
|
|
25333
25885
|
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
|
|
25334
25886
|
import fs8 from "node:fs";
|
|
25335
25887
|
import os10 from "node:os";
|
|
25336
|
-
import
|
|
25888
|
+
import path19 from "node:path";
|
|
25337
25889
|
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
25338
25890
|
var PersistentGrantStore = class {
|
|
25339
25891
|
filePath;
|
|
@@ -25371,15 +25923,15 @@ var PersistentGrantStore = class {
|
|
|
25371
25923
|
};
|
|
25372
25924
|
}
|
|
25373
25925
|
async #write(file) {
|
|
25374
|
-
await mkdir7(
|
|
25926
|
+
await mkdir7(path19.dirname(this.filePath), { recursive: true });
|
|
25375
25927
|
await writeFile6(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
25376
25928
|
`, { mode: 384 });
|
|
25377
25929
|
}
|
|
25378
25930
|
};
|
|
25379
25931
|
function resolveGrantPath(cwd, config) {
|
|
25380
|
-
if (config.path) return
|
|
25381
|
-
if ((config.scope ?? "project") === "user") return
|
|
25382
|
-
return
|
|
25932
|
+
if (config.path) return path19.resolve(cwd, config.path);
|
|
25933
|
+
if ((config.scope ?? "project") === "user") return path19.join(os10.homedir(), ".demian", "grants.json");
|
|
25934
|
+
return path19.join(cwd, ".demian", "grants.json");
|
|
25383
25935
|
}
|
|
25384
25936
|
function isGrantRecord(value) {
|
|
25385
25937
|
if (!value || typeof value !== "object") return false;
|
|
@@ -25401,9 +25953,15 @@ function defaultDecisionForPermissionPreset(preset) {
|
|
|
25401
25953
|
if (preset === "full") return "allow";
|
|
25402
25954
|
return "by-category";
|
|
25403
25955
|
}
|
|
25404
|
-
function
|
|
25956
|
+
function invocationDecisionForPermissionPreset(preset) {
|
|
25957
|
+
if (preset === "deny") return "deny";
|
|
25958
|
+
if (preset === "ask") return "ask";
|
|
25959
|
+
return "allow";
|
|
25960
|
+
}
|
|
25961
|
+
function automaticAnswerForPermissionPreset(preset, yes, request) {
|
|
25405
25962
|
if (preset === "deny") return { decision: "deny", reason: "permission preset is deny" };
|
|
25406
25963
|
if (preset === "full" || yes) return { decision: "allow" };
|
|
25964
|
+
if (preset === "auto" && request && isAutoAllowedPermissionRequest(request)) return { decision: "allow" };
|
|
25407
25965
|
return void 0;
|
|
25408
25966
|
}
|
|
25409
25967
|
function permissionDecisionLine(answer, preset) {
|
|
@@ -25411,13 +25969,19 @@ function permissionDecisionLine(answer, preset) {
|
|
|
25411
25969
|
return `permission: denied${answer.reason ? `: ${answer.reason}` : ""}`;
|
|
25412
25970
|
}
|
|
25413
25971
|
function applyPermissionPresetToAgent(agent, preset) {
|
|
25972
|
+
const invocationDecision = invocationDecisionForPermissionPreset(preset);
|
|
25414
25973
|
return {
|
|
25415
25974
|
...agent,
|
|
25975
|
+
delegation: agent.delegation ? {
|
|
25976
|
+
...agent.delegation,
|
|
25977
|
+
invocationDecision
|
|
25978
|
+
} : agent.delegation,
|
|
25416
25979
|
defaultDecision: defaultDecisionForPermissionPreset(preset)
|
|
25417
25980
|
};
|
|
25418
25981
|
}
|
|
25419
25982
|
function applyPermissionPresetToConfig(config, preset) {
|
|
25420
25983
|
const defaultDecision = defaultDecisionForPermissionPreset(preset);
|
|
25984
|
+
const invocationDecision = invocationDecisionForPermissionPreset(preset);
|
|
25421
25985
|
const profiles = Object.fromEntries(
|
|
25422
25986
|
Object.entries(config.claudeCodePermissions.profiles).map(([name, profile]) => [
|
|
25423
25987
|
name,
|
|
@@ -25429,22 +25993,36 @@ function applyPermissionPresetToConfig(config, preset) {
|
|
|
25429
25993
|
);
|
|
25430
25994
|
return {
|
|
25431
25995
|
...config,
|
|
25996
|
+
delegation: {
|
|
25997
|
+
...config.delegation,
|
|
25998
|
+
defaultInvocationDecision: invocationDecision
|
|
25999
|
+
},
|
|
26000
|
+
cowork: {
|
|
26001
|
+
...config.cowork,
|
|
26002
|
+
defaultInvocationDecision: invocationDecision
|
|
26003
|
+
},
|
|
25432
26004
|
claudeCodePermissions: {
|
|
25433
26005
|
...config.claudeCodePermissions,
|
|
25434
26006
|
profiles
|
|
25435
26007
|
}
|
|
25436
26008
|
};
|
|
25437
26009
|
}
|
|
26010
|
+
function isAutoAllowedPermissionRequest(request) {
|
|
26011
|
+
if (request.targetType === "agent" || request.targetType === "cowork") return true;
|
|
26012
|
+
if (request.shape === "agent" || request.shape === "cowork-group") return true;
|
|
26013
|
+
return AUTO_ALLOWED_ORCHESTRATION_TOOLS.has(request.tool);
|
|
26014
|
+
}
|
|
26015
|
+
var AUTO_ALLOWED_ORCHESTRATION_TOOLS = /* @__PURE__ */ new Set(["delegate_agent", "cowork", "update_plan", "report_progress", "get_goal"]);
|
|
25438
26016
|
|
|
25439
26017
|
// src/root-session.ts
|
|
25440
|
-
import { cp, mkdtemp, rm as rm2 } from "node:fs/promises";
|
|
26018
|
+
import { cp, mkdtemp, readFile as readFile14, rm as rm2 } from "node:fs/promises";
|
|
25441
26019
|
import os12 from "node:os";
|
|
25442
|
-
import
|
|
26020
|
+
import path29 from "node:path";
|
|
25443
26021
|
|
|
25444
26022
|
// src/transcript.ts
|
|
25445
26023
|
import { mkdir as mkdir8, appendFile, writeFile as writeFile7 } from "node:fs/promises";
|
|
25446
26024
|
import os11 from "node:os";
|
|
25447
|
-
import
|
|
26025
|
+
import path20 from "node:path";
|
|
25448
26026
|
var TranscriptWriter = class {
|
|
25449
26027
|
filePath;
|
|
25450
26028
|
#enabled;
|
|
@@ -25452,8 +26030,8 @@ var TranscriptWriter = class {
|
|
|
25452
26030
|
#queue = Promise.resolve();
|
|
25453
26031
|
constructor(options) {
|
|
25454
26032
|
this.#enabled = options.enabled !== false;
|
|
25455
|
-
const dir =
|
|
25456
|
-
this.filePath =
|
|
26033
|
+
const dir = path20.join(options.storageDir ?? defaultDemianStorageDir(), "transcripts", options.sessionId);
|
|
26034
|
+
this.filePath = path20.join(dir, "session.jsonl");
|
|
25457
26035
|
this.#ready = this.#enabled ? mkdir8(dir, { recursive: true }).then(() => writeFile7(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
25458
26036
|
}
|
|
25459
26037
|
write(event) {
|
|
@@ -25470,14 +26048,14 @@ var TranscriptWriter = class {
|
|
|
25470
26048
|
}
|
|
25471
26049
|
};
|
|
25472
26050
|
function defaultDemianStorageDir() {
|
|
25473
|
-
return
|
|
26051
|
+
return path20.join(os11.homedir(), ".demian");
|
|
25474
26052
|
}
|
|
25475
26053
|
|
|
25476
26054
|
// src/external-runtime/snapshot-diff.ts
|
|
25477
26055
|
import { createHash as createHash2 } from "node:crypto";
|
|
25478
26056
|
import { createReadStream } from "node:fs";
|
|
25479
26057
|
import { readdir, readFile as readFile10, stat as stat4 } from "node:fs/promises";
|
|
25480
|
-
import
|
|
26058
|
+
import path21 from "node:path";
|
|
25481
26059
|
|
|
25482
26060
|
// src/workspace/diff.ts
|
|
25483
26061
|
var CONTEXT_LINES = 3;
|
|
@@ -25687,7 +26265,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
25687
26265
|
}
|
|
25688
26266
|
async function walk(root, relativeDir, entries, options) {
|
|
25689
26267
|
if (entries.size >= options.maxFiles) return;
|
|
25690
|
-
const absoluteDir =
|
|
26268
|
+
const absoluteDir = path21.join(root, relativeDir);
|
|
25691
26269
|
let items;
|
|
25692
26270
|
try {
|
|
25693
26271
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -25698,8 +26276,8 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
25698
26276
|
if (entries.size >= options.maxFiles) return;
|
|
25699
26277
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
25700
26278
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
25701
|
-
const relativePath = normalizeRelativePath(
|
|
25702
|
-
const absolutePath =
|
|
26279
|
+
const relativePath = normalizeRelativePath(path21.join(relativeDir, item.name));
|
|
26280
|
+
const absolutePath = path21.join(root, relativePath);
|
|
25703
26281
|
if (item.isDirectory()) {
|
|
25704
26282
|
await walk(root, relativePath, entries, options);
|
|
25705
26283
|
continue;
|
|
@@ -25735,7 +26313,7 @@ function sha256File(filePath) {
|
|
|
25735
26313
|
});
|
|
25736
26314
|
}
|
|
25737
26315
|
function normalizeRelativePath(value) {
|
|
25738
|
-
return value.split(
|
|
26316
|
+
return value.split(path21.sep).join("/");
|
|
25739
26317
|
}
|
|
25740
26318
|
|
|
25741
26319
|
// src/external-runtime/session-runner.ts
|
|
@@ -25745,10 +26323,16 @@ var ExternalAgentSessionRunner = class {
|
|
|
25745
26323
|
events;
|
|
25746
26324
|
#options;
|
|
25747
26325
|
#transcript;
|
|
26326
|
+
#grants;
|
|
26327
|
+
#persistentStore;
|
|
25748
26328
|
#activeTools = /* @__PURE__ */ new Map();
|
|
26329
|
+
#toolStats = { requested: 0, completed: 0, failed: 0 };
|
|
26330
|
+
#recentToolEvents = [];
|
|
25749
26331
|
#cancellationArtifactsEmitted = false;
|
|
25750
26332
|
constructor(options) {
|
|
25751
26333
|
this.#options = options;
|
|
26334
|
+
this.#grants = options.grants ?? new SessionGrants();
|
|
26335
|
+
if (options.persistentGrants?.enabled) this.#persistentStore = new PersistentGrantStore(options.cwd, options.persistentGrants);
|
|
25752
26336
|
this.events = options.eventBus ?? new EventBus();
|
|
25753
26337
|
this.#transcript = options.transcriptWriter ?? new TranscriptWriter({
|
|
25754
26338
|
cwd: options.cwd,
|
|
@@ -25758,6 +26342,7 @@ var ExternalAgentSessionRunner = class {
|
|
|
25758
26342
|
}
|
|
25759
26343
|
async run() {
|
|
25760
26344
|
const options = this.#options;
|
|
26345
|
+
for (const key of await this.#persistentStore?.load(options.cwd) ?? []) this.#grants.add(key);
|
|
25761
26346
|
const messages = [{ role: "system", content: buildSystemPrompt(options.agent) }, ...options.history ?? [], { role: "user", content: options.prompt }];
|
|
25762
26347
|
let finalAnswer = "";
|
|
25763
26348
|
let finalTextEmitted = false;
|
|
@@ -25801,8 +26386,12 @@ var ExternalAgentSessionRunner = class {
|
|
|
25801
26386
|
if (options.signal?.aborted) return this.endCancelled(messages, finalAnswer, externalSessionId, usage, snapshot);
|
|
25802
26387
|
if (recoveredInvalidResume || !shouldRecoverInvalidResume(failure2.error, runtimePreviousExternalSessionId)) {
|
|
25803
26388
|
this.emit({ type: "session.failed", sessionId: this.sessionId, error: failure2.error, ts: now(), ...this.eventContext() });
|
|
25804
|
-
|
|
25805
|
-
|
|
26389
|
+
const answer = this.externalTerminalFinalAnswer("error", finalAnswer || failure2.error);
|
|
26390
|
+
messages.push({ role: "assistant", content: answer });
|
|
26391
|
+
this.emit({ type: "session.ended", sessionId: this.sessionId, reason: "error", ts: now(), ...this.eventContext() });
|
|
26392
|
+
this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
26393
|
+
await this.#transcript.flush();
|
|
26394
|
+
return { sessionId: this.sessionId, externalSessionId, finalAnswer: answer, messages, endReason: "error", usage };
|
|
25806
26395
|
}
|
|
25807
26396
|
const policy = options.onInvalidResume ?? "fresh";
|
|
25808
26397
|
recoveredInvalidResume = true;
|
|
@@ -25854,11 +26443,15 @@ var ExternalAgentSessionRunner = class {
|
|
|
25854
26443
|
model: options.model,
|
|
25855
26444
|
agent: options.agent,
|
|
25856
26445
|
providerName: options.providerName,
|
|
26446
|
+
maxTurns: options.maxTurns,
|
|
25857
26447
|
signal: options.signal ?? new AbortController().signal,
|
|
25858
26448
|
historyPolicy: options.historyPolicy ?? "passthrough-resume",
|
|
25859
26449
|
previousExternalSessionId,
|
|
25860
26450
|
writeScope: options.writeScope,
|
|
25861
26451
|
permissionPrompt: options.permissionPrompt,
|
|
26452
|
+
autoPermission: options.autoPermission,
|
|
26453
|
+
grants: this.#grants,
|
|
26454
|
+
onPermissionGrant: (grantKey) => this.#persistentStore?.add(options.cwd, grantKey) ?? Promise.resolve(),
|
|
25862
26455
|
yes: options.yes,
|
|
25863
26456
|
emit: (runtimeEvent) => this.emit(runtimeEvent)
|
|
25864
26457
|
})) {
|
|
@@ -25873,6 +26466,8 @@ var ExternalAgentSessionRunner = class {
|
|
|
25873
26466
|
this.emit({ type: "model.text", sessionId: this.sessionId, text: event.text, ts: now(), ...this.eventContext() });
|
|
25874
26467
|
} else if (event.type === "tool.requested") {
|
|
25875
26468
|
this.#activeTools.set(event.callId, { name: event.name });
|
|
26469
|
+
this.#toolStats.requested += 1;
|
|
26470
|
+
this.recordRecentToolEvent(`\uC694\uCCAD: ${event.name}`);
|
|
25876
26471
|
this.emit({
|
|
25877
26472
|
type: "tool.requested",
|
|
25878
26473
|
sessionId: this.sessionId,
|
|
@@ -25886,8 +26481,13 @@ var ExternalAgentSessionRunner = class {
|
|
|
25886
26481
|
...this.eventContext()
|
|
25887
26482
|
});
|
|
25888
26483
|
} else if (event.type === "tool.completed") {
|
|
26484
|
+
const activeTool = this.#activeTools.get(event.callId);
|
|
26485
|
+
const name = event.name === "unknown" ? activeTool?.name ?? event.name : event.name;
|
|
25889
26486
|
this.#activeTools.delete(event.callId);
|
|
25890
|
-
|
|
26487
|
+
if (event.ok) this.#toolStats.completed += 1;
|
|
26488
|
+
else this.#toolStats.failed += 1;
|
|
26489
|
+
this.recordRecentToolEvent(`${event.ok ? "\uC644\uB8CC" : "\uC2E4\uD328"}: ${name}${event.preview ? ` - ${event.preview}` : ""}`);
|
|
26490
|
+
this.emit({ type: "tool.completed", sessionId: this.sessionId, callId: event.callId, name, ok: event.ok, preview: event.preview ?? "", executor: "claudecode", qualifiedName: `claudecode.${name}`, agent: options.agent.name, ts: now(), ...this.eventContext() });
|
|
25891
26491
|
} else if (event.type === "usage") {
|
|
25892
26492
|
capture.onUsage(event.usage);
|
|
25893
26493
|
this.emit({ type: "model.usage", sessionId: this.sessionId, usage: event.usage, ts: now(), ...this.eventContext() });
|
|
@@ -25906,12 +26506,31 @@ var ExternalAgentSessionRunner = class {
|
|
|
25906
26506
|
}
|
|
25907
26507
|
async endCancelled(messages, finalAnswer, externalSessionId, usage, snapshot) {
|
|
25908
26508
|
await this.emitCancellationArtifacts(snapshot);
|
|
25909
|
-
const answer = finalAnswer || "Cancelled.";
|
|
26509
|
+
const answer = this.externalTerminalFinalAnswer("cancelled", finalAnswer || "Cancelled.");
|
|
25910
26510
|
messages.push({ role: "assistant", content: answer });
|
|
25911
26511
|
this.emit({ type: "session.ended", sessionId: this.sessionId, reason: "cancelled", ts: now(), ...this.eventContext() });
|
|
26512
|
+
this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
25912
26513
|
await this.#transcript.flush();
|
|
25913
26514
|
return { sessionId: this.sessionId, externalSessionId, finalAnswer: answer, messages, endReason: "cancelled", usage };
|
|
25914
26515
|
}
|
|
26516
|
+
recordRecentToolEvent(text) {
|
|
26517
|
+
this.#recentToolEvents.push(text.replace(/\s+/g, " ").trim());
|
|
26518
|
+
if (this.#recentToolEvents.length > 5) this.#recentToolEvents.splice(0, this.#recentToolEvents.length - 5);
|
|
26519
|
+
}
|
|
26520
|
+
externalTerminalFinalAnswer(reason, lastMessage) {
|
|
26521
|
+
const lines = ["\uC791\uC5C5\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "", `\uC885\uB8CC \uC774\uC720: ${reason === "cancelled" ? "\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4." : "\uC678\uBD80 \uB7F0\uD0C0\uC784 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4."}`];
|
|
26522
|
+
if (lastMessage.trim()) lines.push(`\uB9C8\uC9C0\uB9C9 \uB7F0\uD0C0\uC784 \uBA54\uC2DC\uC9C0: ${lastMessage.trim()}`);
|
|
26523
|
+
lines.push("", "\uD604\uC7AC\uAE4C\uC9C0 \uC9C4\uD589\uD55C \uB0B4\uC6A9\uC740 \uC544\uB798\uC640 \uAC19\uC2B5\uB2C8\uB2E4.");
|
|
26524
|
+
const totalTools = this.#toolStats.requested;
|
|
26525
|
+
if (totalTools > 0) {
|
|
26526
|
+
lines.push(`- Claude Code \uB7F0\uD0C0\uC784\uC5D0\uC11C \uB3C4\uAD6C ${totalTools}\uAC1C\uB97C \uC694\uCCAD\uD588\uACE0, \uC644\uB8CC ${this.#toolStats.completed}\uAC1C, \uC2E4\uD328 ${this.#toolStats.failed}\uAC1C\uB85C \uAE30\uB85D\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`);
|
|
26527
|
+
if (this.#recentToolEvents.length > 0) lines.push(`- \uCD5C\uADFC \uB3C4\uAD6C \uD750\uB984: ${this.#recentToolEvents.join("; ")}`);
|
|
26528
|
+
} else {
|
|
26529
|
+
lines.push("- \uC544\uC9C1 \uC644\uB8CC\uB41C \uB3C4\uAD6C \uC0AC\uC6A9 \uAE30\uB85D\uC740 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
26530
|
+
}
|
|
26531
|
+
lines.push("", "\uC774\uC5B4\uC11C \uC9C4\uD589\uD558\uBA74 \uC704 \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uD655\uC778\uD55C \uC9C0\uC810\uBD80\uD130 \uB2E4\uC2DC \uC774\uC5B4\uAC00\uACA0\uC2B5\uB2C8\uB2E4.");
|
|
26532
|
+
return lines.join("\n");
|
|
26533
|
+
}
|
|
25915
26534
|
async createAbortSnapshot() {
|
|
25916
26535
|
try {
|
|
25917
26536
|
return await createWorkspaceSnapshot(this.#options.cwd);
|
|
@@ -26133,7 +26752,7 @@ var ProgressLedger = class {
|
|
|
26133
26752
|
return this.#plan;
|
|
26134
26753
|
}
|
|
26135
26754
|
};
|
|
26136
|
-
var statuses = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "blocked", "skipped", "failed"]);
|
|
26755
|
+
var statuses = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "blocked", "skipped", "failed", "cancelled"]);
|
|
26137
26756
|
var FINAL_REVIEW_STEP_ID = "final_review";
|
|
26138
26757
|
var FINAL_REVIEW_TITLE = "Final review and verification";
|
|
26139
26758
|
var FINAL_REVIEW_DETAIL = "Review changes, run checks, and confirm the result before finishing.";
|
|
@@ -26340,6 +26959,7 @@ function normalizeActiveStep(plan, preferredActive) {
|
|
|
26340
26959
|
const inProgress = plan.steps.filter((step) => step.status === "in_progress");
|
|
26341
26960
|
let active = preferredActive === null ? void 0 : typeof preferredActive === "string" ? preferredActive : void 0;
|
|
26342
26961
|
if (active && !plan.steps.some((step) => step.id === active)) active = void 0;
|
|
26962
|
+
if (active && isTerminal(plan.steps.find((step) => step.id === active)?.status ?? "pending")) active = void 0;
|
|
26343
26963
|
if (active) {
|
|
26344
26964
|
for (const step of plan.steps) {
|
|
26345
26965
|
if (step.id === active) {
|
|
@@ -26402,7 +27022,7 @@ function isStatus(value) {
|
|
|
26402
27022
|
return typeof value === "string" && statuses.has(value);
|
|
26403
27023
|
}
|
|
26404
27024
|
function isTerminal(status) {
|
|
26405
|
-
return status === "completed" || status === "blocked" || status === "skipped" || status === "failed";
|
|
27025
|
+
return status === "completed" || status === "blocked" || status === "skipped" || status === "failed" || status === "cancelled";
|
|
26406
27026
|
}
|
|
26407
27027
|
function parseLevel(value) {
|
|
26408
27028
|
if (value === "info" || value === "success" || value === "warning" || value === "error") return value;
|
|
@@ -26429,16 +27049,16 @@ function fail(content, metadata) {
|
|
|
26429
27049
|
|
|
26430
27050
|
// src/tools/output.ts
|
|
26431
27051
|
import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
|
|
26432
|
-
import
|
|
27052
|
+
import path22 from "node:path";
|
|
26433
27053
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
26434
27054
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
26435
27055
|
async function capToolOutput(result, options) {
|
|
26436
27056
|
const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
|
|
26437
27057
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
26438
27058
|
if (bytes <= capBytes) return result;
|
|
26439
|
-
const dir =
|
|
27059
|
+
const dir = path22.join(options.cwd, ".demian", "tmp");
|
|
26440
27060
|
await mkdir9(dir, { recursive: true });
|
|
26441
|
-
const outputPath =
|
|
27061
|
+
const outputPath = path22.join(dir, `output-${safeCallId(options.callId)}.txt`);
|
|
26442
27062
|
await writeFile8(outputPath, result.content, "utf8");
|
|
26443
27063
|
return {
|
|
26444
27064
|
...result,
|
|
@@ -26446,7 +27066,7 @@ async function capToolOutput(result, options) {
|
|
|
26446
27066
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
26447
27067
|
`
|
|
26448
27068
|
|
|
26449
|
-
[Full output saved to ${
|
|
27069
|
+
[Full output saved to ${path22.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
26450
27070
|
|
|
26451
27071
|
`,
|
|
26452
27072
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -26471,8 +27091,10 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
26471
27091
|
}
|
|
26472
27092
|
|
|
26473
27093
|
// src/tools/read-file.ts
|
|
27094
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
26474
27095
|
import { open as open2, stat as stat5 } from "node:fs/promises";
|
|
26475
|
-
import
|
|
27096
|
+
import { createInterface } from "node:readline";
|
|
27097
|
+
import path23 from "node:path";
|
|
26476
27098
|
|
|
26477
27099
|
// src/tools/validation.ts
|
|
26478
27100
|
function assertObject(input2, toolName) {
|
|
@@ -26481,7 +27103,7 @@ function assertObject(input2, toolName) {
|
|
|
26481
27103
|
}
|
|
26482
27104
|
return input2;
|
|
26483
27105
|
}
|
|
26484
|
-
function
|
|
27106
|
+
function stringField2(input2, key, options = {}) {
|
|
26485
27107
|
const value = input2[key];
|
|
26486
27108
|
if (typeof value !== "string" || !options.allowEmpty && value.length === 0) {
|
|
26487
27109
|
throw new Error(`${key} must be ${options.allowEmpty ? "a string" : "a non-empty string"}`);
|
|
@@ -26521,12 +27143,19 @@ function positiveInteger(value, fallback, key) {
|
|
|
26521
27143
|
}
|
|
26522
27144
|
|
|
26523
27145
|
// src/tools/read-file.ts
|
|
26524
|
-
var MAX_BYTES = 1024 * 1024;
|
|
26525
27146
|
var SAMPLE_BYTES = 4096;
|
|
26526
27147
|
var DEFAULT_LIMIT = 2e3;
|
|
27148
|
+
var MAX_LIMIT = 2e3;
|
|
26527
27149
|
var readFileTool = {
|
|
26528
27150
|
name: "read_file",
|
|
26529
|
-
description: "Read a text file inside the workspace.
|
|
27151
|
+
description: "Read a text file inside the workspace. Streams large files and returns line-numbered output for the requested range.",
|
|
27152
|
+
metadata: {
|
|
27153
|
+
category: "workspace.read",
|
|
27154
|
+
categoryPath: ["workspace", "read"],
|
|
27155
|
+
sideEffect: "none",
|
|
27156
|
+
defaultDecision: "allow",
|
|
27157
|
+
trust: "builtin"
|
|
27158
|
+
},
|
|
26530
27159
|
inputSchema: {
|
|
26531
27160
|
type: "object",
|
|
26532
27161
|
additionalProperties: false,
|
|
@@ -26534,32 +27163,30 @@ var readFileTool = {
|
|
|
26534
27163
|
properties: {
|
|
26535
27164
|
path: { type: "string", description: "Path to read, relative to cwd or absolute inside cwd." },
|
|
26536
27165
|
offset: { type: "number", description: "1-indexed line offset. Defaults to 1." },
|
|
26537
|
-
limit: { type: "number", description: "Maximum number of lines. Defaults to 2000." }
|
|
27166
|
+
limit: { type: "number", description: "Maximum number of lines. Defaults to 2000 and is capped at 2000." }
|
|
26538
27167
|
}
|
|
26539
27168
|
},
|
|
26540
27169
|
async execute(input2, ctx) {
|
|
26541
27170
|
const object2 = assertObject(input2, "read_file");
|
|
26542
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
27171
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
26543
27172
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
26544
|
-
const
|
|
27173
|
+
const requestedLimit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
27174
|
+
const limit = Math.min(requestedLimit, MAX_LIMIT);
|
|
26545
27175
|
const info = await stat5(filePath);
|
|
26546
27176
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
26547
|
-
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
26548
27177
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
26549
27178
|
if (looksBinary(sample)) throw new Error(`Refusing to read binary file: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
26550
|
-
const
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26555
|
-
|
|
26556
|
-
|
|
26557
|
-
|
|
26558
|
-
|
|
26559
|
-
|
|
26560
|
-
|
|
26561
|
-
totalLines: lines.length,
|
|
26562
|
-
truncated
|
|
27179
|
+
const slice = await readLineSlice(filePath, offset, limit, ctx.signal);
|
|
27180
|
+
return ok(formatLineSlice(slice), {
|
|
27181
|
+
path: path23.relative(ctx.cwd, filePath),
|
|
27182
|
+
offset,
|
|
27183
|
+
limit,
|
|
27184
|
+
requestedLimit,
|
|
27185
|
+
lines: slice.lines.length,
|
|
27186
|
+
totalLines: slice.totalLines,
|
|
27187
|
+
truncated: slice.truncated,
|
|
27188
|
+
nextOffset: slice.nextOffset,
|
|
27189
|
+
fileSizeBytes: info.size
|
|
26563
27190
|
});
|
|
26564
27191
|
}
|
|
26565
27192
|
};
|
|
@@ -26573,15 +27200,50 @@ async function readBytes(filePath, length) {
|
|
|
26573
27200
|
await handle.close();
|
|
26574
27201
|
}
|
|
26575
27202
|
}
|
|
26576
|
-
async function
|
|
26577
|
-
|
|
27203
|
+
async function readLineSlice(filePath, offset, limit, signal) {
|
|
27204
|
+
if (signal.aborted) throw new Error("read_file was aborted");
|
|
27205
|
+
const stream = createReadStream2(filePath, { encoding: "utf8" });
|
|
27206
|
+
const reader = createInterface({ input: stream, crlfDelay: Infinity });
|
|
27207
|
+
const abort = () => stream.destroy(new Error("read_file was aborted"));
|
|
27208
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
27209
|
+
const lines = [];
|
|
27210
|
+
let lineNumber = 0;
|
|
27211
|
+
let truncated = false;
|
|
26578
27212
|
try {
|
|
26579
|
-
const
|
|
26580
|
-
|
|
26581
|
-
|
|
27213
|
+
for await (const line of reader) {
|
|
27214
|
+
lineNumber++;
|
|
27215
|
+
if (lineNumber < offset) continue;
|
|
27216
|
+
if (lines.length < limit) {
|
|
27217
|
+
lines.push(line);
|
|
27218
|
+
continue;
|
|
27219
|
+
}
|
|
27220
|
+
truncated = true;
|
|
27221
|
+
break;
|
|
27222
|
+
}
|
|
26582
27223
|
} finally {
|
|
26583
|
-
|
|
27224
|
+
signal.removeEventListener("abort", abort);
|
|
27225
|
+
reader.close();
|
|
27226
|
+
stream.destroy();
|
|
26584
27227
|
}
|
|
27228
|
+
return {
|
|
27229
|
+
lines,
|
|
27230
|
+
offset,
|
|
27231
|
+
totalLines: truncated ? void 0 : lineNumber,
|
|
27232
|
+
truncated,
|
|
27233
|
+
nextOffset: truncated ? offset + lines.length : void 0
|
|
27234
|
+
};
|
|
27235
|
+
}
|
|
27236
|
+
function formatLineSlice(slice) {
|
|
27237
|
+
if (slice.lines.length === 0) {
|
|
27238
|
+
if (slice.totalLines !== void 0) return `... (no lines at offset ${slice.offset}; file has ${slice.totalLines} lines)`;
|
|
27239
|
+
return `... (no lines at offset ${slice.offset})`;
|
|
27240
|
+
}
|
|
27241
|
+
const lastLine = slice.offset + slice.lines.length - 1;
|
|
27242
|
+
const width = String(lastLine).length;
|
|
27243
|
+
const content = slice.lines.map((line, index) => `${String(slice.offset + index).padStart(width, " ")} | ${line}`).join("\n");
|
|
27244
|
+
if (!slice.truncated || slice.nextOffset === void 0) return content;
|
|
27245
|
+
return `${content}
|
|
27246
|
+
... (more lines available; continue with offset ${slice.nextOffset})`;
|
|
26585
27247
|
}
|
|
26586
27248
|
function looksBinary(buffer) {
|
|
26587
27249
|
if (buffer.length === 0) return false;
|
|
@@ -26595,10 +27257,17 @@ function looksBinary(buffer) {
|
|
|
26595
27257
|
|
|
26596
27258
|
// src/tools/write-file.ts
|
|
26597
27259
|
import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
|
|
26598
|
-
import
|
|
27260
|
+
import path24 from "node:path";
|
|
26599
27261
|
var writeFileTool = {
|
|
26600
27262
|
name: "write_file",
|
|
26601
27263
|
description: "Create or replace a text file inside the workspace.",
|
|
27264
|
+
metadata: {
|
|
27265
|
+
category: "workspace.write",
|
|
27266
|
+
categoryPath: ["workspace", "write"],
|
|
27267
|
+
sideEffect: "workspace",
|
|
27268
|
+
defaultDecision: "ask",
|
|
27269
|
+
trust: "builtin"
|
|
27270
|
+
},
|
|
26602
27271
|
inputSchema: {
|
|
26603
27272
|
type: "object",
|
|
26604
27273
|
additionalProperties: false,
|
|
@@ -26610,10 +27279,10 @@ var writeFileTool = {
|
|
|
26610
27279
|
},
|
|
26611
27280
|
async execute(input2, ctx) {
|
|
26612
27281
|
const object2 = assertObject(input2, "write_file");
|
|
26613
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
26614
|
-
const content =
|
|
27282
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
27283
|
+
const content = stringField2(object2, "content", { allowEmpty: true });
|
|
26615
27284
|
const before = await readOptionalTextFile(filePath);
|
|
26616
|
-
await mkdir10(
|
|
27285
|
+
await mkdir10(path24.dirname(filePath), { recursive: true });
|
|
26617
27286
|
await writeFile9(filePath, content, "utf8");
|
|
26618
27287
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
26619
27288
|
const diff = createTextDiff(before, content, relative);
|
|
@@ -26642,6 +27311,13 @@ import { readFile as readFile12, writeFile as writeFile10 } from "node:fs/promis
|
|
|
26642
27311
|
var editFileTool = {
|
|
26643
27312
|
name: "edit_file",
|
|
26644
27313
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
27314
|
+
metadata: {
|
|
27315
|
+
category: "workspace.write",
|
|
27316
|
+
categoryPath: ["workspace", "write"],
|
|
27317
|
+
sideEffect: "workspace",
|
|
27318
|
+
defaultDecision: "ask",
|
|
27319
|
+
trust: "builtin"
|
|
27320
|
+
},
|
|
26645
27321
|
inputSchema: {
|
|
26646
27322
|
type: "object",
|
|
26647
27323
|
additionalProperties: false,
|
|
@@ -26655,9 +27331,9 @@ var editFileTool = {
|
|
|
26655
27331
|
},
|
|
26656
27332
|
async execute(input2, ctx) {
|
|
26657
27333
|
const object2 = assertObject(input2, "edit_file");
|
|
26658
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
26659
|
-
const oldString =
|
|
26660
|
-
const newString =
|
|
27334
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
27335
|
+
const oldString = stringField2(object2, "oldString");
|
|
27336
|
+
const newString = stringField2(object2, "newString", { allowEmpty: true });
|
|
26661
27337
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
26662
27338
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
26663
27339
|
const before = await readFile12(filePath, "utf8");
|
|
@@ -26689,7 +27365,7 @@ function countMatches(text, needle) {
|
|
|
26689
27365
|
|
|
26690
27366
|
// src/tools/bash.ts
|
|
26691
27367
|
import { spawn as spawn5 } from "node:child_process";
|
|
26692
|
-
import
|
|
27368
|
+
import path26 from "node:path";
|
|
26693
27369
|
|
|
26694
27370
|
// src/sandbox/env-only.ts
|
|
26695
27371
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -26749,7 +27425,7 @@ function bwrapPath() {
|
|
|
26749
27425
|
|
|
26750
27426
|
// src/sandbox/macos.ts
|
|
26751
27427
|
import { existsSync as existsSync3 } from "node:fs";
|
|
26752
|
-
import
|
|
27428
|
+
import path25 from "node:path";
|
|
26753
27429
|
function canUseMacOSSandbox() {
|
|
26754
27430
|
return process.platform === "darwin" && existsSync3("/usr/bin/sandbox-exec");
|
|
26755
27431
|
}
|
|
@@ -26775,7 +27451,7 @@ function macosProfile(cwd, config) {
|
|
|
26775
27451
|
}
|
|
26776
27452
|
if (mode === "workspace-write") {
|
|
26777
27453
|
lines.push("(deny file-write*)");
|
|
26778
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
27454
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path25.resolve(cwd))}"))`);
|
|
26779
27455
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
26780
27456
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
26781
27457
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -26805,6 +27481,13 @@ var MAX_TIMEOUT_MS = 6e5;
|
|
|
26805
27481
|
var bashTool = {
|
|
26806
27482
|
name: "bash",
|
|
26807
27483
|
description: "Run a shell command inside the workspace.",
|
|
27484
|
+
metadata: {
|
|
27485
|
+
category: "process.shell",
|
|
27486
|
+
categoryPath: ["process", "shell"],
|
|
27487
|
+
sideEffect: "process",
|
|
27488
|
+
defaultDecision: "ask",
|
|
27489
|
+
trust: "builtin"
|
|
27490
|
+
},
|
|
26808
27491
|
inputSchema: {
|
|
26809
27492
|
type: "object",
|
|
26810
27493
|
additionalProperties: false,
|
|
@@ -26817,7 +27500,7 @@ var bashTool = {
|
|
|
26817
27500
|
},
|
|
26818
27501
|
async execute(input2, ctx) {
|
|
26819
27502
|
const object2 = assertObject(input2, "bash");
|
|
26820
|
-
const command =
|
|
27503
|
+
const command = stringField2(object2, "command");
|
|
26821
27504
|
const workdirInput = optionalStringField(object2, "workdir");
|
|
26822
27505
|
const workdir = workdirInput ? resolveInsideCwd(ctx.cwd, workdirInput) : ctx.cwd;
|
|
26823
27506
|
const requestedTimeout = optionalNumberField(object2, "timeoutMs");
|
|
@@ -26837,7 +27520,7 @@ ${result.stderr}` : ""
|
|
|
26837
27520
|
].filter(Boolean).join("\n");
|
|
26838
27521
|
return ok(content, {
|
|
26839
27522
|
command,
|
|
26840
|
-
workdir:
|
|
27523
|
+
workdir: path26.relative(ctx.cwd, workdir) || ".",
|
|
26841
27524
|
exitCode: result.exitCode,
|
|
26842
27525
|
signal: result.signal,
|
|
26843
27526
|
timedOut: result.timedOut,
|
|
@@ -26890,11 +27573,18 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
26890
27573
|
// src/tools/grep.ts
|
|
26891
27574
|
import { spawn as spawn6 } from "node:child_process";
|
|
26892
27575
|
import { readdir as readdir2, readFile as readFile13, stat as stat6 } from "node:fs/promises";
|
|
26893
|
-
import
|
|
27576
|
+
import path27 from "node:path";
|
|
26894
27577
|
var MAX_MATCHES = 200;
|
|
26895
27578
|
var grepTool = {
|
|
26896
27579
|
name: "grep",
|
|
26897
27580
|
description: "Search text files inside the workspace. Uses rg when available.",
|
|
27581
|
+
metadata: {
|
|
27582
|
+
category: "workspace.search",
|
|
27583
|
+
categoryPath: ["workspace", "search"],
|
|
27584
|
+
sideEffect: "none",
|
|
27585
|
+
defaultDecision: "allow",
|
|
27586
|
+
trust: "builtin"
|
|
27587
|
+
},
|
|
26898
27588
|
inputSchema: {
|
|
26899
27589
|
type: "object",
|
|
26900
27590
|
additionalProperties: false,
|
|
@@ -26907,7 +27597,7 @@ var grepTool = {
|
|
|
26907
27597
|
},
|
|
26908
27598
|
async execute(input2, ctx) {
|
|
26909
27599
|
const object2 = assertObject(input2, "grep");
|
|
26910
|
-
const pattern =
|
|
27600
|
+
const pattern = stringField2(object2, "pattern");
|
|
26911
27601
|
const baseInput = optionalStringField(object2, "path");
|
|
26912
27602
|
const glob = optionalStringField(object2, "glob");
|
|
26913
27603
|
const base = baseInput ? resolveInsideCwd(ctx.cwd, baseInput) : ctx.cwd;
|
|
@@ -26965,7 +27655,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
26965
27655
|
}
|
|
26966
27656
|
function rewriteRgPath(cwd, line) {
|
|
26967
27657
|
const [file, rest] = splitFirst(line, ":");
|
|
26968
|
-
if (!
|
|
27658
|
+
if (!path27.isAbsolute(file)) return line;
|
|
26969
27659
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
26970
27660
|
}
|
|
26971
27661
|
function splitFirst(value, delimiter) {
|
|
@@ -26982,7 +27672,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
26982
27672
|
if (isIgnoredPath(relative)) return;
|
|
26983
27673
|
const info = await stat6(item);
|
|
26984
27674
|
if (info.isDirectory()) {
|
|
26985
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
27675
|
+
for (const entry of await readdir2(item)) await walk2(path27.join(item, entry));
|
|
26986
27676
|
return;
|
|
26987
27677
|
}
|
|
26988
27678
|
if (glob && !matchGlob(glob, relative)) return;
|
|
@@ -27001,11 +27691,18 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
27001
27691
|
|
|
27002
27692
|
// src/tools/glob.ts
|
|
27003
27693
|
import { readdir as readdir3, stat as stat7 } from "node:fs/promises";
|
|
27004
|
-
import
|
|
27694
|
+
import path28 from "node:path";
|
|
27005
27695
|
var MAX_PATHS = 1e3;
|
|
27006
27696
|
var globTool = {
|
|
27007
27697
|
name: "glob",
|
|
27008
27698
|
description: "Find paths inside the workspace using a glob pattern.",
|
|
27699
|
+
metadata: {
|
|
27700
|
+
category: "workspace.search",
|
|
27701
|
+
categoryPath: ["workspace", "search"],
|
|
27702
|
+
sideEffect: "none",
|
|
27703
|
+
defaultDecision: "allow",
|
|
27704
|
+
trust: "builtin"
|
|
27705
|
+
},
|
|
27009
27706
|
inputSchema: {
|
|
27010
27707
|
type: "object",
|
|
27011
27708
|
additionalProperties: false,
|
|
@@ -27017,7 +27714,7 @@ var globTool = {
|
|
|
27017
27714
|
},
|
|
27018
27715
|
async execute(input2, ctx) {
|
|
27019
27716
|
const object2 = assertObject(input2, "glob");
|
|
27020
|
-
const pattern =
|
|
27717
|
+
const pattern = stringField2(object2, "pattern");
|
|
27021
27718
|
const base = optionalStringField(object2, "path");
|
|
27022
27719
|
const root = base ? resolveInsideCwd(ctx.cwd, base) : ctx.cwd;
|
|
27023
27720
|
const found = await collectPaths(ctx.cwd, root, pattern);
|
|
@@ -27033,7 +27730,7 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
27033
27730
|
async function walk2(dir) {
|
|
27034
27731
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
27035
27732
|
for (const entry of entries) {
|
|
27036
|
-
const absolute =
|
|
27733
|
+
const absolute = path28.join(dir, entry.name);
|
|
27037
27734
|
const relative = relativeToCwd(cwd, absolute);
|
|
27038
27735
|
if (isIgnoredPath(relative)) continue;
|
|
27039
27736
|
const info = await stat7(absolute);
|
|
@@ -27050,6 +27747,8 @@ var updatePlanTool = {
|
|
|
27050
27747
|
name: "update_plan",
|
|
27051
27748
|
description: "Create or update the visible work plan for the current user goal. Write title, summary, step titles, and details in the user's language; for Korean requests, use Korean user-facing text. Keep step ids, code identifiers, paths, commands, and tool names unchanged. Non-empty plans always end with a final review and verification step.",
|
|
27052
27749
|
metadata: {
|
|
27750
|
+
category: "planning.progress",
|
|
27751
|
+
categoryPath: ["planning", "progress"],
|
|
27053
27752
|
sideEffect: "none",
|
|
27054
27753
|
defaultDecision: "allow",
|
|
27055
27754
|
trust: "builtin"
|
|
@@ -27089,7 +27788,7 @@ function stepProperties() {
|
|
|
27089
27788
|
return {
|
|
27090
27789
|
id: { type: "string", maxLength: 64 },
|
|
27091
27790
|
title: { type: "string" },
|
|
27092
|
-
status: { type: "string", enum: ["pending", "in_progress", "completed", "blocked", "skipped", "failed"] },
|
|
27791
|
+
status: { type: "string", enum: ["pending", "in_progress", "completed", "blocked", "skipped", "failed", "cancelled"] },
|
|
27093
27792
|
detail: { type: "string" },
|
|
27094
27793
|
agent: { type: "string" }
|
|
27095
27794
|
};
|
|
@@ -27098,8 +27797,10 @@ function stepProperties() {
|
|
|
27098
27797
|
// src/tools/report-progress.ts
|
|
27099
27798
|
var reportProgressTool = {
|
|
27100
27799
|
name: "report_progress",
|
|
27101
|
-
description: "Report a
|
|
27800
|
+
description: "Report a conversational user-facing progress update for the current work plan. Use it before starting non-trivial tool-heavy work, at meaningful phase changes, and after important evidence so the user understands what is happening. Write 1-3 natural sentences in the user's language that explain what you are doing, what you learned or why it matters, and what you will do next. Do not list raw tool names, command output, file previews, or status fields; those details are already available in the execution accordion. Mention specific code identifiers, paths, commands, or tool names only when they are essential to the user's understanding. For Korean requests, use Korean \uC874\uB313\uB9D0 while keeping necessary identifiers unchanged. Set showInTimeline=true for major phase changes or any longer tool-using work.",
|
|
27102
27801
|
metadata: {
|
|
27802
|
+
category: "planning.progress",
|
|
27803
|
+
categoryPath: ["planning", "progress"],
|
|
27103
27804
|
sideEffect: "none",
|
|
27104
27805
|
defaultDecision: "allow",
|
|
27105
27806
|
trust: "builtin"
|
|
@@ -27130,6 +27831,13 @@ function createWebSearchTool(config = defaultConfig.webSearch, fetcher = fetch)
|
|
|
27130
27831
|
return {
|
|
27131
27832
|
name: "web_search",
|
|
27132
27833
|
description: "Search the web using the configured Brave, Tavily, or Exa web search provider.",
|
|
27834
|
+
metadata: {
|
|
27835
|
+
category: "research.web",
|
|
27836
|
+
categoryPath: ["research", "web"],
|
|
27837
|
+
sideEffect: "network",
|
|
27838
|
+
defaultDecision: "ask",
|
|
27839
|
+
trust: "builtin"
|
|
27840
|
+
},
|
|
27133
27841
|
inputSchema: {
|
|
27134
27842
|
type: "object",
|
|
27135
27843
|
additionalProperties: false,
|
|
@@ -27149,7 +27857,7 @@ function createWebSearchTool(config = defaultConfig.webSearch, fetcher = fetch)
|
|
|
27149
27857
|
},
|
|
27150
27858
|
async execute(input2, ctx) {
|
|
27151
27859
|
const object2 = assertObject(input2, "web_search");
|
|
27152
|
-
const query =
|
|
27860
|
+
const query = stringField2(object2, "query");
|
|
27153
27861
|
const provider = providerField(object2, config.defaultProvider);
|
|
27154
27862
|
const maxResults = optionalNumberField(object2, "maxResults");
|
|
27155
27863
|
if (provider === "brave") return runBraveSearch(config.providers.brave, { query, maxResults, fetcher, signal: ctx.signal });
|
|
@@ -27359,6 +28067,8 @@ var getGoalTool = {
|
|
|
27359
28067
|
return ctx.goals.getGoal();
|
|
27360
28068
|
},
|
|
27361
28069
|
metadata: {
|
|
28070
|
+
category: "goal.state",
|
|
28071
|
+
categoryPath: ["goal", "state"],
|
|
27362
28072
|
sideEffect: "none",
|
|
27363
28073
|
defaultDecision: "allow",
|
|
27364
28074
|
trust: "builtin"
|
|
@@ -27380,6 +28090,8 @@ var createGoalTool = {
|
|
|
27380
28090
|
return ctx.goals.createGoal(input2);
|
|
27381
28091
|
},
|
|
27382
28092
|
metadata: {
|
|
28093
|
+
category: "goal.state",
|
|
28094
|
+
categoryPath: ["goal", "state"],
|
|
27383
28095
|
sideEffect: "workspace",
|
|
27384
28096
|
defaultDecision: "allow",
|
|
27385
28097
|
trust: "builtin"
|
|
@@ -27401,6 +28113,8 @@ var updateGoalTool = {
|
|
|
27401
28113
|
return ctx.goals.updateGoal(input2);
|
|
27402
28114
|
},
|
|
27403
28115
|
metadata: {
|
|
28116
|
+
category: "goal.state",
|
|
28117
|
+
categoryPath: ["goal", "state"],
|
|
27404
28118
|
sideEffect: "workspace",
|
|
27405
28119
|
defaultDecision: "allow",
|
|
27406
28120
|
trust: "builtin"
|
|
@@ -27484,6 +28198,9 @@ function validateToolName(name) {
|
|
|
27484
28198
|
}
|
|
27485
28199
|
|
|
27486
28200
|
// src/session.ts
|
|
28201
|
+
function relaxedToolUseRoundLimit(maxTurns) {
|
|
28202
|
+
return Math.max((maxTurns ?? 25) * 20, 200);
|
|
28203
|
+
}
|
|
27487
28204
|
var SessionRunner = class {
|
|
27488
28205
|
sessionId;
|
|
27489
28206
|
events;
|
|
@@ -27541,9 +28258,28 @@ var SessionRunner = class {
|
|
|
27541
28258
|
});
|
|
27542
28259
|
const messages = [{ role: "system", content: buildSystemPrompt(options.agent, envInfo) }, ...options.history ?? [], { role: "user", content: userContent }];
|
|
27543
28260
|
this.emit({ type: "user.message", sessionId: this.sessionId, text: options.displayPrompt ?? options.prompt, ts: now(), ...this.eventContext() });
|
|
28261
|
+
this.reportSessionKickoff();
|
|
27544
28262
|
await this.#hooks.dispatch("UserPromptSubmit", this.hookBase());
|
|
27545
28263
|
const maxTurns = options.maxTurns ?? 25;
|
|
27546
|
-
|
|
28264
|
+
const countToolUseTurns = options.countToolUseTurns === true;
|
|
28265
|
+
const maxToolUseRounds = options.maxToolUseRounds ?? relaxedToolUseRoundLimit(maxTurns);
|
|
28266
|
+
let countedTurns = 0;
|
|
28267
|
+
let toolUseRounds = 0;
|
|
28268
|
+
let countedTaskScope = this.#progress.activeStepId() ?? "__session__";
|
|
28269
|
+
const refreshTaskScope = () => {
|
|
28270
|
+
const taskScope = this.#progress.activeStepId() ?? "__session__";
|
|
28271
|
+
if (taskScope === countedTaskScope) return;
|
|
28272
|
+
countedTaskScope = taskScope;
|
|
28273
|
+
countedTurns = 0;
|
|
28274
|
+
};
|
|
28275
|
+
for (; ; ) {
|
|
28276
|
+
if (countToolUseTurns) refreshTaskScope();
|
|
28277
|
+
if (countToolUseTurns && countedTurns >= maxTurns) {
|
|
28278
|
+
return this.end(messages, `Stopped after reaching maxTurns (${maxTurns}).`, "max_turns");
|
|
28279
|
+
}
|
|
28280
|
+
if (!countToolUseTurns && toolUseRounds >= maxToolUseRounds) {
|
|
28281
|
+
return this.end(messages, `Stopped after reaching tool-use safety limit (${maxToolUseRounds}) before producing a final answer.`, "tool_loop_limit");
|
|
28282
|
+
}
|
|
27547
28283
|
await this.#hooks.dispatch("BeforeModelRequest", this.hookBase());
|
|
27548
28284
|
const tools = this.#tools.list();
|
|
27549
28285
|
const compiled = compileMessagesForContext(messages, tools, options.context);
|
|
@@ -27621,12 +28357,17 @@ var SessionRunner = class {
|
|
|
27621
28357
|
if (toolCalls.length === 0) {
|
|
27622
28358
|
return this.end(messages, assistant.content ?? "", "end_turn");
|
|
27623
28359
|
}
|
|
28360
|
+
if (countToolUseTurns) {
|
|
28361
|
+
refreshTaskScope();
|
|
28362
|
+
countedTurns++;
|
|
28363
|
+
} else {
|
|
28364
|
+
toolUseRounds++;
|
|
28365
|
+
}
|
|
27624
28366
|
for (const call of toolCalls) {
|
|
27625
28367
|
const toolMessage = await this.runToolCall(call);
|
|
27626
28368
|
messages.push(toolMessage);
|
|
27627
28369
|
}
|
|
27628
28370
|
}
|
|
27629
|
-
return this.end(messages, `Stopped after reaching maxTurns (${maxTurns}).`, "max_turns");
|
|
27630
28371
|
}
|
|
27631
28372
|
async applyAfterModelHooks(message) {
|
|
27632
28373
|
const results = await this.#hooks.dispatch("AfterModelResponse", {
|
|
@@ -27689,15 +28430,7 @@ var SessionRunner = class {
|
|
|
27689
28430
|
return toToolMessage(call, result);
|
|
27690
28431
|
}
|
|
27691
28432
|
if (evaluation.decision === "ask") {
|
|
27692
|
-
|
|
27693
|
-
this.emitGoalToolEvent(call, "permission");
|
|
27694
|
-
await this.#hooks.dispatch("PermissionRequest", {
|
|
27695
|
-
...this.hookBase(),
|
|
27696
|
-
callId: call.id,
|
|
27697
|
-
toolName: call.name,
|
|
27698
|
-
toolInput: input2
|
|
27699
|
-
});
|
|
27700
|
-
const answer = await (this.#options.permissionPrompt ?? ((req) => askPermission(req, { yes: this.#options.yes })))({
|
|
28433
|
+
const permissionRequest = {
|
|
27701
28434
|
tool: call.name,
|
|
27702
28435
|
input: input2,
|
|
27703
28436
|
actor: this.#options.agent.name,
|
|
@@ -27705,16 +28438,33 @@ var SessionRunner = class {
|
|
|
27705
28438
|
targetName: call.name,
|
|
27706
28439
|
proposedDecision: "ask",
|
|
27707
28440
|
reason: evaluation.reason
|
|
27708
|
-
}
|
|
27709
|
-
|
|
27710
|
-
|
|
28441
|
+
};
|
|
28442
|
+
const automatic = await this.#options.autoPermission?.(permissionRequest);
|
|
28443
|
+
if (automatic?.decision === "deny") {
|
|
28444
|
+
this.emit({ type: "permission.denied", sessionId: this.sessionId, callId: call.id, tool: call.name, reason: automatic.reason, targetType: "tool", targetName: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
27711
28445
|
this.emitGoalToolEvent(call, "denied");
|
|
27712
|
-
return toToolMessage(call, toolFailure(`Permission denied${
|
|
27713
|
-
}
|
|
27714
|
-
if (
|
|
27715
|
-
this.#
|
|
27716
|
-
|
|
27717
|
-
this.
|
|
28446
|
+
return toToolMessage(call, toolFailure(`Permission denied${automatic.reason ? `: ${automatic.reason}` : ""}`));
|
|
28447
|
+
}
|
|
28448
|
+
if (automatic?.decision !== "allow") {
|
|
28449
|
+
this.emit({ type: "permission.requested", sessionId: this.sessionId, callId: call.id, tool: call.name, decision: "ask", targetType: "tool", targetName: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
28450
|
+
this.emitGoalToolEvent(call, "permission");
|
|
28451
|
+
await this.#hooks.dispatch("PermissionRequest", {
|
|
28452
|
+
...this.hookBase(),
|
|
28453
|
+
callId: call.id,
|
|
28454
|
+
toolName: call.name,
|
|
28455
|
+
toolInput: input2
|
|
28456
|
+
});
|
|
28457
|
+
const answer = await (this.#options.permissionPrompt ?? ((req) => askPermission(req, { yes: this.#options.yes })))(permissionRequest);
|
|
28458
|
+
if (answer.decision !== "allow") {
|
|
28459
|
+
this.emit({ type: "permission.denied", sessionId: this.sessionId, callId: call.id, tool: call.name, reason: answer.reason, targetType: "tool", targetName: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
28460
|
+
this.emitGoalToolEvent(call, "denied");
|
|
28461
|
+
return toToolMessage(call, toolFailure(`Permission denied${answer.reason ? `: ${answer.reason}` : ""}`));
|
|
28462
|
+
}
|
|
28463
|
+
if (answer.always) {
|
|
28464
|
+
this.#grants.add(evaluation.grantKey);
|
|
28465
|
+
await this.#persistentStore?.add(this.#options.cwd, evaluation.grantKey);
|
|
28466
|
+
this.emit({ type: "permission.granted", sessionId: this.sessionId, callId: call.id, key: evaluation.grantKey, targetType: "tool", targetName: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
28467
|
+
}
|
|
27718
28468
|
}
|
|
27719
28469
|
}
|
|
27720
28470
|
this.emit({ type: "tool.started", sessionId: this.sessionId, callId: call.id, name: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
@@ -27729,7 +28479,7 @@ var SessionRunner = class {
|
|
|
27729
28479
|
cwd: this.#options.cwd,
|
|
27730
28480
|
signal: this.#controller.signal,
|
|
27731
28481
|
emit: (event) => this.emit(event),
|
|
27732
|
-
ask: (req) => (this.#options.permissionPrompt ?? ((request) => askPermission(request, { yes: this.#options.yes })))(req),
|
|
28482
|
+
ask: async (req) => await this.#options.autoPermission?.(req) ?? (this.#options.permissionPrompt ?? ((request) => askPermission(request, { yes: this.#options.yes })))(req),
|
|
27733
28483
|
dryRun: this.#options.dryRun,
|
|
27734
28484
|
sandbox: this.#options.sandbox,
|
|
27735
28485
|
rootSessionId: this.#options.rootSessionId,
|
|
@@ -27769,15 +28519,18 @@ var SessionRunner = class {
|
|
|
27769
28519
|
}
|
|
27770
28520
|
}
|
|
27771
28521
|
async end(messages, finalAnswer, reason) {
|
|
28522
|
+
const answer = reason === "end_turn" ? finalAnswer : terminalFinalAnswer(finalAnswer, reason, this.#progress.snapshot());
|
|
28523
|
+
const returnedMessages = reason === "end_turn" ? messages : [...messages, { role: "assistant", content: answer }];
|
|
27772
28524
|
await this.#hooks.dispatch("Stop", this.hookBase());
|
|
27773
28525
|
await this.#hooks.dispatch("SessionEnd", this.hookBase());
|
|
27774
28526
|
this.completeGoalWorkGroup();
|
|
27775
28527
|
this.emit({ type: "session.ended", sessionId: this.sessionId, reason, ts: now(), ...this.eventContext() });
|
|
28528
|
+
if (reason !== "end_turn" && answer.trim()) this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
27776
28529
|
await this.#transcript.flush();
|
|
27777
28530
|
return {
|
|
27778
28531
|
sessionId: this.sessionId,
|
|
27779
|
-
finalAnswer,
|
|
27780
|
-
messages,
|
|
28532
|
+
finalAnswer: answer,
|
|
28533
|
+
messages: returnedMessages,
|
|
27781
28534
|
endReason: reason,
|
|
27782
28535
|
usage: { ...this.#usage }
|
|
27783
28536
|
};
|
|
@@ -27885,6 +28638,24 @@ var SessionRunner = class {
|
|
|
27885
28638
|
activeStepId: () => this.#progress.activeStepId()
|
|
27886
28639
|
};
|
|
27887
28640
|
}
|
|
28641
|
+
reportSessionKickoff() {
|
|
28642
|
+
if (this.#options.parentInvocationId) return;
|
|
28643
|
+
const message = sessionKickoffMessage(this.#options.displayPrompt ?? this.#options.prompt);
|
|
28644
|
+
if (!message) return;
|
|
28645
|
+
const result = this.#progress.report(
|
|
28646
|
+
{ message, level: "info", showInTimeline: true },
|
|
28647
|
+
{
|
|
28648
|
+
sessionId: this.sessionId,
|
|
28649
|
+
agent: this.#options.agent.name,
|
|
28650
|
+
rootSessionId: this.#options.rootSessionId,
|
|
28651
|
+
agentSessionId: this.#options.agentSessionId,
|
|
28652
|
+
invocationId: this.#options.invocationId,
|
|
28653
|
+
parentInvocationId: this.#options.parentInvocationId,
|
|
28654
|
+
agentPath: this.#options.agentPath
|
|
28655
|
+
}
|
|
28656
|
+
);
|
|
28657
|
+
for (const event of result.events) this.emit(event);
|
|
28658
|
+
}
|
|
27888
28659
|
eventContext() {
|
|
27889
28660
|
return {
|
|
27890
28661
|
rootSessionId: this.#options.rootSessionId,
|
|
@@ -27968,6 +28739,134 @@ function normalizeDiffContent(value) {
|
|
|
27968
28739
|
after: typeof after === "string" ? after : void 0
|
|
27969
28740
|
};
|
|
27970
28741
|
}
|
|
28742
|
+
function sessionKickoffMessage(prompt) {
|
|
28743
|
+
const normalized = prompt.replace(/^\/\w+\s*/, "").trim();
|
|
28744
|
+
if (isCasualPrompt(normalized) || isSimpleQuestionPrompt(normalized)) return void 0;
|
|
28745
|
+
const isKorean = /[가-힣]/.test(normalized);
|
|
28746
|
+
if (isKorean) {
|
|
28747
|
+
if (/(수정|고쳐|개선|변경|추가|삭제|구현|반영|업데이트|만들|작성|전환|마이그레이션)/.test(normalized)) {
|
|
28748
|
+
return "\uBA3C\uC800 \uAD00\uB828 \uB3D9\uC791\uC774 \uC5B4\uB514\uC5D0\uC11C \uB9CC\uB4E4\uC5B4\uC9C0\uB294\uC9C0\uC640 \uC601\uD5A5 \uBC94\uC704\uB97C \uD655\uC778\uD55C \uB4A4, \uD544\uC694\uD55C \uBCC0\uACBD\uC744 \uC791\uC740 \uB2E8\uC704\uB85C \uC9C4\uD589\uD558\uACA0\uC2B5\uB2C8\uB2E4. \uD655\uC778\uD55C \uB0B4\uC6A9\uC774 \uB2E4\uC74C \uC218\uC815\uC5D0 \uC5B4\uB5A4 \uC758\uBBF8\uAC00 \uC788\uB294\uC9C0\uB3C4 \uC911\uAC04\uC911\uAC04 \uC815\uB9AC\uD574 \uB4DC\uB9AC\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28749
|
+
}
|
|
28750
|
+
if (/(테스트|검증|확인|재현|실행|빌드|publish|배포)/i.test(normalized)) {
|
|
28751
|
+
return "\uBA3C\uC800 \uD604\uC7AC \uC0C1\uD0DC\uC640 \uC7AC\uD604 \uC870\uAC74\uC744 \uD655\uC778\uD558\uACE0, \uACB0\uACFC\uAC00 \uC758\uBBF8\uD558\uB294 \uBC14\uB97C \uC815\uB9AC\uD558\uBA74\uC11C \uD544\uC694\uD55C \uD6C4\uC18D \uC870\uCE58\uB97C \uC774\uC5B4\uAC00\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28752
|
+
}
|
|
28753
|
+
if (/(설명|분석|검토|원인|왜|찾아|알려|파악)/.test(normalized)) {
|
|
28754
|
+
return "\uBA3C\uC800 \uC694\uCCAD\uC758 \uD575\uC2EC \uC9C8\uBB38\uC744 \uC881\uD788\uACE0, \uADFC\uAC70\uAC00 \uB420 \uB9CC\uD55C \uAD6C\uC870\uC640 \uD750\uB984\uC744 \uD655\uC778\uD55C \uB4A4 \uC774\uD574\uD558\uAE30 \uC26C\uC6B4 \uB2F5\uC73C\uB85C \uC815\uB9AC\uD558\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28755
|
+
}
|
|
28756
|
+
return "\uBA3C\uC800 \uC694\uCCAD\uC758 \uC758\uB3C4\uB97C \uD655\uC778\uD558\uACE0, \uD544\uC694\uD55C \uB9E5\uB77D\uC744 \uC0B4\uD3B4\uBCF8 \uB4A4 \uB2E4\uC74C\uC5D0 \uD560 \uC77C\uC744 \uC815\uB9AC\uD558\uBA74\uC11C \uC774\uC5B4\uAC00\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28757
|
+
}
|
|
28758
|
+
if (/\b(fix|change|update|add|delete|remove|implement|write|create|migrate|refactor)\b/i.test(normalized)) {
|
|
28759
|
+
return "I\u2019ll first locate the relevant behavior and check the impact area, then make the change in small, verifiable steps while sharing what each finding means.";
|
|
28760
|
+
}
|
|
28761
|
+
if (/\b(test|verify|check|reproduce|run|build|publish|deploy)\b/i.test(normalized)) {
|
|
28762
|
+
return "I\u2019ll first check the current state and reproduction path, then explain what the result means before moving to the next step.";
|
|
28763
|
+
}
|
|
28764
|
+
if (/\b(explain|analyze|review|why|find|investigate|inspect)\b/i.test(normalized)) {
|
|
28765
|
+
return "I\u2019ll narrow the main question first, inspect the evidence that matters, and then summarize the answer in a way that is easy to act on.";
|
|
28766
|
+
}
|
|
28767
|
+
return "I\u2019ll first clarify the shape of the request, then work through the relevant context and share useful updates as I go.";
|
|
28768
|
+
}
|
|
28769
|
+
function isCasualPrompt(prompt) {
|
|
28770
|
+
const compact = prompt.toLowerCase().replace(/[\s.!?。!?~…"'`]+/g, "");
|
|
28771
|
+
if (!compact) return true;
|
|
28772
|
+
const casual = /* @__PURE__ */ new Set([
|
|
28773
|
+
"hi",
|
|
28774
|
+
"hello",
|
|
28775
|
+
"hey",
|
|
28776
|
+
"yo",
|
|
28777
|
+
"ok",
|
|
28778
|
+
"okay",
|
|
28779
|
+
"thanks",
|
|
28780
|
+
"thankyou",
|
|
28781
|
+
"\uC548\uB155",
|
|
28782
|
+
"\uC548\uB155\uD558\uC138\uC694",
|
|
28783
|
+
"\uD558\uC774",
|
|
28784
|
+
"\uD5EC\uB85C",
|
|
28785
|
+
"\uACE0\uB9C8\uC6CC",
|
|
28786
|
+
"\uACE0\uB9D9\uC2B5\uB2C8\uB2E4",
|
|
28787
|
+
"\uAC10\uC0AC\uD569\uB2C8\uB2E4",
|
|
28788
|
+
"\uAC10\uC0AC",
|
|
28789
|
+
"\uC88B\uC544",
|
|
28790
|
+
"\uB124",
|
|
28791
|
+
"\uC751",
|
|
28792
|
+
"\u3147\u314B"
|
|
28793
|
+
]);
|
|
28794
|
+
return casual.has(compact) || compact.length <= 12 && /^(hi|hello|hey|안녕|안녕하세요|하이)/.test(compact);
|
|
28795
|
+
}
|
|
28796
|
+
function isSimpleQuestionPrompt(prompt) {
|
|
28797
|
+
const trimmed = prompt.trim();
|
|
28798
|
+
if (trimmed.length > 80) return false;
|
|
28799
|
+
const hasTaskVerb = /(수정|고쳐|개선|변경|추가|삭제|구현|반영|업데이트|만들|작성|전환|마이그레이션|테스트|검증|확인|재현|실행|빌드|분석|검토|찾아|파악|설명해|알려줘|fix|change|update|add|delete|remove|implement|write|create|migrate|refactor|test|verify|check|reproduce|run|build|analyze|review|find|inspect|explain)/i.test(
|
|
28800
|
+
trimmed
|
|
28801
|
+
);
|
|
28802
|
+
if (hasTaskVerb) return false;
|
|
28803
|
+
return /[??]$/.test(trimmed) || /(뭐야|무엇|누구|언제|어디|어떻게|왜|맞아)$/.test(trimmed);
|
|
28804
|
+
}
|
|
28805
|
+
function terminalFinalAnswer(finalAnswer, reason, progress) {
|
|
28806
|
+
const lines = ["\uC791\uC5C5\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "", `\uC885\uB8CC \uC774\uC720: ${terminalReasonText(reason)}`];
|
|
28807
|
+
const runtimeMessage = finalAnswer.trim();
|
|
28808
|
+
if (runtimeMessage && runtimeMessage !== terminalReasonText(reason)) lines.push(`\uB9C8\uC9C0\uB9C9 \uB7F0\uD0C0\uC784 \uBA54\uC2DC\uC9C0: ${runtimeMessage}`);
|
|
28809
|
+
lines.push("", "\uD604\uC7AC\uAE4C\uC9C0 \uC9C4\uD589\uD55C \uB0B4\uC6A9\uC740 \uC544\uB798\uC640 \uAC19\uC2B5\uB2C8\uB2E4.");
|
|
28810
|
+
const plan = progress.plan;
|
|
28811
|
+
if (plan) {
|
|
28812
|
+
lines.push("", ...planSummaryLines(plan));
|
|
28813
|
+
} else {
|
|
28814
|
+
lines.push("", "- \uC544\uC9C1 \uBA85\uC2DC\uC801\uC778 plan\uC740 \uB9CC\uB4E4\uC5B4\uC9C0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
28815
|
+
}
|
|
28816
|
+
const notes = progress.notes.slice(-3);
|
|
28817
|
+
if (notes.length > 0) {
|
|
28818
|
+
lines.push("", "\uCD5C\uADFC \uC911\uAC04 \uBCF4\uACE0:");
|
|
28819
|
+
for (const note of notes) lines.push(`- ${note.message}`);
|
|
28820
|
+
}
|
|
28821
|
+
lines.push("", terminalNextStepText(reason));
|
|
28822
|
+
return lines.join("\n");
|
|
28823
|
+
}
|
|
28824
|
+
function planSummaryLines(plan) {
|
|
28825
|
+
const lines = [`Plan: ${plan.title ?? "\uC791\uC5C5 \uACC4\uD68D"}`];
|
|
28826
|
+
if (plan.summary) lines.push(`\uC694\uC57D: ${plan.summary}`);
|
|
28827
|
+
const counts = countSteps(plan.steps);
|
|
28828
|
+
lines.push(
|
|
28829
|
+
`\uC9C4\uD589 \uC0C1\uD0DC: \uC644\uB8CC ${counts.completed + counts.skipped}\uAC1C, \uC9C4\uD589 \uC911 ${counts.in_progress}\uAC1C, \uB300\uAE30 ${counts.pending}\uAC1C, \uC2E4\uD328/\uCC28\uB2E8 ${counts.failed + counts.blocked + counts.cancelled}\uAC1C\uC785\uB2C8\uB2E4.`
|
|
28830
|
+
);
|
|
28831
|
+
const completed = plan.steps.filter((step) => step.status === "completed" || step.status === "skipped").slice(-3);
|
|
28832
|
+
if (completed.length > 0) lines.push(`\uC644\uB8CC\uD55C \uC791\uC5C5: ${completed.map(formatStep).join("; ")}`);
|
|
28833
|
+
const active = plan.steps.filter((step) => step.status === "in_progress");
|
|
28834
|
+
if (active.length > 0) lines.push(`\uC9C4\uD589 \uC911\uC774\uB358 \uC791\uC5C5: ${active.map(formatStep).join("; ")}`);
|
|
28835
|
+
const unresolved = plan.steps.filter((step) => step.status === "pending" || step.status === "failed" || step.status === "blocked" || step.status === "cancelled").slice(0, 4);
|
|
28836
|
+
if (unresolved.length > 0) lines.push(`\uB0A8\uC740 \uC791\uC5C5: ${unresolved.map(formatStep).join("; ")}`);
|
|
28837
|
+
return lines;
|
|
28838
|
+
}
|
|
28839
|
+
function countSteps(steps) {
|
|
28840
|
+
const counts = {
|
|
28841
|
+
pending: 0,
|
|
28842
|
+
in_progress: 0,
|
|
28843
|
+
completed: 0,
|
|
28844
|
+
blocked: 0,
|
|
28845
|
+
skipped: 0,
|
|
28846
|
+
failed: 0,
|
|
28847
|
+
cancelled: 0
|
|
28848
|
+
};
|
|
28849
|
+
for (const step of steps) counts[step.status] += 1;
|
|
28850
|
+
return counts;
|
|
28851
|
+
}
|
|
28852
|
+
function formatStep(step) {
|
|
28853
|
+
const detail = step.detail ? ` (${preview(step.detail.replace(/\s+/g, " "), 120)})` : "";
|
|
28854
|
+
return `${step.title}${detail}`;
|
|
28855
|
+
}
|
|
28856
|
+
function terminalReasonText(reason) {
|
|
28857
|
+
if (reason === "max_turns") return "\uC0C1\uC704 task turn \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4.";
|
|
28858
|
+
if (reason === "tool_loop_limit") return "\uB3C4\uAD6C \uC0AC\uC6A9 \uB77C\uC6B4\uB4DC\uAC00 \uB0B4\uBD80 \uC548\uC804 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4.";
|
|
28859
|
+
if (reason === "content_filter") return "\uC81C\uACF5\uC790\uC758 \uC548\uC804 \uD544\uD130\uB85C \uC751\uB2F5\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
28860
|
+
if (reason === "cancelled") return "\uC0AC\uC6A9\uC790 \uC694\uCCAD \uB610\uB294 \uB7F0\uD0C0\uC784 \uC911\uB2E8\uC73C\uB85C \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
28861
|
+
if (reason === "error") return "\uB7F0\uD0C0\uC784 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
28862
|
+
return `${reason} \uC0C1\uD0DC\uB85C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`;
|
|
28863
|
+
}
|
|
28864
|
+
function terminalNextStepText(reason) {
|
|
28865
|
+
if (reason === "tool_loop_limit") return "\uC774\uC5B4\uC11C \uC9C4\uD589\uD558\uB824\uBA74 \uC774\uBBF8 \uD655\uC778\uD55C \uD30C\uC77C\uACFC \uB2E4\uC74C \uD655\uC778 \uBC94\uC704\uB97C \uAE30\uC900\uC73C\uB85C \uB3C4\uAD6C \uC0AC\uC6A9 \uBC94\uC704\uB97C \uC870\uAE08 \uB354 \uC881\uD600 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28866
|
+
if (reason === "max_turns") return "\uC774\uC5B4\uC11C \uC9C4\uD589\uD558\uB824\uBA74 \uAC19\uC740 \uC694\uCCAD\uC744 \uB2E4\uC2DC \uBCF4\uB0B4\uAC70\uB098, plan\uC758 \uD604\uC7AC task\uB97C \uB354 \uC791\uAC8C \uB098\uB204\uC5B4 \uB04A\uAE34 \uC9C0\uC810\uBD80\uD130 \uACC4\uC18D \uC9C4\uD589\uD558\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28867
|
+
if (reason === "content_filter") return "\uC774\uC5B4\uC11C \uC9C4\uD589\uD558\uB824\uBA74 \uC694\uCCAD \uD45C\uD604\uC774\uB098 \uBC94\uC704\uB97C \uC870\uC815\uD55C \uB4A4 \uC548\uC804\uD558\uAC8C \uB2F5\uBCC0 \uAC00\uB2A5\uD55C \uD615\uD0DC\uB85C \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28868
|
+
return "\uC774\uC5B4\uC11C \uC9C4\uD589\uD558\uBA74 \uC704 \uC9C4\uD589 \uC0C1\uD0DC\uB97C \uAE30\uC900\uC73C\uB85C \uB0A8\uC740 \uC791\uC5C5\uBD80\uD130 \uB2E4\uC2DC \uD655\uC778\uD558\uACA0\uC2B5\uB2C8\uB2E4.";
|
|
28869
|
+
}
|
|
27971
28870
|
function toolFailure(content) {
|
|
27972
28871
|
return { ok: false, content };
|
|
27973
28872
|
}
|
|
@@ -28000,11 +28899,15 @@ async function runExecutionSession(options) {
|
|
|
28000
28899
|
providerName: options.providerName,
|
|
28001
28900
|
model: options.backend.model,
|
|
28002
28901
|
agent: options.agent,
|
|
28902
|
+
maxTurns: options.countToolUseTurns === true ? options.maxTurns : null,
|
|
28003
28903
|
yes: options.yes,
|
|
28004
28904
|
transcript: options.transcript,
|
|
28005
28905
|
signal: options.signal,
|
|
28006
28906
|
eventBus: options.eventBus,
|
|
28007
28907
|
permissionPrompt: options.permissionPrompt,
|
|
28908
|
+
autoPermission: options.autoPermission,
|
|
28909
|
+
persistentGrants: options.persistentGrants,
|
|
28910
|
+
grants: options.grants,
|
|
28008
28911
|
transcriptWriter: options.transcriptWriter,
|
|
28009
28912
|
transcriptSessionId: options.transcriptSessionId,
|
|
28010
28913
|
rootSessionId: options.rootSessionId,
|
|
@@ -28022,6 +28925,73 @@ async function runExecutionSession(options) {
|
|
|
28022
28925
|
}).run();
|
|
28023
28926
|
}
|
|
28024
28927
|
|
|
28928
|
+
// src/permissions/auto-agent.ts
|
|
28929
|
+
function createAutoPermissionAgent(options) {
|
|
28930
|
+
return async (request) => {
|
|
28931
|
+
const command = bashCommandFromRequest(request);
|
|
28932
|
+
if (!command || !isPotentiallyReadOnlyShellCommand(command)) return void 0;
|
|
28933
|
+
const result = await classifyWithAgent(command, request, options).catch(() => void 0);
|
|
28934
|
+
if (!result || result.decision !== "allow" || result.confidence < 0.85) return void 0;
|
|
28935
|
+
return { decision: "allow", reason: `auto permission judge: ${preview(result.reason, 160)}` };
|
|
28936
|
+
};
|
|
28937
|
+
}
|
|
28938
|
+
function bashCommandFromRequest(request) {
|
|
28939
|
+
const toolName = (request.qualifiedName ?? request.tool).split(".").at(-1)?.toLowerCase();
|
|
28940
|
+
if (toolName !== "bash") return void 0;
|
|
28941
|
+
const input2 = request.input && typeof request.input === "object" && !Array.isArray(request.input) ? request.input : {};
|
|
28942
|
+
return typeof input2.command === "string" ? input2.command : void 0;
|
|
28943
|
+
}
|
|
28944
|
+
async function classifyWithAgent(command, request, options) {
|
|
28945
|
+
const response = await options.provider.chat({
|
|
28946
|
+
model: options.model,
|
|
28947
|
+
tools: [],
|
|
28948
|
+
maxTokens: 180,
|
|
28949
|
+
temperature: 0,
|
|
28950
|
+
signal: permissionSignal(options.signal, options.timeoutMs ?? 4e3),
|
|
28951
|
+
messages: [
|
|
28952
|
+
{
|
|
28953
|
+
role: "system",
|
|
28954
|
+
content: [
|
|
28955
|
+
"You are Demian's internal permission judge.",
|
|
28956
|
+
"Classify whether a shell command is safe to auto-allow without asking the user.",
|
|
28957
|
+
"Allow only commands that are clearly read-only, inspect local workspace state, and do not write files, mutate git state, install packages, start long-running services, send network requests, change permissions, or execute nested commands.",
|
|
28958
|
+
"If there is any uncertainty, choose ask. Never choose allow just because the command seems common.",
|
|
28959
|
+
'Return only compact JSON with this shape: {"decision":"allow"|"ask","confidence":0..1,"reason":"short reason"}.'
|
|
28960
|
+
].join(" ")
|
|
28961
|
+
},
|
|
28962
|
+
{
|
|
28963
|
+
role: "user",
|
|
28964
|
+
content: JSON.stringify({
|
|
28965
|
+
cwd: options.cwd,
|
|
28966
|
+
tool: request.qualifiedName ?? request.tool,
|
|
28967
|
+
actor: request.actor,
|
|
28968
|
+
command
|
|
28969
|
+
})
|
|
28970
|
+
}
|
|
28971
|
+
]
|
|
28972
|
+
});
|
|
28973
|
+
return parseJudgeResponse(response.message.content);
|
|
28974
|
+
}
|
|
28975
|
+
function permissionSignal(parent, timeoutMs) {
|
|
28976
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
28977
|
+
return parent ? AbortSignal.any([parent, timeout]) : timeout;
|
|
28978
|
+
}
|
|
28979
|
+
function parseJudgeResponse(content) {
|
|
28980
|
+
if (!content) return void 0;
|
|
28981
|
+
const json = content.match(/\{[\s\S]*\}/)?.[0];
|
|
28982
|
+
if (!json) return void 0;
|
|
28983
|
+
try {
|
|
28984
|
+
const parsed = JSON.parse(json);
|
|
28985
|
+
const decision = parsed.decision === "allow" ? "allow" : parsed.decision === "ask" ? "ask" : void 0;
|
|
28986
|
+
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : Number(parsed.confidence);
|
|
28987
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason.trim() : "";
|
|
28988
|
+
if (!decision || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || !reason) return void 0;
|
|
28989
|
+
return { decision, confidence, reason };
|
|
28990
|
+
} catch {
|
|
28991
|
+
return void 0;
|
|
28992
|
+
}
|
|
28993
|
+
}
|
|
28994
|
+
|
|
28025
28995
|
// src/tools/cowork.ts
|
|
28026
28996
|
function createCoworkTool(options) {
|
|
28027
28997
|
const coworkAgentNames = options.agents.coworkable().map((agent) => agent.name);
|
|
@@ -28029,6 +28999,13 @@ function createCoworkTool(options) {
|
|
|
28029
28999
|
return {
|
|
28030
29000
|
name: "cowork",
|
|
28031
29001
|
description: "Run multiple bounded Demian cowork agents and return their combined result. members[].agent must be an exact cowork agent name, not the current parent agent.",
|
|
29002
|
+
metadata: {
|
|
29003
|
+
category: "orchestration.cowork",
|
|
29004
|
+
categoryPath: ["orchestration", "cowork"],
|
|
29005
|
+
sideEffect: "external",
|
|
29006
|
+
defaultDecision: options.config.defaultInvocationDecision,
|
|
29007
|
+
trust: "builtin"
|
|
29008
|
+
},
|
|
28032
29009
|
inputSchema: {
|
|
28033
29010
|
type: "object",
|
|
28034
29011
|
properties: {
|
|
@@ -28043,6 +29020,9 @@ function createCoworkTool(options) {
|
|
|
28043
29020
|
properties: {
|
|
28044
29021
|
id: { type: "string", pattern: "^[a-z0-9_-]{1,32}$" },
|
|
28045
29022
|
agent: { type: "string", enum: coworkAgentNames, description: agentDescription },
|
|
29023
|
+
provider: { type: "string", description: "Optional provider override. Must be listed in cowork.providers." },
|
|
29024
|
+
model: { type: "string", description: "Optional model display name or raw model id for this member's provider." },
|
|
29025
|
+
modelProfileName: { type: "string", description: "Optional stable model profile name for this member's provider." },
|
|
28046
29026
|
task: { type: "string" },
|
|
28047
29027
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
28048
29028
|
role: { type: "string" },
|
|
@@ -28053,7 +29033,7 @@ function createCoworkTool(options) {
|
|
|
28053
29033
|
constraints: { type: "array", items: { type: "string" } },
|
|
28054
29034
|
expectedOutput: { type: "string" },
|
|
28055
29035
|
maxTurns: { type: "integer", minimum: 1 },
|
|
28056
|
-
mode: { type: "string", enum: ["read-only", "write", "review"] },
|
|
29036
|
+
mode: { type: "string", enum: ["read-only", "write", "review", "manage"] },
|
|
28057
29037
|
writeScope: { type: "array", items: { type: "string" } },
|
|
28058
29038
|
returnMode: { type: "string", enum: ["brief", "normal"] }
|
|
28059
29039
|
},
|
|
@@ -28119,6 +29099,9 @@ function parseMember(input2, index) {
|
|
|
28119
29099
|
agent: object2.agent.trim(),
|
|
28120
29100
|
task: object2.task.trim()
|
|
28121
29101
|
};
|
|
29102
|
+
if (typeof object2.provider === "string" && object2.provider.trim()) member.provider = object2.provider.trim();
|
|
29103
|
+
if (typeof object2.model === "string" && object2.model.trim()) member.model = object2.model.trim();
|
|
29104
|
+
if (typeof object2.modelProfileName === "string" && object2.modelProfileName.trim()) member.modelProfileName = object2.modelProfileName.trim();
|
|
28122
29105
|
if (typeof object2.id === "string" && object2.id.trim()) member.id = object2.id.trim();
|
|
28123
29106
|
if (typeof object2.role === "string" && object2.role.trim()) member.role = object2.role.trim();
|
|
28124
29107
|
if (typeof object2.context === "string") member.context = object2.context;
|
|
@@ -28126,7 +29109,7 @@ function parseMember(input2, index) {
|
|
|
28126
29109
|
if (object2.returnMode === "brief" || object2.returnMode === "normal") member.returnMode = object2.returnMode;
|
|
28127
29110
|
if (object2.parentDetail === "preview" || object2.parentDetail === "full" || object2.parentDetail === "none") member.parentDetail = object2.parentDetail;
|
|
28128
29111
|
if (object2.mode !== void 0) {
|
|
28129
|
-
if (object2.mode !== "read-only" && object2.mode !== "write" && object2.mode !== "review") return { ok: false, error: `cowork.members[${index}].mode must be read-only, review, or write.` };
|
|
29112
|
+
if (object2.mode !== "read-only" && object2.mode !== "write" && object2.mode !== "review" && object2.mode !== "manage") return { ok: false, error: `cowork.members[${index}].mode must be read-only, review, manage, or write.` };
|
|
28130
29113
|
member.mode = object2.mode;
|
|
28131
29114
|
}
|
|
28132
29115
|
if (typeof object2.maxTurns === "number" && Number.isInteger(object2.maxTurns) && object2.maxTurns > 0) member.maxTurns = object2.maxTurns;
|
|
@@ -28157,6 +29140,13 @@ function createDelegateAgentTool(options) {
|
|
|
28157
29140
|
return {
|
|
28158
29141
|
name: "delegate_agent",
|
|
28159
29142
|
description: "Delegate a bounded task to a configured demian agent and return its result.",
|
|
29143
|
+
metadata: {
|
|
29144
|
+
category: "orchestration.agent",
|
|
29145
|
+
categoryPath: ["orchestration", "agent"],
|
|
29146
|
+
sideEffect: "external",
|
|
29147
|
+
defaultDecision: "ask",
|
|
29148
|
+
trust: "builtin"
|
|
29149
|
+
},
|
|
28160
29150
|
inputSchema: {
|
|
28161
29151
|
type: "object",
|
|
28162
29152
|
properties: {
|
|
@@ -28264,6 +29254,172 @@ function sha256(value) {
|
|
|
28264
29254
|
return crypto6.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
28265
29255
|
}
|
|
28266
29256
|
|
|
29257
|
+
// src/cowork/permissions.ts
|
|
29258
|
+
function resolveCoworkPermissionIntent(agent, mode) {
|
|
29259
|
+
if (agent.permissionIntent) return agent.permissionIntent;
|
|
29260
|
+
if (mode === "write") return "write";
|
|
29261
|
+
if (mode === "review") return "review";
|
|
29262
|
+
if (mode === "manage") return "manage";
|
|
29263
|
+
const category = agent.catalog?.category;
|
|
29264
|
+
if (category === "builder") return "write";
|
|
29265
|
+
if (category === "reviewer") return "review";
|
|
29266
|
+
if (category === "planner") return "manage";
|
|
29267
|
+
return "read-only";
|
|
29268
|
+
}
|
|
29269
|
+
function compileCoworkPermissionOverlay(options) {
|
|
29270
|
+
const intent = resolveCoworkPermissionIntent(options.agent, options.mode);
|
|
29271
|
+
const mapping = options.config.permissionOverlay.modeMappings[intent];
|
|
29272
|
+
const providerNamespace = options.config.permissionOverlay.providerToolNamespaces[options.providerProfile];
|
|
29273
|
+
const providerTools = providerNamespace ? [...mapping?.providerTools[options.providerProfile] ?? []] : [];
|
|
29274
|
+
const demianTools = [...mapping?.demianTools ?? []];
|
|
29275
|
+
const defaultDecision = mapping?.defaultDecision ?? options.agent.defaultDecision ?? "ask";
|
|
29276
|
+
const rules = overlayRules({ intent, providerNamespace, providerTools, demianTools });
|
|
29277
|
+
return {
|
|
29278
|
+
intent,
|
|
29279
|
+
providerProfile: options.providerProfile,
|
|
29280
|
+
providerNamespace,
|
|
29281
|
+
demianTools,
|
|
29282
|
+
providerTools,
|
|
29283
|
+
rules,
|
|
29284
|
+
defaultDecision,
|
|
29285
|
+
explanation: [
|
|
29286
|
+
`permission intent ${intent}`,
|
|
29287
|
+
providerNamespace ? `${providerNamespace} tools: ${providerTools.join(", ") || "(none)"}` : "no provider tool namespace",
|
|
29288
|
+
`default decision ${defaultDecision}`
|
|
29289
|
+
]
|
|
29290
|
+
};
|
|
29291
|
+
}
|
|
29292
|
+
function overlayRules(options) {
|
|
29293
|
+
const rules = [];
|
|
29294
|
+
for (const tool of options.demianTools) {
|
|
29295
|
+
rules.push({ tool, decision: demianToolDecision(options.intent, tool) });
|
|
29296
|
+
}
|
|
29297
|
+
if (options.providerNamespace) {
|
|
29298
|
+
const allowed = new Set(options.providerTools);
|
|
29299
|
+
for (const tool of options.providerTools) {
|
|
29300
|
+
rules.push({ tool: `${options.providerNamespace}.${tool}`, decision: providerToolDecision(options.intent, tool) });
|
|
29301
|
+
}
|
|
29302
|
+
for (const tool of ["Edit", "Write", "MultiEdit", "Bash"]) {
|
|
29303
|
+
if (!allowed.has(tool)) rules.push({ tool: `${options.providerNamespace}.${tool}`, decision: "deny", reason: `${options.intent} cowork permission overlay` });
|
|
29304
|
+
}
|
|
29305
|
+
}
|
|
29306
|
+
return rules;
|
|
29307
|
+
}
|
|
29308
|
+
function demianToolDecision(intent, tool) {
|
|
29309
|
+
if (intent !== "write") return "allow";
|
|
29310
|
+
return tool === "edit_file" || tool === "write_file" || tool === "bash" ? "ask" : "allow";
|
|
29311
|
+
}
|
|
29312
|
+
function providerToolDecision(intent, tool) {
|
|
29313
|
+
if (intent !== "write") return "allow";
|
|
29314
|
+
return tool === "Edit" || tool === "Write" || tool === "MultiEdit" || tool === "Bash" ? "ask" : "allow";
|
|
29315
|
+
}
|
|
29316
|
+
|
|
29317
|
+
// src/cowork/routing.ts
|
|
29318
|
+
function resolveCoworkAssignment(options) {
|
|
29319
|
+
const memberProvider = options.member.provider?.trim();
|
|
29320
|
+
if (memberProvider && !options.config.providers.includes(memberProvider)) {
|
|
29321
|
+
throw new Error(`cowork member provider ${memberProvider} is not in cowork.providers.`);
|
|
29322
|
+
}
|
|
29323
|
+
const agentName = coworkAgentName(options.agent.name);
|
|
29324
|
+
const sourceAndOrder = providerOrder({
|
|
29325
|
+
memberProvider,
|
|
29326
|
+
memberModel: options.member.model,
|
|
29327
|
+
memberModelProfileName: options.member.modelProfileName,
|
|
29328
|
+
agentName,
|
|
29329
|
+
agent: options.agent,
|
|
29330
|
+
config: options.config
|
|
29331
|
+
});
|
|
29332
|
+
const constrainedOrder = sourceAndOrder.source === "legacy-agent-provider" ? sourceAndOrder.order : sourceAndOrder.order.filter((selection) => options.config.providers.includes(selection.provider));
|
|
29333
|
+
const filtered = constrainedOrder.filter((selection) => options.availability?.[selection.provider] !== "unavailable");
|
|
29334
|
+
if (filtered.length === 0) {
|
|
29335
|
+
throw new Error(`No available cowork provider for ${options.agent.name}. Tried: ${sourceAndOrder.order.map(providerSelectionLabel).join(", ") || "(none)"}.`);
|
|
29336
|
+
}
|
|
29337
|
+
const selected = filtered[0];
|
|
29338
|
+
const providerProfile = selected.provider;
|
|
29339
|
+
const mode = options.member.mode ?? "read-only";
|
|
29340
|
+
const permissionOverlay = compileCoworkPermissionOverlay({
|
|
29341
|
+
config: options.config,
|
|
29342
|
+
agent: options.agent,
|
|
29343
|
+
providerProfile,
|
|
29344
|
+
mode
|
|
29345
|
+
});
|
|
29346
|
+
return {
|
|
29347
|
+
agent: options.agent,
|
|
29348
|
+
agentName,
|
|
29349
|
+
providerProfile,
|
|
29350
|
+
model: selected.model,
|
|
29351
|
+
modelProfileName: selected.modelProfileName,
|
|
29352
|
+
remainingProviders: filtered.slice(1).map((selection) => selection.provider),
|
|
29353
|
+
remainingProviderSelections: filtered.slice(1),
|
|
29354
|
+
source: sourceAndOrder.source,
|
|
29355
|
+
permissionOverlay,
|
|
29356
|
+
explanation: [
|
|
29357
|
+
`provider source: ${sourceAndOrder.source}`,
|
|
29358
|
+
`provider/model order: ${filtered.map(providerSelectionLabel).join(" > ")}`,
|
|
29359
|
+
...permissionOverlay.explanation
|
|
29360
|
+
]
|
|
29361
|
+
};
|
|
29362
|
+
}
|
|
29363
|
+
function coworkProviderAvailability(config, providers) {
|
|
29364
|
+
const availability = {};
|
|
29365
|
+
for (const provider of config.providers) {
|
|
29366
|
+
const providerConfig = providers[provider];
|
|
29367
|
+
if (!providerConfig || providerConfig.hidden) availability[provider] = "unavailable";
|
|
29368
|
+
else availability[provider] = "configured";
|
|
29369
|
+
}
|
|
29370
|
+
return availability;
|
|
29371
|
+
}
|
|
29372
|
+
function providerOrder(options) {
|
|
29373
|
+
if (options.memberProvider) {
|
|
29374
|
+
return {
|
|
29375
|
+
source: "explicit-provider",
|
|
29376
|
+
order: [
|
|
29377
|
+
{
|
|
29378
|
+
provider: options.memberProvider,
|
|
29379
|
+
...options.memberModel ? { model: options.memberModel } : {},
|
|
29380
|
+
...options.memberModelProfileName ? { modelProfileName: options.memberModelProfileName } : {}
|
|
29381
|
+
}
|
|
29382
|
+
]
|
|
29383
|
+
};
|
|
29384
|
+
}
|
|
29385
|
+
if (options.agent.provider?.profile && !options.agent.defaultProviders?.length) return { source: "legacy-agent-provider", order: [{ provider: options.agent.provider.profile }] };
|
|
29386
|
+
const userModelPool = options.agentName ? options.config.routing.agentModelPools[options.agentName] : void 0;
|
|
29387
|
+
if (userModelPool?.length) return { source: "user-model-pool", order: uniqueProviderSelections(userModelPool) };
|
|
29388
|
+
const userOrder = options.agentName ? options.config.routing.agentProviders[options.agentName] : void 0;
|
|
29389
|
+
if (userOrder?.length) return { source: "user-routing", order: unique2(userOrder).map((provider) => ({ provider })) };
|
|
29390
|
+
if (options.agent.defaultProviders?.length) return { source: "agent-default", order: unique2(options.agent.defaultProviders).map((provider) => ({ provider })) };
|
|
29391
|
+
if (options.agent.provider?.profile) return { source: "legacy-agent-provider", order: [{ provider: options.agent.provider.profile }] };
|
|
29392
|
+
return { source: "cowork-pool", order: unique2(options.config.providers).map((provider) => ({ provider })) };
|
|
29393
|
+
}
|
|
29394
|
+
function coworkAgentName(value) {
|
|
29395
|
+
return COWORK_AGENT_NAMES.includes(value) ? value : void 0;
|
|
29396
|
+
}
|
|
29397
|
+
function unique2(values) {
|
|
29398
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29399
|
+
const out = [];
|
|
29400
|
+
for (const value of values) {
|
|
29401
|
+
if (seen.has(value)) continue;
|
|
29402
|
+
seen.add(value);
|
|
29403
|
+
out.push(value);
|
|
29404
|
+
}
|
|
29405
|
+
return out;
|
|
29406
|
+
}
|
|
29407
|
+
function uniqueProviderSelections(values) {
|
|
29408
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29409
|
+
const out = [];
|
|
29410
|
+
for (const value of values) {
|
|
29411
|
+
const key = `${value.provider}\0${value.modelProfileName ?? ""}\0${value.model ?? ""}`;
|
|
29412
|
+
if (seen.has(key)) continue;
|
|
29413
|
+
seen.add(key);
|
|
29414
|
+
out.push({ ...value });
|
|
29415
|
+
}
|
|
29416
|
+
return out;
|
|
29417
|
+
}
|
|
29418
|
+
function providerSelectionLabel(selection) {
|
|
29419
|
+
const model = selection.modelProfileName ?? selection.model;
|
|
29420
|
+
return model ? `${selection.provider}:${model}` : selection.provider;
|
|
29421
|
+
}
|
|
29422
|
+
|
|
28267
29423
|
// src/root-session.ts
|
|
28268
29424
|
var demianOnlyToolNames = /* @__PURE__ */ new Set(["delegate_agent", "cowork", "get_goal", "create_goal", "update_goal"]);
|
|
28269
29425
|
var RootSession = class {
|
|
@@ -28277,6 +29433,7 @@ var RootSession = class {
|
|
|
28277
29433
|
#transcript;
|
|
28278
29434
|
#agentSessions = /* @__PURE__ */ new Map();
|
|
28279
29435
|
#externalSessions = new ClaudeCodeSessionMap();
|
|
29436
|
+
#coworkProviderAvailability;
|
|
28280
29437
|
#mainExternalSessionKey;
|
|
28281
29438
|
#currentPrompt = "";
|
|
28282
29439
|
#progress;
|
|
@@ -28285,6 +29442,7 @@ var RootSession = class {
|
|
|
28285
29442
|
constructor(options) {
|
|
28286
29443
|
this.#options = options;
|
|
28287
29444
|
this.#permissionPreset = normalizePermissionPreset(options.permissionPreset);
|
|
29445
|
+
this.#coworkProviderAvailability = coworkProviderAvailability(options.config.cowork, options.config.providers);
|
|
28288
29446
|
this.events = options.eventBus ?? new EventBus();
|
|
28289
29447
|
this.agents = new AgentRegistry();
|
|
28290
29448
|
this.#progress = new ProgressLedger({ rootSessionId: this.id });
|
|
@@ -28331,6 +29489,8 @@ var RootSession = class {
|
|
|
28331
29489
|
agent: mainAgent,
|
|
28332
29490
|
hooks: this.#options.hooks ?? this.#options.config.hooks,
|
|
28333
29491
|
maxTurns: this.#options.maxTurns ?? this.#options.config.maxTurns,
|
|
29492
|
+
countToolUseTurns: false,
|
|
29493
|
+
maxToolUseRounds: relaxedToolUseRoundLimit(this.#options.maxTurns ?? this.#options.config.maxTurns),
|
|
28334
29494
|
yes: this.#options.yes,
|
|
28335
29495
|
dryRun: this.#options.dryRun,
|
|
28336
29496
|
streaming: this.#options.streaming ?? this.#options.config.streaming.enabled,
|
|
@@ -28343,6 +29503,7 @@ var RootSession = class {
|
|
|
28343
29503
|
signal: options.signal,
|
|
28344
29504
|
eventBus: this.events,
|
|
28345
29505
|
permissionPrompt: this.permissionPrompt,
|
|
29506
|
+
autoPermission: this.autoPermissionForBackend(backend, this.#options.cwd, options.signal),
|
|
28346
29507
|
toolRegistry: this.tools,
|
|
28347
29508
|
grants: this.#grants,
|
|
28348
29509
|
transcriptWriter: this.#transcript,
|
|
@@ -28391,7 +29552,7 @@ var RootSession = class {
|
|
|
28391
29552
|
const providerProfile = childAgent.provider?.profile ?? this.#options.providerName;
|
|
28392
29553
|
const invocationGrantKey = grantKeyForAgentInvocation(childAgent.name, providerProfile);
|
|
28393
29554
|
if (this.#grants.has(invocationGrantKey)) return { ok: true };
|
|
28394
|
-
const decision =
|
|
29555
|
+
const decision = invocationDecisionForPermissionPreset(this.#permissionPreset);
|
|
28395
29556
|
if (decision === "deny") return { ok: false, error: `Agent invocation denied: ${childAgent.name}` };
|
|
28396
29557
|
if (decision !== "ask") return { ok: true };
|
|
28397
29558
|
this.events.emit({
|
|
@@ -28510,6 +29671,8 @@ var RootSession = class {
|
|
|
28510
29671
|
agent: childRuntimeAgent,
|
|
28511
29672
|
hooks: this.#options.hooks ?? this.#options.config.hooks,
|
|
28512
29673
|
maxTurns,
|
|
29674
|
+
countToolUseTurns: false,
|
|
29675
|
+
maxToolUseRounds: relaxedToolUseRoundLimit(maxTurns),
|
|
28513
29676
|
yes: this.#options.yes,
|
|
28514
29677
|
dryRun: this.#options.dryRun,
|
|
28515
29678
|
streaming: false,
|
|
@@ -28522,6 +29685,7 @@ var RootSession = class {
|
|
|
28522
29685
|
signal,
|
|
28523
29686
|
eventBus: this.events,
|
|
28524
29687
|
permissionPrompt: this.permissionPrompt,
|
|
29688
|
+
autoPermission: this.autoPermissionForBackend(backend, cwd, signal),
|
|
28525
29689
|
toolRegistry: this.tools,
|
|
28526
29690
|
grants: this.#grants,
|
|
28527
29691
|
transcriptWriter: this.#transcript,
|
|
@@ -28544,6 +29708,31 @@ var RootSession = class {
|
|
|
28544
29708
|
session.externalSessionKey = externalKey;
|
|
28545
29709
|
}
|
|
28546
29710
|
}
|
|
29711
|
+
if (result.endReason !== "end_turn") {
|
|
29712
|
+
const message = childExecutionFailureMessage(result.endReason, result.finalAnswer);
|
|
29713
|
+
if (result.endReason === "cancelled") {
|
|
29714
|
+
this.events.emit({
|
|
29715
|
+
type: "agent.invocation.cancelled",
|
|
29716
|
+
rootSessionId: this.id,
|
|
29717
|
+
agentSessionId: session.id,
|
|
29718
|
+
invocationId,
|
|
29719
|
+
agent: childRuntimeAgent.name,
|
|
29720
|
+
reason: message,
|
|
29721
|
+
ts: now()
|
|
29722
|
+
});
|
|
29723
|
+
return { ok: false, error: `Agent invocation cancelled: ${message}`, invocationId, agentName: childRuntimeAgent.name, providerProfile, model: backend.model, metadata: { endReason: result.endReason } };
|
|
29724
|
+
}
|
|
29725
|
+
this.events.emit({
|
|
29726
|
+
type: "agent.invocation.failed",
|
|
29727
|
+
rootSessionId: this.id,
|
|
29728
|
+
agentSessionId: session.id,
|
|
29729
|
+
invocationId,
|
|
29730
|
+
agent: childRuntimeAgent.name,
|
|
29731
|
+
error: message,
|
|
29732
|
+
ts: now()
|
|
29733
|
+
});
|
|
29734
|
+
return { ok: false, error: `Agent invocation failed: ${message}`, invocationId, agentName: childRuntimeAgent.name, providerProfile, model: backend.model, metadata: { endReason: result.endReason } };
|
|
29735
|
+
}
|
|
28547
29736
|
this.updateAgentSession(session, input2, result.messages, result.finalAnswer);
|
|
28548
29737
|
if (beforeMessages !== session.messages.length) {
|
|
28549
29738
|
this.events.emit({
|
|
@@ -28619,7 +29808,7 @@ var RootSession = class {
|
|
|
28619
29808
|
}))
|
|
28620
29809
|
});
|
|
28621
29810
|
if (!this.#grants.has(grantKey)) {
|
|
28622
|
-
const decision = this.#
|
|
29811
|
+
const decision = invocationDecisionForPermissionPreset(this.#permissionPreset);
|
|
28623
29812
|
if (decision === "deny") return fail("Cowork invocation denied by configuration.");
|
|
28624
29813
|
if (decision === "ask") {
|
|
28625
29814
|
this.events.emit({
|
|
@@ -28803,43 +29992,27 @@ var RootSession = class {
|
|
|
28803
29992
|
}
|
|
28804
29993
|
async runCoworkMember(member, group, outcomes, context) {
|
|
28805
29994
|
const startedAt = now();
|
|
28806
|
-
const coworkAgent = this.decorateCoworkMemberAgent(member.agent, member.mode);
|
|
28807
|
-
const runtimeAgent = this.decorateChildAgent(coworkAgent);
|
|
28808
|
-
const backend = this.resolveInvocationBackend(member.providerProfile, void 0, runtimeAgent);
|
|
28809
|
-
this.events.emit({
|
|
28810
|
-
type: "cowork.member.started",
|
|
28811
|
-
rootSessionId: this.id,
|
|
28812
|
-
parentInvocationId: context.parent.parentInvocationId,
|
|
28813
|
-
groupId: context.groupId,
|
|
28814
|
-
memberId: member.memberId,
|
|
28815
|
-
agent: member.agent.name,
|
|
28816
|
-
provider: member.providerProfile,
|
|
28817
|
-
model: backend.model,
|
|
28818
|
-
mode: member.mode,
|
|
28819
|
-
ts: startedAt
|
|
28820
|
-
});
|
|
28821
29995
|
const input2 = withParentContext(member.input, group, outcomes);
|
|
28822
29996
|
const workspace = member.isolated ? await this.createCoworkIsolatedWorkspace(context.groupId, member.memberId) : { cwd: this.#options.cwd };
|
|
28823
29997
|
const snapshot = member.mode === "write" ? await this.createCoworkWriterSnapshot(workspace.cwd) : void 0;
|
|
28824
|
-
const
|
|
28825
|
-
sessionScope: `cowork:${context.groupId}:${member.memberId}`,
|
|
28826
|
-
signal: context.signal,
|
|
28827
|
-
writeScope: member.writeScope,
|
|
28828
|
-
cwd: workspace.cwd
|
|
28829
|
-
});
|
|
29998
|
+
const attempt = member.mode === "write" ? await this.runCoworkWriteAttempt(member, input2, snapshot, workspace.cwd, context) : await this.runCoworkNonWriteAttempts(member, input2, workspace.cwd, context);
|
|
28830
29999
|
const durationMs = now() - startedAt;
|
|
28831
30000
|
const audit = member.mode === "write" ? await this.auditCoworkWriter(member, snapshot, workspace.cwd, member.isolated) : void 0;
|
|
28832
30001
|
if ("cleanup" in workspace) await workspace.cleanup().catch(() => void 0);
|
|
30002
|
+
const result = attempt.result;
|
|
30003
|
+
const providerProfile = result.providerProfile;
|
|
28833
30004
|
if (result.ok) {
|
|
28834
30005
|
const outOfScope2 = audit?.outOfScopeFiles ?? [];
|
|
28835
30006
|
const outcome2 = {
|
|
28836
30007
|
memberId: member.memberId,
|
|
28837
30008
|
agent: member.agent.name,
|
|
28838
|
-
provider:
|
|
30009
|
+
provider: providerProfile,
|
|
28839
30010
|
status: outOfScope2.length ? "failed" : "completed",
|
|
28840
|
-
summary: preview(coworkMemberSummary(result.finalAnswer.trim() || "Completed.", audit), 1e3),
|
|
30011
|
+
summary: preview(coworkMemberSummary(result.finalAnswer.trim() || "Completed.", audit, attempt), 1e3),
|
|
28841
30012
|
artifacts: audit ? [coworkAuditArtifact(audit)] : [],
|
|
28842
30013
|
reason: outOfScope2.length ? "out_of_scope" : void 0,
|
|
30014
|
+
fallbackReason: attempt.fallbackReason,
|
|
30015
|
+
repairReason: attempt.repairReason,
|
|
28843
30016
|
error: outOfScope2.length ? `Writer changed files outside writeScope: ${outOfScope2.join(", ")}` : void 0,
|
|
28844
30017
|
invocationId: result.invocationId,
|
|
28845
30018
|
tokens: result.usage,
|
|
@@ -28857,11 +30030,13 @@ var RootSession = class {
|
|
|
28857
30030
|
const outcome = {
|
|
28858
30031
|
memberId: member.memberId,
|
|
28859
30032
|
agent: member.agent.name,
|
|
28860
|
-
provider:
|
|
30033
|
+
provider: providerProfile,
|
|
28861
30034
|
status: cancelled ? "cancelled" : "failed",
|
|
28862
|
-
summary: preview(coworkMemberSummary(result.error, audit), 1e3),
|
|
30035
|
+
summary: preview(coworkMemberSummary(result.error, audit, attempt), 1e3),
|
|
28863
30036
|
artifacts: audit ? [coworkAuditArtifact(audit)] : [],
|
|
28864
30037
|
reason: cancelled ? "cancelled" : outOfScope.length ? "out_of_scope" : void 0,
|
|
30038
|
+
fallbackReason: attempt.fallbackReason,
|
|
30039
|
+
repairReason: attempt.repairReason,
|
|
28865
30040
|
error: outOfScope.length ? `${result.error}
|
|
28866
30041
|
Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.error,
|
|
28867
30042
|
invocationId: result.invocationId,
|
|
@@ -28874,6 +30049,115 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
28874
30049
|
}
|
|
28875
30050
|
return outcome;
|
|
28876
30051
|
}
|
|
30052
|
+
async runCoworkNonWriteAttempts(member, input2, cwd, context) {
|
|
30053
|
+
const providers = [{ provider: member.providerProfile, model: member.model, modelProfileName: member.modelProfileName }, ...member.remainingProviderSelections];
|
|
30054
|
+
const errors = [];
|
|
30055
|
+
let fallbackReason;
|
|
30056
|
+
for (const [index, selection] of providers.entries()) {
|
|
30057
|
+
const overlay = index === 0 && member.permissionOverlay ? member.permissionOverlay : this.compileCoworkOverlay(member, selection.provider);
|
|
30058
|
+
const result2 = await this.executeCoworkMemberAttempt(member, selection, overlay, input2, cwd, context, `cowork:${context.groupId}:${member.memberId}:${providerSelectionScope(selection)}`);
|
|
30059
|
+
if (result2.ok) return { result: result2, fallbackReason };
|
|
30060
|
+
errors.push(`${providerSelectionLabel2(selection)}: ${result2.error}`);
|
|
30061
|
+
if (context.signal.aborted) return { result: result2, fallbackReason };
|
|
30062
|
+
const policy = pickCoworkNonWriteFallbackPolicy(result2.error, this.#options.config.cowork.fallback.nonWrite);
|
|
30063
|
+
const nextProvider = providers[index + 1];
|
|
30064
|
+
if ((policy === "next-provider" || policy === "skip-provider") && nextProvider) {
|
|
30065
|
+
if (policy === "skip-provider") this.#coworkProviderAvailability[selection.provider] = "unavailable";
|
|
30066
|
+
fallbackReason = `${providerSelectionLabel2(selection)} failed (${coworkFailureKind(result2.error)}); switched to ${providerSelectionLabel2(nextProvider)}`;
|
|
30067
|
+
continue;
|
|
30068
|
+
}
|
|
30069
|
+
return { result: withCombinedCoworkErrors(result2, errors), fallbackReason };
|
|
30070
|
+
}
|
|
30071
|
+
const result = {
|
|
30072
|
+
ok: false,
|
|
30073
|
+
error: `Agent invocation failed: all cowork providers failed for ${member.agent.name}.
|
|
30074
|
+
${errors.join("\n")}`,
|
|
30075
|
+
agentName: member.agent.name,
|
|
30076
|
+
providerProfile: providers.at(-1)?.provider ?? member.providerProfile
|
|
30077
|
+
};
|
|
30078
|
+
return { result, fallbackReason };
|
|
30079
|
+
}
|
|
30080
|
+
async runCoworkWriteAttempt(member, input2, snapshot, cwd, context) {
|
|
30081
|
+
const selection = { provider: member.providerProfile, model: member.model, modelProfileName: member.modelProfileName };
|
|
30082
|
+
const result = await this.executeCoworkMemberAttempt(member, selection, member.permissionOverlay, input2, cwd, context, `cowork:${context.groupId}:${member.memberId}`);
|
|
30083
|
+
if (result.ok || context.signal.aborted) return { result };
|
|
30084
|
+
const policy = this.#options.config.cowork.fallback.write;
|
|
30085
|
+
if (policy.onFailure !== "repair-from-worktree" || policy.maxRepairAttempts < 1) return { result };
|
|
30086
|
+
const repairContext = await this.buildCoworkRepairContext(result.error, snapshot, cwd);
|
|
30087
|
+
if (!repairContext.hasChangedFiles) return { result };
|
|
30088
|
+
let current = result;
|
|
30089
|
+
let repairReason = `write attempt failed on ${member.providerProfile}; retried same provider after reading changed files`;
|
|
30090
|
+
for (let attempt = 1; attempt <= policy.maxRepairAttempts; attempt++) {
|
|
30091
|
+
const repairInput = {
|
|
30092
|
+
...input2,
|
|
30093
|
+
task: buildCoworkRepairTask(input2.task, current.error, attempt),
|
|
30094
|
+
context: [input2.context, repairContext.text].filter(Boolean).join("\n\n") || void 0
|
|
30095
|
+
};
|
|
30096
|
+
current = await this.executeCoworkMemberAttempt(member, selection, member.permissionOverlay, repairInput, cwd, context, `cowork:${context.groupId}:${member.memberId}:repair:${attempt}`);
|
|
30097
|
+
if (current.ok) return { result: current, repairReason };
|
|
30098
|
+
if (context.signal.aborted) return { result: current, repairReason };
|
|
30099
|
+
}
|
|
30100
|
+
return { result: current, repairReason };
|
|
30101
|
+
}
|
|
30102
|
+
async executeCoworkMemberAttempt(member, selection, overlay, input2, cwd, context, sessionScope) {
|
|
30103
|
+
const providerProfile = selection.provider;
|
|
30104
|
+
const decoratedCoworkAgent = this.decorateCoworkMemberAgent(member.agent, member.mode, overlay);
|
|
30105
|
+
const coworkAgent2 = { ...decoratedCoworkAgent, provider: { ...decoratedCoworkAgent.provider, profile: providerProfile } };
|
|
30106
|
+
const runtimeAgent = this.decorateChildAgent(coworkAgent2);
|
|
30107
|
+
let model = "";
|
|
30108
|
+
try {
|
|
30109
|
+
model = this.resolveInvocationBackend(providerProfile, { model: selection.model, modelProfileName: selection.modelProfileName }, runtimeAgent).model;
|
|
30110
|
+
} catch (error) {
|
|
30111
|
+
return { ok: false, error: `Agent invocation failed: ${errorMessage(error)}`, agentName: runtimeAgent.name, providerProfile };
|
|
30112
|
+
}
|
|
30113
|
+
this.events.emit({
|
|
30114
|
+
type: "cowork.member.started",
|
|
30115
|
+
rootSessionId: this.id,
|
|
30116
|
+
parentInvocationId: context.parent.parentInvocationId,
|
|
30117
|
+
groupId: context.groupId,
|
|
30118
|
+
memberId: member.memberId,
|
|
30119
|
+
agent: member.agent.name,
|
|
30120
|
+
provider: providerProfile,
|
|
30121
|
+
model,
|
|
30122
|
+
mode: member.mode,
|
|
30123
|
+
ts: now()
|
|
30124
|
+
});
|
|
30125
|
+
return this.executeChildAgentSession(input2, coworkAgent2, context.parent, {
|
|
30126
|
+
sessionScope,
|
|
30127
|
+
signal: context.signal,
|
|
30128
|
+
writeScope: member.writeScope,
|
|
30129
|
+
cwd
|
|
30130
|
+
});
|
|
30131
|
+
}
|
|
30132
|
+
compileCoworkOverlay(member, providerProfile) {
|
|
30133
|
+
return compileCoworkPermissionOverlay({
|
|
30134
|
+
config: this.#options.config.cowork,
|
|
30135
|
+
agent: member.agent,
|
|
30136
|
+
providerProfile,
|
|
30137
|
+
mode: member.mode
|
|
30138
|
+
});
|
|
30139
|
+
}
|
|
30140
|
+
async buildCoworkRepairContext(error, snapshot, cwd) {
|
|
30141
|
+
const summary = snapshot ? await diffWorkspaceSnapshot(snapshot).catch(() => void 0) : void 0;
|
|
30142
|
+
const files = summary?.files ?? [];
|
|
30143
|
+
const chunks = [
|
|
30144
|
+
"Cowork write repair context:",
|
|
30145
|
+
"- Do not switch providers for this write repair.",
|
|
30146
|
+
"- Re-read the current workspace state below and repair from it.",
|
|
30147
|
+
"",
|
|
30148
|
+
"Failure:",
|
|
30149
|
+
error,
|
|
30150
|
+
"",
|
|
30151
|
+
files.length ? "Changed files since the failed attempt:" : "No changed files were detected after the failed attempt."
|
|
30152
|
+
];
|
|
30153
|
+
for (const file of files) chunks.push(`- ${file.path} (${file.kind}, +${file.addedLines}/-${file.removedLines})`);
|
|
30154
|
+
if (summary?.diff) chunks.push("", "Diff since original snapshot:", preview(summary.diff, 6e3));
|
|
30155
|
+
if (this.#options.config.cowork.fallback.write.readChangedFilesBeforeRepair) {
|
|
30156
|
+
const fileContents = await readCoworkRepairFiles(cwd, files.map((file) => file.path));
|
|
30157
|
+
if (fileContents.length) chunks.push("", "Current changed file contents:", ...fileContents);
|
|
30158
|
+
}
|
|
30159
|
+
return { text: chunks.join("\n"), hasChangedFiles: files.length > 0 };
|
|
30160
|
+
}
|
|
28877
30161
|
async createCoworkWriterSnapshot(cwd) {
|
|
28878
30162
|
try {
|
|
28879
30163
|
return await createWorkspaceSnapshot(cwd);
|
|
@@ -28900,8 +30184,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
28900
30184
|
}
|
|
28901
30185
|
}
|
|
28902
30186
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
28903
|
-
const root = await mkdtemp(
|
|
28904
|
-
const cwd =
|
|
30187
|
+
const root = await mkdtemp(path29.join(os12.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
30188
|
+
const cwd = path29.join(root, "workspace");
|
|
28905
30189
|
await cp(this.#options.cwd, cwd, {
|
|
28906
30190
|
recursive: true,
|
|
28907
30191
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -28977,11 +30261,28 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
28977
30261
|
if (denied?.includes(target.name)) return { ok: false, error: `Agent ${parent.parentAgent.name} is configured not to cowork with ${target.name}.` };
|
|
28978
30262
|
const maxDepth = parent.parentAgent.delegation?.maxDepth ?? this.#options.config.delegation.maxDepth;
|
|
28979
30263
|
if (parent.agentPath.length > maxDepth) return { ok: false, error: `Max delegation depth (${maxDepth}) exceeded.` };
|
|
30264
|
+
let assignment;
|
|
30265
|
+
try {
|
|
30266
|
+
assignment = resolveCoworkAssignment({
|
|
30267
|
+
member,
|
|
30268
|
+
agent: target,
|
|
30269
|
+
config: this.#options.config.cowork,
|
|
30270
|
+
defaultProvider: this.#options.providerName,
|
|
30271
|
+
availability: this.#coworkProviderAvailability
|
|
30272
|
+
});
|
|
30273
|
+
} catch (error) {
|
|
30274
|
+
return { ok: false, error: errorMessage(error) };
|
|
30275
|
+
}
|
|
28980
30276
|
group.push({
|
|
28981
30277
|
memberId,
|
|
28982
30278
|
input: member,
|
|
28983
30279
|
agent: target,
|
|
28984
|
-
providerProfile:
|
|
30280
|
+
providerProfile: assignment.providerProfile,
|
|
30281
|
+
model: assignment.model,
|
|
30282
|
+
modelProfileName: assignment.modelProfileName,
|
|
30283
|
+
remainingProviders: assignment.remainingProviders,
|
|
30284
|
+
remainingProviderSelections: assignment.remainingProviderSelections,
|
|
30285
|
+
permissionOverlay: assignment.permissionOverlay,
|
|
28985
30286
|
mode,
|
|
28986
30287
|
writeScope,
|
|
28987
30288
|
isolated: mode === "write" && strategy === "multi-patch" && this.#options.config.cowork.writerIsolation === "snapshot-diff",
|
|
@@ -29043,24 +30344,54 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29043
30344
|
systemPrompt: [agent.systemPrompt, this.callableCatalogPrompt(agent)].filter(Boolean).join("\n\n")
|
|
29044
30345
|
});
|
|
29045
30346
|
}
|
|
29046
|
-
decorateCoworkMemberAgent(agent, mode) {
|
|
29047
|
-
|
|
30347
|
+
decorateCoworkMemberAgent(agent, mode, overlay) {
|
|
30348
|
+
const overlayPermissions = overlay?.rules ?? [];
|
|
30349
|
+
const overlayDefaultDecision = overlay?.defaultDecision ?? agent.defaultDecision;
|
|
30350
|
+
const systemPrompt = [agent.systemPrompt, coworkMemberToolBoundaryPrompt(mode)].join("\n\n");
|
|
30351
|
+
if (mode === "write") {
|
|
30352
|
+
return this.withPermissionPreset({
|
|
30353
|
+
...agent,
|
|
30354
|
+
systemPrompt,
|
|
30355
|
+
permissions: [...agent.permissions, ...overlayPermissions],
|
|
30356
|
+
defaultDecision: overlayDefaultDecision
|
|
30357
|
+
});
|
|
30358
|
+
}
|
|
29048
30359
|
const readTools = /* @__PURE__ */ new Set(["read_file", "grep", "glob", "web_search", "update_plan", "report_progress"]);
|
|
29049
30360
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
29050
30361
|
return this.withPermissionPreset({
|
|
29051
30362
|
...agent,
|
|
29052
|
-
|
|
30363
|
+
systemPrompt,
|
|
29053
30364
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
29054
30365
|
permissions: [
|
|
29055
30366
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
30367
|
+
...overlayPermissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
29056
30368
|
...denyTools.map((tool) => ({ tool, decision: "deny", reason: "cowork read-only member" }))
|
|
29057
30369
|
],
|
|
29058
|
-
defaultDecision: "deny"
|
|
30370
|
+
defaultDecision: overlayDefaultDecision === "allow" ? "allow" : "deny"
|
|
29059
30371
|
});
|
|
29060
30372
|
}
|
|
29061
30373
|
withPermissionPreset(agent) {
|
|
29062
30374
|
return applyPermissionPresetToAgent(agent, this.#permissionPreset);
|
|
29063
30375
|
}
|
|
30376
|
+
autoPermissionForBackend(backend, cwd, signal) {
|
|
30377
|
+
if (this.#permissionPreset !== "auto") return void 0;
|
|
30378
|
+
if (backend.kind === "provider") return createAutoPermissionAgent({ provider: backend.provider, model: backend.model, cwd, signal });
|
|
30379
|
+
return this.autoPermissionForExternalRuntime(cwd, signal);
|
|
30380
|
+
}
|
|
30381
|
+
autoPermissionForExternalRuntime(cwd, signal) {
|
|
30382
|
+
if (this.#permissionPreset !== "auto") return void 0;
|
|
30383
|
+
const candidates = /* @__PURE__ */ new Set([this.#options.providerName, this.#options.config.defaultProvider, ...Object.keys(this.#options.config.providers)]);
|
|
30384
|
+
for (const profileName of candidates) {
|
|
30385
|
+
try {
|
|
30386
|
+
const providerConfig = resolveProviderConfig(this.#options.config, profileName);
|
|
30387
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName });
|
|
30388
|
+
const backend = this.#options.resolveExecutionBackend ? this.#options.resolveExecutionBackend(profileName, providerConfig, runtime.model) : this.#options.resolveProvider ? { kind: "provider", ...this.#options.resolveProvider(profileName, providerConfig, runtime.model) } : resolveExecutionBackend(providerConfig, { model: runtime.model, config: this.#options.config });
|
|
30389
|
+
if (backend.kind === "provider") return createAutoPermissionAgent({ provider: backend.provider, model: backend.model, cwd, signal });
|
|
30390
|
+
} catch {
|
|
30391
|
+
}
|
|
30392
|
+
}
|
|
30393
|
+
return void 0;
|
|
30394
|
+
}
|
|
29064
30395
|
callableCatalogPrompt(caller) {
|
|
29065
30396
|
const allowed = caller.delegation?.allowedAgents;
|
|
29066
30397
|
const denied = new Set(caller.delegation?.deniedAgents ?? []);
|
|
@@ -29095,10 +30426,13 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
29095
30426
|
}
|
|
29096
30427
|
return lines.join("\n");
|
|
29097
30428
|
}
|
|
29098
|
-
resolveInvocationBackend(profileName,
|
|
29099
|
-
const
|
|
30429
|
+
resolveInvocationBackend(profileName, modelSelection, agent) {
|
|
30430
|
+
const modelOverride = typeof modelSelection === "string" ? modelSelection : modelSelection?.model;
|
|
30431
|
+
const modelProfileName = typeof modelSelection === "string" ? void 0 : modelSelection?.modelProfileName;
|
|
30432
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride, modelProfileName });
|
|
29100
30433
|
const providerConfig = runtime.providerConfig;
|
|
29101
|
-
|
|
30434
|
+
const selectedModel = modelOverride ?? (modelProfileName ? runtime.model : void 0);
|
|
30435
|
+
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, selectedModel);
|
|
29102
30436
|
if (this.#options.resolveProvider) {
|
|
29103
30437
|
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
29104
30438
|
return { kind: "provider", ...resolved };
|
|
@@ -29205,17 +30539,17 @@ function findDependencyCycle(group) {
|
|
|
29205
30539
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
29206
30540
|
const visiting = /* @__PURE__ */ new Set();
|
|
29207
30541
|
const visited = /* @__PURE__ */ new Set();
|
|
29208
|
-
const
|
|
30542
|
+
const path30 = [];
|
|
29209
30543
|
const visit = (id) => {
|
|
29210
|
-
if (visiting.has(id)) return [...
|
|
30544
|
+
if (visiting.has(id)) return [...path30.slice(path30.indexOf(id)), id];
|
|
29211
30545
|
if (visited.has(id)) return void 0;
|
|
29212
30546
|
visiting.add(id);
|
|
29213
|
-
|
|
30547
|
+
path30.push(id);
|
|
29214
30548
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
29215
30549
|
const cycle = visit(dep);
|
|
29216
30550
|
if (cycle) return cycle;
|
|
29217
30551
|
}
|
|
29218
|
-
|
|
30552
|
+
path30.pop();
|
|
29219
30553
|
visiting.delete(id);
|
|
29220
30554
|
visited.add(id);
|
|
29221
30555
|
return void 0;
|
|
@@ -29256,7 +30590,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
29256
30590
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
29257
30591
|
}
|
|
29258
30592
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
29259
|
-
const relative =
|
|
30593
|
+
const relative = path29.relative(root, source).split(path29.sep).join("/");
|
|
29260
30594
|
if (!relative) return true;
|
|
29261
30595
|
const parts = relative.split("/");
|
|
29262
30596
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -29387,8 +30721,14 @@ function summarizeCoworkResult(goal, outcomes) {
|
|
|
29387
30721
|
}
|
|
29388
30722
|
return lines.join("\n");
|
|
29389
30723
|
}
|
|
29390
|
-
function coworkMemberSummary(text, audit) {
|
|
30724
|
+
function coworkMemberSummary(text, audit, attempt) {
|
|
29391
30725
|
const lines = [text];
|
|
30726
|
+
if (attempt?.fallbackReason) {
|
|
30727
|
+
lines.push("", `Fallback: ${attempt.fallbackReason}`);
|
|
30728
|
+
}
|
|
30729
|
+
if (attempt?.repairReason) {
|
|
30730
|
+
lines.push("", `Repair: ${attempt.repairReason}`);
|
|
30731
|
+
}
|
|
29392
30732
|
if (audit?.summary?.files.length) {
|
|
29393
30733
|
lines.push("", `Workspace changes: ${audit.summary.files.map((file) => file.path).join(", ")}`);
|
|
29394
30734
|
}
|
|
@@ -29409,6 +30749,16 @@ function externalMainRuntimePrompt() {
|
|
|
29409
30749
|
"- Do not announce that delegate_agent or cowork is unavailable unless the user explicitly requested one of those tools."
|
|
29410
30750
|
].join("\n");
|
|
29411
30751
|
}
|
|
30752
|
+
function coworkMemberToolBoundaryPrompt(mode) {
|
|
30753
|
+
const capability = mode === "write" ? "read/write tools allowed by the delegated writeScope" : "read-only inspection tools";
|
|
30754
|
+
return [
|
|
30755
|
+
"Cowork member tool boundary:",
|
|
30756
|
+
"- You are already running inside a Demian cowork group; do not try to call cowork or delegate_agent from this member.",
|
|
30757
|
+
`- Use only the ${capability} exposed by your current runtime.`,
|
|
30758
|
+
"- When the provider is Claude Code, Demian maps workspace capabilities through Claude Code native tools such as Read, Grep, Glob, LS, Edit, MultiEdit, Write, and Bash according to the permission overlay.",
|
|
30759
|
+
"- Do not report missing cowork/delegate_agent as a blocker; report a blocker only when the delegated workspace capability itself is missing."
|
|
30760
|
+
].join("\n");
|
|
30761
|
+
}
|
|
29412
30762
|
function coworkAuditArtifact(audit) {
|
|
29413
30763
|
return {
|
|
29414
30764
|
type: "workspace-diff-audit",
|
|
@@ -29437,6 +30787,80 @@ function formatCoworkResult(result) {
|
|
|
29437
30787
|
)
|
|
29438
30788
|
].join("\n");
|
|
29439
30789
|
}
|
|
30790
|
+
function pickCoworkNonWriteFallbackPolicy(error, policy) {
|
|
30791
|
+
const kind = coworkFailureKind(error);
|
|
30792
|
+
if (kind === "auth") return policy.onAuthFailure;
|
|
30793
|
+
if (kind === "token-limit") return policy.onTokenLimit;
|
|
30794
|
+
if (kind === "connection") return policy.onConnectionError;
|
|
30795
|
+
return "fail-member";
|
|
30796
|
+
}
|
|
30797
|
+
function coworkFailureKind(error) {
|
|
30798
|
+
const text = error.toLowerCase();
|
|
30799
|
+
if (/\b(401|403)\b|unauthorized|forbidden|auth|api key|apikey|invalid key|invalid_key/.test(text)) return "auth";
|
|
30800
|
+
if (/token|context length|quota|rate limit|too many requests|\b429\b|maximum context|max_tokens|max tokens/.test(text)) return "token-limit";
|
|
30801
|
+
if (/econnreset|econnrefused|etimedout|enotfound|network|fetch failed|connection|socket|timeout|temporarily unavailable|no scripted response/.test(text)) return "connection";
|
|
30802
|
+
return "unknown";
|
|
30803
|
+
}
|
|
30804
|
+
function childExecutionFailureMessage(endReason, finalAnswer) {
|
|
30805
|
+
const answer = finalAnswer?.trim();
|
|
30806
|
+
if (answer) return answer;
|
|
30807
|
+
if (endReason === "max_turns") return "Stopped after reaching maxTurns before producing a verified result.";
|
|
30808
|
+
if (endReason === "tool_loop_limit") return "Stopped after reaching the tool-use safety limit before producing a verified result.";
|
|
30809
|
+
if (endReason === "content_filter") return "Provider safety filter blocked the child agent response.";
|
|
30810
|
+
if (endReason === "cancelled") return "Cancelled.";
|
|
30811
|
+
if (endReason === "error") return "Child agent runtime ended with an error.";
|
|
30812
|
+
return `Child agent ended with ${endReason}.`;
|
|
30813
|
+
}
|
|
30814
|
+
function withCombinedCoworkErrors(result, errors) {
|
|
30815
|
+
if (result.ok || errors.length <= 1) return result;
|
|
30816
|
+
return {
|
|
30817
|
+
...result,
|
|
30818
|
+
error: `${result.error}
|
|
30819
|
+
|
|
30820
|
+
Cowork provider attempts:
|
|
30821
|
+
${errors.join("\n")}`
|
|
30822
|
+
};
|
|
30823
|
+
}
|
|
30824
|
+
function providerSelectionLabel2(selection) {
|
|
30825
|
+
const model = selection.modelProfileName ?? selection.model;
|
|
30826
|
+
return model ? `${selection.provider}:${model}` : selection.provider;
|
|
30827
|
+
}
|
|
30828
|
+
function providerSelectionScope(selection) {
|
|
30829
|
+
return providerSelectionLabel2(selection).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
30830
|
+
}
|
|
30831
|
+
function buildCoworkRepairTask(originalTask, error, attempt) {
|
|
30832
|
+
return [
|
|
30833
|
+
`Repair attempt ${attempt} for a failed cowork write task.`,
|
|
30834
|
+
"",
|
|
30835
|
+
"Original task:",
|
|
30836
|
+
originalTask,
|
|
30837
|
+
"",
|
|
30838
|
+
"Previous failure:",
|
|
30839
|
+
error,
|
|
30840
|
+
"",
|
|
30841
|
+
"Continue from the current workspace state. Do not restart blindly or overwrite unrelated changes. Stay inside the delegated writeScope."
|
|
30842
|
+
].join("\n");
|
|
30843
|
+
}
|
|
30844
|
+
async function readCoworkRepairFiles(cwd, relativePaths) {
|
|
30845
|
+
const chunks = [];
|
|
30846
|
+
let budget = 12e3;
|
|
30847
|
+
for (const relativePath of relativePaths.slice(0, 8)) {
|
|
30848
|
+
if (budget <= 0) break;
|
|
30849
|
+
const normalized = relativePath.split(path29.sep).join("/");
|
|
30850
|
+
const absolutePath = path29.resolve(cwd, normalized);
|
|
30851
|
+
if (!absolutePath.startsWith(path29.resolve(cwd) + path29.sep) && absolutePath !== path29.resolve(cwd)) continue;
|
|
30852
|
+
try {
|
|
30853
|
+
const content = await readFile14(absolutePath, "utf8");
|
|
30854
|
+
const clipped = preview(content, budget);
|
|
30855
|
+
budget -= clipped.length;
|
|
30856
|
+
chunks.push([`File: ${normalized}`, "```", clipped, "```"].join("\n"));
|
|
30857
|
+
} catch (error) {
|
|
30858
|
+
chunks.push(`File: ${normalized}
|
|
30859
|
+
(unavailable: ${errorMessage(error)})`);
|
|
30860
|
+
}
|
|
30861
|
+
}
|
|
30862
|
+
return chunks;
|
|
30863
|
+
}
|
|
29440
30864
|
function sumUsage(outcomes) {
|
|
29441
30865
|
return outcomes.reduce(
|
|
29442
30866
|
(sum, outcome) => ({
|
|
@@ -29459,6 +30883,8 @@ export {
|
|
|
29459
30883
|
CLAUDE_CODE_SONNET_MODEL,
|
|
29460
30884
|
CODEX_KEYRING_SERVICE,
|
|
29461
30885
|
CODEX_OAUTH_CLIENT_ID,
|
|
30886
|
+
COWORK_AGENT_MIGRATIONS,
|
|
30887
|
+
COWORK_AGENT_NAMES,
|
|
29462
30888
|
ClaudeCodeAuthError,
|
|
29463
30889
|
ClaudeCodeAuthStore,
|
|
29464
30890
|
ClaudeCodeProvider,
|
|
@@ -29476,6 +30902,8 @@ export {
|
|
|
29476
30902
|
DEFAULT_CLAUDE_CODE_USER_AGENT,
|
|
29477
30903
|
DEFAULT_CLAUDE_CODE_VERSION,
|
|
29478
30904
|
DEFAULT_CODEX_BASE_URL,
|
|
30905
|
+
DEFAULT_COWORK_AGENT_PROVIDERS,
|
|
30906
|
+
DEFAULT_COWORK_PERMISSION_OVERLAY,
|
|
29479
30907
|
EventBus,
|
|
29480
30908
|
HookDispatcher,
|
|
29481
30909
|
OllamaProvider,
|
|
@@ -29534,6 +30962,7 @@ export {
|
|
|
29534
30962
|
inferOpenAICompatibleAuth,
|
|
29535
30963
|
injectClaudeCodeSystemPrefix,
|
|
29536
30964
|
invalidateCatalogPingCache,
|
|
30965
|
+
invocationDecisionForPermissionPreset,
|
|
29537
30966
|
isAzureOpenAIEndpoint,
|
|
29538
30967
|
isCallableAgent,
|
|
29539
30968
|
isCoworkableAgent,
|
|
@@ -29566,6 +30995,7 @@ export {
|
|
|
29566
30995
|
providerModelOptions,
|
|
29567
30996
|
providerModelProfiles,
|
|
29568
30997
|
relativeToCwd,
|
|
30998
|
+
relaxedToolUseRoundLimit,
|
|
29569
30999
|
resolveAgentMode,
|
|
29570
31000
|
resolveClaudeCodeRuntimeConfig,
|
|
29571
31001
|
resolveClaudeConfigDir,
|
|
@@ -29584,5 +31014,6 @@ export {
|
|
|
29584
31014
|
toCodexResponsesTool,
|
|
29585
31015
|
toOpenAIMessage,
|
|
29586
31016
|
toOpenAITool,
|
|
29587
|
-
toResponsesInputItems
|
|
31017
|
+
toResponsesInputItems,
|
|
31018
|
+
validateCoworkAgentName
|
|
29588
31019
|
};
|