demian-cli 1.2.0 → 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 +47835 -1348
- package/dist/vscode-worker.mjs +3157 -661
- package/docs/ko/README.md +25 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import
|
|
5
|
+
import path36 from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/agents/types.ts
|
|
8
8
|
function normalizeAgent(input2) {
|
|
@@ -17,6 +17,9 @@ function normalizeAgent(input2) {
|
|
|
17
17
|
defaultDecision: definition.defaultDecision ?? definition.authority?.defaultDecision,
|
|
18
18
|
mode: definition.mode,
|
|
19
19
|
hidden: definition.hidden,
|
|
20
|
+
defaultProviders: definition.defaultProviders,
|
|
21
|
+
permissionIntent: definition.permissionIntent,
|
|
22
|
+
runtimeOverlays: definition.runtimeOverlays,
|
|
20
23
|
provider: definition.provider,
|
|
21
24
|
delegation: definition.delegation,
|
|
22
25
|
catalog: definition.catalog,
|
|
@@ -42,7 +45,7 @@ function isCoworkableAgent(agent) {
|
|
|
42
45
|
if (agent.hidden) return false;
|
|
43
46
|
const mode = agent.mode ?? "primary";
|
|
44
47
|
if (mode !== "subagent" && mode !== "all") return false;
|
|
45
|
-
return agent.delegation?.coworkable
|
|
48
|
+
return agent.delegation?.coworkable === true;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// src/agents/build.ts
|
|
@@ -50,10 +53,12 @@ var buildAgent = {
|
|
|
50
53
|
name: "build",
|
|
51
54
|
description: "General coding agent for scoped implementation work.",
|
|
52
55
|
mode: "all",
|
|
53
|
-
tools: ["read_file", "write_file", "edit_file", "bash", "grep", "glob", "web_search"],
|
|
56
|
+
tools: ["read_file", "write_file", "edit_file", "bash", "grep", "glob", "web_search", "update_plan", "report_progress"],
|
|
54
57
|
systemPrompt: [
|
|
55
58
|
"You are demian build, a local coding agent.",
|
|
56
59
|
"Use tools to inspect and modify the workspace. Keep changes scoped to the user's request.",
|
|
60
|
+
"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.",
|
|
61
|
+
"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.",
|
|
57
62
|
"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.",
|
|
58
63
|
"Use web_search when current external information is needed.",
|
|
59
64
|
"Tool names are fixed by the runtime. In multi-agent mode, delegate_agent may also be available for bounded specialist work.",
|
|
@@ -64,6 +69,8 @@ var buildAgent = {
|
|
|
64
69
|
{ tool: "read_file", decision: "allow" },
|
|
65
70
|
{ tool: "grep", decision: "allow" },
|
|
66
71
|
{ tool: "glob", decision: "allow" },
|
|
72
|
+
{ tool: "update_plan", decision: "allow" },
|
|
73
|
+
{ tool: "report_progress", decision: "allow" },
|
|
67
74
|
{ tool: "web_search", decision: "ask" },
|
|
68
75
|
{ tool: "write_file", decision: "ask" },
|
|
69
76
|
{ tool: "edit_file", decision: "ask" },
|
|
@@ -109,6 +116,7 @@ var claudeCodeAgent = {
|
|
|
109
116
|
{ tool: "claudecode.Read", decision: "allow" },
|
|
110
117
|
{ tool: "claudecode.Grep", decision: "allow" },
|
|
111
118
|
{ tool: "claudecode.Glob", decision: "allow" },
|
|
119
|
+
{ tool: "claudecode.LS", decision: "allow" },
|
|
112
120
|
{ tool: "claudecode.Edit", decision: "ask" },
|
|
113
121
|
{ tool: "claudecode.Write", decision: "ask" },
|
|
114
122
|
{ tool: "claudecode.MultiEdit", decision: "ask" },
|
|
@@ -137,144 +145,186 @@ var claudeCodeAgent = {
|
|
|
137
145
|
]
|
|
138
146
|
}
|
|
139
147
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
148
|
+
|
|
149
|
+
// src/agents/cowork.ts
|
|
150
|
+
var readPermissions = [
|
|
151
|
+
{ tool: "read_file", decision: "allow" },
|
|
152
|
+
{ tool: "grep", decision: "allow" },
|
|
153
|
+
{ tool: "glob", decision: "allow" },
|
|
154
|
+
{ tool: "update_plan", decision: "allow" },
|
|
155
|
+
{ tool: "report_progress", decision: "allow" }
|
|
156
|
+
];
|
|
157
|
+
var readProgressTools = ["read_file", "grep", "glob", "update_plan", "report_progress"];
|
|
158
|
+
var writePermissions = [
|
|
159
|
+
...readPermissions,
|
|
160
|
+
{ tool: "edit_file", decision: "ask" },
|
|
161
|
+
{ tool: "write_file", decision: "ask" },
|
|
162
|
+
{ tool: "bash", decision: "ask" },
|
|
163
|
+
{ tool: "*", match: { pathGlob: "**/.env" }, decision: "deny", reason: "secrets" },
|
|
164
|
+
{ tool: "*", match: { pathGlob: "**/.env.*" }, decision: "deny", reason: "secrets" },
|
|
165
|
+
{ tool: "*", match: { pathGlob: "node_modules/**" }, decision: "deny", reason: "vendored" }
|
|
166
|
+
];
|
|
167
|
+
var plannerAgent = coworkAgent({
|
|
168
|
+
name: "planner",
|
|
169
|
+
description: "Provider-agnostic planner for decomposing ambiguous or multi-step engineering work.",
|
|
170
|
+
defaultProviders: ["codex", "anthropic", "openai"],
|
|
171
|
+
permissionIntent: "manage",
|
|
172
|
+
category: "planner",
|
|
173
|
+
cost: "standard",
|
|
174
|
+
tools: readProgressTools,
|
|
175
|
+
permissions: readPermissions,
|
|
176
|
+
defaultDecision: "deny",
|
|
177
|
+
prompt: [
|
|
178
|
+
"You are demian planner, a planning cowork sub agent for Demian.",
|
|
179
|
+
"Inspect the delegated scope read-only and return an implementation plan, sequencing, risks, and verification strategy.",
|
|
180
|
+
"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.",
|
|
181
|
+
"Do not edit files. Do not ask the user directly; return blockers or questions for the parent Demian coordinator."
|
|
159
182
|
],
|
|
183
|
+
useWhen: ["A parallel planning perspective may clarify scope, sequencing, risks, or verification."],
|
|
184
|
+
avoidWhen: ["The work is already clear and a bounded writer can proceed."]
|
|
185
|
+
});
|
|
186
|
+
var architectAgent = coworkAgent({
|
|
187
|
+
name: "architect",
|
|
188
|
+
description: "Provider-agnostic architect for structure, design, and long-term maintainability decisions.",
|
|
189
|
+
defaultProviders: ["codex", "anthropic"],
|
|
190
|
+
permissionIntent: "manage",
|
|
191
|
+
category: "planner",
|
|
192
|
+
cost: "standard",
|
|
193
|
+
tools: readProgressTools,
|
|
194
|
+
permissions: readPermissions,
|
|
160
195
|
defaultDecision: "deny",
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
prompt: [
|
|
197
|
+
"You are demian architect, a design cowork sub agent for Demian.",
|
|
198
|
+
"Inspect the repository and proposed work read-only. Focus on architecture, interfaces, coupling, migration risk, and maintainability.",
|
|
199
|
+
"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.",
|
|
200
|
+
"Return concrete design recommendations and risks for the parent Demian coordinator."
|
|
201
|
+
],
|
|
202
|
+
useWhen: ["A change needs architectural judgment, interface design, or cross-module risk assessment."],
|
|
203
|
+
avoidWhen: ["The task is a small mechanical edit."]
|
|
204
|
+
});
|
|
205
|
+
var supervisorAgent = coworkAgent({
|
|
206
|
+
name: "supervisor",
|
|
207
|
+
description: "Provider-agnostic supervisory reviewer for overall risk, integration quality, and final review.",
|
|
208
|
+
defaultProviders: ["codex", "anthropic"],
|
|
209
|
+
permissionIntent: "review",
|
|
210
|
+
category: "reviewer",
|
|
211
|
+
cost: "standard",
|
|
212
|
+
tools: readProgressTools,
|
|
213
|
+
permissions: readPermissions,
|
|
214
|
+
defaultDecision: "deny",
|
|
215
|
+
prompt: [
|
|
216
|
+
"You are demian supervisor, a skeptical supervisory cowork sub agent for Demian.",
|
|
217
|
+
"Review plans, diffs, integration risks, missing tests, and final readiness. Do not edit files.",
|
|
218
|
+
"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.",
|
|
219
|
+
"Return concise findings with severity, file references when useful, and a clear recommendation."
|
|
220
|
+
],
|
|
221
|
+
useWhen: ["A second model should review a design, implementation plan, diff, or test strategy before Demian answers."],
|
|
222
|
+
avoidWhen: ["The task needs direct workspace edits from this member."]
|
|
223
|
+
});
|
|
224
|
+
var researcherAgent = coworkAgent({
|
|
225
|
+
name: "researcher",
|
|
226
|
+
description: "Provider-agnostic researcher for repo inspection, external research, and evidence gathering.",
|
|
227
|
+
defaultProviders: ["codex", "claudecode", "anthropic"],
|
|
228
|
+
permissionIntent: "read-only",
|
|
229
|
+
category: "researcher",
|
|
230
|
+
cost: "standard",
|
|
231
|
+
tools: [...readProgressTools, "web_search"],
|
|
232
|
+
permissions: [...readPermissions, { tool: "web_search", decision: "ask" }],
|
|
233
|
+
defaultDecision: "deny",
|
|
234
|
+
prompt: [
|
|
235
|
+
"You are demian researcher, a read-only cowork sub agent for Demian.",
|
|
236
|
+
"Gather evidence from the repository and, when needed, current external sources. Do not edit files.",
|
|
237
|
+
"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.",
|
|
238
|
+
"Return findings with enough context for the parent Demian coordinator to act."
|
|
198
239
|
],
|
|
240
|
+
useWhen: ["The task needs repository exploration, usage tracing, documentation lookup, or evidence gathering."],
|
|
241
|
+
avoidWhen: ["The task is already scoped for implementation."]
|
|
242
|
+
});
|
|
243
|
+
var builderAgent = coworkAgent({
|
|
244
|
+
name: "builder",
|
|
245
|
+
description: "Provider-agnostic builder for bounded implementation and module-level development.",
|
|
246
|
+
defaultProviders: ["claudecode", "anthropic", "openai"],
|
|
247
|
+
permissionIntent: "write",
|
|
248
|
+
category: "builder",
|
|
249
|
+
cost: "expensive",
|
|
250
|
+
tools: [...readProgressTools, "edit_file", "write_file", "bash"],
|
|
251
|
+
permissions: writePermissions,
|
|
199
252
|
defaultDecision: "ask",
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
},
|
|
207
|
-
catalog: {
|
|
208
|
-
category: "builder",
|
|
209
|
-
cost: "expensive",
|
|
210
|
-
useWhen: ["Claude Code's local coding-agent runtime should implement a bounded writeScope while Demian coordinates review."],
|
|
211
|
-
avoidWhen: ["The task is read-only, exploratory, or can be handled by a normal Demian tool call."]
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
// src/agents/cowork.ts
|
|
216
|
-
var codexReviewerAgent = {
|
|
217
|
-
name: "codex-reviewer",
|
|
218
|
-
description: "Codex-backed reviewer for architecture, risk, and patch review.",
|
|
219
|
-
mode: "subagent",
|
|
220
|
-
provider: { profile: "codex" },
|
|
221
|
-
tools: ["read_file", "grep", "glob"],
|
|
222
|
-
systemPrompt: [
|
|
223
|
-
"You are demian codex-reviewer, a skeptical reviewer working as a cowork sub agent for Demian.",
|
|
224
|
-
"Inspect the delegated context, plans, diffs, risks, and missing tests. Do not edit files.",
|
|
225
|
-
"Return concise findings with severity, file references when useful, and a clear recommendation for the parent Demian coordinator."
|
|
226
|
-
].join("\n"),
|
|
227
|
-
permissions: [
|
|
228
|
-
{ tool: "read_file", decision: "allow" },
|
|
229
|
-
{ tool: "grep", decision: "allow" },
|
|
230
|
-
{ tool: "glob", decision: "allow" }
|
|
253
|
+
prompt: [
|
|
254
|
+
"You are demian builder, a bounded implementation cowork sub agent for Demian.",
|
|
255
|
+
"Implement only the delegated task and only inside the provided writeScope.",
|
|
256
|
+
"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.",
|
|
257
|
+
"If you need broader scope, credentials, user approval, or product decisions, stop and return a concise blocker for Demian.",
|
|
258
|
+
"Return changed files, verification performed, patch summary, and remaining blockers."
|
|
231
259
|
],
|
|
260
|
+
useWhen: ["A bounded writeScope should be implemented while Demian coordinates review."],
|
|
261
|
+
avoidWhen: ["The task is read-only, exploratory, or can be handled by a normal Demian tool call."]
|
|
262
|
+
});
|
|
263
|
+
var reviewerAgent = coworkAgent({
|
|
264
|
+
name: "reviewer",
|
|
265
|
+
description: "Provider-agnostic detailed reviewer for diff-level code review.",
|
|
266
|
+
defaultProviders: ["claudecode", "anthropic", "codex"],
|
|
267
|
+
permissionIntent: "review",
|
|
268
|
+
category: "reviewer",
|
|
269
|
+
cost: "standard",
|
|
270
|
+
tools: readProgressTools,
|
|
271
|
+
permissions: readPermissions,
|
|
232
272
|
defaultDecision: "deny",
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
invocationDecision: "allow"
|
|
239
|
-
},
|
|
240
|
-
catalog: {
|
|
241
|
-
category: "reviewer",
|
|
242
|
-
cost: "standard",
|
|
243
|
-
useWhen: ["A second model should review a design, implementation plan, diff, or test strategy before Demian answers."],
|
|
244
|
-
avoidWhen: ["The task needs direct workspace edits from this member."]
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
var codexPlannerAgent = {
|
|
248
|
-
name: "codex-planner",
|
|
249
|
-
description: "Codex-backed planner for decomposing ambiguous or multi-step engineering work.",
|
|
250
|
-
mode: "subagent",
|
|
251
|
-
provider: { profile: "codex" },
|
|
252
|
-
tools: ["read_file", "grep", "glob"],
|
|
253
|
-
systemPrompt: [
|
|
254
|
-
"You are demian codex-planner, a planning cowork sub agent for Demian.",
|
|
255
|
-
"Inspect the delegated scope read-only and return an implementation plan, sequencing, risks, and verification strategy.",
|
|
256
|
-
"Do not edit files. Do not ask the user directly; return blockers or questions for the parent Demian coordinator."
|
|
257
|
-
].join("\n"),
|
|
258
|
-
permissions: [
|
|
259
|
-
{ tool: "read_file", decision: "allow" },
|
|
260
|
-
{ tool: "grep", decision: "allow" },
|
|
261
|
-
{ tool: "glob", decision: "allow" }
|
|
273
|
+
prompt: [
|
|
274
|
+
"You are demian reviewer, a detailed code-review cowork sub agent for Demian.",
|
|
275
|
+
"Inspect the delegated context read-only. Focus on correctness, edge cases, tests, consistency, and maintainability.",
|
|
276
|
+
"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.",
|
|
277
|
+
"Return findings first, ordered by severity, with file references when useful."
|
|
262
278
|
],
|
|
279
|
+
useWhen: ["A diff or focused implementation needs detailed code review."],
|
|
280
|
+
avoidWhen: ["The task needs broad architecture or direct editing."]
|
|
281
|
+
});
|
|
282
|
+
var specialistAgent = coworkAgent({
|
|
283
|
+
name: "specialist",
|
|
284
|
+
description: "Provider-agnostic specialist for focused domain, module, or technology expertise.",
|
|
285
|
+
defaultProviders: ["claudecode", "anthropic"],
|
|
286
|
+
permissionIntent: "read-only",
|
|
287
|
+
category: "researcher",
|
|
288
|
+
cost: "standard",
|
|
289
|
+
tools: readProgressTools,
|
|
290
|
+
permissions: readPermissions,
|
|
263
291
|
defaultDecision: "deny",
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
292
|
+
prompt: [
|
|
293
|
+
"You are demian specialist, a focused domain cowork sub agent for Demian.",
|
|
294
|
+
"Inspect the requested module, technology, or domain read-only unless explicitly assigned write mode with a narrow writeScope.",
|
|
295
|
+
"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.",
|
|
296
|
+
"Return precise, specialist-level findings and recommendations."
|
|
297
|
+
],
|
|
298
|
+
useWhen: ["A specific module, library, language, or subsystem needs focused expert analysis."],
|
|
299
|
+
avoidWhen: ["The task is broad coordination or final synthesis."]
|
|
300
|
+
});
|
|
301
|
+
var coworkAgents = [plannerAgent, architectAgent, supervisorAgent, researcherAgent, builderAgent, reviewerAgent, specialistAgent];
|
|
302
|
+
function coworkAgent(options) {
|
|
303
|
+
return {
|
|
304
|
+
name: options.name,
|
|
305
|
+
description: options.description,
|
|
306
|
+
mode: "subagent",
|
|
307
|
+
tools: options.tools,
|
|
308
|
+
permissions: options.permissions,
|
|
309
|
+
defaultDecision: options.defaultDecision,
|
|
310
|
+
defaultProviders: options.defaultProviders,
|
|
311
|
+
permissionIntent: options.permissionIntent,
|
|
312
|
+
systemPrompt: options.prompt.join("\n"),
|
|
313
|
+
delegation: {
|
|
314
|
+
callable: true,
|
|
315
|
+
coworkable: true,
|
|
316
|
+
canDelegate: false,
|
|
317
|
+
canCowork: false,
|
|
318
|
+
invocationDecision: options.permissionIntent === "write" ? "ask" : "allow"
|
|
319
|
+
},
|
|
320
|
+
catalog: {
|
|
321
|
+
category: options.category,
|
|
322
|
+
cost: options.cost,
|
|
323
|
+
useWhen: options.useWhen,
|
|
324
|
+
avoidWhen: options.avoidWhen
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
278
328
|
|
|
279
329
|
// src/agents/execute.ts
|
|
280
330
|
var executeAgent = {
|
|
@@ -286,8 +336,8 @@ var executeAgent = {
|
|
|
286
336
|
"You are demian execute, a bounded implementation agent.",
|
|
287
337
|
"You receive one task from a coordinator. Treat that task scope as a hard boundary.",
|
|
288
338
|
"Read the relevant files before changing them. Do not refactor unrelated code.",
|
|
289
|
-
"Before changing files, use update_plan to mark your assigned step in_progress and report_progress to say what you are starting.",
|
|
290
|
-
"Use report_progress for meaningful milestones during longer work.",
|
|
339
|
+
"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.",
|
|
340
|
+
"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.",
|
|
291
341
|
"Keep the visible work plan in sync with update_plan when you start, finish, block, skip, or fail your assigned step.",
|
|
292
342
|
"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.",
|
|
293
343
|
"When finished, return changed files, verification results, and any remaining issues.",
|
|
@@ -337,9 +387,10 @@ var generalAgent = {
|
|
|
337
387
|
"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.",
|
|
338
388
|
"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.",
|
|
339
389
|
"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.",
|
|
390
|
+
"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.",
|
|
340
391
|
"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.",
|
|
341
392
|
"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.",
|
|
342
|
-
"Keep the plan synchronized as steps move from pending to in_progress to completed, blocked, skipped, or
|
|
393
|
+
"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.",
|
|
343
394
|
"Use web_search when current external information is needed.",
|
|
344
395
|
"Keep changes scoped, preserve user work, and explain the outcome clearly.",
|
|
345
396
|
"Tool names are fixed by the runtime. In multi-agent mode, delegate_agent may also be available for bounded specialist work.",
|
|
@@ -386,7 +437,7 @@ var planAgent = {
|
|
|
386
437
|
"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.",
|
|
387
438
|
"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.",
|
|
388
439
|
"A planning-only run can leave all steps pending. If you are actively investigating one step, mark only that step in_progress.",
|
|
389
|
-
"Use report_progress for major planning milestones, not for every read.",
|
|
440
|
+
"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.",
|
|
390
441
|
"Suggest bounded tasks that can be delegated to execute."
|
|
391
442
|
].join("\n"),
|
|
392
443
|
permissions: [
|
|
@@ -414,7 +465,7 @@ var planAgent = {
|
|
|
414
465
|
var AgentRegistry = class {
|
|
415
466
|
#agents = /* @__PURE__ */ new Map();
|
|
416
467
|
#builtinNames = /* @__PURE__ */ new Set();
|
|
417
|
-
constructor(agents = [generalAgent, buildAgent, planAgent, executeAgent, claudeCodeAgent,
|
|
468
|
+
constructor(agents = [generalAgent, buildAgent, planAgent, executeAgent, claudeCodeAgent, ...coworkAgents]) {
|
|
418
469
|
for (const agent of agents) {
|
|
419
470
|
const normalized = normalizeAgent(agent);
|
|
420
471
|
this.#builtinNames.add(normalized.name);
|
|
@@ -468,7 +519,14 @@ var AgentRegistry = class {
|
|
|
468
519
|
}
|
|
469
520
|
};
|
|
470
521
|
function buildSystemPrompt(agent, envInfo) {
|
|
471
|
-
return [
|
|
522
|
+
return [
|
|
523
|
+
agent.systemPrompt,
|
|
524
|
+
envInfo,
|
|
525
|
+
"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.",
|
|
526
|
+
"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.",
|
|
527
|
+
"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.",
|
|
528
|
+
"When a tool result reports an error, adjust your next step instead of repeating the same call."
|
|
529
|
+
].filter(Boolean).join("\n\n");
|
|
472
530
|
}
|
|
473
531
|
function validateAgentName(name) {
|
|
474
532
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
@@ -481,7 +539,113 @@ import { readFile as readFile6 } from "node:fs/promises";
|
|
|
481
539
|
import crypto4 from "node:crypto";
|
|
482
540
|
import fs7 from "node:fs";
|
|
483
541
|
import os7 from "node:os";
|
|
484
|
-
import
|
|
542
|
+
import path14 from "node:path";
|
|
543
|
+
|
|
544
|
+
// src/util.ts
|
|
545
|
+
function stableStringify(value) {
|
|
546
|
+
return JSON.stringify(sortValue(value));
|
|
547
|
+
}
|
|
548
|
+
function sortValue(value) {
|
|
549
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
550
|
+
if (!value || typeof value !== "object") return value;
|
|
551
|
+
const object2 = value;
|
|
552
|
+
const out = {};
|
|
553
|
+
for (const key of Object.keys(object2).sort()) out[key] = sortValue(object2[key]);
|
|
554
|
+
return out;
|
|
555
|
+
}
|
|
556
|
+
function createSessionId() {
|
|
557
|
+
return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
558
|
+
}
|
|
559
|
+
function createRootSessionId() {
|
|
560
|
+
return `root_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
561
|
+
}
|
|
562
|
+
function createAgentSessionId() {
|
|
563
|
+
return `as_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
564
|
+
}
|
|
565
|
+
function createInvocationId() {
|
|
566
|
+
return `inv_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
567
|
+
}
|
|
568
|
+
function preview(value, max = 500) {
|
|
569
|
+
if (value.length <= max) return value;
|
|
570
|
+
return `${value.slice(0, max)}...`;
|
|
571
|
+
}
|
|
572
|
+
function errorMessage(error) {
|
|
573
|
+
if (!(error instanceof Error)) return String(error);
|
|
574
|
+
const primary = error.message || error.name || "Error";
|
|
575
|
+
const cause = errorCauseSummary(error);
|
|
576
|
+
const hint = networkFailureHint(`${primary}${cause ? ` ${cause}` : ""}`);
|
|
577
|
+
const details = [cause, hint].filter(Boolean);
|
|
578
|
+
return details.length ? `${primary} (${details.join("; ")})` : primary;
|
|
579
|
+
}
|
|
580
|
+
function errorCauseSummary(error) {
|
|
581
|
+
const cause = error.cause;
|
|
582
|
+
const detail = causeDetail(cause, /* @__PURE__ */ new Set());
|
|
583
|
+
return detail ? `cause: ${detail}` : void 0;
|
|
584
|
+
}
|
|
585
|
+
function causeDetail(cause, seen) {
|
|
586
|
+
if (cause === void 0 || cause === null) return void 0;
|
|
587
|
+
if (seen.has(cause)) return void 0;
|
|
588
|
+
if (typeof cause === "object" || typeof cause === "function") seen.add(cause);
|
|
589
|
+
if (cause instanceof AggregateError) {
|
|
590
|
+
const details = cause.errors.map((item) => causeDetail(item, seen)).filter((item) => Boolean(item)).slice(0, 3);
|
|
591
|
+
return details.length ? details.join("; ") : cleanMessage(cause.message);
|
|
592
|
+
}
|
|
593
|
+
if (cause instanceof Error) {
|
|
594
|
+
const message = cleanMessage(cause.message);
|
|
595
|
+
const fields = errorFields(cause);
|
|
596
|
+
const nested = causeDetail(cause.cause, seen);
|
|
597
|
+
return [message, fields, nested ? `cause: ${nested}` : void 0].filter(Boolean).join("; ");
|
|
598
|
+
}
|
|
599
|
+
if (typeof cause === "object") {
|
|
600
|
+
const fields = errorFields(cause);
|
|
601
|
+
return fields || cleanMessage(safeStringify(cause));
|
|
602
|
+
}
|
|
603
|
+
return cleanMessage(String(cause));
|
|
604
|
+
}
|
|
605
|
+
function errorFields(value) {
|
|
606
|
+
if (!value || typeof value !== "object") return void 0;
|
|
607
|
+
const object2 = value;
|
|
608
|
+
const parts = [];
|
|
609
|
+
for (const key of ["code", "errno", "syscall"]) {
|
|
610
|
+
const field = object2[key];
|
|
611
|
+
if (typeof field === "string" || typeof field === "number") parts.push(`${key}=${field}`);
|
|
612
|
+
}
|
|
613
|
+
const host = stringField(object2, "hostname") ?? stringField(object2, "host") ?? stringField(object2, "address");
|
|
614
|
+
const port = stringField(object2, "port");
|
|
615
|
+
if (host) parts.push(`target=${host}${port ? `:${port}` : ""}`);
|
|
616
|
+
return unique(parts).join(", ");
|
|
617
|
+
}
|
|
618
|
+
function networkFailureHint(text) {
|
|
619
|
+
const lower = text.toLowerCase();
|
|
620
|
+
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;
|
|
621
|
+
if (lower.includes("enotfound") || lower.includes("eai_again")) return "DNS lookup failed; check the provider baseURL, network, VPN, or proxy settings.";
|
|
622
|
+
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.";
|
|
623
|
+
if (lower.includes("econnrefused")) return "Connection was refused; check the provider endpoint host/port and whether the service is running.";
|
|
624
|
+
if (lower.includes("econnreset") || lower.includes("socket")) return "Connection was reset while contacting the provider; retry, or check VPN/proxy/provider stability.";
|
|
625
|
+
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.";
|
|
626
|
+
if (lower.includes("fetch failed")) return "Network request failed before an HTTP response was received; check network, proxy, DNS, and provider endpoint settings.";
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
function cleanMessage(value) {
|
|
630
|
+
const text = value?.replace(/\s+/g, " ").trim();
|
|
631
|
+
return text || void 0;
|
|
632
|
+
}
|
|
633
|
+
function stringField(object2, key) {
|
|
634
|
+
const value = object2[key];
|
|
635
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
636
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
function unique(values) {
|
|
640
|
+
return [...new Set(values)];
|
|
641
|
+
}
|
|
642
|
+
function safeStringify(value) {
|
|
643
|
+
try {
|
|
644
|
+
return JSON.stringify(value);
|
|
645
|
+
} catch {
|
|
646
|
+
return void 0;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
485
649
|
|
|
486
650
|
// src/providers/retry.ts
|
|
487
651
|
var RETRY_STATUS = /* @__PURE__ */ new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -532,8 +696,7 @@ function retryDelay(error, attempt) {
|
|
|
532
696
|
}
|
|
533
697
|
function retryReason(error) {
|
|
534
698
|
if (error instanceof ProviderHttpError) return `HTTP ${error.status}`;
|
|
535
|
-
|
|
536
|
-
return String(error);
|
|
699
|
+
return errorMessage(error);
|
|
537
700
|
}
|
|
538
701
|
function sleep(ms2, signal) {
|
|
539
702
|
return new Promise((resolve, reject) => {
|
|
@@ -3181,7 +3344,8 @@ function buildClaudeCliArgs(config, req, flags, resumeSessionId) {
|
|
|
3181
3344
|
const args = ["-p", "--output-format", config.outputFormat ?? "stream-json", "--input-format", config.inputFormat ?? "text", "--verbose"];
|
|
3182
3345
|
if (flags.includePartialMessages) args.push("--include-partial-messages");
|
|
3183
3346
|
if (req.model) args.push("--model", req.model);
|
|
3184
|
-
|
|
3347
|
+
const maxTurns = req.maxTurns === null ? void 0 : req.maxTurns ?? config.maxTurns;
|
|
3348
|
+
if (maxTurns !== void 0 && flags.maxTurns) args.push("--max-turns", String(maxTurns));
|
|
3185
3349
|
if (config.permissionMode && flags.permissionMode) args.push("--permission-mode", config.permissionMode);
|
|
3186
3350
|
if (config.maxBudgetUsd !== void 0 && flags.maxBudgetUsd) args.push("--max-budget-usd", String(config.maxBudgetUsd));
|
|
3187
3351
|
if (resumeSessionId) args.push("--resume", resumeSessionId);
|
|
@@ -22701,44 +22865,10 @@ function now() {
|
|
|
22701
22865
|
}
|
|
22702
22866
|
|
|
22703
22867
|
// src/permissions/engine.ts
|
|
22704
|
-
import
|
|
22868
|
+
import path12 from "node:path";
|
|
22705
22869
|
|
|
22706
22870
|
// src/permissions/grants.ts
|
|
22707
22871
|
import path9 from "node:path";
|
|
22708
|
-
|
|
22709
|
-
// src/util.ts
|
|
22710
|
-
function stableStringify(value) {
|
|
22711
|
-
return JSON.stringify(sortValue(value));
|
|
22712
|
-
}
|
|
22713
|
-
function sortValue(value) {
|
|
22714
|
-
if (Array.isArray(value)) return value.map(sortValue);
|
|
22715
|
-
if (!value || typeof value !== "object") return value;
|
|
22716
|
-
const object2 = value;
|
|
22717
|
-
const out = {};
|
|
22718
|
-
for (const key of Object.keys(object2).sort()) out[key] = sortValue(object2[key]);
|
|
22719
|
-
return out;
|
|
22720
|
-
}
|
|
22721
|
-
function createSessionId() {
|
|
22722
|
-
return `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22723
|
-
}
|
|
22724
|
-
function createRootSessionId() {
|
|
22725
|
-
return `root_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22726
|
-
}
|
|
22727
|
-
function createAgentSessionId() {
|
|
22728
|
-
return `as_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22729
|
-
}
|
|
22730
|
-
function createInvocationId() {
|
|
22731
|
-
return `inv_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
22732
|
-
}
|
|
22733
|
-
function preview(value, max = 500) {
|
|
22734
|
-
if (value.length <= max) return value;
|
|
22735
|
-
return `${value.slice(0, max)}...`;
|
|
22736
|
-
}
|
|
22737
|
-
function errorMessage(error) {
|
|
22738
|
-
return error instanceof Error ? error.message : String(error);
|
|
22739
|
-
}
|
|
22740
|
-
|
|
22741
|
-
// src/permissions/grants.ts
|
|
22742
22872
|
var SessionGrants = class {
|
|
22743
22873
|
#keys = /* @__PURE__ */ new Set();
|
|
22744
22874
|
has(key) {
|
|
@@ -22753,6 +22883,9 @@ var SessionGrants = class {
|
|
|
22753
22883
|
};
|
|
22754
22884
|
function grantKeyFor(tool, input2, cwd) {
|
|
22755
22885
|
const object2 = input2 && typeof input2 === "object" && !Array.isArray(input2) ? input2 : {};
|
|
22886
|
+
if (CLAUDE_CODE_WORKSPACE_READ_GRANTS.has(tool)) {
|
|
22887
|
+
return `${tool}:workspace-read`;
|
|
22888
|
+
}
|
|
22756
22889
|
if (tool === "bash") {
|
|
22757
22890
|
const command = typeof object2.command === "string" ? object2.command : "";
|
|
22758
22891
|
return `bash:${firstCommandTokens(command, 2).join(" ")}`;
|
|
@@ -22764,6 +22897,7 @@ function grantKeyFor(tool, input2, cwd) {
|
|
|
22764
22897
|
}
|
|
22765
22898
|
return `${tool}:${stableStringify(input2)}`;
|
|
22766
22899
|
}
|
|
22900
|
+
var CLAUDE_CODE_WORKSPACE_READ_GRANTS = /* @__PURE__ */ new Set(["claudecode.Read", "claudecode.Grep", "claudecode.Glob", "claudecode.LS"]);
|
|
22767
22901
|
function grantKeyForAgentInvocation(agentName, providerProfile) {
|
|
22768
22902
|
return `agent:${agentName}:${providerProfile}`;
|
|
22769
22903
|
}
|
|
@@ -22786,31 +22920,151 @@ function firstCommandTokens(command, count) {
|
|
|
22786
22920
|
return tokens;
|
|
22787
22921
|
}
|
|
22788
22922
|
|
|
22789
|
-
// src/
|
|
22923
|
+
// src/permissions/shell-safety.ts
|
|
22790
22924
|
import path10 from "node:path";
|
|
22925
|
+
function isClearlyReadOnlyShellCommand(command) {
|
|
22926
|
+
const tokens = splitSimpleShellWords(command);
|
|
22927
|
+
if (!tokens?.length) return false;
|
|
22928
|
+
if (tokens.some(isUnsafePathToken)) return false;
|
|
22929
|
+
const executable = path10.basename(tokens[0] ?? "");
|
|
22930
|
+
if (SIMPLE_READ_ONLY_COMMANDS.has(executable)) return true;
|
|
22931
|
+
if (executable === "find") return isReadOnlyFind(tokens);
|
|
22932
|
+
if (executable === "git") return isReadOnlyGit(tokens);
|
|
22933
|
+
return false;
|
|
22934
|
+
}
|
|
22935
|
+
function isPotentiallyReadOnlyShellCommand(command) {
|
|
22936
|
+
const tokens = splitSimpleShellWords(command);
|
|
22937
|
+
if (!tokens?.length) return false;
|
|
22938
|
+
if (tokens.some(isUnsafePathToken)) return false;
|
|
22939
|
+
const executable = path10.basename(tokens[0] ?? "");
|
|
22940
|
+
if (isClearlyReadOnlyShellCommand(command)) return true;
|
|
22941
|
+
if (POTENTIAL_AGENT_JUDGED_COMMANDS.has(executable)) return !hasKnownMutatingToken(executable, tokens);
|
|
22942
|
+
return false;
|
|
22943
|
+
}
|
|
22944
|
+
function splitSimpleShellWords(command) {
|
|
22945
|
+
if (!command.trim() || /[\r\n]/.test(command)) return void 0;
|
|
22946
|
+
const words = [];
|
|
22947
|
+
let current = "";
|
|
22948
|
+
let quote;
|
|
22949
|
+
for (let index = 0; index < command.length; index++) {
|
|
22950
|
+
const char = command[index];
|
|
22951
|
+
if (quote) {
|
|
22952
|
+
if (char === quote) {
|
|
22953
|
+
quote = void 0;
|
|
22954
|
+
continue;
|
|
22955
|
+
}
|
|
22956
|
+
if (quote === '"' && (char === "$" || char === "`")) return void 0;
|
|
22957
|
+
if (char === "\\" && quote === '"' && index + 1 < command.length) {
|
|
22958
|
+
current += command[++index];
|
|
22959
|
+
continue;
|
|
22960
|
+
}
|
|
22961
|
+
current += char;
|
|
22962
|
+
continue;
|
|
22963
|
+
}
|
|
22964
|
+
if (/\s/.test(char)) {
|
|
22965
|
+
if (current) {
|
|
22966
|
+
words.push(current);
|
|
22967
|
+
current = "";
|
|
22968
|
+
}
|
|
22969
|
+
continue;
|
|
22970
|
+
}
|
|
22971
|
+
if (char === "'" || char === '"') {
|
|
22972
|
+
quote = char;
|
|
22973
|
+
continue;
|
|
22974
|
+
}
|
|
22975
|
+
if (SHELL_CONTROL_CHARS.has(char)) return void 0;
|
|
22976
|
+
if (char === "\\" && index + 1 < command.length) {
|
|
22977
|
+
current += command[++index];
|
|
22978
|
+
continue;
|
|
22979
|
+
}
|
|
22980
|
+
current += char;
|
|
22981
|
+
}
|
|
22982
|
+
if (quote) return void 0;
|
|
22983
|
+
if (current) words.push(current);
|
|
22984
|
+
return words;
|
|
22985
|
+
}
|
|
22986
|
+
function isUnsafePathToken(token) {
|
|
22987
|
+
if (token === "." || token === "-") return false;
|
|
22988
|
+
if (token === "~" || token.startsWith("~/") || token.startsWith("/")) return true;
|
|
22989
|
+
if (token === ".." || token.startsWith("../") || token.includes("/../")) return true;
|
|
22990
|
+
const assignmentValue = token.match(/^[A-Za-z_][A-Za-z0-9_-]*=(.+)$/)?.[1];
|
|
22991
|
+
if (assignmentValue) return isUnsafePathToken(assignmentValue);
|
|
22992
|
+
const optionValue = token.match(/^--?[A-Za-z0-9][A-Za-z0-9_-]*=(.+)$/)?.[1];
|
|
22993
|
+
return optionValue ? isUnsafePathToken(optionValue) : false;
|
|
22994
|
+
}
|
|
22995
|
+
function isReadOnlyFind(tokens) {
|
|
22996
|
+
return tokens.slice(1).every((token) => !UNSAFE_FIND_TOKENS.has(token));
|
|
22997
|
+
}
|
|
22998
|
+
function isReadOnlyGit(tokens) {
|
|
22999
|
+
const parsed = gitSubcommand(tokens);
|
|
23000
|
+
if (!parsed) return false;
|
|
23001
|
+
if (!READ_ONLY_GIT_SUBCOMMANDS.has(parsed.command)) return false;
|
|
23002
|
+
if (parsed.command === "branch") return parsed.args.every((arg) => READ_ONLY_GIT_BRANCH_ARGS.has(arg));
|
|
23003
|
+
if (parsed.command === "remote") return parsed.args.length === 0 || parsed.args.every((arg) => arg === "-v" || arg === "--verbose" || arg === "get-url");
|
|
23004
|
+
if (parsed.command === "config") return parsed.args.some((arg) => READ_ONLY_GIT_CONFIG_ARGS.has(arg));
|
|
23005
|
+
return true;
|
|
23006
|
+
}
|
|
23007
|
+
function gitSubcommand(tokens) {
|
|
23008
|
+
let index = 1;
|
|
23009
|
+
while (index < tokens.length) {
|
|
23010
|
+
const token = tokens[index];
|
|
23011
|
+
if (token === "--no-pager") {
|
|
23012
|
+
index++;
|
|
23013
|
+
continue;
|
|
23014
|
+
}
|
|
23015
|
+
if (token === "-C") {
|
|
23016
|
+
index += 2;
|
|
23017
|
+
continue;
|
|
23018
|
+
}
|
|
23019
|
+
if (token?.startsWith("-")) return void 0;
|
|
23020
|
+
return { command: token ?? "", args: tokens.slice(index + 1) };
|
|
23021
|
+
}
|
|
23022
|
+
return void 0;
|
|
23023
|
+
}
|
|
23024
|
+
function hasKnownMutatingToken(executable, tokens) {
|
|
23025
|
+
if (executable === "sed") {
|
|
23026
|
+
return tokens.slice(1).some((token) => token === "-i" || token.startsWith("-i") || /^w(\s|$)/.test(token) || /;\s*w(\s|$)/.test(token));
|
|
23027
|
+
}
|
|
23028
|
+
if (executable === "git") {
|
|
23029
|
+
const parsed = gitSubcommand(tokens);
|
|
23030
|
+
return !parsed || MUTATING_GIT_SUBCOMMANDS.has(parsed.command);
|
|
23031
|
+
}
|
|
23032
|
+
return true;
|
|
23033
|
+
}
|
|
23034
|
+
var SHELL_CONTROL_CHARS = /* @__PURE__ */ new Set([";", "&", "|", ">", "<", "`", "$", "(", ")"]);
|
|
23035
|
+
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"]);
|
|
23036
|
+
var POTENTIAL_AGENT_JUDGED_COMMANDS = /* @__PURE__ */ new Set(["sed", "git"]);
|
|
23037
|
+
var UNSAFE_FIND_TOKENS = /* @__PURE__ */ new Set(["-delete", "-exec", "-execdir", "-ok", "-okdir", "-fls", "-fprint", "-fprintf"]);
|
|
23038
|
+
var READ_ONLY_GIT_SUBCOMMANDS = /* @__PURE__ */ new Set(["status", "diff", "log", "show", "ls-files", "grep", "rev-parse", "describe", "blame", "shortlog", "branch", "remote", "config"]);
|
|
23039
|
+
var READ_ONLY_GIT_BRANCH_ARGS = /* @__PURE__ */ new Set(["--show-current", "-a", "--all", "-r", "--remotes", "-v", "-vv", "--verbose", "--color", "--no-color"]);
|
|
23040
|
+
var READ_ONLY_GIT_CONFIG_ARGS = /* @__PURE__ */ new Set(["--get", "--get-all", "--get-regexp", "--list", "-l"]);
|
|
23041
|
+
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"]);
|
|
23042
|
+
|
|
23043
|
+
// src/workspace/paths.ts
|
|
23044
|
+
import path11 from "node:path";
|
|
22791
23045
|
function normalizePathForMatch(value) {
|
|
22792
|
-
return value.split(
|
|
23046
|
+
return value.split(path11.sep).join("/");
|
|
22793
23047
|
}
|
|
22794
23048
|
function isInsidePath(root, candidate) {
|
|
22795
|
-
const relative =
|
|
22796
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
23049
|
+
const relative = path11.relative(path11.resolve(root), path11.resolve(candidate));
|
|
23050
|
+
return relative === "" || !relative.startsWith("..") && !path11.isAbsolute(relative);
|
|
22797
23051
|
}
|
|
22798
23052
|
function resolveInsideCwd(cwd, inputPath) {
|
|
22799
23053
|
if (typeof inputPath !== "string" || inputPath.length === 0) {
|
|
22800
23054
|
throw new Error("path must be a non-empty string");
|
|
22801
23055
|
}
|
|
22802
|
-
const resolved =
|
|
23056
|
+
const resolved = path11.resolve(cwd, inputPath);
|
|
22803
23057
|
if (!isInsidePath(cwd, resolved)) {
|
|
22804
23058
|
throw new Error(`Path is outside the workspace: ${inputPath}`);
|
|
22805
23059
|
}
|
|
22806
23060
|
return resolved;
|
|
22807
23061
|
}
|
|
22808
23062
|
function relativeToCwd(cwd, absolutePath) {
|
|
22809
|
-
const relative =
|
|
23063
|
+
const relative = path11.relative(cwd, absolutePath);
|
|
22810
23064
|
return normalizePathForMatch(relative || ".");
|
|
22811
23065
|
}
|
|
22812
23066
|
function isEnvFile(filePath) {
|
|
22813
|
-
const base =
|
|
23067
|
+
const base = path11.basename(filePath);
|
|
22814
23068
|
if (base === ".env.example") return false;
|
|
22815
23069
|
return base === ".env" || base.startsWith(".env.");
|
|
22816
23070
|
}
|
|
@@ -22896,7 +23150,7 @@ var PermissionEngine = class {
|
|
|
22896
23150
|
}
|
|
22897
23151
|
if (this.#defaultDecision === "by-category") {
|
|
22898
23152
|
return {
|
|
22899
|
-
decision: defaultDecisionByCategory(ref.toolName),
|
|
23153
|
+
decision: defaultDecisionByCategory(ref.toolName, input2),
|
|
22900
23154
|
grantKey,
|
|
22901
23155
|
source: "category-default"
|
|
22902
23156
|
};
|
|
@@ -22917,7 +23171,7 @@ var PermissionEngine = class {
|
|
|
22917
23171
|
#hardDeny(tool, input2, grantKey) {
|
|
22918
23172
|
const paths = inputPaths(tool, input2);
|
|
22919
23173
|
for (const item of paths) {
|
|
22920
|
-
const resolved =
|
|
23174
|
+
const resolved = path12.resolve(this.#cwd, item);
|
|
22921
23175
|
if (!isInsidePath(this.#cwd, resolved)) {
|
|
22922
23176
|
return {
|
|
22923
23177
|
decision: "deny",
|
|
@@ -22971,7 +23225,7 @@ function matchesRule(rule, tool, input2, cwd) {
|
|
|
22971
23225
|
if (rule.match.pathGlob) {
|
|
22972
23226
|
const paths = inputPaths(tool, input2);
|
|
22973
23227
|
if (paths.length === 0) return false;
|
|
22974
|
-
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd,
|
|
23228
|
+
if (!paths.some((item) => matchesPathRule(rule.match?.pathGlob, relativeToCwd(cwd, path12.resolve(cwd, item))))) {
|
|
22975
23229
|
return false;
|
|
22976
23230
|
}
|
|
22977
23231
|
}
|
|
@@ -23018,7 +23272,7 @@ function rulePriority(rule, ref) {
|
|
|
23018
23272
|
}
|
|
23019
23273
|
function matchesPathRule(pattern, relativePath) {
|
|
23020
23274
|
if (!pattern) return true;
|
|
23021
|
-
if (
|
|
23275
|
+
if (path12.basename(relativePath) === ".env.example" && pattern.includes(".env.*")) return false;
|
|
23022
23276
|
return matchGlob(pattern, relativePath);
|
|
23023
23277
|
}
|
|
23024
23278
|
function mostSpecificRule(rules, input2, cwd, ref) {
|
|
@@ -23048,10 +23302,12 @@ function commandInput(input2) {
|
|
|
23048
23302
|
const object2 = input2 && typeof input2 === "object" && !Array.isArray(input2) ? input2 : {};
|
|
23049
23303
|
return typeof object2.command === "string" ? object2.command : "";
|
|
23050
23304
|
}
|
|
23051
|
-
function defaultDecisionByCategory(toolName) {
|
|
23052
|
-
|
|
23305
|
+
function defaultDecisionByCategory(toolName, input2) {
|
|
23306
|
+
if (toolName.toLowerCase() === "bash") return isClearlyReadOnlyShellCommand(commandInput(input2)) ? "allow" : "ask";
|
|
23307
|
+
return READ_ONLY_TOOLS.has(toolName) || ORCHESTRATION_TOOLS.has(toolName) ? "allow" : "ask";
|
|
23053
23308
|
}
|
|
23054
|
-
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "read_file", "glob", "grep"]);
|
|
23309
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "LS", "read_file", "glob", "grep"]);
|
|
23310
|
+
var ORCHESTRATION_TOOLS = /* @__PURE__ */ new Set(["delegate_agent", "cowork", "update_plan", "report_progress", "get_goal"]);
|
|
23055
23311
|
|
|
23056
23312
|
// src/permissions/prompt.ts
|
|
23057
23313
|
import readline2 from "node:readline/promises";
|
|
@@ -23096,13 +23352,13 @@ function permissionLabel(req) {
|
|
|
23096
23352
|
if ((req.tool === "write_file" || req.tool === "edit_file" || req.tool === "read_file") && typeof inputObject.path === "string") {
|
|
23097
23353
|
return `${req.tool}: ${inputObject.path}`;
|
|
23098
23354
|
}
|
|
23099
|
-
const
|
|
23100
|
-
if (
|
|
23355
|
+
const path37 = typeof inputObject.path === "string" ? inputObject.path : typeof inputObject.file_path === "string" ? inputObject.file_path : void 0;
|
|
23356
|
+
if (path37) return `${tool}: ${path37}`;
|
|
23101
23357
|
return tool;
|
|
23102
23358
|
}
|
|
23103
23359
|
|
|
23104
23360
|
// src/workspace/write-scope.ts
|
|
23105
|
-
import
|
|
23361
|
+
import path13 from "node:path";
|
|
23106
23362
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "edit_file", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
23107
23363
|
var WORKSPACE_SCOPE = "**";
|
|
23108
23364
|
function normalizeWriteScope(cwd, scope) {
|
|
@@ -23124,7 +23380,7 @@ function enforceToolWriteScope(input2) {
|
|
|
23124
23380
|
if (paths.length === 0) return { ok: true, paths: [] };
|
|
23125
23381
|
const checked = [];
|
|
23126
23382
|
for (const target of paths) {
|
|
23127
|
-
const resolved =
|
|
23383
|
+
const resolved = path13.resolve(input2.cwd, target);
|
|
23128
23384
|
const relative = relativeToCwd(input2.cwd, resolved);
|
|
23129
23385
|
checked.push(relative);
|
|
23130
23386
|
const allowed = isPathAllowedByWriteScope(input2.cwd, target, input2.writeScope);
|
|
@@ -23148,7 +23404,7 @@ function outOfScopeDiffFiles(cwd, summary, writeScope) {
|
|
|
23148
23404
|
return out;
|
|
23149
23405
|
}
|
|
23150
23406
|
function isPathAllowedByWriteScope(cwd, inputPath, writeScope) {
|
|
23151
|
-
const resolved =
|
|
23407
|
+
const resolved = path13.resolve(cwd, inputPath);
|
|
23152
23408
|
if (!isInsidePath(cwd, resolved)) {
|
|
23153
23409
|
return { ok: false, error: "path is outside the workspace", paths: [inputPath] };
|
|
23154
23410
|
}
|
|
@@ -23167,9 +23423,9 @@ function normalizeScopePattern(cwd, raw) {
|
|
|
23167
23423
|
if (!value) return { ok: false, error: "writeScope entries must be non-empty." };
|
|
23168
23424
|
if (value.includes("\0")) return { ok: false, error: "writeScope entries must not contain NUL bytes." };
|
|
23169
23425
|
const hasGlob2 = /[*?[\]{}]/.test(value);
|
|
23170
|
-
if (
|
|
23426
|
+
if (path13.isAbsolute(value)) {
|
|
23171
23427
|
if (hasGlob2) return { ok: false, error: `writeScope absolute globs are not supported: ${raw}` };
|
|
23172
|
-
const resolved =
|
|
23428
|
+
const resolved = path13.resolve(value);
|
|
23173
23429
|
if (!isInsidePath(cwd, resolved)) return { ok: false, error: `writeScope path is outside the workspace: ${raw}` };
|
|
23174
23430
|
value = relativeToCwd(cwd, resolved);
|
|
23175
23431
|
}
|
|
@@ -23244,7 +23500,7 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23244
23500
|
return { behavior: "allow", updatedInput: input2, toolUseID: ctx.toolUseID };
|
|
23245
23501
|
}
|
|
23246
23502
|
if (evaluation.decision === "allow") {
|
|
23247
|
-
return
|
|
23503
|
+
return allowResult(input2, ctx.toolUseID, evaluation.source === "grant" ? "user_permanent" : void 0);
|
|
23248
23504
|
}
|
|
23249
23505
|
if (evaluation.decision === "deny") {
|
|
23250
23506
|
emitPermission(options, {
|
|
@@ -23256,15 +23512,7 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23256
23512
|
});
|
|
23257
23513
|
return { behavior: "deny", message: evaluation.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23258
23514
|
}
|
|
23259
|
-
|
|
23260
|
-
type: "permission.requested",
|
|
23261
|
-
callId: ctx.toolUseID,
|
|
23262
|
-
tool: qualifiedName,
|
|
23263
|
-
decision: "ask",
|
|
23264
|
-
targetName: toolName
|
|
23265
|
-
});
|
|
23266
|
-
const prompt = options.permissionPrompt ?? ((req) => askPermission(req, { yes: options.yes }));
|
|
23267
|
-
const answer = await prompt({
|
|
23515
|
+
const permissionRequest = {
|
|
23268
23516
|
tool: qualifiedName,
|
|
23269
23517
|
executor: "claudecode",
|
|
23270
23518
|
qualifiedName,
|
|
@@ -23275,10 +23523,32 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23275
23523
|
proposedDecision: "ask",
|
|
23276
23524
|
reason: ctx.title ?? ctx.decisionReason ?? evaluation.reason,
|
|
23277
23525
|
description: ctx.description ?? ctx.displayName
|
|
23278
|
-
}
|
|
23279
|
-
|
|
23280
|
-
|
|
23281
|
-
|
|
23526
|
+
};
|
|
23527
|
+
const automatic = await options.autoPermission?.(permissionRequest);
|
|
23528
|
+
if (automatic?.decision === "allow") return allowResult(input2, ctx.toolUseID);
|
|
23529
|
+
if (automatic?.decision === "deny") {
|
|
23530
|
+
emitPermission(options, {
|
|
23531
|
+
type: "permission.denied",
|
|
23532
|
+
callId: ctx.toolUseID,
|
|
23533
|
+
tool: qualifiedName,
|
|
23534
|
+
reason: automatic.reason,
|
|
23535
|
+
targetName: toolName
|
|
23536
|
+
});
|
|
23537
|
+
return { behavior: "deny", message: automatic.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23538
|
+
}
|
|
23539
|
+
emitPermission(options, {
|
|
23540
|
+
type: "permission.requested",
|
|
23541
|
+
callId: ctx.toolUseID,
|
|
23542
|
+
tool: qualifiedName,
|
|
23543
|
+
decision: "ask",
|
|
23544
|
+
targetName: toolName
|
|
23545
|
+
});
|
|
23546
|
+
const prompt = options.permissionPrompt ?? ((req) => askPermission(req, { yes: options.yes }));
|
|
23547
|
+
const answer = await prompt(permissionRequest);
|
|
23548
|
+
if (answer.decision === "allow") {
|
|
23549
|
+
if (answer.always) {
|
|
23550
|
+
grants.add(evaluation.grantKey);
|
|
23551
|
+
await options.onAlwaysGrant?.(evaluation.grantKey);
|
|
23282
23552
|
emitPermission(options, {
|
|
23283
23553
|
type: "permission.granted",
|
|
23284
23554
|
callId: ctx.toolUseID,
|
|
@@ -23286,7 +23556,10 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23286
23556
|
targetName: toolName
|
|
23287
23557
|
});
|
|
23288
23558
|
}
|
|
23289
|
-
return {
|
|
23559
|
+
return {
|
|
23560
|
+
...allowResult(input2, ctx.toolUseID, answer.always ? "user_permanent" : "user_temporary"),
|
|
23561
|
+
updatedPermissions: answer.always ? ctx.suggestions : void 0
|
|
23562
|
+
};
|
|
23290
23563
|
}
|
|
23291
23564
|
emitPermission(options, {
|
|
23292
23565
|
type: "permission.denied",
|
|
@@ -23298,6 +23571,14 @@ function createClaudeCodeCanUseTool(options) {
|
|
|
23298
23571
|
return { behavior: "deny", message: answer.reason ?? `Permission denied for ${qualifiedName}`, toolUseID: ctx.toolUseID };
|
|
23299
23572
|
};
|
|
23300
23573
|
}
|
|
23574
|
+
function allowResult(input2, toolUseID, decisionClassification) {
|
|
23575
|
+
return {
|
|
23576
|
+
behavior: "allow",
|
|
23577
|
+
updatedInput: input2,
|
|
23578
|
+
toolUseID,
|
|
23579
|
+
...decisionClassification ? { decisionClassification } : {}
|
|
23580
|
+
};
|
|
23581
|
+
}
|
|
23301
23582
|
function isImplicitPlanExit(options, toolName, source) {
|
|
23302
23583
|
if (options.permissionMode !== "plan" || toolName !== "ExitPlanMode") return false;
|
|
23303
23584
|
return source === "default" || source === "category-default";
|
|
@@ -23427,7 +23708,7 @@ function buildClaudeCodeSdkOptions(config, req, abortController, resumeSessionId
|
|
|
23427
23708
|
permissionMode,
|
|
23428
23709
|
disallowedTools: uniqueDisallowedTools.length ? uniqueDisallowedTools : void 0,
|
|
23429
23710
|
tools,
|
|
23430
|
-
maxTurns: config.maxTurns,
|
|
23711
|
+
maxTurns: req.maxTurns === null ? void 0 : req.maxTurns ?? config.maxTurns,
|
|
23431
23712
|
maxBudgetUsd: config.maxBudgetUsd,
|
|
23432
23713
|
extraArgs: config.useBareMode ? { bare: null } : void 0,
|
|
23433
23714
|
allowDangerouslySkipPermissions: permissionMode === "bypassPermissions" ? true : void 0,
|
|
@@ -23438,7 +23719,10 @@ function buildClaudeCodeSdkOptions(config, req, abortController, resumeSessionId
|
|
|
23438
23719
|
permissionMode,
|
|
23439
23720
|
defaultDecision: config.defaultDecision,
|
|
23440
23721
|
writeScope: req.writeScope,
|
|
23722
|
+
grants: req.grants,
|
|
23723
|
+
onAlwaysGrant: req.onPermissionGrant,
|
|
23441
23724
|
permissionPrompt: req.permissionPrompt,
|
|
23725
|
+
autoPermission: req.autoPermission,
|
|
23442
23726
|
yes: req.yes,
|
|
23443
23727
|
sessionId: req.sessionId,
|
|
23444
23728
|
emit: req.emit,
|
|
@@ -23487,6 +23771,9 @@ function mapSdkMessage(message) {
|
|
|
23487
23771
|
if (message.type === "assistant") {
|
|
23488
23772
|
return mapAssistantMessage(message);
|
|
23489
23773
|
}
|
|
23774
|
+
if (message.type === "user") {
|
|
23775
|
+
return mapUserMessage(message);
|
|
23776
|
+
}
|
|
23490
23777
|
if (message.type === "stream_event") {
|
|
23491
23778
|
return mapPartialAssistantMessage(message);
|
|
23492
23779
|
}
|
|
@@ -23526,6 +23813,35 @@ function mapAssistantMessage(message) {
|
|
|
23526
23813
|
}
|
|
23527
23814
|
return events;
|
|
23528
23815
|
}
|
|
23816
|
+
function mapUserMessage(message) {
|
|
23817
|
+
const events = [];
|
|
23818
|
+
const content = Array.isArray(message.message.content) ? message.message.content : [];
|
|
23819
|
+
for (const block of content) {
|
|
23820
|
+
if (!block || typeof block !== "object" || block.type !== "tool_result") continue;
|
|
23821
|
+
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : `tool_result_${events.length + 1}`;
|
|
23822
|
+
events.push({
|
|
23823
|
+
type: "tool.completed",
|
|
23824
|
+
callId,
|
|
23825
|
+
name: "unknown",
|
|
23826
|
+
ok: block.is_error !== true,
|
|
23827
|
+
preview: toolResultPreview(block.content),
|
|
23828
|
+
raw: message
|
|
23829
|
+
});
|
|
23830
|
+
}
|
|
23831
|
+
return events;
|
|
23832
|
+
}
|
|
23833
|
+
function toolResultPreview(value) {
|
|
23834
|
+
if (typeof value === "string") return value.slice(0, 800);
|
|
23835
|
+
if (Array.isArray(value)) {
|
|
23836
|
+
return value.map((item) => {
|
|
23837
|
+
if (typeof item === "string") return item;
|
|
23838
|
+
if (item && typeof item === "object" && typeof item.text === "string") return String(item.text);
|
|
23839
|
+
return JSON.stringify(item);
|
|
23840
|
+
}).join("\n").slice(0, 800);
|
|
23841
|
+
}
|
|
23842
|
+
if (value === void 0) return "";
|
|
23843
|
+
return JSON.stringify(value).slice(0, 800);
|
|
23844
|
+
}
|
|
23529
23845
|
function mapPartialAssistantMessage(message) {
|
|
23530
23846
|
const event = message.event;
|
|
23531
23847
|
if (event.type !== "content_block_delta") return [];
|
|
@@ -23546,10 +23862,62 @@ function claudeCodePreviewEnabled(config = {}, env = process.env) {
|
|
|
23546
23862
|
}
|
|
23547
23863
|
|
|
23548
23864
|
// src/config.ts
|
|
23865
|
+
var COWORK_AGENT_NAMES = ["planner", "architect", "supervisor", "researcher", "builder", "reviewer", "specialist"];
|
|
23866
|
+
var COWORK_AGENT_MIGRATIONS = {
|
|
23867
|
+
"codex-planner": { replacement: "planner", note: "planning and coordination work moved to planner" },
|
|
23868
|
+
"codex-reviewer": { replacement: "supervisor", note: "supervisory risk review moved to supervisor" },
|
|
23869
|
+
"claudecode-explorer": { replacement: "researcher", note: "repository exploration moved to researcher; use reviewer for diff-level review" },
|
|
23870
|
+
"claudecode-builder": { replacement: "builder", note: "bounded implementation moved to builder" },
|
|
23871
|
+
claudecode: { replacement: "builder", note: "Claude Code cowork implementation moved to builder" }
|
|
23872
|
+
};
|
|
23549
23873
|
var BUILTIN_PROVIDER_ORDER = ["openai", "anthropic", "gemini", "groq", "azure", "lmstudio", "ollama-local", "ollama-cloud", "llamacpp", "vllm", "codex", "claudecode"];
|
|
23550
23874
|
var CLAUDE_CODE_SONNET_MODEL = "claude-sonnet-4-6";
|
|
23551
23875
|
var CLAUDE_CODE_OPUS_MODEL = "claude-opus-4-7";
|
|
23552
23876
|
var CLAUDE_CODE_MODELS = [CLAUDE_CODE_SONNET_MODEL, CLAUDE_CODE_OPUS_MODEL];
|
|
23877
|
+
var DEFAULT_COWORK_AGENT_PROVIDERS = {
|
|
23878
|
+
planner: ["codex", "anthropic", "openai"],
|
|
23879
|
+
architect: ["codex", "anthropic"],
|
|
23880
|
+
supervisor: ["codex", "anthropic"],
|
|
23881
|
+
researcher: ["codex", "claudecode", "anthropic"],
|
|
23882
|
+
builder: ["claudecode", "anthropic", "openai"],
|
|
23883
|
+
reviewer: ["claudecode", "anthropic", "codex"],
|
|
23884
|
+
specialist: ["claudecode", "anthropic"]
|
|
23885
|
+
};
|
|
23886
|
+
var DEFAULT_COWORK_PERMISSION_OVERLAY = {
|
|
23887
|
+
providerToolNamespaces: {
|
|
23888
|
+
claudecode: "claudecode"
|
|
23889
|
+
},
|
|
23890
|
+
modeMappings: {
|
|
23891
|
+
"read-only": {
|
|
23892
|
+
demianTools: ["read_file", "grep", "glob", "list_dir"],
|
|
23893
|
+
providerTools: {
|
|
23894
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23895
|
+
},
|
|
23896
|
+
defaultDecision: "allow"
|
|
23897
|
+
},
|
|
23898
|
+
review: {
|
|
23899
|
+
demianTools: ["read_file", "grep", "glob", "git_diff"],
|
|
23900
|
+
providerTools: {
|
|
23901
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23902
|
+
},
|
|
23903
|
+
defaultDecision: "allow"
|
|
23904
|
+
},
|
|
23905
|
+
write: {
|
|
23906
|
+
demianTools: ["read_file", "grep", "glob", "edit_file", "write_file", "bash"],
|
|
23907
|
+
providerTools: {
|
|
23908
|
+
claudecode: ["Read", "Grep", "Glob", "LS", "Edit", "MultiEdit", "Write", "Bash"]
|
|
23909
|
+
},
|
|
23910
|
+
defaultDecision: "ask"
|
|
23911
|
+
},
|
|
23912
|
+
manage: {
|
|
23913
|
+
demianTools: ["read_file", "grep", "glob", "task_control", "git_diff"],
|
|
23914
|
+
providerTools: {
|
|
23915
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
23916
|
+
},
|
|
23917
|
+
defaultDecision: "ask"
|
|
23918
|
+
}
|
|
23919
|
+
}
|
|
23920
|
+
};
|
|
23553
23921
|
var defaultConfig = {
|
|
23554
23922
|
agentMode: "single-agent",
|
|
23555
23923
|
defaultAgent: "general",
|
|
@@ -23590,7 +23958,31 @@ var defaultConfig = {
|
|
|
23590
23958
|
defaultMergeStrategy: "synthesize",
|
|
23591
23959
|
defaultInvocationDecision: "ask",
|
|
23592
23960
|
readOnlyParallelism: true,
|
|
23593
|
-
writerIsolation: "off"
|
|
23961
|
+
writerIsolation: "off",
|
|
23962
|
+
providers: ["codex", "claudecode"],
|
|
23963
|
+
preflight: {
|
|
23964
|
+
mode: "session-start",
|
|
23965
|
+
ttlMs: 10 * 60 * 1e3
|
|
23966
|
+
},
|
|
23967
|
+
fallback: {
|
|
23968
|
+
nonWrite: {
|
|
23969
|
+
onConnectionError: "next-provider",
|
|
23970
|
+
onTokenLimit: "next-provider",
|
|
23971
|
+
onAuthFailure: "skip-provider",
|
|
23972
|
+
onAllProvidersFailed: "fail-member"
|
|
23973
|
+
},
|
|
23974
|
+
write: {
|
|
23975
|
+
onFailure: "repair-from-worktree",
|
|
23976
|
+
maxRepairAttempts: 1,
|
|
23977
|
+
readChangedFilesBeforeRepair: true
|
|
23978
|
+
}
|
|
23979
|
+
},
|
|
23980
|
+
routing: {
|
|
23981
|
+
preset: "codex-manager-claudecode-worker",
|
|
23982
|
+
agentProviders: structuredClone(DEFAULT_COWORK_AGENT_PROVIDERS),
|
|
23983
|
+
agentModelPools: {}
|
|
23984
|
+
},
|
|
23985
|
+
permissionOverlay: structuredClone(DEFAULT_COWORK_PERMISSION_OVERLAY)
|
|
23594
23986
|
},
|
|
23595
23987
|
goals: {
|
|
23596
23988
|
enabled: true,
|
|
@@ -23670,8 +24062,13 @@ var defaultConfig = {
|
|
|
23670
24062
|
build: "build",
|
|
23671
24063
|
execute: "build",
|
|
23672
24064
|
claudecode: "build",
|
|
23673
|
-
|
|
23674
|
-
|
|
24065
|
+
planner: "plan",
|
|
24066
|
+
architect: "plan",
|
|
24067
|
+
supervisor: "readonly",
|
|
24068
|
+
researcher: "readonly",
|
|
24069
|
+
builder: "build",
|
|
24070
|
+
reviewer: "readonly",
|
|
24071
|
+
specialist: "readonly"
|
|
23675
24072
|
},
|
|
23676
24073
|
profiles: {
|
|
23677
24074
|
default: {
|
|
@@ -23857,8 +24254,8 @@ var defaultConfig = {
|
|
|
23857
24254
|
};
|
|
23858
24255
|
async function loadConfig(options) {
|
|
23859
24256
|
const configs = [];
|
|
23860
|
-
const userConfigDir =
|
|
23861
|
-
const configPaths = [
|
|
24257
|
+
const userConfigDir = path14.join(os7.homedir(), ".demian");
|
|
24258
|
+
const configPaths = [path14.join(userConfigDir, "config.json"), path14.join(userConfigDir, "config.jsond"), options.configPath].filter(Boolean);
|
|
23862
24259
|
for (const filePath of configPaths) {
|
|
23863
24260
|
if (!fs7.existsSync(filePath)) continue;
|
|
23864
24261
|
const parsed = parseConfigText(await readFile6(filePath, "utf8"), filePath);
|
|
@@ -24046,14 +24443,7 @@ function mergeConfig(base, overlay) {
|
|
|
24046
24443
|
...base.delegation,
|
|
24047
24444
|
...overlay.delegation
|
|
24048
24445
|
},
|
|
24049
|
-
cowork:
|
|
24050
|
-
...base.cowork,
|
|
24051
|
-
...overlay.cowork,
|
|
24052
|
-
maxConcurrentPerProvider: {
|
|
24053
|
-
...base.cowork.maxConcurrentPerProvider,
|
|
24054
|
-
...overlay.cowork?.maxConcurrentPerProvider
|
|
24055
|
-
}
|
|
24056
|
-
},
|
|
24446
|
+
cowork: mergeCoworkConfig(base.cowork, overlay.cowork),
|
|
24057
24447
|
goals: {
|
|
24058
24448
|
...base.goals,
|
|
24059
24449
|
...overlay.goals,
|
|
@@ -24184,6 +24574,168 @@ function resolveProviderRuntimeConfig(config, selection) {
|
|
|
24184
24574
|
modelProfileName: profile.name
|
|
24185
24575
|
};
|
|
24186
24576
|
}
|
|
24577
|
+
function mergeCoworkConfig(base, overlay) {
|
|
24578
|
+
if (!overlay) return structuredClone(base);
|
|
24579
|
+
return {
|
|
24580
|
+
...base,
|
|
24581
|
+
...overlay,
|
|
24582
|
+
providers: overlay.providers !== void 0 ? canonicalStringArray(overlay.providers, "cowork.providers") : [...base.providers],
|
|
24583
|
+
maxConcurrentPerProvider: {
|
|
24584
|
+
...base.maxConcurrentPerProvider,
|
|
24585
|
+
...overlay.maxConcurrentPerProvider
|
|
24586
|
+
},
|
|
24587
|
+
preflight: {
|
|
24588
|
+
...base.preflight,
|
|
24589
|
+
...overlay.preflight
|
|
24590
|
+
},
|
|
24591
|
+
fallback: {
|
|
24592
|
+
nonWrite: {
|
|
24593
|
+
...base.fallback.nonWrite,
|
|
24594
|
+
...overlay.fallback?.nonWrite
|
|
24595
|
+
},
|
|
24596
|
+
write: {
|
|
24597
|
+
...base.fallback.write,
|
|
24598
|
+
...overlay.fallback?.write
|
|
24599
|
+
}
|
|
24600
|
+
},
|
|
24601
|
+
routing: {
|
|
24602
|
+
...base.routing,
|
|
24603
|
+
...overlay.routing,
|
|
24604
|
+
agentProviders: mergeCoworkAgentProviders(base.routing.agentProviders, overlay.routing?.agentProviders),
|
|
24605
|
+
agentModelPools: mergeCoworkAgentModelPools(base.routing.agentModelPools, overlay.routing?.agentModelPools)
|
|
24606
|
+
},
|
|
24607
|
+
permissionOverlay: mergeCoworkPermissionOverlay(base.permissionOverlay, overlay.permissionOverlay)
|
|
24608
|
+
};
|
|
24609
|
+
}
|
|
24610
|
+
function mergeCoworkAgentProviders(base, overlay) {
|
|
24611
|
+
const out = { ...cloneAgentProviders(base) };
|
|
24612
|
+
for (const [rawAgent, rawProviders] of Object.entries(overlay ?? {})) {
|
|
24613
|
+
const agent = validateCoworkAgentName(rawAgent, `cowork.routing.agentProviders.${rawAgent}`);
|
|
24614
|
+
out[agent] = canonicalStringArray(rawProviders, `cowork.routing.agentProviders.${agent}`);
|
|
24615
|
+
}
|
|
24616
|
+
return out;
|
|
24617
|
+
}
|
|
24618
|
+
function cloneAgentProviders(input2) {
|
|
24619
|
+
const out = {};
|
|
24620
|
+
for (const [agent, providers] of Object.entries(input2)) {
|
|
24621
|
+
out[agent] = [...providers ?? []];
|
|
24622
|
+
}
|
|
24623
|
+
return out;
|
|
24624
|
+
}
|
|
24625
|
+
function mergeCoworkAgentModelPools(base, overlay) {
|
|
24626
|
+
const out = { ...cloneAgentModelPools(base) };
|
|
24627
|
+
for (const [rawAgent, rawEntries] of Object.entries(overlay ?? {})) {
|
|
24628
|
+
const agent = validateCoworkAgentName(rawAgent, `cowork.routing.agentModelPools.${rawAgent}`);
|
|
24629
|
+
out[agent] = canonicalCoworkModelPool(rawEntries, `cowork.routing.agentModelPools.${agent}`);
|
|
24630
|
+
}
|
|
24631
|
+
return out;
|
|
24632
|
+
}
|
|
24633
|
+
function cloneAgentModelPools(input2) {
|
|
24634
|
+
const out = {};
|
|
24635
|
+
for (const [agent, entries] of Object.entries(input2)) {
|
|
24636
|
+
out[agent] = (entries ?? []).map((entry) => ({ ...entry }));
|
|
24637
|
+
}
|
|
24638
|
+
return out;
|
|
24639
|
+
}
|
|
24640
|
+
function canonicalCoworkModelPool(value, pathName) {
|
|
24641
|
+
if (!Array.isArray(value)) throw new Error(`${pathName} must be an array.`);
|
|
24642
|
+
const out = [];
|
|
24643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24644
|
+
for (const [index, raw] of value.entries()) {
|
|
24645
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`${pathName}[${index}] must be an object.`);
|
|
24646
|
+
const entry = raw;
|
|
24647
|
+
if (typeof entry.provider !== "string" || !entry.provider.trim()) throw new Error(`${pathName}[${index}].provider must be a non-empty string.`);
|
|
24648
|
+
const provider = entry.provider.trim();
|
|
24649
|
+
const model = typeof entry.model === "string" && entry.model.trim() ? entry.model.trim() : void 0;
|
|
24650
|
+
const modelProfileName = typeof entry.modelProfileName === "string" && entry.modelProfileName.trim() ? entry.modelProfileName.trim() : void 0;
|
|
24651
|
+
const key = `${provider}\0${modelProfileName ?? ""}\0${model ?? ""}`;
|
|
24652
|
+
if (seen.has(key)) continue;
|
|
24653
|
+
seen.add(key);
|
|
24654
|
+
out.push({
|
|
24655
|
+
provider,
|
|
24656
|
+
...model ? { model } : {},
|
|
24657
|
+
...modelProfileName ? { modelProfileName } : {}
|
|
24658
|
+
});
|
|
24659
|
+
}
|
|
24660
|
+
return out;
|
|
24661
|
+
}
|
|
24662
|
+
function mergeCoworkPermissionOverlay(base, overlay) {
|
|
24663
|
+
const providerToolNamespaces = {
|
|
24664
|
+
...base.providerToolNamespaces,
|
|
24665
|
+
...stringMap(overlay?.providerToolNamespaces, "cowork.permissionOverlay.providerToolNamespaces")
|
|
24666
|
+
};
|
|
24667
|
+
const modeMappings = {};
|
|
24668
|
+
for (const [mode, mapping] of Object.entries(base.modeMappings)) {
|
|
24669
|
+
if (mapping) modeMappings[mode] = clonePermissionModeOverlay(mapping);
|
|
24670
|
+
}
|
|
24671
|
+
for (const [rawMode, rawMapping] of Object.entries(overlay?.modeMappings ?? {})) {
|
|
24672
|
+
const mode = validateCoworkPermissionIntent(rawMode, `cowork.permissionOverlay.modeMappings.${rawMode}`);
|
|
24673
|
+
const baseMapping = modeMappings[mode];
|
|
24674
|
+
if (!rawMapping || typeof rawMapping !== "object" || Array.isArray(rawMapping)) throw new Error(`cowork.permissionOverlay.modeMappings.${mode} must be an object.`);
|
|
24675
|
+
const mapping = rawMapping;
|
|
24676
|
+
modeMappings[mode] = {
|
|
24677
|
+
demianTools: mapping.demianTools !== void 0 ? canonicalStringArray(mapping.demianTools, `cowork.permissionOverlay.modeMappings.${mode}.demianTools`) : [...baseMapping?.demianTools ?? []],
|
|
24678
|
+
providerTools: {
|
|
24679
|
+
...baseMapping ? cloneProviderTools(baseMapping.providerTools) : {},
|
|
24680
|
+
...providerToolsMap(mapping.providerTools, `cowork.permissionOverlay.modeMappings.${mode}.providerTools`)
|
|
24681
|
+
},
|
|
24682
|
+
defaultDecision: validatePermissionDefault(mapping.defaultDecision ?? baseMapping?.defaultDecision ?? "ask", `cowork.permissionOverlay.modeMappings.${mode}.defaultDecision`)
|
|
24683
|
+
};
|
|
24684
|
+
}
|
|
24685
|
+
return { providerToolNamespaces, modeMappings };
|
|
24686
|
+
}
|
|
24687
|
+
function clonePermissionModeOverlay(mapping) {
|
|
24688
|
+
return {
|
|
24689
|
+
demianTools: [...mapping.demianTools],
|
|
24690
|
+
providerTools: cloneProviderTools(mapping.providerTools),
|
|
24691
|
+
defaultDecision: mapping.defaultDecision
|
|
24692
|
+
};
|
|
24693
|
+
}
|
|
24694
|
+
function cloneProviderTools(input2) {
|
|
24695
|
+
return Object.fromEntries(Object.entries(input2).map(([provider, tools]) => [provider, [...tools]]));
|
|
24696
|
+
}
|
|
24697
|
+
function providerToolsMap(value, pathName) {
|
|
24698
|
+
if (value === void 0) return {};
|
|
24699
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${pathName} must be an object.`);
|
|
24700
|
+
const out = {};
|
|
24701
|
+
for (const [provider, tools] of Object.entries(value)) {
|
|
24702
|
+
out[provider] = canonicalStringArray(tools, `${pathName}.${provider}`);
|
|
24703
|
+
}
|
|
24704
|
+
return out;
|
|
24705
|
+
}
|
|
24706
|
+
function stringMap(value, pathName) {
|
|
24707
|
+
if (value === void 0) return {};
|
|
24708
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${pathName} must be an object.`);
|
|
24709
|
+
const out = {};
|
|
24710
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
24711
|
+
if (typeof raw !== "string" || !raw.trim()) throw new Error(`${pathName}.${key} must be a non-empty string.`);
|
|
24712
|
+
out[key] = raw.trim();
|
|
24713
|
+
}
|
|
24714
|
+
return out;
|
|
24715
|
+
}
|
|
24716
|
+
function canonicalStringArray(value, pathName) {
|
|
24717
|
+
if (!Array.isArray(value)) throw new Error(`${pathName} must be an array of strings.`);
|
|
24718
|
+
const out = [];
|
|
24719
|
+
for (const [index, item] of value.entries()) {
|
|
24720
|
+
if (typeof item !== "string" || !item.trim()) throw new Error(`${pathName}[${index}] must be a non-empty string.`);
|
|
24721
|
+
out.push(item.trim());
|
|
24722
|
+
}
|
|
24723
|
+
return [...new Set(out)];
|
|
24724
|
+
}
|
|
24725
|
+
function validatePermissionDefault(value, pathName) {
|
|
24726
|
+
if (value === "allow" || value === "ask" || value === "deny") return value;
|
|
24727
|
+
throw new Error(`${pathName} must be allow, ask, or deny.`);
|
|
24728
|
+
}
|
|
24729
|
+
function validateCoworkAgentName(value, pathName = "cowork agent") {
|
|
24730
|
+
if (COWORK_AGENT_NAMES.includes(value)) return value;
|
|
24731
|
+
const migration = COWORK_AGENT_MIGRATIONS[value];
|
|
24732
|
+
if (migration) throw new Error(`${pathName} uses removed cowork agent "${value}". Use "${migration.replacement}" instead (${migration.note}).`);
|
|
24733
|
+
throw new Error(`${pathName} must be one of: ${COWORK_AGENT_NAMES.join(", ")}.`);
|
|
24734
|
+
}
|
|
24735
|
+
function validateCoworkPermissionIntent(value, pathName) {
|
|
24736
|
+
if (value === "read-only" || value === "review" || value === "write" || value === "manage") return value;
|
|
24737
|
+
throw new Error(`${pathName} must be read-only, review, write, or manage.`);
|
|
24738
|
+
}
|
|
24187
24739
|
function mergeModelProfile(providerConfig, profile) {
|
|
24188
24740
|
const merged = {
|
|
24189
24741
|
...providerConfig,
|
|
@@ -24445,20 +24997,21 @@ var CLAUDE_CODE_PERMISSION_PROFILE_KEYS = ["permissionMode", "defaultDecision",
|
|
|
24445
24997
|
import { spawn as spawn4 } from "node:child_process";
|
|
24446
24998
|
import { stat as stat4 } from "node:fs/promises";
|
|
24447
24999
|
import os10 from "node:os";
|
|
24448
|
-
import
|
|
25000
|
+
import path17 from "node:path";
|
|
24449
25001
|
import readline3 from "node:readline/promises";
|
|
24450
25002
|
|
|
24451
25003
|
// src/config-scaffold.ts
|
|
24452
25004
|
import { chmod as chmod3, mkdir as mkdir6, readFile as readFile7, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
24453
25005
|
import os8 from "node:os";
|
|
24454
|
-
import
|
|
25006
|
+
import path15 from "node:path";
|
|
24455
25007
|
function defaultUserConfigPath() {
|
|
24456
|
-
return
|
|
25008
|
+
return path15.join(os8.homedir(), ".demian", "config.json");
|
|
24457
25009
|
}
|
|
24458
25010
|
function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
24459
25011
|
return {
|
|
24460
25012
|
version: 2,
|
|
24461
25013
|
defaultProvider,
|
|
25014
|
+
cowork: defaultCoworkUserConfig(),
|
|
24462
25015
|
providers: {
|
|
24463
25016
|
openai: {
|
|
24464
25017
|
type: "openai-compatible",
|
|
@@ -24568,6 +25121,76 @@ function defaultUserConfig(defaultProvider = detectDefaultProvider()) {
|
|
|
24568
25121
|
}
|
|
24569
25122
|
};
|
|
24570
25123
|
}
|
|
25124
|
+
function defaultCoworkUserConfig() {
|
|
25125
|
+
return {
|
|
25126
|
+
providers: ["codex", "claudecode"],
|
|
25127
|
+
preflight: {
|
|
25128
|
+
mode: "session-start",
|
|
25129
|
+
ttlMs: 6e5
|
|
25130
|
+
},
|
|
25131
|
+
fallback: {
|
|
25132
|
+
nonWrite: {
|
|
25133
|
+
onConnectionError: "next-provider",
|
|
25134
|
+
onTokenLimit: "next-provider",
|
|
25135
|
+
onAuthFailure: "skip-provider",
|
|
25136
|
+
onAllProvidersFailed: "fail-member"
|
|
25137
|
+
},
|
|
25138
|
+
write: {
|
|
25139
|
+
onFailure: "repair-from-worktree",
|
|
25140
|
+
maxRepairAttempts: 1,
|
|
25141
|
+
readChangedFilesBeforeRepair: true
|
|
25142
|
+
}
|
|
25143
|
+
},
|
|
25144
|
+
routing: {
|
|
25145
|
+
preset: "codex-manager-claudecode-worker",
|
|
25146
|
+
agentModelPools: {},
|
|
25147
|
+
agentProviders: {
|
|
25148
|
+
planner: ["codex", "anthropic", "openai"],
|
|
25149
|
+
architect: ["codex", "anthropic"],
|
|
25150
|
+
supervisor: ["codex", "anthropic"],
|
|
25151
|
+
researcher: ["codex", "claudecode", "anthropic"],
|
|
25152
|
+
builder: ["claudecode", "anthropic", "openai"],
|
|
25153
|
+
reviewer: ["claudecode", "anthropic", "codex"],
|
|
25154
|
+
specialist: ["claudecode", "anthropic"]
|
|
25155
|
+
}
|
|
25156
|
+
},
|
|
25157
|
+
permissionOverlay: {
|
|
25158
|
+
providerToolNamespaces: {
|
|
25159
|
+
claudecode: "claudecode"
|
|
25160
|
+
},
|
|
25161
|
+
modeMappings: {
|
|
25162
|
+
"read-only": {
|
|
25163
|
+
demianTools: ["read_file", "grep", "glob", "list_dir"],
|
|
25164
|
+
providerTools: {
|
|
25165
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
25166
|
+
},
|
|
25167
|
+
defaultDecision: "allow"
|
|
25168
|
+
},
|
|
25169
|
+
review: {
|
|
25170
|
+
demianTools: ["read_file", "grep", "glob", "git_diff"],
|
|
25171
|
+
providerTools: {
|
|
25172
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
25173
|
+
},
|
|
25174
|
+
defaultDecision: "allow"
|
|
25175
|
+
},
|
|
25176
|
+
write: {
|
|
25177
|
+
demianTools: ["read_file", "grep", "glob", "edit_file", "write_file", "bash"],
|
|
25178
|
+
providerTools: {
|
|
25179
|
+
claudecode: ["Read", "Grep", "Glob", "LS", "Edit", "MultiEdit", "Write", "Bash"]
|
|
25180
|
+
},
|
|
25181
|
+
defaultDecision: "ask"
|
|
25182
|
+
},
|
|
25183
|
+
manage: {
|
|
25184
|
+
demianTools: ["read_file", "grep", "glob", "task_control", "git_diff"],
|
|
25185
|
+
providerTools: {
|
|
25186
|
+
claudecode: ["Read", "Grep", "Glob", "LS"]
|
|
25187
|
+
},
|
|
25188
|
+
defaultDecision: "ask"
|
|
25189
|
+
}
|
|
25190
|
+
}
|
|
25191
|
+
}
|
|
25192
|
+
};
|
|
25193
|
+
}
|
|
24571
25194
|
async function createUserConfig(options = {}) {
|
|
24572
25195
|
const filePath = expandHome2(options.path ?? defaultUserConfigPath());
|
|
24573
25196
|
const content = `${JSON.stringify(defaultUserConfig(options.defaultProvider), null, 2)}
|
|
@@ -24753,7 +25376,7 @@ function apiKeyAuthFields(options, defaultApiKeyEnv) {
|
|
|
24753
25376
|
};
|
|
24754
25377
|
}
|
|
24755
25378
|
async function writeJsonAtomic(filePath, content) {
|
|
24756
|
-
await mkdir6(
|
|
25379
|
+
await mkdir6(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
24757
25380
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
24758
25381
|
await writeFile5(temp, content, { mode: 384 });
|
|
24759
25382
|
await rename4(temp, filePath);
|
|
@@ -24809,7 +25432,7 @@ function expandHome2(value) {
|
|
|
24809
25432
|
import crypto5 from "node:crypto";
|
|
24810
25433
|
import { mkdir as mkdir7, readFile as readFile8, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
|
|
24811
25434
|
import os9 from "node:os";
|
|
24812
|
-
import
|
|
25435
|
+
import path16 from "node:path";
|
|
24813
25436
|
var DEFAULT_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
24814
25437
|
var PING_TIMEOUT_MS = 1500;
|
|
24815
25438
|
var DEFAULT_CODEX_CLIENT_VERSION = "0.0.0";
|
|
@@ -25117,7 +25740,7 @@ async function fetchJson(url, options) {
|
|
|
25117
25740
|
return text ? JSON.parse(text) : {};
|
|
25118
25741
|
}
|
|
25119
25742
|
function cachePath(cacheKey) {
|
|
25120
|
-
return
|
|
25743
|
+
return path16.join(os9.homedir(), ".demian", "cache", "models", `${safeCacheName(cacheKey)}.json`);
|
|
25121
25744
|
}
|
|
25122
25745
|
async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
25123
25746
|
if (!allow) return void 0;
|
|
@@ -25132,7 +25755,7 @@ async function readCatalogCache(cacheKey, ttlMs, allow) {
|
|
|
25132
25755
|
}
|
|
25133
25756
|
async function writeCatalogCache(cacheKey, result) {
|
|
25134
25757
|
const filePath = cachePath(cacheKey);
|
|
25135
|
-
await mkdir7(
|
|
25758
|
+
await mkdir7(path16.dirname(filePath), { recursive: true, mode: 448 });
|
|
25136
25759
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
25137
25760
|
await writeFile6(temp, JSON.stringify({ savedAt: Date.now(), result }, null, 2), { mode: 384 });
|
|
25138
25761
|
await rename5(temp, filePath);
|
|
@@ -25561,8 +26184,8 @@ async function fileExists2(filePath) {
|
|
|
25561
26184
|
}
|
|
25562
26185
|
async function readJsonSafe(filePath) {
|
|
25563
26186
|
try {
|
|
25564
|
-
const { readFile:
|
|
25565
|
-
const text = await
|
|
26187
|
+
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
26188
|
+
const text = await readFile19(filePath, "utf8");
|
|
25566
26189
|
return JSON.parse(text);
|
|
25567
26190
|
} catch {
|
|
25568
26191
|
return void 0;
|
|
@@ -25663,7 +26286,7 @@ async function walkProviderSetup() {
|
|
|
25663
26286
|
process.stdout.write("\nSetup complete. Run `demian` to start a session.\n\n");
|
|
25664
26287
|
}
|
|
25665
26288
|
async function checkCodexAuth() {
|
|
25666
|
-
const candidates = [
|
|
26289
|
+
const candidates = [path17.join(process.env.HOME ?? os10.homedir(), ".codex", "auth.json"), path17.join(process.env.HOME ?? os10.homedir(), ".codex", "credentials.json")];
|
|
25667
26290
|
for (const candidate of candidates) {
|
|
25668
26291
|
if (await fileExists2(candidate)) {
|
|
25669
26292
|
return { ok: true, source: "codex-store", message: candidate };
|
|
@@ -25677,7 +26300,7 @@ async function checkCodexAuth() {
|
|
|
25677
26300
|
import { existsSync as existsSync2 } from "node:fs";
|
|
25678
26301
|
import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
|
|
25679
26302
|
import os11 from "node:os";
|
|
25680
|
-
import
|
|
26303
|
+
import path18 from "node:path";
|
|
25681
26304
|
function isDoctorCommand(argv) {
|
|
25682
26305
|
return argv[0] === "doctor";
|
|
25683
26306
|
}
|
|
@@ -25697,7 +26320,7 @@ async function runDoctorCommand(argv, options = {}) {
|
|
|
25697
26320
|
stderr.write("Unsupported doctor command. Run `demian doctor policies --upgrade-namespaces [--write]`.\n");
|
|
25698
26321
|
return 1;
|
|
25699
26322
|
}
|
|
25700
|
-
const cwd =
|
|
26323
|
+
const cwd = path18.resolve(flags.cwd ?? options.cwd ?? process.cwd());
|
|
25701
26324
|
const filePaths = resolvePolicyConfigPaths(cwd, flags.configPaths);
|
|
25702
26325
|
if (filePaths.length === 0) {
|
|
25703
26326
|
stdout.write("No demian config files found to inspect.\n");
|
|
@@ -25798,11 +26421,11 @@ function parseDoctorFlags(argv) {
|
|
|
25798
26421
|
return flags;
|
|
25799
26422
|
}
|
|
25800
26423
|
function resolvePolicyConfigPaths(cwd, explicitPaths) {
|
|
25801
|
-
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) =>
|
|
25802
|
-
|
|
25803
|
-
|
|
25804
|
-
|
|
25805
|
-
|
|
26424
|
+
const candidates = explicitPaths.length > 0 ? explicitPaths.map((item) => path18.resolve(cwd, item)) : [
|
|
26425
|
+
path18.join(os11.homedir(), ".demian", "config.json"),
|
|
26426
|
+
path18.join(os11.homedir(), ".demian", "config.jsond"),
|
|
26427
|
+
path18.join(cwd, ".demian", "config.json"),
|
|
26428
|
+
path18.join(cwd, ".demian", "config.jsond")
|
|
25806
26429
|
];
|
|
25807
26430
|
return [...new Set(candidates)].filter((item) => existsSync2(item));
|
|
25808
26431
|
}
|
|
@@ -25897,10 +26520,68 @@ Flags:
|
|
|
25897
26520
|
`;
|
|
25898
26521
|
}
|
|
25899
26522
|
|
|
25900
|
-
// src/
|
|
25901
|
-
import { mkdir as mkdir8,
|
|
26523
|
+
// src/permissions/persistent-grants.ts
|
|
26524
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
|
|
26525
|
+
import fs8 from "node:fs";
|
|
25902
26526
|
import os12 from "node:os";
|
|
25903
|
-
import
|
|
26527
|
+
import path19 from "node:path";
|
|
26528
|
+
var DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
26529
|
+
var PersistentGrantStore = class {
|
|
26530
|
+
filePath;
|
|
26531
|
+
#config;
|
|
26532
|
+
constructor(cwd, config) {
|
|
26533
|
+
this.#config = config;
|
|
26534
|
+
this.filePath = resolveGrantPath(cwd, config);
|
|
26535
|
+
}
|
|
26536
|
+
async load(workspace) {
|
|
26537
|
+
if (!this.#config.enabled) return [];
|
|
26538
|
+
const file = await this.#read();
|
|
26539
|
+
const now2 = Date.now();
|
|
26540
|
+
const grants = file.grants.filter((item) => !item.expiresAt || item.expiresAt > now2);
|
|
26541
|
+
const changed = grants.length !== file.grants.length;
|
|
26542
|
+
const keys = grants.filter((item) => item.workspace === workspace).map((item) => item.key);
|
|
26543
|
+
if (changed) await this.#write({ version: 1, grants });
|
|
26544
|
+
return [...new Set(keys)].sort();
|
|
26545
|
+
}
|
|
26546
|
+
async add(workspace, key) {
|
|
26547
|
+
if (!this.#config.enabled) return;
|
|
26548
|
+
const file = await this.#read();
|
|
26549
|
+
const now2 = Date.now();
|
|
26550
|
+
const ttlMs = this.#config.ttlMs ?? DEFAULT_TTL_MS;
|
|
26551
|
+
const expiresAt = ttlMs > 0 ? now2 + ttlMs : void 0;
|
|
26552
|
+
const grants = file.grants.filter((item) => !(item.workspace === workspace && item.key === key));
|
|
26553
|
+
grants.push({ workspace, key, createdAt: now2, lastUsedAt: now2, expiresAt });
|
|
26554
|
+
await this.#write({ version: 1, grants });
|
|
26555
|
+
}
|
|
26556
|
+
async #read() {
|
|
26557
|
+
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
26558
|
+
const data = JSON.parse(await readFile10(this.filePath, "utf8"));
|
|
26559
|
+
return {
|
|
26560
|
+
version: 1,
|
|
26561
|
+
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
26562
|
+
};
|
|
26563
|
+
}
|
|
26564
|
+
async #write(file) {
|
|
26565
|
+
await mkdir8(path19.dirname(this.filePath), { recursive: true });
|
|
26566
|
+
await writeFile8(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
26567
|
+
`, { mode: 384 });
|
|
26568
|
+
}
|
|
26569
|
+
};
|
|
26570
|
+
function resolveGrantPath(cwd, config) {
|
|
26571
|
+
if (config.path) return path19.resolve(cwd, config.path);
|
|
26572
|
+
if ((config.scope ?? "project") === "user") return path19.join(os12.homedir(), ".demian", "grants.json");
|
|
26573
|
+
return path19.join(cwd, ".demian", "grants.json");
|
|
26574
|
+
}
|
|
26575
|
+
function isGrantRecord(value) {
|
|
26576
|
+
if (!value || typeof value !== "object") return false;
|
|
26577
|
+
const item = value;
|
|
26578
|
+
return typeof item.workspace === "string" && typeof item.key === "string" && typeof item.createdAt === "number" && typeof item.lastUsedAt === "number";
|
|
26579
|
+
}
|
|
26580
|
+
|
|
26581
|
+
// src/transcript.ts
|
|
26582
|
+
import { mkdir as mkdir9, appendFile, writeFile as writeFile9 } from "node:fs/promises";
|
|
26583
|
+
import os13 from "node:os";
|
|
26584
|
+
import path20 from "node:path";
|
|
25904
26585
|
var TranscriptWriter = class {
|
|
25905
26586
|
filePath;
|
|
25906
26587
|
#enabled;
|
|
@@ -25908,9 +26589,9 @@ var TranscriptWriter = class {
|
|
|
25908
26589
|
#queue = Promise.resolve();
|
|
25909
26590
|
constructor(options) {
|
|
25910
26591
|
this.#enabled = options.enabled !== false;
|
|
25911
|
-
const dir =
|
|
25912
|
-
this.filePath =
|
|
25913
|
-
this.#ready = this.#enabled ?
|
|
26592
|
+
const dir = path20.join(options.storageDir ?? defaultDemianStorageDir(), "transcripts", options.sessionId);
|
|
26593
|
+
this.filePath = path20.join(dir, "session.jsonl");
|
|
26594
|
+
this.#ready = this.#enabled ? mkdir9(dir, { recursive: true }).then(() => writeFile9(this.filePath, "", { flag: "a" })) : Promise.resolve();
|
|
25914
26595
|
}
|
|
25915
26596
|
write(event) {
|
|
25916
26597
|
if (!this.#enabled) return Promise.resolve();
|
|
@@ -25926,14 +26607,14 @@ var TranscriptWriter = class {
|
|
|
25926
26607
|
}
|
|
25927
26608
|
};
|
|
25928
26609
|
function defaultDemianStorageDir() {
|
|
25929
|
-
return
|
|
26610
|
+
return path20.join(os13.homedir(), ".demian");
|
|
25930
26611
|
}
|
|
25931
26612
|
|
|
25932
26613
|
// src/external-runtime/snapshot-diff.ts
|
|
25933
26614
|
import { createHash as createHash2 } from "node:crypto";
|
|
25934
26615
|
import { createReadStream } from "node:fs";
|
|
25935
|
-
import { readdir, readFile as
|
|
25936
|
-
import
|
|
26616
|
+
import { readdir, readFile as readFile11, stat as stat5 } from "node:fs/promises";
|
|
26617
|
+
import path21 from "node:path";
|
|
25937
26618
|
|
|
25938
26619
|
// src/workspace/diff.ts
|
|
25939
26620
|
var CONTEXT_LINES = 3;
|
|
@@ -26143,7 +26824,7 @@ async function diffWorkspaceSnapshot(before) {
|
|
|
26143
26824
|
}
|
|
26144
26825
|
async function walk(root, relativeDir, entries, options) {
|
|
26145
26826
|
if (entries.size >= options.maxFiles) return;
|
|
26146
|
-
const absoluteDir =
|
|
26827
|
+
const absoluteDir = path21.join(root, relativeDir);
|
|
26147
26828
|
let items;
|
|
26148
26829
|
try {
|
|
26149
26830
|
items = await readdir(absoluteDir, { withFileTypes: true });
|
|
@@ -26154,8 +26835,8 @@ async function walk(root, relativeDir, entries, options) {
|
|
|
26154
26835
|
if (entries.size >= options.maxFiles) return;
|
|
26155
26836
|
if (item.name.startsWith(".") && item.name !== ".env.example" && SKIP_DIRS.has(item.name)) continue;
|
|
26156
26837
|
if (SKIP_DIRS.has(item.name)) continue;
|
|
26157
|
-
const relativePath = normalizeRelativePath(
|
|
26158
|
-
const absolutePath =
|
|
26838
|
+
const relativePath = normalizeRelativePath(path21.join(relativeDir, item.name));
|
|
26839
|
+
const absolutePath = path21.join(root, relativePath);
|
|
26159
26840
|
if (item.isDirectory()) {
|
|
26160
26841
|
await walk(root, relativePath, entries, options);
|
|
26161
26842
|
continue;
|
|
@@ -26177,7 +26858,7 @@ function snapshotDiffText(entry) {
|
|
|
26177
26858
|
`;
|
|
26178
26859
|
}
|
|
26179
26860
|
async function readTextContent(filePath) {
|
|
26180
|
-
const buffer = await
|
|
26861
|
+
const buffer = await readFile11(filePath);
|
|
26181
26862
|
if (buffer.includes(0)) return void 0;
|
|
26182
26863
|
return buffer.toString("utf8");
|
|
26183
26864
|
}
|
|
@@ -26191,7 +26872,7 @@ function sha256File(filePath) {
|
|
|
26191
26872
|
});
|
|
26192
26873
|
}
|
|
26193
26874
|
function normalizeRelativePath(value) {
|
|
26194
|
-
return value.split(
|
|
26875
|
+
return value.split(path21.sep).join("/");
|
|
26195
26876
|
}
|
|
26196
26877
|
|
|
26197
26878
|
// src/external-runtime/session-runner.ts
|
|
@@ -26201,10 +26882,16 @@ var ExternalAgentSessionRunner = class {
|
|
|
26201
26882
|
events;
|
|
26202
26883
|
#options;
|
|
26203
26884
|
#transcript;
|
|
26885
|
+
#grants;
|
|
26886
|
+
#persistentStore;
|
|
26204
26887
|
#activeTools = /* @__PURE__ */ new Map();
|
|
26888
|
+
#toolStats = { requested: 0, completed: 0, failed: 0 };
|
|
26889
|
+
#recentToolEvents = [];
|
|
26205
26890
|
#cancellationArtifactsEmitted = false;
|
|
26206
26891
|
constructor(options) {
|
|
26207
26892
|
this.#options = options;
|
|
26893
|
+
this.#grants = options.grants ?? new SessionGrants();
|
|
26894
|
+
if (options.persistentGrants?.enabled) this.#persistentStore = new PersistentGrantStore(options.cwd, options.persistentGrants);
|
|
26208
26895
|
this.events = options.eventBus ?? new EventBus();
|
|
26209
26896
|
this.#transcript = options.transcriptWriter ?? new TranscriptWriter({
|
|
26210
26897
|
cwd: options.cwd,
|
|
@@ -26214,6 +26901,7 @@ var ExternalAgentSessionRunner = class {
|
|
|
26214
26901
|
}
|
|
26215
26902
|
async run() {
|
|
26216
26903
|
const options = this.#options;
|
|
26904
|
+
for (const key of await this.#persistentStore?.load(options.cwd) ?? []) this.#grants.add(key);
|
|
26217
26905
|
const messages = [{ role: "system", content: buildSystemPrompt(options.agent) }, ...options.history ?? [], { role: "user", content: options.prompt }];
|
|
26218
26906
|
let finalAnswer = "";
|
|
26219
26907
|
let finalTextEmitted = false;
|
|
@@ -26257,8 +26945,12 @@ var ExternalAgentSessionRunner = class {
|
|
|
26257
26945
|
if (options.signal?.aborted) return this.endCancelled(messages, finalAnswer, externalSessionId, usage, snapshot);
|
|
26258
26946
|
if (recoveredInvalidResume || !shouldRecoverInvalidResume(failure2.error, runtimePreviousExternalSessionId)) {
|
|
26259
26947
|
this.emit({ type: "session.failed", sessionId: this.sessionId, error: failure2.error, ts: now(), ...this.eventContext() });
|
|
26260
|
-
|
|
26261
|
-
|
|
26948
|
+
const answer = this.externalTerminalFinalAnswer("error", finalAnswer || failure2.error);
|
|
26949
|
+
messages.push({ role: "assistant", content: answer });
|
|
26950
|
+
this.emit({ type: "session.ended", sessionId: this.sessionId, reason: "error", ts: now(), ...this.eventContext() });
|
|
26951
|
+
this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
26952
|
+
await this.#transcript.flush();
|
|
26953
|
+
return { sessionId: this.sessionId, externalSessionId, finalAnswer: answer, messages, endReason: "error", usage };
|
|
26262
26954
|
}
|
|
26263
26955
|
const policy = options.onInvalidResume ?? "fresh";
|
|
26264
26956
|
recoveredInvalidResume = true;
|
|
@@ -26310,11 +27002,15 @@ var ExternalAgentSessionRunner = class {
|
|
|
26310
27002
|
model: options.model,
|
|
26311
27003
|
agent: options.agent,
|
|
26312
27004
|
providerName: options.providerName,
|
|
27005
|
+
maxTurns: options.maxTurns,
|
|
26313
27006
|
signal: options.signal ?? new AbortController().signal,
|
|
26314
27007
|
historyPolicy: options.historyPolicy ?? "passthrough-resume",
|
|
26315
27008
|
previousExternalSessionId,
|
|
26316
27009
|
writeScope: options.writeScope,
|
|
26317
27010
|
permissionPrompt: options.permissionPrompt,
|
|
27011
|
+
autoPermission: options.autoPermission,
|
|
27012
|
+
grants: this.#grants,
|
|
27013
|
+
onPermissionGrant: (grantKey) => this.#persistentStore?.add(options.cwd, grantKey) ?? Promise.resolve(),
|
|
26318
27014
|
yes: options.yes,
|
|
26319
27015
|
emit: (runtimeEvent) => this.emit(runtimeEvent)
|
|
26320
27016
|
})) {
|
|
@@ -26329,6 +27025,8 @@ var ExternalAgentSessionRunner = class {
|
|
|
26329
27025
|
this.emit({ type: "model.text", sessionId: this.sessionId, text: event.text, ts: now(), ...this.eventContext() });
|
|
26330
27026
|
} else if (event.type === "tool.requested") {
|
|
26331
27027
|
this.#activeTools.set(event.callId, { name: event.name });
|
|
27028
|
+
this.#toolStats.requested += 1;
|
|
27029
|
+
this.recordRecentToolEvent(`\uC694\uCCAD: ${event.name}`);
|
|
26332
27030
|
this.emit({
|
|
26333
27031
|
type: "tool.requested",
|
|
26334
27032
|
sessionId: this.sessionId,
|
|
@@ -26342,8 +27040,13 @@ var ExternalAgentSessionRunner = class {
|
|
|
26342
27040
|
...this.eventContext()
|
|
26343
27041
|
});
|
|
26344
27042
|
} else if (event.type === "tool.completed") {
|
|
27043
|
+
const activeTool = this.#activeTools.get(event.callId);
|
|
27044
|
+
const name = event.name === "unknown" ? activeTool?.name ?? event.name : event.name;
|
|
26345
27045
|
this.#activeTools.delete(event.callId);
|
|
26346
|
-
|
|
27046
|
+
if (event.ok) this.#toolStats.completed += 1;
|
|
27047
|
+
else this.#toolStats.failed += 1;
|
|
27048
|
+
this.recordRecentToolEvent(`${event.ok ? "\uC644\uB8CC" : "\uC2E4\uD328"}: ${name}${event.preview ? ` - ${event.preview}` : ""}`);
|
|
27049
|
+
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() });
|
|
26347
27050
|
} else if (event.type === "usage") {
|
|
26348
27051
|
capture.onUsage(event.usage);
|
|
26349
27052
|
this.emit({ type: "model.usage", sessionId: this.sessionId, usage: event.usage, ts: now(), ...this.eventContext() });
|
|
@@ -26362,12 +27065,31 @@ var ExternalAgentSessionRunner = class {
|
|
|
26362
27065
|
}
|
|
26363
27066
|
async endCancelled(messages, finalAnswer, externalSessionId, usage, snapshot) {
|
|
26364
27067
|
await this.emitCancellationArtifacts(snapshot);
|
|
26365
|
-
const answer = finalAnswer || "Cancelled.";
|
|
27068
|
+
const answer = this.externalTerminalFinalAnswer("cancelled", finalAnswer || "Cancelled.");
|
|
26366
27069
|
messages.push({ role: "assistant", content: answer });
|
|
26367
27070
|
this.emit({ type: "session.ended", sessionId: this.sessionId, reason: "cancelled", ts: now(), ...this.eventContext() });
|
|
27071
|
+
this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
26368
27072
|
await this.#transcript.flush();
|
|
26369
27073
|
return { sessionId: this.sessionId, externalSessionId, finalAnswer: answer, messages, endReason: "cancelled", usage };
|
|
26370
27074
|
}
|
|
27075
|
+
recordRecentToolEvent(text) {
|
|
27076
|
+
this.#recentToolEvents.push(text.replace(/\s+/g, " ").trim());
|
|
27077
|
+
if (this.#recentToolEvents.length > 5) this.#recentToolEvents.splice(0, this.#recentToolEvents.length - 5);
|
|
27078
|
+
}
|
|
27079
|
+
externalTerminalFinalAnswer(reason, lastMessage) {
|
|
27080
|
+
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."}`];
|
|
27081
|
+
if (lastMessage.trim()) lines.push(`\uB9C8\uC9C0\uB9C9 \uB7F0\uD0C0\uC784 \uBA54\uC2DC\uC9C0: ${lastMessage.trim()}`);
|
|
27082
|
+
lines.push("", "\uD604\uC7AC\uAE4C\uC9C0 \uC9C4\uD589\uD55C \uB0B4\uC6A9\uC740 \uC544\uB798\uC640 \uAC19\uC2B5\uB2C8\uB2E4.");
|
|
27083
|
+
const totalTools = this.#toolStats.requested;
|
|
27084
|
+
if (totalTools > 0) {
|
|
27085
|
+
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.`);
|
|
27086
|
+
if (this.#recentToolEvents.length > 0) lines.push(`- \uCD5C\uADFC \uB3C4\uAD6C \uD750\uB984: ${this.#recentToolEvents.join("; ")}`);
|
|
27087
|
+
} else {
|
|
27088
|
+
lines.push("- \uC544\uC9C1 \uC644\uB8CC\uB41C \uB3C4\uAD6C \uC0AC\uC6A9 \uAE30\uB85D\uC740 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
27089
|
+
}
|
|
27090
|
+
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.");
|
|
27091
|
+
return lines.join("\n");
|
|
27092
|
+
}
|
|
26371
27093
|
async createAbortSnapshot() {
|
|
26372
27094
|
try {
|
|
26373
27095
|
return await createWorkspaceSnapshot(this.#options.cwd);
|
|
@@ -26626,7 +27348,7 @@ function safeJson(value) {
|
|
|
26626
27348
|
}
|
|
26627
27349
|
|
|
26628
27350
|
// src/hooks/dispatcher.ts
|
|
26629
|
-
import
|
|
27351
|
+
import path23 from "node:path";
|
|
26630
27352
|
|
|
26631
27353
|
// src/hooks/command.ts
|
|
26632
27354
|
import { spawn as spawn5 } from "node:child_process";
|
|
@@ -26719,7 +27441,7 @@ var blockDangerousBashHook = {
|
|
|
26719
27441
|
};
|
|
26720
27442
|
|
|
26721
27443
|
// src/hooks/builtin/protect-env-files.ts
|
|
26722
|
-
import
|
|
27444
|
+
import path22 from "node:path";
|
|
26723
27445
|
var protectEnvFilesHook = {
|
|
26724
27446
|
name: "protect-env-files",
|
|
26725
27447
|
event: "PreToolUse",
|
|
@@ -26727,7 +27449,7 @@ var protectEnvFilesHook = {
|
|
|
26727
27449
|
run(ctx) {
|
|
26728
27450
|
if (ctx.toolName !== "write_file" && ctx.toolName !== "edit_file") return { decision: "allow" };
|
|
26729
27451
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
26730
|
-
const filePath = typeof input2.path === "string" ?
|
|
27452
|
+
const filePath = typeof input2.path === "string" ? path22.resolve(ctx.cwd, input2.path) : "";
|
|
26731
27453
|
if (filePath && isEnvFile(filePath)) {
|
|
26732
27454
|
return {
|
|
26733
27455
|
decision: "block",
|
|
@@ -26764,7 +27486,7 @@ var maskSecretsHook = {
|
|
|
26764
27486
|
};
|
|
26765
27487
|
|
|
26766
27488
|
// src/hooks/builtin/inject-env-info.ts
|
|
26767
|
-
import
|
|
27489
|
+
import os14 from "node:os";
|
|
26768
27490
|
var injectEnvInfoHook = {
|
|
26769
27491
|
name: "inject-env-info",
|
|
26770
27492
|
event: "SessionStart",
|
|
@@ -26773,7 +27495,7 @@ var injectEnvInfoHook = {
|
|
|
26773
27495
|
decision: "allow",
|
|
26774
27496
|
patch: {
|
|
26775
27497
|
systemNote: [
|
|
26776
|
-
`Environment: ${
|
|
27498
|
+
`Environment: ${os14.type()} ${os14.release()} (${os14.platform()}/${os14.arch()})`,
|
|
26777
27499
|
`cwd: ${ctx.cwd}`,
|
|
26778
27500
|
`provider: ${ctx.provider ?? "unknown"}`,
|
|
26779
27501
|
`model: ${ctx.model ?? "unknown"}`,
|
|
@@ -26842,14 +27564,14 @@ function matchesHook(match, ctx) {
|
|
|
26842
27564
|
const input2 = ctx.toolInput && typeof ctx.toolInput === "object" ? ctx.toolInput : {};
|
|
26843
27565
|
const filePath = typeof input2.path === "string" ? input2.path : typeof input2.workdir === "string" ? input2.workdir : void 0;
|
|
26844
27566
|
if (!filePath) return false;
|
|
26845
|
-
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd,
|
|
27567
|
+
if (!matchGlob(match.pathGlob, relativeToCwd(ctx.cwd, path23.resolve(ctx.cwd, filePath)))) return false;
|
|
26846
27568
|
}
|
|
26847
27569
|
return true;
|
|
26848
27570
|
}
|
|
26849
27571
|
|
|
26850
27572
|
// src/multimodal.ts
|
|
26851
|
-
import { readFile as
|
|
26852
|
-
import
|
|
27573
|
+
import { readFile as readFile12, stat as stat6 } from "node:fs/promises";
|
|
27574
|
+
import path24 from "node:path";
|
|
26853
27575
|
var DEFAULT_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
26854
27576
|
async function buildUserContent(prompt, images = [], options) {
|
|
26855
27577
|
if (images.length === 0) return prompt;
|
|
@@ -26873,11 +27595,11 @@ async function imageToUrl(input2, options) {
|
|
|
26873
27595
|
const maxImageBytes = options.maxImageBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
26874
27596
|
if (info.size > maxImageBytes) throw new Error(`Image is larger than ${maxImageBytes} bytes: ${input2}`);
|
|
26875
27597
|
const mime = mimeFromPath(filePath);
|
|
26876
|
-
const bytes = await
|
|
27598
|
+
const bytes = await readFile12(filePath);
|
|
26877
27599
|
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
26878
27600
|
}
|
|
26879
27601
|
function mimeFromPath(filePath) {
|
|
26880
|
-
switch (
|
|
27602
|
+
switch (path24.extname(filePath).toLowerCase()) {
|
|
26881
27603
|
case ".jpg":
|
|
26882
27604
|
case ".jpeg":
|
|
26883
27605
|
return "image/jpeg";
|
|
@@ -26888,81 +27610,23 @@ function mimeFromPath(filePath) {
|
|
|
26888
27610
|
case ".webp":
|
|
26889
27611
|
return "image/webp";
|
|
26890
27612
|
default:
|
|
26891
|
-
throw new Error(`Unsupported image extension: ${
|
|
27613
|
+
throw new Error(`Unsupported image extension: ${path24.extname(filePath) || "(none)"}`);
|
|
26892
27614
|
}
|
|
26893
27615
|
}
|
|
26894
27616
|
|
|
26895
|
-
// src/
|
|
26896
|
-
|
|
26897
|
-
|
|
26898
|
-
|
|
26899
|
-
|
|
26900
|
-
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
|
|
26904
|
-
|
|
26905
|
-
this.#
|
|
26906
|
-
this.filePath = resolveGrantPath(cwd, config);
|
|
26907
|
-
}
|
|
26908
|
-
async load(workspace) {
|
|
26909
|
-
if (!this.#config.enabled) return [];
|
|
26910
|
-
const file = await this.#read();
|
|
26911
|
-
const now2 = Date.now();
|
|
26912
|
-
const grants = file.grants.filter((item) => !item.expiresAt || item.expiresAt > now2);
|
|
26913
|
-
const changed = grants.length !== file.grants.length;
|
|
26914
|
-
const keys = grants.filter((item) => item.workspace === workspace).map((item) => item.key);
|
|
26915
|
-
if (changed) await this.#write({ version: 1, grants });
|
|
26916
|
-
return [...new Set(keys)].sort();
|
|
26917
|
-
}
|
|
26918
|
-
async add(workspace, key) {
|
|
26919
|
-
if (!this.#config.enabled) return;
|
|
26920
|
-
const file = await this.#read();
|
|
26921
|
-
const now2 = Date.now();
|
|
26922
|
-
const ttlMs = this.#config.ttlMs ?? DEFAULT_TTL_MS;
|
|
26923
|
-
const expiresAt = ttlMs > 0 ? now2 + ttlMs : void 0;
|
|
26924
|
-
const grants = file.grants.filter((item) => !(item.workspace === workspace && item.key === key));
|
|
26925
|
-
grants.push({ workspace, key, createdAt: now2, lastUsedAt: now2, expiresAt });
|
|
26926
|
-
await this.#write({ version: 1, grants });
|
|
27617
|
+
// src/progress/ledger.ts
|
|
27618
|
+
var ProgressLedger = class {
|
|
27619
|
+
#rootSessionId;
|
|
27620
|
+
#plan;
|
|
27621
|
+
#notes = [];
|
|
27622
|
+
#maxNotes;
|
|
27623
|
+
#planCounter = 0;
|
|
27624
|
+
#noteCounter = 0;
|
|
27625
|
+
constructor(options = {}) {
|
|
27626
|
+
this.#rootSessionId = options.rootSessionId;
|
|
27627
|
+
this.#maxNotes = options.maxNotes ?? 50;
|
|
26927
27628
|
}
|
|
26928
|
-
|
|
26929
|
-
if (!fs8.existsSync(this.filePath)) return { version: 1, grants: [] };
|
|
26930
|
-
const data = JSON.parse(await readFile12(this.filePath, "utf8"));
|
|
26931
|
-
return {
|
|
26932
|
-
version: 1,
|
|
26933
|
-
grants: Array.isArray(data.grants) ? data.grants.filter(isGrantRecord) : []
|
|
26934
|
-
};
|
|
26935
|
-
}
|
|
26936
|
-
async #write(file) {
|
|
26937
|
-
await mkdir9(path23.dirname(this.filePath), { recursive: true });
|
|
26938
|
-
await writeFile9(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
26939
|
-
`, { mode: 384 });
|
|
26940
|
-
}
|
|
26941
|
-
};
|
|
26942
|
-
function resolveGrantPath(cwd, config) {
|
|
26943
|
-
if (config.path) return path23.resolve(cwd, config.path);
|
|
26944
|
-
if ((config.scope ?? "project") === "user") return path23.join(os14.homedir(), ".demian", "grants.json");
|
|
26945
|
-
return path23.join(cwd, ".demian", "grants.json");
|
|
26946
|
-
}
|
|
26947
|
-
function isGrantRecord(value) {
|
|
26948
|
-
if (!value || typeof value !== "object") return false;
|
|
26949
|
-
const item = value;
|
|
26950
|
-
return typeof item.workspace === "string" && typeof item.key === "string" && typeof item.createdAt === "number" && typeof item.lastUsedAt === "number";
|
|
26951
|
-
}
|
|
26952
|
-
|
|
26953
|
-
// src/progress/ledger.ts
|
|
26954
|
-
var ProgressLedger = class {
|
|
26955
|
-
#rootSessionId;
|
|
26956
|
-
#plan;
|
|
26957
|
-
#notes = [];
|
|
26958
|
-
#maxNotes;
|
|
26959
|
-
#planCounter = 0;
|
|
26960
|
-
#noteCounter = 0;
|
|
26961
|
-
constructor(options = {}) {
|
|
26962
|
-
this.#rootSessionId = options.rootSessionId;
|
|
26963
|
-
this.#maxNotes = options.maxNotes ?? 50;
|
|
26964
|
-
}
|
|
26965
|
-
snapshot() {
|
|
27629
|
+
snapshot() {
|
|
26966
27630
|
return {
|
|
26967
27631
|
plan: this.state(),
|
|
26968
27632
|
notes: this.#notes.map((note) => ({ ...note }))
|
|
@@ -27080,7 +27744,7 @@ var ProgressLedger = class {
|
|
|
27080
27744
|
return this.#plan;
|
|
27081
27745
|
}
|
|
27082
27746
|
};
|
|
27083
|
-
var statuses = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "blocked", "skipped", "failed"]);
|
|
27747
|
+
var statuses = /* @__PURE__ */ new Set(["pending", "in_progress", "completed", "blocked", "skipped", "failed", "cancelled"]);
|
|
27084
27748
|
var FINAL_REVIEW_STEP_ID = "final_review";
|
|
27085
27749
|
var FINAL_REVIEW_TITLE = "Final review and verification";
|
|
27086
27750
|
var FINAL_REVIEW_DETAIL = "Review changes, run checks, and confirm the result before finishing.";
|
|
@@ -27287,6 +27951,7 @@ function normalizeActiveStep(plan, preferredActive) {
|
|
|
27287
27951
|
const inProgress = plan.steps.filter((step) => step.status === "in_progress");
|
|
27288
27952
|
let active = preferredActive === null ? void 0 : typeof preferredActive === "string" ? preferredActive : void 0;
|
|
27289
27953
|
if (active && !plan.steps.some((step) => step.id === active)) active = void 0;
|
|
27954
|
+
if (active && isTerminal(plan.steps.find((step) => step.id === active)?.status ?? "pending")) active = void 0;
|
|
27290
27955
|
if (active) {
|
|
27291
27956
|
for (const step of plan.steps) {
|
|
27292
27957
|
if (step.id === active) {
|
|
@@ -27349,7 +28014,7 @@ function isStatus(value) {
|
|
|
27349
28014
|
return typeof value === "string" && statuses.has(value);
|
|
27350
28015
|
}
|
|
27351
28016
|
function isTerminal(status) {
|
|
27352
|
-
return status === "completed" || status === "blocked" || status === "skipped" || status === "failed";
|
|
28017
|
+
return status === "completed" || status === "blocked" || status === "skipped" || status === "failed" || status === "cancelled";
|
|
27353
28018
|
}
|
|
27354
28019
|
function parseLevel(value) {
|
|
27355
28020
|
if (value === "info" || value === "success" || value === "warning" || value === "error") return value;
|
|
@@ -27376,16 +28041,16 @@ function fail(content, metadata) {
|
|
|
27376
28041
|
|
|
27377
28042
|
// src/tools/output.ts
|
|
27378
28043
|
import { mkdir as mkdir10, writeFile as writeFile10 } from "node:fs/promises";
|
|
27379
|
-
import
|
|
28044
|
+
import path25 from "node:path";
|
|
27380
28045
|
var DEFAULT_CAP_BYTES = 32 * 1024;
|
|
27381
28046
|
var HALF_PREVIEW_BYTES = 16 * 1024;
|
|
27382
28047
|
async function capToolOutput(result, options) {
|
|
27383
28048
|
const capBytes = options.capBytes ?? DEFAULT_CAP_BYTES;
|
|
27384
28049
|
const bytes = Buffer.byteLength(result.content, "utf8");
|
|
27385
28050
|
if (bytes <= capBytes) return result;
|
|
27386
|
-
const dir =
|
|
28051
|
+
const dir = path25.join(options.cwd, ".demian", "tmp");
|
|
27387
28052
|
await mkdir10(dir, { recursive: true });
|
|
27388
|
-
const outputPath =
|
|
28053
|
+
const outputPath = path25.join(dir, `output-${safeCallId(options.callId)}.txt`);
|
|
27389
28054
|
await writeFile10(outputPath, result.content, "utf8");
|
|
27390
28055
|
return {
|
|
27391
28056
|
...result,
|
|
@@ -27393,7 +28058,7 @@ async function capToolOutput(result, options) {
|
|
|
27393
28058
|
sliceUtf8(result.content, 0, HALF_PREVIEW_BYTES),
|
|
27394
28059
|
`
|
|
27395
28060
|
|
|
27396
|
-
[Full output saved to ${
|
|
28061
|
+
[Full output saved to ${path25.relative(options.cwd, outputPath)}; showing head and tail because output exceeded ${capBytes} bytes]
|
|
27397
28062
|
|
|
27398
28063
|
`,
|
|
27399
28064
|
sliceUtf8(result.content, Math.max(0, bytes - HALF_PREVIEW_BYTES), bytes)
|
|
@@ -27418,8 +28083,10 @@ function sliceUtf8(text, startByte, endByte) {
|
|
|
27418
28083
|
}
|
|
27419
28084
|
|
|
27420
28085
|
// src/tools/read-file.ts
|
|
28086
|
+
import { createReadStream as createReadStream2 } from "node:fs";
|
|
27421
28087
|
import { open as open2, stat as stat7 } from "node:fs/promises";
|
|
27422
|
-
import
|
|
28088
|
+
import { createInterface } from "node:readline";
|
|
28089
|
+
import path26 from "node:path";
|
|
27423
28090
|
|
|
27424
28091
|
// src/tools/validation.ts
|
|
27425
28092
|
function assertObject(input2, toolName) {
|
|
@@ -27428,7 +28095,7 @@ function assertObject(input2, toolName) {
|
|
|
27428
28095
|
}
|
|
27429
28096
|
return input2;
|
|
27430
28097
|
}
|
|
27431
|
-
function
|
|
28098
|
+
function stringField2(input2, key, options = {}) {
|
|
27432
28099
|
const value = input2[key];
|
|
27433
28100
|
if (typeof value !== "string" || !options.allowEmpty && value.length === 0) {
|
|
27434
28101
|
throw new Error(`${key} must be ${options.allowEmpty ? "a string" : "a non-empty string"}`);
|
|
@@ -27468,12 +28135,19 @@ function positiveInteger(value, fallback, key) {
|
|
|
27468
28135
|
}
|
|
27469
28136
|
|
|
27470
28137
|
// src/tools/read-file.ts
|
|
27471
|
-
var MAX_BYTES = 1024 * 1024;
|
|
27472
28138
|
var SAMPLE_BYTES = 4096;
|
|
27473
28139
|
var DEFAULT_LIMIT = 2e3;
|
|
28140
|
+
var MAX_LIMIT = 2e3;
|
|
27474
28141
|
var readFileTool = {
|
|
27475
28142
|
name: "read_file",
|
|
27476
|
-
description: "Read a text file inside the workspace.
|
|
28143
|
+
description: "Read a text file inside the workspace. Streams large files and returns line-numbered output for the requested range.",
|
|
28144
|
+
metadata: {
|
|
28145
|
+
category: "workspace.read",
|
|
28146
|
+
categoryPath: ["workspace", "read"],
|
|
28147
|
+
sideEffect: "none",
|
|
28148
|
+
defaultDecision: "allow",
|
|
28149
|
+
trust: "builtin"
|
|
28150
|
+
},
|
|
27477
28151
|
inputSchema: {
|
|
27478
28152
|
type: "object",
|
|
27479
28153
|
additionalProperties: false,
|
|
@@ -27481,32 +28155,30 @@ var readFileTool = {
|
|
|
27481
28155
|
properties: {
|
|
27482
28156
|
path: { type: "string", description: "Path to read, relative to cwd or absolute inside cwd." },
|
|
27483
28157
|
offset: { type: "number", description: "1-indexed line offset. Defaults to 1." },
|
|
27484
|
-
limit: { type: "number", description: "Maximum number of lines. Defaults to 2000." }
|
|
28158
|
+
limit: { type: "number", description: "Maximum number of lines. Defaults to 2000 and is capped at 2000." }
|
|
27485
28159
|
}
|
|
27486
28160
|
},
|
|
27487
28161
|
async execute(input2, ctx) {
|
|
27488
28162
|
const object2 = assertObject(input2, "read_file");
|
|
27489
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
28163
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
27490
28164
|
const offset = positiveInteger(optionalNumberField(object2, "offset"), 1, "offset");
|
|
27491
|
-
const
|
|
28165
|
+
const requestedLimit = positiveInteger(optionalNumberField(object2, "limit"), DEFAULT_LIMIT, "limit");
|
|
28166
|
+
const limit = Math.min(requestedLimit, MAX_LIMIT);
|
|
27492
28167
|
const info = await stat7(filePath);
|
|
27493
28168
|
if (info.isDirectory()) throw new Error(`read_file expected a file but got a directory: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
27494
|
-
if (info.size > MAX_BYTES) throw new Error(`File is larger than ${MAX_BYTES} bytes: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
27495
28169
|
const sample = await readBytes(filePath, Math.min(SAMPLE_BYTES, info.size));
|
|
27496
28170
|
if (looksBinary(sample)) throw new Error(`Refusing to read binary file: ${relativeToCwd(ctx.cwd, filePath)}`);
|
|
27497
|
-
const
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
totalLines: lines.length,
|
|
27509
|
-
truncated
|
|
28171
|
+
const slice = await readLineSlice(filePath, offset, limit, ctx.signal);
|
|
28172
|
+
return ok(formatLineSlice(slice), {
|
|
28173
|
+
path: path26.relative(ctx.cwd, filePath),
|
|
28174
|
+
offset,
|
|
28175
|
+
limit,
|
|
28176
|
+
requestedLimit,
|
|
28177
|
+
lines: slice.lines.length,
|
|
28178
|
+
totalLines: slice.totalLines,
|
|
28179
|
+
truncated: slice.truncated,
|
|
28180
|
+
nextOffset: slice.nextOffset,
|
|
28181
|
+
fileSizeBytes: info.size
|
|
27510
28182
|
});
|
|
27511
28183
|
}
|
|
27512
28184
|
};
|
|
@@ -27520,15 +28192,50 @@ async function readBytes(filePath, length) {
|
|
|
27520
28192
|
await handle.close();
|
|
27521
28193
|
}
|
|
27522
28194
|
}
|
|
27523
|
-
async function
|
|
27524
|
-
|
|
28195
|
+
async function readLineSlice(filePath, offset, limit, signal) {
|
|
28196
|
+
if (signal.aborted) throw new Error("read_file was aborted");
|
|
28197
|
+
const stream = createReadStream2(filePath, { encoding: "utf8" });
|
|
28198
|
+
const reader = createInterface({ input: stream, crlfDelay: Infinity });
|
|
28199
|
+
const abort = () => stream.destroy(new Error("read_file was aborted"));
|
|
28200
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
28201
|
+
const lines = [];
|
|
28202
|
+
let lineNumber = 0;
|
|
28203
|
+
let truncated = false;
|
|
27525
28204
|
try {
|
|
27526
|
-
const
|
|
27527
|
-
|
|
27528
|
-
|
|
28205
|
+
for await (const line of reader) {
|
|
28206
|
+
lineNumber++;
|
|
28207
|
+
if (lineNumber < offset) continue;
|
|
28208
|
+
if (lines.length < limit) {
|
|
28209
|
+
lines.push(line);
|
|
28210
|
+
continue;
|
|
28211
|
+
}
|
|
28212
|
+
truncated = true;
|
|
28213
|
+
break;
|
|
28214
|
+
}
|
|
27529
28215
|
} finally {
|
|
27530
|
-
|
|
28216
|
+
signal.removeEventListener("abort", abort);
|
|
28217
|
+
reader.close();
|
|
28218
|
+
stream.destroy();
|
|
27531
28219
|
}
|
|
28220
|
+
return {
|
|
28221
|
+
lines,
|
|
28222
|
+
offset,
|
|
28223
|
+
totalLines: truncated ? void 0 : lineNumber,
|
|
28224
|
+
truncated,
|
|
28225
|
+
nextOffset: truncated ? offset + lines.length : void 0
|
|
28226
|
+
};
|
|
28227
|
+
}
|
|
28228
|
+
function formatLineSlice(slice) {
|
|
28229
|
+
if (slice.lines.length === 0) {
|
|
28230
|
+
if (slice.totalLines !== void 0) return `... (no lines at offset ${slice.offset}; file has ${slice.totalLines} lines)`;
|
|
28231
|
+
return `... (no lines at offset ${slice.offset})`;
|
|
28232
|
+
}
|
|
28233
|
+
const lastLine = slice.offset + slice.lines.length - 1;
|
|
28234
|
+
const width = String(lastLine).length;
|
|
28235
|
+
const content = slice.lines.map((line, index) => `${String(slice.offset + index).padStart(width, " ")} | ${line}`).join("\n");
|
|
28236
|
+
if (!slice.truncated || slice.nextOffset === void 0) return content;
|
|
28237
|
+
return `${content}
|
|
28238
|
+
... (more lines available; continue with offset ${slice.nextOffset})`;
|
|
27532
28239
|
}
|
|
27533
28240
|
function looksBinary(buffer) {
|
|
27534
28241
|
if (buffer.length === 0) return false;
|
|
@@ -27542,10 +28249,17 @@ function looksBinary(buffer) {
|
|
|
27542
28249
|
|
|
27543
28250
|
// src/tools/write-file.ts
|
|
27544
28251
|
import { mkdir as mkdir11, readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
|
|
27545
|
-
import
|
|
28252
|
+
import path27 from "node:path";
|
|
27546
28253
|
var writeFileTool = {
|
|
27547
28254
|
name: "write_file",
|
|
27548
28255
|
description: "Create or replace a text file inside the workspace.",
|
|
28256
|
+
metadata: {
|
|
28257
|
+
category: "workspace.write",
|
|
28258
|
+
categoryPath: ["workspace", "write"],
|
|
28259
|
+
sideEffect: "workspace",
|
|
28260
|
+
defaultDecision: "ask",
|
|
28261
|
+
trust: "builtin"
|
|
28262
|
+
},
|
|
27549
28263
|
inputSchema: {
|
|
27550
28264
|
type: "object",
|
|
27551
28265
|
additionalProperties: false,
|
|
@@ -27557,10 +28271,10 @@ var writeFileTool = {
|
|
|
27557
28271
|
},
|
|
27558
28272
|
async execute(input2, ctx) {
|
|
27559
28273
|
const object2 = assertObject(input2, "write_file");
|
|
27560
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
27561
|
-
const content =
|
|
28274
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
28275
|
+
const content = stringField2(object2, "content", { allowEmpty: true });
|
|
27562
28276
|
const before = await readOptionalTextFile(filePath);
|
|
27563
|
-
await mkdir11(
|
|
28277
|
+
await mkdir11(path27.dirname(filePath), { recursive: true });
|
|
27564
28278
|
await writeFile11(filePath, content, "utf8");
|
|
27565
28279
|
const relative = relativeToCwd(ctx.cwd, filePath);
|
|
27566
28280
|
const diff = createTextDiff(before, content, relative);
|
|
@@ -27589,6 +28303,13 @@ import { readFile as readFile14, writeFile as writeFile12 } from "node:fs/promis
|
|
|
27589
28303
|
var editFileTool = {
|
|
27590
28304
|
name: "edit_file",
|
|
27591
28305
|
description: "Replace an exact string in a text file inside the workspace.",
|
|
28306
|
+
metadata: {
|
|
28307
|
+
category: "workspace.write",
|
|
28308
|
+
categoryPath: ["workspace", "write"],
|
|
28309
|
+
sideEffect: "workspace",
|
|
28310
|
+
defaultDecision: "ask",
|
|
28311
|
+
trust: "builtin"
|
|
28312
|
+
},
|
|
27592
28313
|
inputSchema: {
|
|
27593
28314
|
type: "object",
|
|
27594
28315
|
additionalProperties: false,
|
|
@@ -27602,9 +28323,9 @@ var editFileTool = {
|
|
|
27602
28323
|
},
|
|
27603
28324
|
async execute(input2, ctx) {
|
|
27604
28325
|
const object2 = assertObject(input2, "edit_file");
|
|
27605
|
-
const filePath = resolveInsideCwd(ctx.cwd,
|
|
27606
|
-
const oldString =
|
|
27607
|
-
const newString =
|
|
28326
|
+
const filePath = resolveInsideCwd(ctx.cwd, stringField2(object2, "path"));
|
|
28327
|
+
const oldString = stringField2(object2, "oldString");
|
|
28328
|
+
const newString = stringField2(object2, "newString", { allowEmpty: true });
|
|
27608
28329
|
const replaceAll = optionalBooleanField(object2, "replaceAll") ?? false;
|
|
27609
28330
|
if (oldString === newString) throw new Error("oldString and newString must be different");
|
|
27610
28331
|
const before = await readFile14(filePath, "utf8");
|
|
@@ -27636,7 +28357,7 @@ function countMatches(text, needle) {
|
|
|
27636
28357
|
|
|
27637
28358
|
// src/tools/bash.ts
|
|
27638
28359
|
import { spawn as spawn6 } from "node:child_process";
|
|
27639
|
-
import
|
|
28360
|
+
import path29 from "node:path";
|
|
27640
28361
|
|
|
27641
28362
|
// src/sandbox/env-only.ts
|
|
27642
28363
|
function buildEnvOnlyLaunch(command, config) {
|
|
@@ -27696,7 +28417,7 @@ function bwrapPath() {
|
|
|
27696
28417
|
|
|
27697
28418
|
// src/sandbox/macos.ts
|
|
27698
28419
|
import { existsSync as existsSync4 } from "node:fs";
|
|
27699
|
-
import
|
|
28420
|
+
import path28 from "node:path";
|
|
27700
28421
|
function canUseMacOSSandbox() {
|
|
27701
28422
|
return process.platform === "darwin" && existsSync4("/usr/bin/sandbox-exec");
|
|
27702
28423
|
}
|
|
@@ -27722,7 +28443,7 @@ function macosProfile(cwd, config) {
|
|
|
27722
28443
|
}
|
|
27723
28444
|
if (mode === "workspace-write") {
|
|
27724
28445
|
lines.push("(deny file-write*)");
|
|
27725
|
-
lines.push(`(allow file-write* (subpath "${escapeProfilePath(
|
|
28446
|
+
lines.push(`(allow file-write* (subpath "${escapeProfilePath(path28.resolve(cwd))}"))`);
|
|
27726
28447
|
lines.push('(allow file-write* (subpath "/tmp"))');
|
|
27727
28448
|
lines.push('(allow file-write* (subpath "/private/tmp"))');
|
|
27728
28449
|
lines.push('(allow file-write* (subpath "/private/var/folders"))');
|
|
@@ -27752,6 +28473,13 @@ var MAX_TIMEOUT_MS = 6e5;
|
|
|
27752
28473
|
var bashTool = {
|
|
27753
28474
|
name: "bash",
|
|
27754
28475
|
description: "Run a shell command inside the workspace.",
|
|
28476
|
+
metadata: {
|
|
28477
|
+
category: "process.shell",
|
|
28478
|
+
categoryPath: ["process", "shell"],
|
|
28479
|
+
sideEffect: "process",
|
|
28480
|
+
defaultDecision: "ask",
|
|
28481
|
+
trust: "builtin"
|
|
28482
|
+
},
|
|
27755
28483
|
inputSchema: {
|
|
27756
28484
|
type: "object",
|
|
27757
28485
|
additionalProperties: false,
|
|
@@ -27764,7 +28492,7 @@ var bashTool = {
|
|
|
27764
28492
|
},
|
|
27765
28493
|
async execute(input2, ctx) {
|
|
27766
28494
|
const object2 = assertObject(input2, "bash");
|
|
27767
|
-
const command =
|
|
28495
|
+
const command = stringField2(object2, "command");
|
|
27768
28496
|
const workdirInput = optionalStringField(object2, "workdir");
|
|
27769
28497
|
const workdir = workdirInput ? resolveInsideCwd(ctx.cwd, workdirInput) : ctx.cwd;
|
|
27770
28498
|
const requestedTimeout = optionalNumberField(object2, "timeoutMs");
|
|
@@ -27784,7 +28512,7 @@ ${result.stderr}` : ""
|
|
|
27784
28512
|
].filter(Boolean).join("\n");
|
|
27785
28513
|
return ok(content, {
|
|
27786
28514
|
command,
|
|
27787
|
-
workdir:
|
|
28515
|
+
workdir: path29.relative(ctx.cwd, workdir) || ".",
|
|
27788
28516
|
exitCode: result.exitCode,
|
|
27789
28517
|
signal: result.signal,
|
|
27790
28518
|
timedOut: result.timedOut,
|
|
@@ -27837,11 +28565,18 @@ function runCommand(command, cwd, timeoutMs, signal, sandbox = { mode: "workspac
|
|
|
27837
28565
|
// src/tools/grep.ts
|
|
27838
28566
|
import { spawn as spawn7 } from "node:child_process";
|
|
27839
28567
|
import { readdir as readdir2, readFile as readFile15, stat as stat8 } from "node:fs/promises";
|
|
27840
|
-
import
|
|
28568
|
+
import path30 from "node:path";
|
|
27841
28569
|
var MAX_MATCHES = 200;
|
|
27842
28570
|
var grepTool = {
|
|
27843
28571
|
name: "grep",
|
|
27844
28572
|
description: "Search text files inside the workspace. Uses rg when available.",
|
|
28573
|
+
metadata: {
|
|
28574
|
+
category: "workspace.search",
|
|
28575
|
+
categoryPath: ["workspace", "search"],
|
|
28576
|
+
sideEffect: "none",
|
|
28577
|
+
defaultDecision: "allow",
|
|
28578
|
+
trust: "builtin"
|
|
28579
|
+
},
|
|
27845
28580
|
inputSchema: {
|
|
27846
28581
|
type: "object",
|
|
27847
28582
|
additionalProperties: false,
|
|
@@ -27854,7 +28589,7 @@ var grepTool = {
|
|
|
27854
28589
|
},
|
|
27855
28590
|
async execute(input2, ctx) {
|
|
27856
28591
|
const object2 = assertObject(input2, "grep");
|
|
27857
|
-
const pattern =
|
|
28592
|
+
const pattern = stringField2(object2, "pattern");
|
|
27858
28593
|
const baseInput = optionalStringField(object2, "path");
|
|
27859
28594
|
const glob = optionalStringField(object2, "glob");
|
|
27860
28595
|
const base = baseInput ? resolveInsideCwd(ctx.cwd, baseInput) : ctx.cwd;
|
|
@@ -27912,7 +28647,7 @@ function runRg(cwd, base, pattern, glob, signal) {
|
|
|
27912
28647
|
}
|
|
27913
28648
|
function rewriteRgPath(cwd, line) {
|
|
27914
28649
|
const [file, rest] = splitFirst(line, ":");
|
|
27915
|
-
if (!
|
|
28650
|
+
if (!path30.isAbsolute(file)) return line;
|
|
27916
28651
|
return `${relativeToCwd(cwd, file)}:${rest}`;
|
|
27917
28652
|
}
|
|
27918
28653
|
function splitFirst(value, delimiter) {
|
|
@@ -27929,7 +28664,7 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
27929
28664
|
if (isIgnoredPath(relative)) return;
|
|
27930
28665
|
const info = await stat8(item);
|
|
27931
28666
|
if (info.isDirectory()) {
|
|
27932
|
-
for (const entry of await readdir2(item)) await walk2(
|
|
28667
|
+
for (const entry of await readdir2(item)) await walk2(path30.join(item, entry));
|
|
27933
28668
|
return;
|
|
27934
28669
|
}
|
|
27935
28670
|
if (glob && !matchGlob(glob, relative)) return;
|
|
@@ -27948,11 +28683,18 @@ async function fallbackSearch(cwd, base, pattern, glob) {
|
|
|
27948
28683
|
|
|
27949
28684
|
// src/tools/glob.ts
|
|
27950
28685
|
import { readdir as readdir3, stat as stat9 } from "node:fs/promises";
|
|
27951
|
-
import
|
|
28686
|
+
import path31 from "node:path";
|
|
27952
28687
|
var MAX_PATHS = 1e3;
|
|
27953
28688
|
var globTool = {
|
|
27954
28689
|
name: "glob",
|
|
27955
28690
|
description: "Find paths inside the workspace using a glob pattern.",
|
|
28691
|
+
metadata: {
|
|
28692
|
+
category: "workspace.search",
|
|
28693
|
+
categoryPath: ["workspace", "search"],
|
|
28694
|
+
sideEffect: "none",
|
|
28695
|
+
defaultDecision: "allow",
|
|
28696
|
+
trust: "builtin"
|
|
28697
|
+
},
|
|
27956
28698
|
inputSchema: {
|
|
27957
28699
|
type: "object",
|
|
27958
28700
|
additionalProperties: false,
|
|
@@ -27964,7 +28706,7 @@ var globTool = {
|
|
|
27964
28706
|
},
|
|
27965
28707
|
async execute(input2, ctx) {
|
|
27966
28708
|
const object2 = assertObject(input2, "glob");
|
|
27967
|
-
const pattern =
|
|
28709
|
+
const pattern = stringField2(object2, "pattern");
|
|
27968
28710
|
const base = optionalStringField(object2, "path");
|
|
27969
28711
|
const root = base ? resolveInsideCwd(ctx.cwd, base) : ctx.cwd;
|
|
27970
28712
|
const found = await collectPaths(ctx.cwd, root, pattern);
|
|
@@ -27980,7 +28722,7 @@ async function collectPaths(cwd, root, pattern) {
|
|
|
27980
28722
|
async function walk2(dir) {
|
|
27981
28723
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
27982
28724
|
for (const entry of entries) {
|
|
27983
|
-
const absolute =
|
|
28725
|
+
const absolute = path31.join(dir, entry.name);
|
|
27984
28726
|
const relative = relativeToCwd(cwd, absolute);
|
|
27985
28727
|
if (isIgnoredPath(relative)) continue;
|
|
27986
28728
|
const info = await stat9(absolute);
|
|
@@ -27997,6 +28739,8 @@ var updatePlanTool = {
|
|
|
27997
28739
|
name: "update_plan",
|
|
27998
28740
|
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.",
|
|
27999
28741
|
metadata: {
|
|
28742
|
+
category: "planning.progress",
|
|
28743
|
+
categoryPath: ["planning", "progress"],
|
|
28000
28744
|
sideEffect: "none",
|
|
28001
28745
|
defaultDecision: "allow",
|
|
28002
28746
|
trust: "builtin"
|
|
@@ -28036,7 +28780,7 @@ function stepProperties() {
|
|
|
28036
28780
|
return {
|
|
28037
28781
|
id: { type: "string", maxLength: 64 },
|
|
28038
28782
|
title: { type: "string" },
|
|
28039
|
-
status: { type: "string", enum: ["pending", "in_progress", "completed", "blocked", "skipped", "failed"] },
|
|
28783
|
+
status: { type: "string", enum: ["pending", "in_progress", "completed", "blocked", "skipped", "failed", "cancelled"] },
|
|
28040
28784
|
detail: { type: "string" },
|
|
28041
28785
|
agent: { type: "string" }
|
|
28042
28786
|
};
|
|
@@ -28045,8 +28789,10 @@ function stepProperties() {
|
|
|
28045
28789
|
// src/tools/report-progress.ts
|
|
28046
28790
|
var reportProgressTool = {
|
|
28047
28791
|
name: "report_progress",
|
|
28048
|
-
description: "Report a
|
|
28792
|
+
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.",
|
|
28049
28793
|
metadata: {
|
|
28794
|
+
category: "planning.progress",
|
|
28795
|
+
categoryPath: ["planning", "progress"],
|
|
28050
28796
|
sideEffect: "none",
|
|
28051
28797
|
defaultDecision: "allow",
|
|
28052
28798
|
trust: "builtin"
|
|
@@ -28077,6 +28823,13 @@ function createWebSearchTool(config = defaultConfig.webSearch, fetcher = fetch)
|
|
|
28077
28823
|
return {
|
|
28078
28824
|
name: "web_search",
|
|
28079
28825
|
description: "Search the web using the configured Brave, Tavily, or Exa web search provider.",
|
|
28826
|
+
metadata: {
|
|
28827
|
+
category: "research.web",
|
|
28828
|
+
categoryPath: ["research", "web"],
|
|
28829
|
+
sideEffect: "network",
|
|
28830
|
+
defaultDecision: "ask",
|
|
28831
|
+
trust: "builtin"
|
|
28832
|
+
},
|
|
28080
28833
|
inputSchema: {
|
|
28081
28834
|
type: "object",
|
|
28082
28835
|
additionalProperties: false,
|
|
@@ -28096,7 +28849,7 @@ function createWebSearchTool(config = defaultConfig.webSearch, fetcher = fetch)
|
|
|
28096
28849
|
},
|
|
28097
28850
|
async execute(input2, ctx) {
|
|
28098
28851
|
const object2 = assertObject(input2, "web_search");
|
|
28099
|
-
const query =
|
|
28852
|
+
const query = stringField2(object2, "query");
|
|
28100
28853
|
const provider = providerField(object2, config.defaultProvider);
|
|
28101
28854
|
const maxResults = optionalNumberField(object2, "maxResults");
|
|
28102
28855
|
if (provider === "brave") return runBraveSearch(config.providers.brave, { query, maxResults, fetcher, signal: ctx.signal });
|
|
@@ -28307,6 +29060,8 @@ var getGoalTool = {
|
|
|
28307
29060
|
return ctx.goals.getGoal();
|
|
28308
29061
|
},
|
|
28309
29062
|
metadata: {
|
|
29063
|
+
category: "goal.state",
|
|
29064
|
+
categoryPath: ["goal", "state"],
|
|
28310
29065
|
sideEffect: "none",
|
|
28311
29066
|
defaultDecision: "allow",
|
|
28312
29067
|
trust: "builtin"
|
|
@@ -28328,6 +29083,8 @@ var createGoalTool = {
|
|
|
28328
29083
|
return ctx.goals.createGoal(input2);
|
|
28329
29084
|
},
|
|
28330
29085
|
metadata: {
|
|
29086
|
+
category: "goal.state",
|
|
29087
|
+
categoryPath: ["goal", "state"],
|
|
28331
29088
|
sideEffect: "workspace",
|
|
28332
29089
|
defaultDecision: "allow",
|
|
28333
29090
|
trust: "builtin"
|
|
@@ -28349,6 +29106,8 @@ var updateGoalTool = {
|
|
|
28349
29106
|
return ctx.goals.updateGoal(input2);
|
|
28350
29107
|
},
|
|
28351
29108
|
metadata: {
|
|
29109
|
+
category: "goal.state",
|
|
29110
|
+
categoryPath: ["goal", "state"],
|
|
28352
29111
|
sideEffect: "workspace",
|
|
28353
29112
|
defaultDecision: "allow",
|
|
28354
29113
|
trust: "builtin"
|
|
@@ -28443,6 +29202,9 @@ function validateToolName(name) {
|
|
|
28443
29202
|
}
|
|
28444
29203
|
|
|
28445
29204
|
// src/session.ts
|
|
29205
|
+
function relaxedToolUseRoundLimit(maxTurns) {
|
|
29206
|
+
return Math.max((maxTurns ?? 25) * 20, 200);
|
|
29207
|
+
}
|
|
28446
29208
|
var SessionRunner = class {
|
|
28447
29209
|
sessionId;
|
|
28448
29210
|
events;
|
|
@@ -28500,9 +29262,28 @@ var SessionRunner = class {
|
|
|
28500
29262
|
});
|
|
28501
29263
|
const messages = [{ role: "system", content: buildSystemPrompt(options.agent, envInfo) }, ...options.history ?? [], { role: "user", content: userContent }];
|
|
28502
29264
|
this.emit({ type: "user.message", sessionId: this.sessionId, text: options.displayPrompt ?? options.prompt, ts: now(), ...this.eventContext() });
|
|
29265
|
+
this.reportSessionKickoff();
|
|
28503
29266
|
await this.#hooks.dispatch("UserPromptSubmit", this.hookBase());
|
|
28504
29267
|
const maxTurns = options.maxTurns ?? 25;
|
|
28505
|
-
|
|
29268
|
+
const countToolUseTurns = options.countToolUseTurns === true;
|
|
29269
|
+
const maxToolUseRounds = options.maxToolUseRounds ?? relaxedToolUseRoundLimit(maxTurns);
|
|
29270
|
+
let countedTurns = 0;
|
|
29271
|
+
let toolUseRounds = 0;
|
|
29272
|
+
let countedTaskScope = this.#progress.activeStepId() ?? "__session__";
|
|
29273
|
+
const refreshTaskScope = () => {
|
|
29274
|
+
const taskScope = this.#progress.activeStepId() ?? "__session__";
|
|
29275
|
+
if (taskScope === countedTaskScope) return;
|
|
29276
|
+
countedTaskScope = taskScope;
|
|
29277
|
+
countedTurns = 0;
|
|
29278
|
+
};
|
|
29279
|
+
for (; ; ) {
|
|
29280
|
+
if (countToolUseTurns) refreshTaskScope();
|
|
29281
|
+
if (countToolUseTurns && countedTurns >= maxTurns) {
|
|
29282
|
+
return this.end(messages, `Stopped after reaching maxTurns (${maxTurns}).`, "max_turns");
|
|
29283
|
+
}
|
|
29284
|
+
if (!countToolUseTurns && toolUseRounds >= maxToolUseRounds) {
|
|
29285
|
+
return this.end(messages, `Stopped after reaching tool-use safety limit (${maxToolUseRounds}) before producing a final answer.`, "tool_loop_limit");
|
|
29286
|
+
}
|
|
28506
29287
|
await this.#hooks.dispatch("BeforeModelRequest", this.hookBase());
|
|
28507
29288
|
const tools = this.#tools.list();
|
|
28508
29289
|
const compiled = compileMessagesForContext(messages, tools, options.context);
|
|
@@ -28580,12 +29361,17 @@ var SessionRunner = class {
|
|
|
28580
29361
|
if (toolCalls.length === 0) {
|
|
28581
29362
|
return this.end(messages, assistant.content ?? "", "end_turn");
|
|
28582
29363
|
}
|
|
29364
|
+
if (countToolUseTurns) {
|
|
29365
|
+
refreshTaskScope();
|
|
29366
|
+
countedTurns++;
|
|
29367
|
+
} else {
|
|
29368
|
+
toolUseRounds++;
|
|
29369
|
+
}
|
|
28583
29370
|
for (const call of toolCalls) {
|
|
28584
29371
|
const toolMessage = await this.runToolCall(call);
|
|
28585
29372
|
messages.push(toolMessage);
|
|
28586
29373
|
}
|
|
28587
29374
|
}
|
|
28588
|
-
return this.end(messages, `Stopped after reaching maxTurns (${maxTurns}).`, "max_turns");
|
|
28589
29375
|
}
|
|
28590
29376
|
async applyAfterModelHooks(message) {
|
|
28591
29377
|
const results = await this.#hooks.dispatch("AfterModelResponse", {
|
|
@@ -28648,15 +29434,7 @@ var SessionRunner = class {
|
|
|
28648
29434
|
return toToolMessage(call, result);
|
|
28649
29435
|
}
|
|
28650
29436
|
if (evaluation.decision === "ask") {
|
|
28651
|
-
|
|
28652
|
-
this.emitGoalToolEvent(call, "permission");
|
|
28653
|
-
await this.#hooks.dispatch("PermissionRequest", {
|
|
28654
|
-
...this.hookBase(),
|
|
28655
|
-
callId: call.id,
|
|
28656
|
-
toolName: call.name,
|
|
28657
|
-
toolInput: input2
|
|
28658
|
-
});
|
|
28659
|
-
const answer = await (this.#options.permissionPrompt ?? ((req) => askPermission(req, { yes: this.#options.yes })))({
|
|
29437
|
+
const permissionRequest = {
|
|
28660
29438
|
tool: call.name,
|
|
28661
29439
|
input: input2,
|
|
28662
29440
|
actor: this.#options.agent.name,
|
|
@@ -28664,16 +29442,33 @@ var SessionRunner = class {
|
|
|
28664
29442
|
targetName: call.name,
|
|
28665
29443
|
proposedDecision: "ask",
|
|
28666
29444
|
reason: evaluation.reason
|
|
28667
|
-
}
|
|
28668
|
-
|
|
28669
|
-
|
|
29445
|
+
};
|
|
29446
|
+
const automatic = await this.#options.autoPermission?.(permissionRequest);
|
|
29447
|
+
if (automatic?.decision === "deny") {
|
|
29448
|
+
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() });
|
|
28670
29449
|
this.emitGoalToolEvent(call, "denied");
|
|
28671
|
-
return toToolMessage(call, toolFailure(`Permission denied${
|
|
28672
|
-
}
|
|
28673
|
-
if (
|
|
28674
|
-
this.#
|
|
28675
|
-
|
|
28676
|
-
this.
|
|
29450
|
+
return toToolMessage(call, toolFailure(`Permission denied${automatic.reason ? `: ${automatic.reason}` : ""}`));
|
|
29451
|
+
}
|
|
29452
|
+
if (automatic?.decision !== "allow") {
|
|
29453
|
+
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() });
|
|
29454
|
+
this.emitGoalToolEvent(call, "permission");
|
|
29455
|
+
await this.#hooks.dispatch("PermissionRequest", {
|
|
29456
|
+
...this.hookBase(),
|
|
29457
|
+
callId: call.id,
|
|
29458
|
+
toolName: call.name,
|
|
29459
|
+
toolInput: input2
|
|
29460
|
+
});
|
|
29461
|
+
const answer = await (this.#options.permissionPrompt ?? ((req) => askPermission(req, { yes: this.#options.yes })))(permissionRequest);
|
|
29462
|
+
if (answer.decision !== "allow") {
|
|
29463
|
+
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() });
|
|
29464
|
+
this.emitGoalToolEvent(call, "denied");
|
|
29465
|
+
return toToolMessage(call, toolFailure(`Permission denied${answer.reason ? `: ${answer.reason}` : ""}`));
|
|
29466
|
+
}
|
|
29467
|
+
if (answer.always) {
|
|
29468
|
+
this.#grants.add(evaluation.grantKey);
|
|
29469
|
+
await this.#persistentStore?.add(this.#options.cwd, evaluation.grantKey);
|
|
29470
|
+
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() });
|
|
29471
|
+
}
|
|
28677
29472
|
}
|
|
28678
29473
|
}
|
|
28679
29474
|
this.emit({ type: "tool.started", sessionId: this.sessionId, callId: call.id, name: call.name, agent: this.#options.agent.name, ts: now(), ...this.eventContext() });
|
|
@@ -28688,7 +29483,7 @@ var SessionRunner = class {
|
|
|
28688
29483
|
cwd: this.#options.cwd,
|
|
28689
29484
|
signal: this.#controller.signal,
|
|
28690
29485
|
emit: (event) => this.emit(event),
|
|
28691
|
-
ask: (req) => (this.#options.permissionPrompt ?? ((request) => askPermission(request, { yes: this.#options.yes })))(req),
|
|
29486
|
+
ask: async (req) => await this.#options.autoPermission?.(req) ?? (this.#options.permissionPrompt ?? ((request) => askPermission(request, { yes: this.#options.yes })))(req),
|
|
28692
29487
|
dryRun: this.#options.dryRun,
|
|
28693
29488
|
sandbox: this.#options.sandbox,
|
|
28694
29489
|
rootSessionId: this.#options.rootSessionId,
|
|
@@ -28728,15 +29523,18 @@ var SessionRunner = class {
|
|
|
28728
29523
|
}
|
|
28729
29524
|
}
|
|
28730
29525
|
async end(messages, finalAnswer, reason) {
|
|
29526
|
+
const answer = reason === "end_turn" ? finalAnswer : terminalFinalAnswer(finalAnswer, reason, this.#progress.snapshot());
|
|
29527
|
+
const returnedMessages = reason === "end_turn" ? messages : [...messages, { role: "assistant", content: answer }];
|
|
28731
29528
|
await this.#hooks.dispatch("Stop", this.hookBase());
|
|
28732
29529
|
await this.#hooks.dispatch("SessionEnd", this.hookBase());
|
|
28733
29530
|
this.completeGoalWorkGroup();
|
|
28734
29531
|
this.emit({ type: "session.ended", sessionId: this.sessionId, reason, ts: now(), ...this.eventContext() });
|
|
29532
|
+
if (reason !== "end_turn" && answer.trim()) this.emit({ type: "model.text", sessionId: this.sessionId, text: answer, ts: now(), ...this.eventContext() });
|
|
28735
29533
|
await this.#transcript.flush();
|
|
28736
29534
|
return {
|
|
28737
29535
|
sessionId: this.sessionId,
|
|
28738
|
-
finalAnswer,
|
|
28739
|
-
messages,
|
|
29536
|
+
finalAnswer: answer,
|
|
29537
|
+
messages: returnedMessages,
|
|
28740
29538
|
endReason: reason,
|
|
28741
29539
|
usage: { ...this.#usage }
|
|
28742
29540
|
};
|
|
@@ -28844,6 +29642,24 @@ var SessionRunner = class {
|
|
|
28844
29642
|
activeStepId: () => this.#progress.activeStepId()
|
|
28845
29643
|
};
|
|
28846
29644
|
}
|
|
29645
|
+
reportSessionKickoff() {
|
|
29646
|
+
if (this.#options.parentInvocationId) return;
|
|
29647
|
+
const message = sessionKickoffMessage(this.#options.displayPrompt ?? this.#options.prompt);
|
|
29648
|
+
if (!message) return;
|
|
29649
|
+
const result = this.#progress.report(
|
|
29650
|
+
{ message, level: "info", showInTimeline: true },
|
|
29651
|
+
{
|
|
29652
|
+
sessionId: this.sessionId,
|
|
29653
|
+
agent: this.#options.agent.name,
|
|
29654
|
+
rootSessionId: this.#options.rootSessionId,
|
|
29655
|
+
agentSessionId: this.#options.agentSessionId,
|
|
29656
|
+
invocationId: this.#options.invocationId,
|
|
29657
|
+
parentInvocationId: this.#options.parentInvocationId,
|
|
29658
|
+
agentPath: this.#options.agentPath
|
|
29659
|
+
}
|
|
29660
|
+
);
|
|
29661
|
+
for (const event of result.events) this.emit(event);
|
|
29662
|
+
}
|
|
28847
29663
|
eventContext() {
|
|
28848
29664
|
return {
|
|
28849
29665
|
rootSessionId: this.#options.rootSessionId,
|
|
@@ -28927,6 +29743,134 @@ function normalizeDiffContent(value) {
|
|
|
28927
29743
|
after: typeof after === "string" ? after : void 0
|
|
28928
29744
|
};
|
|
28929
29745
|
}
|
|
29746
|
+
function sessionKickoffMessage(prompt) {
|
|
29747
|
+
const normalized = prompt.replace(/^\/\w+\s*/, "").trim();
|
|
29748
|
+
if (isCasualPrompt(normalized) || isSimpleQuestionPrompt(normalized)) return void 0;
|
|
29749
|
+
const isKorean = /[가-힣]/.test(normalized);
|
|
29750
|
+
if (isKorean) {
|
|
29751
|
+
if (/(수정|고쳐|개선|변경|추가|삭제|구현|반영|업데이트|만들|작성|전환|마이그레이션)/.test(normalized)) {
|
|
29752
|
+
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.";
|
|
29753
|
+
}
|
|
29754
|
+
if (/(테스트|검증|확인|재현|실행|빌드|publish|배포)/i.test(normalized)) {
|
|
29755
|
+
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.";
|
|
29756
|
+
}
|
|
29757
|
+
if (/(설명|분석|검토|원인|왜|찾아|알려|파악)/.test(normalized)) {
|
|
29758
|
+
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.";
|
|
29759
|
+
}
|
|
29760
|
+
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.";
|
|
29761
|
+
}
|
|
29762
|
+
if (/\b(fix|change|update|add|delete|remove|implement|write|create|migrate|refactor)\b/i.test(normalized)) {
|
|
29763
|
+
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.";
|
|
29764
|
+
}
|
|
29765
|
+
if (/\b(test|verify|check|reproduce|run|build|publish|deploy)\b/i.test(normalized)) {
|
|
29766
|
+
return "I\u2019ll first check the current state and reproduction path, then explain what the result means before moving to the next step.";
|
|
29767
|
+
}
|
|
29768
|
+
if (/\b(explain|analyze|review|why|find|investigate|inspect)\b/i.test(normalized)) {
|
|
29769
|
+
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.";
|
|
29770
|
+
}
|
|
29771
|
+
return "I\u2019ll first clarify the shape of the request, then work through the relevant context and share useful updates as I go.";
|
|
29772
|
+
}
|
|
29773
|
+
function isCasualPrompt(prompt) {
|
|
29774
|
+
const compact = prompt.toLowerCase().replace(/[\s.!?。!?~…"'`]+/g, "");
|
|
29775
|
+
if (!compact) return true;
|
|
29776
|
+
const casual = /* @__PURE__ */ new Set([
|
|
29777
|
+
"hi",
|
|
29778
|
+
"hello",
|
|
29779
|
+
"hey",
|
|
29780
|
+
"yo",
|
|
29781
|
+
"ok",
|
|
29782
|
+
"okay",
|
|
29783
|
+
"thanks",
|
|
29784
|
+
"thankyou",
|
|
29785
|
+
"\uC548\uB155",
|
|
29786
|
+
"\uC548\uB155\uD558\uC138\uC694",
|
|
29787
|
+
"\uD558\uC774",
|
|
29788
|
+
"\uD5EC\uB85C",
|
|
29789
|
+
"\uACE0\uB9C8\uC6CC",
|
|
29790
|
+
"\uACE0\uB9D9\uC2B5\uB2C8\uB2E4",
|
|
29791
|
+
"\uAC10\uC0AC\uD569\uB2C8\uB2E4",
|
|
29792
|
+
"\uAC10\uC0AC",
|
|
29793
|
+
"\uC88B\uC544",
|
|
29794
|
+
"\uB124",
|
|
29795
|
+
"\uC751",
|
|
29796
|
+
"\u3147\u314B"
|
|
29797
|
+
]);
|
|
29798
|
+
return casual.has(compact) || compact.length <= 12 && /^(hi|hello|hey|안녕|안녕하세요|하이)/.test(compact);
|
|
29799
|
+
}
|
|
29800
|
+
function isSimpleQuestionPrompt(prompt) {
|
|
29801
|
+
const trimmed = prompt.trim();
|
|
29802
|
+
if (trimmed.length > 80) return false;
|
|
29803
|
+
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(
|
|
29804
|
+
trimmed
|
|
29805
|
+
);
|
|
29806
|
+
if (hasTaskVerb) return false;
|
|
29807
|
+
return /[??]$/.test(trimmed) || /(뭐야|무엇|누구|언제|어디|어떻게|왜|맞아)$/.test(trimmed);
|
|
29808
|
+
}
|
|
29809
|
+
function terminalFinalAnswer(finalAnswer, reason, progress) {
|
|
29810
|
+
const lines = ["\uC791\uC5C5\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", "", `\uC885\uB8CC \uC774\uC720: ${terminalReasonText(reason)}`];
|
|
29811
|
+
const runtimeMessage = finalAnswer.trim();
|
|
29812
|
+
if (runtimeMessage && runtimeMessage !== terminalReasonText(reason)) lines.push(`\uB9C8\uC9C0\uB9C9 \uB7F0\uD0C0\uC784 \uBA54\uC2DC\uC9C0: ${runtimeMessage}`);
|
|
29813
|
+
lines.push("", "\uD604\uC7AC\uAE4C\uC9C0 \uC9C4\uD589\uD55C \uB0B4\uC6A9\uC740 \uC544\uB798\uC640 \uAC19\uC2B5\uB2C8\uB2E4.");
|
|
29814
|
+
const plan = progress.plan;
|
|
29815
|
+
if (plan) {
|
|
29816
|
+
lines.push("", ...planSummaryLines(plan));
|
|
29817
|
+
} else {
|
|
29818
|
+
lines.push("", "- \uC544\uC9C1 \uBA85\uC2DC\uC801\uC778 plan\uC740 \uB9CC\uB4E4\uC5B4\uC9C0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
|
|
29819
|
+
}
|
|
29820
|
+
const notes = progress.notes.slice(-3);
|
|
29821
|
+
if (notes.length > 0) {
|
|
29822
|
+
lines.push("", "\uCD5C\uADFC \uC911\uAC04 \uBCF4\uACE0:");
|
|
29823
|
+
for (const note of notes) lines.push(`- ${note.message}`);
|
|
29824
|
+
}
|
|
29825
|
+
lines.push("", terminalNextStepText(reason));
|
|
29826
|
+
return lines.join("\n");
|
|
29827
|
+
}
|
|
29828
|
+
function planSummaryLines(plan) {
|
|
29829
|
+
const lines = [`Plan: ${plan.title ?? "\uC791\uC5C5 \uACC4\uD68D"}`];
|
|
29830
|
+
if (plan.summary) lines.push(`\uC694\uC57D: ${plan.summary}`);
|
|
29831
|
+
const counts = countSteps(plan.steps);
|
|
29832
|
+
lines.push(
|
|
29833
|
+
`\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.`
|
|
29834
|
+
);
|
|
29835
|
+
const completed = plan.steps.filter((step) => step.status === "completed" || step.status === "skipped").slice(-3);
|
|
29836
|
+
if (completed.length > 0) lines.push(`\uC644\uB8CC\uD55C \uC791\uC5C5: ${completed.map(formatStep).join("; ")}`);
|
|
29837
|
+
const active = plan.steps.filter((step) => step.status === "in_progress");
|
|
29838
|
+
if (active.length > 0) lines.push(`\uC9C4\uD589 \uC911\uC774\uB358 \uC791\uC5C5: ${active.map(formatStep).join("; ")}`);
|
|
29839
|
+
const unresolved = plan.steps.filter((step) => step.status === "pending" || step.status === "failed" || step.status === "blocked" || step.status === "cancelled").slice(0, 4);
|
|
29840
|
+
if (unresolved.length > 0) lines.push(`\uB0A8\uC740 \uC791\uC5C5: ${unresolved.map(formatStep).join("; ")}`);
|
|
29841
|
+
return lines;
|
|
29842
|
+
}
|
|
29843
|
+
function countSteps(steps) {
|
|
29844
|
+
const counts = {
|
|
29845
|
+
pending: 0,
|
|
29846
|
+
in_progress: 0,
|
|
29847
|
+
completed: 0,
|
|
29848
|
+
blocked: 0,
|
|
29849
|
+
skipped: 0,
|
|
29850
|
+
failed: 0,
|
|
29851
|
+
cancelled: 0
|
|
29852
|
+
};
|
|
29853
|
+
for (const step of steps) counts[step.status] += 1;
|
|
29854
|
+
return counts;
|
|
29855
|
+
}
|
|
29856
|
+
function formatStep(step) {
|
|
29857
|
+
const detail = step.detail ? ` (${preview(step.detail.replace(/\s+/g, " "), 120)})` : "";
|
|
29858
|
+
return `${step.title}${detail}`;
|
|
29859
|
+
}
|
|
29860
|
+
function terminalReasonText(reason) {
|
|
29861
|
+
if (reason === "max_turns") return "\uC0C1\uC704 task turn \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4.";
|
|
29862
|
+
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.";
|
|
29863
|
+
if (reason === "content_filter") return "\uC81C\uACF5\uC790\uC758 \uC548\uC804 \uD544\uD130\uB85C \uC751\uB2F5\uC774 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
29864
|
+
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.";
|
|
29865
|
+
if (reason === "error") return "\uB7F0\uD0C0\uC784 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
29866
|
+
return `${reason} \uC0C1\uD0DC\uB85C \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`;
|
|
29867
|
+
}
|
|
29868
|
+
function terminalNextStepText(reason) {
|
|
29869
|
+
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.";
|
|
29870
|
+
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.";
|
|
29871
|
+
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.";
|
|
29872
|
+
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.";
|
|
29873
|
+
}
|
|
28930
29874
|
function toolFailure(content) {
|
|
28931
29875
|
return { ok: false, content };
|
|
28932
29876
|
}
|
|
@@ -28959,11 +29903,15 @@ async function runExecutionSession(options) {
|
|
|
28959
29903
|
providerName: options.providerName,
|
|
28960
29904
|
model: options.backend.model,
|
|
28961
29905
|
agent: options.agent,
|
|
29906
|
+
maxTurns: options.countToolUseTurns === true ? options.maxTurns : null,
|
|
28962
29907
|
yes: options.yes,
|
|
28963
29908
|
transcript: options.transcript,
|
|
28964
29909
|
signal: options.signal,
|
|
28965
29910
|
eventBus: options.eventBus,
|
|
28966
29911
|
permissionPrompt: options.permissionPrompt,
|
|
29912
|
+
autoPermission: options.autoPermission,
|
|
29913
|
+
persistentGrants: options.persistentGrants,
|
|
29914
|
+
grants: options.grants,
|
|
28967
29915
|
transcriptWriter: options.transcriptWriter,
|
|
28968
29916
|
transcriptSessionId: options.transcriptSessionId,
|
|
28969
29917
|
rootSessionId: options.rootSessionId,
|
|
@@ -28983,13 +29931,13 @@ async function runExecutionSession(options) {
|
|
|
28983
29931
|
|
|
28984
29932
|
// src/goals/lock.ts
|
|
28985
29933
|
import { mkdir as mkdir13, open as open3, rm as rm2 } from "node:fs/promises";
|
|
28986
|
-
import
|
|
29934
|
+
import path33 from "node:path";
|
|
28987
29935
|
|
|
28988
29936
|
// src/goals/storage.ts
|
|
28989
29937
|
import { createHash as createHash3 } from "node:crypto";
|
|
28990
29938
|
import { mkdir as mkdir12, readFile as readFile16, rename as rename6, writeFile as writeFile13 } from "node:fs/promises";
|
|
28991
29939
|
import fs9 from "node:fs";
|
|
28992
|
-
import
|
|
29940
|
+
import path32 from "node:path";
|
|
28993
29941
|
var GoalStore = class {
|
|
28994
29942
|
cwd;
|
|
28995
29943
|
dir;
|
|
@@ -29000,8 +29948,8 @@ var GoalStore = class {
|
|
|
29000
29948
|
this.cwd = cwd;
|
|
29001
29949
|
this.scope = normalizeGoalStoreScope(options.scope);
|
|
29002
29950
|
this.dir = goalStoreDir(cwd, this.scope);
|
|
29003
|
-
this.activePath =
|
|
29004
|
-
this.archiveDir =
|
|
29951
|
+
this.activePath = path32.join(this.dir, "active.json");
|
|
29952
|
+
this.archiveDir = path32.join(this.dir, "archive");
|
|
29005
29953
|
}
|
|
29006
29954
|
async loadActive() {
|
|
29007
29955
|
if (!fs9.existsSync(this.activePath)) return void 0;
|
|
@@ -29015,7 +29963,7 @@ var GoalStore = class {
|
|
|
29015
29963
|
}
|
|
29016
29964
|
async saveActive(state) {
|
|
29017
29965
|
await mkdir12(this.dir, { recursive: true });
|
|
29018
|
-
const tmp =
|
|
29966
|
+
const tmp = path32.join(this.dir, `active.${process.pid}.${Date.now()}.tmp`);
|
|
29019
29967
|
await writeFile13(tmp, `${JSON.stringify(state, null, 2)}
|
|
29020
29968
|
`, "utf8");
|
|
29021
29969
|
await rename6(tmp, this.activePath);
|
|
@@ -29029,21 +29977,21 @@ var GoalStore = class {
|
|
|
29029
29977
|
status,
|
|
29030
29978
|
updatedAt: Date.now()
|
|
29031
29979
|
};
|
|
29032
|
-
await writeFile13(
|
|
29980
|
+
await writeFile13(path32.join(this.archiveDir, `${archived.id}.json`), `${JSON.stringify(archived, null, 2)}
|
|
29033
29981
|
`, "utf8");
|
|
29034
29982
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29035
29983
|
return archived;
|
|
29036
29984
|
}
|
|
29037
29985
|
async backupCorrupted(text) {
|
|
29038
29986
|
await mkdir12(this.dir, { recursive: true });
|
|
29039
|
-
const backup =
|
|
29987
|
+
const backup = path32.join(this.dir, `active.corrupt.${Date.now()}.json`);
|
|
29040
29988
|
await writeFile13(backup, text, "utf8");
|
|
29041
29989
|
await fs9.promises.rm(this.activePath, { force: true });
|
|
29042
29990
|
}
|
|
29043
29991
|
};
|
|
29044
29992
|
function goalStoreDir(cwd, scope) {
|
|
29045
29993
|
const safeScope = normalizeGoalStoreScope(scope);
|
|
29046
|
-
return safeScope ?
|
|
29994
|
+
return safeScope ? path32.join(cwd, ".demian", "goals", "sessions", safeScope) : path32.join(cwd, ".demian", "goals");
|
|
29047
29995
|
}
|
|
29048
29996
|
function normalizeGoalStoreScope(scope) {
|
|
29049
29997
|
const trimmed = scope?.trim();
|
|
@@ -29057,10 +30005,10 @@ function normalizeGoalStoreScope(scope) {
|
|
|
29057
30005
|
var GoalLock = class {
|
|
29058
30006
|
path;
|
|
29059
30007
|
constructor(cwd, scope) {
|
|
29060
|
-
this.path =
|
|
30008
|
+
this.path = path33.join(goalStoreDir(cwd, scope), "active.lock");
|
|
29061
30009
|
}
|
|
29062
30010
|
async acquire() {
|
|
29063
|
-
await mkdir13(
|
|
30011
|
+
await mkdir13(path33.dirname(this.path), { recursive: true });
|
|
29064
30012
|
const handle = await open3(this.path, "wx").catch((error) => {
|
|
29065
30013
|
const code = typeof error === "object" && error && "code" in error ? String(error.code) : "";
|
|
29066
30014
|
if (code === "EEXIST") throw new Error("Another goal is already active in this workspace.");
|
|
@@ -29394,6 +30342,12 @@ function evaluateGoalIteration(input2) {
|
|
|
29394
30342
|
const state = input2.state;
|
|
29395
30343
|
const language = goalUserLanguage(state);
|
|
29396
30344
|
const verification = input2.verification ?? state.verification ?? { status: "not_required" };
|
|
30345
|
+
const reasonContext = {
|
|
30346
|
+
state,
|
|
30347
|
+
finalAnswer: input2.finalAnswer,
|
|
30348
|
+
reason: input2.reason,
|
|
30349
|
+
verification
|
|
30350
|
+
};
|
|
29397
30351
|
const completion = input2.completedByTool || state.status === "completed" ? "met" : "unmet";
|
|
29398
30352
|
const verificationGate = verification.required ? verification.status : verification.status === "passed" || verification.status === "failed" || verification.status === "missing" ? verification.status : "not_required";
|
|
29399
30353
|
const prohibition = input2.blockedReason ? "violated" : "clear";
|
|
@@ -29401,28 +30355,28 @@ function evaluateGoalIteration(input2) {
|
|
|
29401
30355
|
const progress = input2.madeProgress === false ? "stalled" : input2.reason === "max_turns" ? "stalled" : "advanced";
|
|
29402
30356
|
const resource = state.iteration >= state.maxIterations ? "iteration_limited" : state.tokenBudget && state.tokensUsed >= state.tokenBudget ? "budget_limited" : "within_limits";
|
|
29403
30357
|
let decision = "continue";
|
|
29404
|
-
let reason = goalReason("continue", language);
|
|
30358
|
+
let reason = goalReason("continue", language, reasonContext);
|
|
29405
30359
|
if (state.status === "paused") {
|
|
29406
30360
|
decision = "paused";
|
|
29407
|
-
reason = goalReason("paused", language);
|
|
30361
|
+
reason = goalReason("paused", language, reasonContext);
|
|
29408
30362
|
} else if (prohibition === "violated" || scope === "drifted") {
|
|
29409
30363
|
decision = "blocked";
|
|
29410
|
-
reason = input2.blockedReason ?? goalReason("blocked_guardrail", language);
|
|
30364
|
+
reason = input2.blockedReason ?? goalReason("blocked_guardrail", language, reasonContext);
|
|
29411
30365
|
} else if (resource === "budget_limited") {
|
|
29412
30366
|
decision = "budget_limited";
|
|
29413
|
-
reason = goalReason("budget_limited", language);
|
|
30367
|
+
reason = goalReason("budget_limited", language, reasonContext);
|
|
29414
30368
|
} else if (resource === "iteration_limited") {
|
|
29415
30369
|
decision = "iteration_limited";
|
|
29416
|
-
reason = goalReason("iteration_limited", language);
|
|
30370
|
+
reason = goalReason("iteration_limited", language, reasonContext);
|
|
29417
30371
|
} else if (completion === "met" && (verificationGate === "passed" || verificationGate === "not_required")) {
|
|
29418
30372
|
decision = "complete";
|
|
29419
|
-
reason = goalReason("complete", language);
|
|
30373
|
+
reason = goalReason("complete", language, reasonContext);
|
|
29420
30374
|
} else if (completion === "met" && (verificationGate === "failed" || verificationGate === "missing")) {
|
|
29421
30375
|
decision = "blocked";
|
|
29422
|
-
reason = goalReason("verification_failed", language);
|
|
30376
|
+
reason = goalReason("verification_failed", language, reasonContext);
|
|
29423
30377
|
} else if (progress === "stalled" && input2.reason === "max_turns") {
|
|
29424
|
-
decision = "
|
|
29425
|
-
reason = goalReason("max_turns", language);
|
|
30378
|
+
decision = "continue";
|
|
30379
|
+
reason = goalReason("max_turns", language, reasonContext);
|
|
29426
30380
|
}
|
|
29427
30381
|
return {
|
|
29428
30382
|
gates: {
|
|
@@ -29556,29 +30510,69 @@ function normalizeSessionId(sessionId) {
|
|
|
29556
30510
|
const trimmed = sessionId?.trim();
|
|
29557
30511
|
return trimmed ? trimmed : void 0;
|
|
29558
30512
|
}
|
|
29559
|
-
function goalReason(key, language) {
|
|
30513
|
+
function goalReason(key, language, context = {}) {
|
|
30514
|
+
if (key === "max_turns") return maxTurnsGoalReason(language, context);
|
|
30515
|
+
if (key === "iteration_limited") return iterationLimitedGoalReason(language, context);
|
|
30516
|
+
if (key === "budget_limited") return budgetLimitedGoalReason(language, context);
|
|
29560
30517
|
if (language === "ko") {
|
|
29561
30518
|
return {
|
|
29562
30519
|
continue: "\uBAA9\uD45C\uB294 \uC544\uC9C1 \uD65C\uC131 \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uB2E4\uC74C \uC548\uC804\uD55C \uC791\uC5C5\uC744 \uACC4\uC18D \uC9C4\uD589\uD569\uB2C8\uB2E4.",
|
|
29563
30520
|
paused: "\uC0AC\uC6A9\uC790 \uC694\uCCAD\uC73C\uB85C \uBAA9\uD45C\uAC00 \uC77C\uC2DC \uC911\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
29564
30521
|
blocked_guardrail: "\uAE08\uC9C0\uC870\uAC74 \uB610\uB294 \uBC94\uC704 \uAD00\uB9AC \uAE30\uC900\uC744 \uC704\uBC18\uD558\uC5EC \uBAA9\uD45C\uAC00 \uCC28\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
29565
|
-
budget_limited: "\uC644\uB8CC\uAC00 \uAC80\uC99D\uB418\uAE30 \uC804\uC5D0 \uBAA9\uD45C\uAC00 \uD1A0\uD070 \uC608\uC0B0\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4.",
|
|
29566
|
-
iteration_limited: "\uC644\uB8CC\uAC00 \uAC80\uC99D\uB418\uAE30 \uC804\uC5D0 \uBAA9\uD45C\uAC00 \uBC18\uBCF5 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4.",
|
|
29567
30522
|
complete: "\uC644\uB8CC\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C \uC2E0\uD638\uB418\uC5C8\uACE0 \uC885\uB8CC \uAC80\uC99D \uC694\uAD6C\uC0AC\uD56D\uC774 \uCDA9\uC871\uB418\uC5C8\uC2B5\uB2C8\uB2E4.",
|
|
29568
|
-
verification_failed: "\uC644\uB8CC\uAC00 \uC2E0\uD638\uB418\uC5C8\uC9C0\uB9CC \uD544\uC694\uD55C \uAC80\uC99D\uC744 \uD1B5\uACFC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."
|
|
29569
|
-
|
|
29570
|
-
}[key];
|
|
30523
|
+
verification_failed: "\uC644\uB8CC\uAC00 \uC2E0\uD638\uB418\uC5C8\uC9C0\uB9CC \uD544\uC694\uD55C \uAC80\uC99D\uC744 \uD1B5\uACFC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4."
|
|
30524
|
+
}[key] ?? "\uBAA9\uD45C \uC0C1\uD0DC\uB97C \uD3C9\uAC00\uD588\uC2B5\uB2C8\uB2E4.";
|
|
29571
30525
|
}
|
|
29572
30526
|
return {
|
|
29573
30527
|
continue: "Goal remains active; continue with the next safe action.",
|
|
29574
30528
|
paused: "Goal is paused by user request.",
|
|
29575
30529
|
blocked_guardrail: "Goal is blocked because a prohibition or scope guardrail was violated.",
|
|
29576
|
-
budget_limited: "Goal reached its token budget before completion was verified.",
|
|
29577
|
-
iteration_limited: "Goal reached its iteration limit before completion was verified.",
|
|
29578
30530
|
complete: "Completion was explicitly signaled and termination verification requirements are satisfied.",
|
|
29579
|
-
verification_failed: "Completion was signaled, but required verification did not pass."
|
|
29580
|
-
|
|
29581
|
-
|
|
30531
|
+
verification_failed: "Completion was signaled, but required verification did not pass."
|
|
30532
|
+
}[key] ?? "Goal state was evaluated.";
|
|
30533
|
+
}
|
|
30534
|
+
function maxTurnsGoalReason(language, context) {
|
|
30535
|
+
const maxTurns = parseMaxTurns(context.finalAnswer);
|
|
30536
|
+
const maxTurnsText = maxTurns ? `maxTurns(${maxTurns})` : "maxTurns";
|
|
30537
|
+
const iteration = goalIterationProgress(context.state);
|
|
30538
|
+
if (language === "ko") {
|
|
30539
|
+
const run2 = iteration ? `${iteration}\uD68C\uCC28 \uC2E4\uD589 \uC911` : "\uC774\uBC88 \uC2E4\uD589\uC5D0\uC11C";
|
|
30540
|
+
return `\uC774\uBC88 \uBAA9\uD45C\uB294 \uC544\uC9C1 \uC644\uB8CC\uB85C \uAC80\uC99D\uB418\uC9C0 \uC54A\uC558\uACE0, ${run2} \uD558\uB098\uC758 \uC791\uC5C5 \uC2DC\uB3C4\uAC00 ${maxTurnsText}\uC5D0 \uB3C4\uB2EC\uD574 \uBA48\uCDC4\uC2B5\uB2C8\uB2E4. goal\uC5D0\uC11C\uB294 \uC774 \uAC12\uC744 \uB3C4\uAD6C \uD638\uCD9C \uC218\uAC00 \uC544\uB2C8\uB77C \uAC19\uC740 \uBAA9\uD45C\uB97C \uC774\uC5B4\uC11C \uC7AC\uC2DC\uB3C4\uD55C \uC0C1\uC704 \uC2E4\uD589 \uD69F\uC218\uB85C \uBD05\uB2C8\uB2E4. \uB530\uB77C\uC11C \uBAA9\uD45C \uC790\uCCB4\uB294 \uBC18\uBCF5 \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD558\uAE30 \uC804\uAE4C\uC9C0 \uACC4\uC18D \uD65C\uC131 \uC0C1\uD0DC\uB85C \uB0A8\uAE30\uACE0, \uB3C4\uAD6C \uD638\uCD9C\uCC98\uB7FC \uC815\uC0C1\uC801\uC778 \uD558\uC704 \uC791\uC5C5\uC740 \uBCC4\uB3C4 \uC548\uC804 \uD55C\uB3C4\uB85C \uB2E4\uB8F9\uB2C8\uB2E4. /retry\uB85C \uB04A\uAE34 \uC9C0\uC810\uBD80\uD130 \uC774\uC5B4\uC11C \uC9C4\uD589\uD574 \uC8FC\uC138\uC694.`;
|
|
30541
|
+
}
|
|
30542
|
+
const run = iteration ? `at goal iteration ${iteration}` : "during this run";
|
|
30543
|
+
return `This goal is not verified complete yet, and one task attempt stopped ${run} because it reached ${maxTurnsText}. In goal mode, this value is treated as a high-level retry/attempt limit for the same objective, not as a count of tool calls. The goal stays active until its iteration limit is actually reached, while ordinary tool-use subwork is guarded by a separate safety limit. Retry from the current state to continue.`;
|
|
30544
|
+
}
|
|
30545
|
+
function iterationLimitedGoalReason(language, context) {
|
|
30546
|
+
const iteration = goalIterationProgress(context.state);
|
|
30547
|
+
if (language === "ko") {
|
|
30548
|
+
const limit2 = iteration ? `${iteration} \uBC18\uBCF5 \uD55C\uB3C4` : "\uBC18\uBCF5 \uD55C\uB3C4";
|
|
30549
|
+
return `\uBAA9\uD45C\uAC00 ${limit2}\uC5D0 \uB3C4\uB2EC\uD588\uC9C0\uB9CC \uC644\uB8CC \uAC80\uC99D\uC744 \uD1B5\uACFC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uCD5C\uADFC \uC2E4\uD589 \uACB0\uACFC\uC640 \uC2E4\uD328 \uB610\uB294 \uBBF8\uC644\uB8CC step\uC744 \uD655\uC778\uD55C \uB4A4, \uBC94\uC704\uB97C \uC904\uC774\uAC70\uB098 \uC0C8 \uBAA9\uD45C\uB85C \uB2E4\uC2DC \uC2DC\uC791\uD574 \uC8FC\uC138\uC694.`;
|
|
30550
|
+
}
|
|
30551
|
+
const limit = iteration ? `its ${iteration} iteration limit` : "its iteration limit";
|
|
30552
|
+
return `The goal reached ${limit} before completion was verified. Review the latest run output and any failed or unresolved steps, then narrow the scope or start a new goal.`;
|
|
30553
|
+
}
|
|
30554
|
+
function budgetLimitedGoalReason(language, context) {
|
|
30555
|
+
const budget = goalBudgetProgress(context.state);
|
|
30556
|
+
if (language === "ko") {
|
|
30557
|
+
const limit2 = budget ? `\uD1A0\uD070 \uC608\uC0B0 ${budget}` : "\uD1A0\uD070 \uC608\uC0B0";
|
|
30558
|
+
return `\uC644\uB8CC\uAC00 \uAC80\uC99D\uB418\uAE30 \uC804\uC5D0 \uBAA9\uD45C\uAC00 ${limit2}\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4. \uD604\uC7AC\uAE4C\uC9C0\uC758 \uACB0\uACFC\uB97C \uD655\uC778\uD55C \uB4A4, \uBC94\uC704\uB97C \uC904\uC774\uAC70\uB098 \uC608\uC0B0\uC744 \uB298\uB824 \uB2E4\uC2DC \uC9C4\uD589\uD574 \uC8FC\uC138\uC694.`;
|
|
30559
|
+
}
|
|
30560
|
+
const limit = budget ? `token budget ${budget}` : "token budget";
|
|
30561
|
+
return `The goal reached its ${limit} before completion was verified. Review the current result, then narrow the scope or increase the budget before continuing.`;
|
|
30562
|
+
}
|
|
30563
|
+
function parseMaxTurns(finalAnswer) {
|
|
30564
|
+
const value = finalAnswer?.match(/\bmaxTurns\s*\(\s*(\d+)\s*\)/i)?.[1];
|
|
30565
|
+
if (!value) return void 0;
|
|
30566
|
+
const parsed = Number(value);
|
|
30567
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
30568
|
+
}
|
|
30569
|
+
function goalIterationProgress(state) {
|
|
30570
|
+
if (!state || !Number.isFinite(state.iteration) || !Number.isFinite(state.maxIterations) || state.maxIterations <= 0) return void 0;
|
|
30571
|
+
return `${state.iteration}/${state.maxIterations}`;
|
|
30572
|
+
}
|
|
30573
|
+
function goalBudgetProgress(state) {
|
|
30574
|
+
if (!state || !Number.isFinite(state.tokensUsed) || !Number.isFinite(state.tokenBudget) || !state.tokenBudget || state.tokenBudget <= 0) return void 0;
|
|
30575
|
+
return `${state.tokensUsed}/${state.tokenBudget}`;
|
|
29582
30576
|
}
|
|
29583
30577
|
function createGoalId() {
|
|
29584
30578
|
return `goal_${createSessionId().slice("ses_".length)}`;
|
|
@@ -29647,9 +30641,76 @@ function sha256(value) {
|
|
|
29647
30641
|
}
|
|
29648
30642
|
|
|
29649
30643
|
// src/root-session.ts
|
|
29650
|
-
import { cp, mkdtemp, rm as rm3 } from "node:fs/promises";
|
|
30644
|
+
import { cp, mkdtemp, readFile as readFile17, rm as rm3 } from "node:fs/promises";
|
|
29651
30645
|
import os15 from "node:os";
|
|
29652
|
-
import
|
|
30646
|
+
import path34 from "node:path";
|
|
30647
|
+
|
|
30648
|
+
// src/permissions/auto-agent.ts
|
|
30649
|
+
function createAutoPermissionAgent(options) {
|
|
30650
|
+
return async (request) => {
|
|
30651
|
+
const command = bashCommandFromRequest(request);
|
|
30652
|
+
if (!command || !isPotentiallyReadOnlyShellCommand(command)) return void 0;
|
|
30653
|
+
const result = await classifyWithAgent(command, request, options).catch(() => void 0);
|
|
30654
|
+
if (!result || result.decision !== "allow" || result.confidence < 0.85) return void 0;
|
|
30655
|
+
return { decision: "allow", reason: `auto permission judge: ${preview(result.reason, 160)}` };
|
|
30656
|
+
};
|
|
30657
|
+
}
|
|
30658
|
+
function bashCommandFromRequest(request) {
|
|
30659
|
+
const toolName = (request.qualifiedName ?? request.tool).split(".").at(-1)?.toLowerCase();
|
|
30660
|
+
if (toolName !== "bash") return void 0;
|
|
30661
|
+
const input2 = request.input && typeof request.input === "object" && !Array.isArray(request.input) ? request.input : {};
|
|
30662
|
+
return typeof input2.command === "string" ? input2.command : void 0;
|
|
30663
|
+
}
|
|
30664
|
+
async function classifyWithAgent(command, request, options) {
|
|
30665
|
+
const response = await options.provider.chat({
|
|
30666
|
+
model: options.model,
|
|
30667
|
+
tools: [],
|
|
30668
|
+
maxTokens: 180,
|
|
30669
|
+
temperature: 0,
|
|
30670
|
+
signal: permissionSignal(options.signal, options.timeoutMs ?? 4e3),
|
|
30671
|
+
messages: [
|
|
30672
|
+
{
|
|
30673
|
+
role: "system",
|
|
30674
|
+
content: [
|
|
30675
|
+
"You are Demian's internal permission judge.",
|
|
30676
|
+
"Classify whether a shell command is safe to auto-allow without asking the user.",
|
|
30677
|
+
"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.",
|
|
30678
|
+
"If there is any uncertainty, choose ask. Never choose allow just because the command seems common.",
|
|
30679
|
+
'Return only compact JSON with this shape: {"decision":"allow"|"ask","confidence":0..1,"reason":"short reason"}.'
|
|
30680
|
+
].join(" ")
|
|
30681
|
+
},
|
|
30682
|
+
{
|
|
30683
|
+
role: "user",
|
|
30684
|
+
content: JSON.stringify({
|
|
30685
|
+
cwd: options.cwd,
|
|
30686
|
+
tool: request.qualifiedName ?? request.tool,
|
|
30687
|
+
actor: request.actor,
|
|
30688
|
+
command
|
|
30689
|
+
})
|
|
30690
|
+
}
|
|
30691
|
+
]
|
|
30692
|
+
});
|
|
30693
|
+
return parseJudgeResponse(response.message.content);
|
|
30694
|
+
}
|
|
30695
|
+
function permissionSignal(parent, timeoutMs) {
|
|
30696
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
30697
|
+
return parent ? AbortSignal.any([parent, timeout]) : timeout;
|
|
30698
|
+
}
|
|
30699
|
+
function parseJudgeResponse(content) {
|
|
30700
|
+
if (!content) return void 0;
|
|
30701
|
+
const json = content.match(/\{[\s\S]*\}/)?.[0];
|
|
30702
|
+
if (!json) return void 0;
|
|
30703
|
+
try {
|
|
30704
|
+
const parsed = JSON.parse(json);
|
|
30705
|
+
const decision = parsed.decision === "allow" ? "allow" : parsed.decision === "ask" ? "ask" : void 0;
|
|
30706
|
+
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : Number(parsed.confidence);
|
|
30707
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason.trim() : "";
|
|
30708
|
+
if (!decision || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || !reason) return void 0;
|
|
30709
|
+
return { decision, confidence, reason };
|
|
30710
|
+
} catch {
|
|
30711
|
+
return void 0;
|
|
30712
|
+
}
|
|
30713
|
+
}
|
|
29653
30714
|
|
|
29654
30715
|
// src/permissions/presets.ts
|
|
29655
30716
|
var PERMISSION_PRESETS = ["deny", "ask", "auto", "full"];
|
|
@@ -29665,9 +30726,19 @@ function defaultDecisionForPermissionPreset(preset) {
|
|
|
29665
30726
|
if (preset === "full") return "allow";
|
|
29666
30727
|
return "by-category";
|
|
29667
30728
|
}
|
|
30729
|
+
function invocationDecisionForPermissionPreset(preset) {
|
|
30730
|
+
if (preset === "deny") return "deny";
|
|
30731
|
+
if (preset === "ask") return "ask";
|
|
30732
|
+
return "allow";
|
|
30733
|
+
}
|
|
29668
30734
|
function applyPermissionPresetToAgent(agent, preset) {
|
|
30735
|
+
const invocationDecision = invocationDecisionForPermissionPreset(preset);
|
|
29669
30736
|
return {
|
|
29670
30737
|
...agent,
|
|
30738
|
+
delegation: agent.delegation ? {
|
|
30739
|
+
...agent.delegation,
|
|
30740
|
+
invocationDecision
|
|
30741
|
+
} : agent.delegation,
|
|
29671
30742
|
defaultDecision: defaultDecisionForPermissionPreset(preset)
|
|
29672
30743
|
};
|
|
29673
30744
|
}
|
|
@@ -29679,6 +30750,13 @@ function createCoworkTool(options) {
|
|
|
29679
30750
|
return {
|
|
29680
30751
|
name: "cowork",
|
|
29681
30752
|
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.",
|
|
30753
|
+
metadata: {
|
|
30754
|
+
category: "orchestration.cowork",
|
|
30755
|
+
categoryPath: ["orchestration", "cowork"],
|
|
30756
|
+
sideEffect: "external",
|
|
30757
|
+
defaultDecision: options.config.defaultInvocationDecision,
|
|
30758
|
+
trust: "builtin"
|
|
30759
|
+
},
|
|
29682
30760
|
inputSchema: {
|
|
29683
30761
|
type: "object",
|
|
29684
30762
|
properties: {
|
|
@@ -29693,6 +30771,9 @@ function createCoworkTool(options) {
|
|
|
29693
30771
|
properties: {
|
|
29694
30772
|
id: { type: "string", pattern: "^[a-z0-9_-]{1,32}$" },
|
|
29695
30773
|
agent: { type: "string", enum: coworkAgentNames, description: agentDescription },
|
|
30774
|
+
provider: { type: "string", description: "Optional provider override. Must be listed in cowork.providers." },
|
|
30775
|
+
model: { type: "string", description: "Optional model display name or raw model id for this member's provider." },
|
|
30776
|
+
modelProfileName: { type: "string", description: "Optional stable model profile name for this member's provider." },
|
|
29696
30777
|
task: { type: "string" },
|
|
29697
30778
|
dependsOn: { type: "array", items: { type: "string" } },
|
|
29698
30779
|
role: { type: "string" },
|
|
@@ -29703,7 +30784,7 @@ function createCoworkTool(options) {
|
|
|
29703
30784
|
constraints: { type: "array", items: { type: "string" } },
|
|
29704
30785
|
expectedOutput: { type: "string" },
|
|
29705
30786
|
maxTurns: { type: "integer", minimum: 1 },
|
|
29706
|
-
mode: { type: "string", enum: ["read-only", "write", "review"] },
|
|
30787
|
+
mode: { type: "string", enum: ["read-only", "write", "review", "manage"] },
|
|
29707
30788
|
writeScope: { type: "array", items: { type: "string" } },
|
|
29708
30789
|
returnMode: { type: "string", enum: ["brief", "normal"] }
|
|
29709
30790
|
},
|
|
@@ -29769,6 +30850,9 @@ function parseMember(input2, index) {
|
|
|
29769
30850
|
agent: object2.agent.trim(),
|
|
29770
30851
|
task: object2.task.trim()
|
|
29771
30852
|
};
|
|
30853
|
+
if (typeof object2.provider === "string" && object2.provider.trim()) member.provider = object2.provider.trim();
|
|
30854
|
+
if (typeof object2.model === "string" && object2.model.trim()) member.model = object2.model.trim();
|
|
30855
|
+
if (typeof object2.modelProfileName === "string" && object2.modelProfileName.trim()) member.modelProfileName = object2.modelProfileName.trim();
|
|
29772
30856
|
if (typeof object2.id === "string" && object2.id.trim()) member.id = object2.id.trim();
|
|
29773
30857
|
if (typeof object2.role === "string" && object2.role.trim()) member.role = object2.role.trim();
|
|
29774
30858
|
if (typeof object2.context === "string") member.context = object2.context;
|
|
@@ -29776,7 +30860,7 @@ function parseMember(input2, index) {
|
|
|
29776
30860
|
if (object2.returnMode === "brief" || object2.returnMode === "normal") member.returnMode = object2.returnMode;
|
|
29777
30861
|
if (object2.parentDetail === "preview" || object2.parentDetail === "full" || object2.parentDetail === "none") member.parentDetail = object2.parentDetail;
|
|
29778
30862
|
if (object2.mode !== void 0) {
|
|
29779
|
-
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.` };
|
|
30863
|
+
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.` };
|
|
29780
30864
|
member.mode = object2.mode;
|
|
29781
30865
|
}
|
|
29782
30866
|
if (typeof object2.maxTurns === "number" && Number.isInteger(object2.maxTurns) && object2.maxTurns > 0) member.maxTurns = object2.maxTurns;
|
|
@@ -29807,6 +30891,13 @@ function createDelegateAgentTool(options) {
|
|
|
29807
30891
|
return {
|
|
29808
30892
|
name: "delegate_agent",
|
|
29809
30893
|
description: "Delegate a bounded task to a configured demian agent and return its result.",
|
|
30894
|
+
metadata: {
|
|
30895
|
+
category: "orchestration.agent",
|
|
30896
|
+
categoryPath: ["orchestration", "agent"],
|
|
30897
|
+
sideEffect: "external",
|
|
30898
|
+
defaultDecision: "ask",
|
|
30899
|
+
trust: "builtin"
|
|
30900
|
+
},
|
|
29810
30901
|
inputSchema: {
|
|
29811
30902
|
type: "object",
|
|
29812
30903
|
properties: {
|
|
@@ -29857,6 +30948,172 @@ function stringArray2(value) {
|
|
|
29857
30948
|
return value.filter((item) => typeof item === "string");
|
|
29858
30949
|
}
|
|
29859
30950
|
|
|
30951
|
+
// src/cowork/permissions.ts
|
|
30952
|
+
function resolveCoworkPermissionIntent(agent, mode) {
|
|
30953
|
+
if (agent.permissionIntent) return agent.permissionIntent;
|
|
30954
|
+
if (mode === "write") return "write";
|
|
30955
|
+
if (mode === "review") return "review";
|
|
30956
|
+
if (mode === "manage") return "manage";
|
|
30957
|
+
const category = agent.catalog?.category;
|
|
30958
|
+
if (category === "builder") return "write";
|
|
30959
|
+
if (category === "reviewer") return "review";
|
|
30960
|
+
if (category === "planner") return "manage";
|
|
30961
|
+
return "read-only";
|
|
30962
|
+
}
|
|
30963
|
+
function compileCoworkPermissionOverlay(options) {
|
|
30964
|
+
const intent = resolveCoworkPermissionIntent(options.agent, options.mode);
|
|
30965
|
+
const mapping = options.config.permissionOverlay.modeMappings[intent];
|
|
30966
|
+
const providerNamespace = options.config.permissionOverlay.providerToolNamespaces[options.providerProfile];
|
|
30967
|
+
const providerTools = providerNamespace ? [...mapping?.providerTools[options.providerProfile] ?? []] : [];
|
|
30968
|
+
const demianTools = [...mapping?.demianTools ?? []];
|
|
30969
|
+
const defaultDecision = mapping?.defaultDecision ?? options.agent.defaultDecision ?? "ask";
|
|
30970
|
+
const rules = overlayRules({ intent, providerNamespace, providerTools, demianTools });
|
|
30971
|
+
return {
|
|
30972
|
+
intent,
|
|
30973
|
+
providerProfile: options.providerProfile,
|
|
30974
|
+
providerNamespace,
|
|
30975
|
+
demianTools,
|
|
30976
|
+
providerTools,
|
|
30977
|
+
rules,
|
|
30978
|
+
defaultDecision,
|
|
30979
|
+
explanation: [
|
|
30980
|
+
`permission intent ${intent}`,
|
|
30981
|
+
providerNamespace ? `${providerNamespace} tools: ${providerTools.join(", ") || "(none)"}` : "no provider tool namespace",
|
|
30982
|
+
`default decision ${defaultDecision}`
|
|
30983
|
+
]
|
|
30984
|
+
};
|
|
30985
|
+
}
|
|
30986
|
+
function overlayRules(options) {
|
|
30987
|
+
const rules = [];
|
|
30988
|
+
for (const tool of options.demianTools) {
|
|
30989
|
+
rules.push({ tool, decision: demianToolDecision(options.intent, tool) });
|
|
30990
|
+
}
|
|
30991
|
+
if (options.providerNamespace) {
|
|
30992
|
+
const allowed = new Set(options.providerTools);
|
|
30993
|
+
for (const tool of options.providerTools) {
|
|
30994
|
+
rules.push({ tool: `${options.providerNamespace}.${tool}`, decision: providerToolDecision(options.intent, tool) });
|
|
30995
|
+
}
|
|
30996
|
+
for (const tool of ["Edit", "Write", "MultiEdit", "Bash"]) {
|
|
30997
|
+
if (!allowed.has(tool)) rules.push({ tool: `${options.providerNamespace}.${tool}`, decision: "deny", reason: `${options.intent} cowork permission overlay` });
|
|
30998
|
+
}
|
|
30999
|
+
}
|
|
31000
|
+
return rules;
|
|
31001
|
+
}
|
|
31002
|
+
function demianToolDecision(intent, tool) {
|
|
31003
|
+
if (intent !== "write") return "allow";
|
|
31004
|
+
return tool === "edit_file" || tool === "write_file" || tool === "bash" ? "ask" : "allow";
|
|
31005
|
+
}
|
|
31006
|
+
function providerToolDecision(intent, tool) {
|
|
31007
|
+
if (intent !== "write") return "allow";
|
|
31008
|
+
return tool === "Edit" || tool === "Write" || tool === "MultiEdit" || tool === "Bash" ? "ask" : "allow";
|
|
31009
|
+
}
|
|
31010
|
+
|
|
31011
|
+
// src/cowork/routing.ts
|
|
31012
|
+
function resolveCoworkAssignment(options) {
|
|
31013
|
+
const memberProvider = options.member.provider?.trim();
|
|
31014
|
+
if (memberProvider && !options.config.providers.includes(memberProvider)) {
|
|
31015
|
+
throw new Error(`cowork member provider ${memberProvider} is not in cowork.providers.`);
|
|
31016
|
+
}
|
|
31017
|
+
const agentName = coworkAgentName(options.agent.name);
|
|
31018
|
+
const sourceAndOrder = providerOrder({
|
|
31019
|
+
memberProvider,
|
|
31020
|
+
memberModel: options.member.model,
|
|
31021
|
+
memberModelProfileName: options.member.modelProfileName,
|
|
31022
|
+
agentName,
|
|
31023
|
+
agent: options.agent,
|
|
31024
|
+
config: options.config
|
|
31025
|
+
});
|
|
31026
|
+
const constrainedOrder = sourceAndOrder.source === "legacy-agent-provider" ? sourceAndOrder.order : sourceAndOrder.order.filter((selection) => options.config.providers.includes(selection.provider));
|
|
31027
|
+
const filtered = constrainedOrder.filter((selection) => options.availability?.[selection.provider] !== "unavailable");
|
|
31028
|
+
if (filtered.length === 0) {
|
|
31029
|
+
throw new Error(`No available cowork provider for ${options.agent.name}. Tried: ${sourceAndOrder.order.map(providerSelectionLabel).join(", ") || "(none)"}.`);
|
|
31030
|
+
}
|
|
31031
|
+
const selected = filtered[0];
|
|
31032
|
+
const providerProfile = selected.provider;
|
|
31033
|
+
const mode = options.member.mode ?? "read-only";
|
|
31034
|
+
const permissionOverlay = compileCoworkPermissionOverlay({
|
|
31035
|
+
config: options.config,
|
|
31036
|
+
agent: options.agent,
|
|
31037
|
+
providerProfile,
|
|
31038
|
+
mode
|
|
31039
|
+
});
|
|
31040
|
+
return {
|
|
31041
|
+
agent: options.agent,
|
|
31042
|
+
agentName,
|
|
31043
|
+
providerProfile,
|
|
31044
|
+
model: selected.model,
|
|
31045
|
+
modelProfileName: selected.modelProfileName,
|
|
31046
|
+
remainingProviders: filtered.slice(1).map((selection) => selection.provider),
|
|
31047
|
+
remainingProviderSelections: filtered.slice(1),
|
|
31048
|
+
source: sourceAndOrder.source,
|
|
31049
|
+
permissionOverlay,
|
|
31050
|
+
explanation: [
|
|
31051
|
+
`provider source: ${sourceAndOrder.source}`,
|
|
31052
|
+
`provider/model order: ${filtered.map(providerSelectionLabel).join(" > ")}`,
|
|
31053
|
+
...permissionOverlay.explanation
|
|
31054
|
+
]
|
|
31055
|
+
};
|
|
31056
|
+
}
|
|
31057
|
+
function coworkProviderAvailability(config, providers) {
|
|
31058
|
+
const availability = {};
|
|
31059
|
+
for (const provider of config.providers) {
|
|
31060
|
+
const providerConfig = providers[provider];
|
|
31061
|
+
if (!providerConfig || providerConfig.hidden) availability[provider] = "unavailable";
|
|
31062
|
+
else availability[provider] = "configured";
|
|
31063
|
+
}
|
|
31064
|
+
return availability;
|
|
31065
|
+
}
|
|
31066
|
+
function providerOrder(options) {
|
|
31067
|
+
if (options.memberProvider) {
|
|
31068
|
+
return {
|
|
31069
|
+
source: "explicit-provider",
|
|
31070
|
+
order: [
|
|
31071
|
+
{
|
|
31072
|
+
provider: options.memberProvider,
|
|
31073
|
+
...options.memberModel ? { model: options.memberModel } : {},
|
|
31074
|
+
...options.memberModelProfileName ? { modelProfileName: options.memberModelProfileName } : {}
|
|
31075
|
+
}
|
|
31076
|
+
]
|
|
31077
|
+
};
|
|
31078
|
+
}
|
|
31079
|
+
if (options.agent.provider?.profile && !options.agent.defaultProviders?.length) return { source: "legacy-agent-provider", order: [{ provider: options.agent.provider.profile }] };
|
|
31080
|
+
const userModelPool = options.agentName ? options.config.routing.agentModelPools[options.agentName] : void 0;
|
|
31081
|
+
if (userModelPool?.length) return { source: "user-model-pool", order: uniqueProviderSelections(userModelPool) };
|
|
31082
|
+
const userOrder = options.agentName ? options.config.routing.agentProviders[options.agentName] : void 0;
|
|
31083
|
+
if (userOrder?.length) return { source: "user-routing", order: unique2(userOrder).map((provider) => ({ provider })) };
|
|
31084
|
+
if (options.agent.defaultProviders?.length) return { source: "agent-default", order: unique2(options.agent.defaultProviders).map((provider) => ({ provider })) };
|
|
31085
|
+
if (options.agent.provider?.profile) return { source: "legacy-agent-provider", order: [{ provider: options.agent.provider.profile }] };
|
|
31086
|
+
return { source: "cowork-pool", order: unique2(options.config.providers).map((provider) => ({ provider })) };
|
|
31087
|
+
}
|
|
31088
|
+
function coworkAgentName(value) {
|
|
31089
|
+
return COWORK_AGENT_NAMES.includes(value) ? value : void 0;
|
|
31090
|
+
}
|
|
31091
|
+
function unique2(values) {
|
|
31092
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31093
|
+
const out = [];
|
|
31094
|
+
for (const value of values) {
|
|
31095
|
+
if (seen.has(value)) continue;
|
|
31096
|
+
seen.add(value);
|
|
31097
|
+
out.push(value);
|
|
31098
|
+
}
|
|
31099
|
+
return out;
|
|
31100
|
+
}
|
|
31101
|
+
function uniqueProviderSelections(values) {
|
|
31102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31103
|
+
const out = [];
|
|
31104
|
+
for (const value of values) {
|
|
31105
|
+
const key = `${value.provider}\0${value.modelProfileName ?? ""}\0${value.model ?? ""}`;
|
|
31106
|
+
if (seen.has(key)) continue;
|
|
31107
|
+
seen.add(key);
|
|
31108
|
+
out.push({ ...value });
|
|
31109
|
+
}
|
|
31110
|
+
return out;
|
|
31111
|
+
}
|
|
31112
|
+
function providerSelectionLabel(selection) {
|
|
31113
|
+
const model = selection.modelProfileName ?? selection.model;
|
|
31114
|
+
return model ? `${selection.provider}:${model}` : selection.provider;
|
|
31115
|
+
}
|
|
31116
|
+
|
|
29860
31117
|
// src/root-session.ts
|
|
29861
31118
|
var demianOnlyToolNames = /* @__PURE__ */ new Set(["delegate_agent", "cowork", "get_goal", "create_goal", "update_goal"]);
|
|
29862
31119
|
var RootSession = class {
|
|
@@ -29870,6 +31127,7 @@ var RootSession = class {
|
|
|
29870
31127
|
#transcript;
|
|
29871
31128
|
#agentSessions = /* @__PURE__ */ new Map();
|
|
29872
31129
|
#externalSessions = new ClaudeCodeSessionMap();
|
|
31130
|
+
#coworkProviderAvailability;
|
|
29873
31131
|
#mainExternalSessionKey;
|
|
29874
31132
|
#currentPrompt = "";
|
|
29875
31133
|
#progress;
|
|
@@ -29878,6 +31136,7 @@ var RootSession = class {
|
|
|
29878
31136
|
constructor(options) {
|
|
29879
31137
|
this.#options = options;
|
|
29880
31138
|
this.#permissionPreset = normalizePermissionPreset(options.permissionPreset);
|
|
31139
|
+
this.#coworkProviderAvailability = coworkProviderAvailability(options.config.cowork, options.config.providers);
|
|
29881
31140
|
this.events = options.eventBus ?? new EventBus();
|
|
29882
31141
|
this.agents = new AgentRegistry();
|
|
29883
31142
|
this.#progress = new ProgressLedger({ rootSessionId: this.id });
|
|
@@ -29924,6 +31183,8 @@ var RootSession = class {
|
|
|
29924
31183
|
agent: mainAgent,
|
|
29925
31184
|
hooks: this.#options.hooks ?? this.#options.config.hooks,
|
|
29926
31185
|
maxTurns: this.#options.maxTurns ?? this.#options.config.maxTurns,
|
|
31186
|
+
countToolUseTurns: false,
|
|
31187
|
+
maxToolUseRounds: relaxedToolUseRoundLimit(this.#options.maxTurns ?? this.#options.config.maxTurns),
|
|
29927
31188
|
yes: this.#options.yes,
|
|
29928
31189
|
dryRun: this.#options.dryRun,
|
|
29929
31190
|
streaming: this.#options.streaming ?? this.#options.config.streaming.enabled,
|
|
@@ -29936,6 +31197,7 @@ var RootSession = class {
|
|
|
29936
31197
|
signal: options.signal,
|
|
29937
31198
|
eventBus: this.events,
|
|
29938
31199
|
permissionPrompt: this.permissionPrompt,
|
|
31200
|
+
autoPermission: this.autoPermissionForBackend(backend, this.#options.cwd, options.signal),
|
|
29939
31201
|
toolRegistry: this.tools,
|
|
29940
31202
|
grants: this.#grants,
|
|
29941
31203
|
transcriptWriter: this.#transcript,
|
|
@@ -29984,7 +31246,7 @@ var RootSession = class {
|
|
|
29984
31246
|
const providerProfile = childAgent.provider?.profile ?? this.#options.providerName;
|
|
29985
31247
|
const invocationGrantKey = grantKeyForAgentInvocation(childAgent.name, providerProfile);
|
|
29986
31248
|
if (this.#grants.has(invocationGrantKey)) return { ok: true };
|
|
29987
|
-
const decision =
|
|
31249
|
+
const decision = invocationDecisionForPermissionPreset(this.#permissionPreset);
|
|
29988
31250
|
if (decision === "deny") return { ok: false, error: `Agent invocation denied: ${childAgent.name}` };
|
|
29989
31251
|
if (decision !== "ask") return { ok: true };
|
|
29990
31252
|
this.events.emit({
|
|
@@ -30103,6 +31365,8 @@ var RootSession = class {
|
|
|
30103
31365
|
agent: childRuntimeAgent,
|
|
30104
31366
|
hooks: this.#options.hooks ?? this.#options.config.hooks,
|
|
30105
31367
|
maxTurns,
|
|
31368
|
+
countToolUseTurns: false,
|
|
31369
|
+
maxToolUseRounds: relaxedToolUseRoundLimit(maxTurns),
|
|
30106
31370
|
yes: this.#options.yes,
|
|
30107
31371
|
dryRun: this.#options.dryRun,
|
|
30108
31372
|
streaming: false,
|
|
@@ -30115,6 +31379,7 @@ var RootSession = class {
|
|
|
30115
31379
|
signal,
|
|
30116
31380
|
eventBus: this.events,
|
|
30117
31381
|
permissionPrompt: this.permissionPrompt,
|
|
31382
|
+
autoPermission: this.autoPermissionForBackend(backend, cwd, signal),
|
|
30118
31383
|
toolRegistry: this.tools,
|
|
30119
31384
|
grants: this.#grants,
|
|
30120
31385
|
transcriptWriter: this.#transcript,
|
|
@@ -30137,6 +31402,31 @@ var RootSession = class {
|
|
|
30137
31402
|
session.externalSessionKey = externalKey;
|
|
30138
31403
|
}
|
|
30139
31404
|
}
|
|
31405
|
+
if (result.endReason !== "end_turn") {
|
|
31406
|
+
const message = childExecutionFailureMessage(result.endReason, result.finalAnswer);
|
|
31407
|
+
if (result.endReason === "cancelled") {
|
|
31408
|
+
this.events.emit({
|
|
31409
|
+
type: "agent.invocation.cancelled",
|
|
31410
|
+
rootSessionId: this.id,
|
|
31411
|
+
agentSessionId: session.id,
|
|
31412
|
+
invocationId,
|
|
31413
|
+
agent: childRuntimeAgent.name,
|
|
31414
|
+
reason: message,
|
|
31415
|
+
ts: now()
|
|
31416
|
+
});
|
|
31417
|
+
return { ok: false, error: `Agent invocation cancelled: ${message}`, invocationId, agentName: childRuntimeAgent.name, providerProfile, model: backend.model, metadata: { endReason: result.endReason } };
|
|
31418
|
+
}
|
|
31419
|
+
this.events.emit({
|
|
31420
|
+
type: "agent.invocation.failed",
|
|
31421
|
+
rootSessionId: this.id,
|
|
31422
|
+
agentSessionId: session.id,
|
|
31423
|
+
invocationId,
|
|
31424
|
+
agent: childRuntimeAgent.name,
|
|
31425
|
+
error: message,
|
|
31426
|
+
ts: now()
|
|
31427
|
+
});
|
|
31428
|
+
return { ok: false, error: `Agent invocation failed: ${message}`, invocationId, agentName: childRuntimeAgent.name, providerProfile, model: backend.model, metadata: { endReason: result.endReason } };
|
|
31429
|
+
}
|
|
30140
31430
|
this.updateAgentSession(session, input2, result.messages, result.finalAnswer);
|
|
30141
31431
|
if (beforeMessages !== session.messages.length) {
|
|
30142
31432
|
this.events.emit({
|
|
@@ -30212,7 +31502,7 @@ var RootSession = class {
|
|
|
30212
31502
|
}))
|
|
30213
31503
|
});
|
|
30214
31504
|
if (!this.#grants.has(grantKey)) {
|
|
30215
|
-
const decision = this.#
|
|
31505
|
+
const decision = invocationDecisionForPermissionPreset(this.#permissionPreset);
|
|
30216
31506
|
if (decision === "deny") return fail("Cowork invocation denied by configuration.");
|
|
30217
31507
|
if (decision === "ask") {
|
|
30218
31508
|
this.events.emit({
|
|
@@ -30396,43 +31686,27 @@ var RootSession = class {
|
|
|
30396
31686
|
}
|
|
30397
31687
|
async runCoworkMember(member, group, outcomes, context) {
|
|
30398
31688
|
const startedAt = now();
|
|
30399
|
-
const coworkAgent = this.decorateCoworkMemberAgent(member.agent, member.mode);
|
|
30400
|
-
const runtimeAgent = this.decorateChildAgent(coworkAgent);
|
|
30401
|
-
const backend = this.resolveInvocationBackend(member.providerProfile, void 0, runtimeAgent);
|
|
30402
|
-
this.events.emit({
|
|
30403
|
-
type: "cowork.member.started",
|
|
30404
|
-
rootSessionId: this.id,
|
|
30405
|
-
parentInvocationId: context.parent.parentInvocationId,
|
|
30406
|
-
groupId: context.groupId,
|
|
30407
|
-
memberId: member.memberId,
|
|
30408
|
-
agent: member.agent.name,
|
|
30409
|
-
provider: member.providerProfile,
|
|
30410
|
-
model: backend.model,
|
|
30411
|
-
mode: member.mode,
|
|
30412
|
-
ts: startedAt
|
|
30413
|
-
});
|
|
30414
31689
|
const input2 = withParentContext(member.input, group, outcomes);
|
|
30415
31690
|
const workspace = member.isolated ? await this.createCoworkIsolatedWorkspace(context.groupId, member.memberId) : { cwd: this.#options.cwd };
|
|
30416
31691
|
const snapshot = member.mode === "write" ? await this.createCoworkWriterSnapshot(workspace.cwd) : void 0;
|
|
30417
|
-
const
|
|
30418
|
-
sessionScope: `cowork:${context.groupId}:${member.memberId}`,
|
|
30419
|
-
signal: context.signal,
|
|
30420
|
-
writeScope: member.writeScope,
|
|
30421
|
-
cwd: workspace.cwd
|
|
30422
|
-
});
|
|
31692
|
+
const attempt = member.mode === "write" ? await this.runCoworkWriteAttempt(member, input2, snapshot, workspace.cwd, context) : await this.runCoworkNonWriteAttempts(member, input2, workspace.cwd, context);
|
|
30423
31693
|
const durationMs = now() - startedAt;
|
|
30424
31694
|
const audit = member.mode === "write" ? await this.auditCoworkWriter(member, snapshot, workspace.cwd, member.isolated) : void 0;
|
|
30425
31695
|
if ("cleanup" in workspace) await workspace.cleanup().catch(() => void 0);
|
|
31696
|
+
const result = attempt.result;
|
|
31697
|
+
const providerProfile = result.providerProfile;
|
|
30426
31698
|
if (result.ok) {
|
|
30427
31699
|
const outOfScope2 = audit?.outOfScopeFiles ?? [];
|
|
30428
31700
|
const outcome2 = {
|
|
30429
31701
|
memberId: member.memberId,
|
|
30430
31702
|
agent: member.agent.name,
|
|
30431
|
-
provider:
|
|
31703
|
+
provider: providerProfile,
|
|
30432
31704
|
status: outOfScope2.length ? "failed" : "completed",
|
|
30433
|
-
summary: preview(coworkMemberSummary(result.finalAnswer.trim() || "Completed.", audit), 1e3),
|
|
31705
|
+
summary: preview(coworkMemberSummary(result.finalAnswer.trim() || "Completed.", audit, attempt), 1e3),
|
|
30434
31706
|
artifacts: audit ? [coworkAuditArtifact(audit)] : [],
|
|
30435
31707
|
reason: outOfScope2.length ? "out_of_scope" : void 0,
|
|
31708
|
+
fallbackReason: attempt.fallbackReason,
|
|
31709
|
+
repairReason: attempt.repairReason,
|
|
30436
31710
|
error: outOfScope2.length ? `Writer changed files outside writeScope: ${outOfScope2.join(", ")}` : void 0,
|
|
30437
31711
|
invocationId: result.invocationId,
|
|
30438
31712
|
tokens: result.usage,
|
|
@@ -30450,11 +31724,13 @@ var RootSession = class {
|
|
|
30450
31724
|
const outcome = {
|
|
30451
31725
|
memberId: member.memberId,
|
|
30452
31726
|
agent: member.agent.name,
|
|
30453
|
-
provider:
|
|
31727
|
+
provider: providerProfile,
|
|
30454
31728
|
status: cancelled ? "cancelled" : "failed",
|
|
30455
|
-
summary: preview(coworkMemberSummary(result.error, audit), 1e3),
|
|
31729
|
+
summary: preview(coworkMemberSummary(result.error, audit, attempt), 1e3),
|
|
30456
31730
|
artifacts: audit ? [coworkAuditArtifact(audit)] : [],
|
|
30457
31731
|
reason: cancelled ? "cancelled" : outOfScope.length ? "out_of_scope" : void 0,
|
|
31732
|
+
fallbackReason: attempt.fallbackReason,
|
|
31733
|
+
repairReason: attempt.repairReason,
|
|
30458
31734
|
error: outOfScope.length ? `${result.error}
|
|
30459
31735
|
Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.error,
|
|
30460
31736
|
invocationId: result.invocationId,
|
|
@@ -30467,6 +31743,115 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
30467
31743
|
}
|
|
30468
31744
|
return outcome;
|
|
30469
31745
|
}
|
|
31746
|
+
async runCoworkNonWriteAttempts(member, input2, cwd, context) {
|
|
31747
|
+
const providers = [{ provider: member.providerProfile, model: member.model, modelProfileName: member.modelProfileName }, ...member.remainingProviderSelections];
|
|
31748
|
+
const errors = [];
|
|
31749
|
+
let fallbackReason;
|
|
31750
|
+
for (const [index, selection] of providers.entries()) {
|
|
31751
|
+
const overlay = index === 0 && member.permissionOverlay ? member.permissionOverlay : this.compileCoworkOverlay(member, selection.provider);
|
|
31752
|
+
const result2 = await this.executeCoworkMemberAttempt(member, selection, overlay, input2, cwd, context, `cowork:${context.groupId}:${member.memberId}:${providerSelectionScope(selection)}`);
|
|
31753
|
+
if (result2.ok) return { result: result2, fallbackReason };
|
|
31754
|
+
errors.push(`${providerSelectionLabel2(selection)}: ${result2.error}`);
|
|
31755
|
+
if (context.signal.aborted) return { result: result2, fallbackReason };
|
|
31756
|
+
const policy = pickCoworkNonWriteFallbackPolicy(result2.error, this.#options.config.cowork.fallback.nonWrite);
|
|
31757
|
+
const nextProvider = providers[index + 1];
|
|
31758
|
+
if ((policy === "next-provider" || policy === "skip-provider") && nextProvider) {
|
|
31759
|
+
if (policy === "skip-provider") this.#coworkProviderAvailability[selection.provider] = "unavailable";
|
|
31760
|
+
fallbackReason = `${providerSelectionLabel2(selection)} failed (${coworkFailureKind(result2.error)}); switched to ${providerSelectionLabel2(nextProvider)}`;
|
|
31761
|
+
continue;
|
|
31762
|
+
}
|
|
31763
|
+
return { result: withCombinedCoworkErrors(result2, errors), fallbackReason };
|
|
31764
|
+
}
|
|
31765
|
+
const result = {
|
|
31766
|
+
ok: false,
|
|
31767
|
+
error: `Agent invocation failed: all cowork providers failed for ${member.agent.name}.
|
|
31768
|
+
${errors.join("\n")}`,
|
|
31769
|
+
agentName: member.agent.name,
|
|
31770
|
+
providerProfile: providers.at(-1)?.provider ?? member.providerProfile
|
|
31771
|
+
};
|
|
31772
|
+
return { result, fallbackReason };
|
|
31773
|
+
}
|
|
31774
|
+
async runCoworkWriteAttempt(member, input2, snapshot, cwd, context) {
|
|
31775
|
+
const selection = { provider: member.providerProfile, model: member.model, modelProfileName: member.modelProfileName };
|
|
31776
|
+
const result = await this.executeCoworkMemberAttempt(member, selection, member.permissionOverlay, input2, cwd, context, `cowork:${context.groupId}:${member.memberId}`);
|
|
31777
|
+
if (result.ok || context.signal.aborted) return { result };
|
|
31778
|
+
const policy = this.#options.config.cowork.fallback.write;
|
|
31779
|
+
if (policy.onFailure !== "repair-from-worktree" || policy.maxRepairAttempts < 1) return { result };
|
|
31780
|
+
const repairContext = await this.buildCoworkRepairContext(result.error, snapshot, cwd);
|
|
31781
|
+
if (!repairContext.hasChangedFiles) return { result };
|
|
31782
|
+
let current = result;
|
|
31783
|
+
let repairReason = `write attempt failed on ${member.providerProfile}; retried same provider after reading changed files`;
|
|
31784
|
+
for (let attempt = 1; attempt <= policy.maxRepairAttempts; attempt++) {
|
|
31785
|
+
const repairInput = {
|
|
31786
|
+
...input2,
|
|
31787
|
+
task: buildCoworkRepairTask(input2.task, current.error, attempt),
|
|
31788
|
+
context: [input2.context, repairContext.text].filter(Boolean).join("\n\n") || void 0
|
|
31789
|
+
};
|
|
31790
|
+
current = await this.executeCoworkMemberAttempt(member, selection, member.permissionOverlay, repairInput, cwd, context, `cowork:${context.groupId}:${member.memberId}:repair:${attempt}`);
|
|
31791
|
+
if (current.ok) return { result: current, repairReason };
|
|
31792
|
+
if (context.signal.aborted) return { result: current, repairReason };
|
|
31793
|
+
}
|
|
31794
|
+
return { result: current, repairReason };
|
|
31795
|
+
}
|
|
31796
|
+
async executeCoworkMemberAttempt(member, selection, overlay, input2, cwd, context, sessionScope) {
|
|
31797
|
+
const providerProfile = selection.provider;
|
|
31798
|
+
const decoratedCoworkAgent = this.decorateCoworkMemberAgent(member.agent, member.mode, overlay);
|
|
31799
|
+
const coworkAgent2 = { ...decoratedCoworkAgent, provider: { ...decoratedCoworkAgent.provider, profile: providerProfile } };
|
|
31800
|
+
const runtimeAgent = this.decorateChildAgent(coworkAgent2);
|
|
31801
|
+
let model = "";
|
|
31802
|
+
try {
|
|
31803
|
+
model = this.resolveInvocationBackend(providerProfile, { model: selection.model, modelProfileName: selection.modelProfileName }, runtimeAgent).model;
|
|
31804
|
+
} catch (error) {
|
|
31805
|
+
return { ok: false, error: `Agent invocation failed: ${errorMessage(error)}`, agentName: runtimeAgent.name, providerProfile };
|
|
31806
|
+
}
|
|
31807
|
+
this.events.emit({
|
|
31808
|
+
type: "cowork.member.started",
|
|
31809
|
+
rootSessionId: this.id,
|
|
31810
|
+
parentInvocationId: context.parent.parentInvocationId,
|
|
31811
|
+
groupId: context.groupId,
|
|
31812
|
+
memberId: member.memberId,
|
|
31813
|
+
agent: member.agent.name,
|
|
31814
|
+
provider: providerProfile,
|
|
31815
|
+
model,
|
|
31816
|
+
mode: member.mode,
|
|
31817
|
+
ts: now()
|
|
31818
|
+
});
|
|
31819
|
+
return this.executeChildAgentSession(input2, coworkAgent2, context.parent, {
|
|
31820
|
+
sessionScope,
|
|
31821
|
+
signal: context.signal,
|
|
31822
|
+
writeScope: member.writeScope,
|
|
31823
|
+
cwd
|
|
31824
|
+
});
|
|
31825
|
+
}
|
|
31826
|
+
compileCoworkOverlay(member, providerProfile) {
|
|
31827
|
+
return compileCoworkPermissionOverlay({
|
|
31828
|
+
config: this.#options.config.cowork,
|
|
31829
|
+
agent: member.agent,
|
|
31830
|
+
providerProfile,
|
|
31831
|
+
mode: member.mode
|
|
31832
|
+
});
|
|
31833
|
+
}
|
|
31834
|
+
async buildCoworkRepairContext(error, snapshot, cwd) {
|
|
31835
|
+
const summary = snapshot ? await diffWorkspaceSnapshot(snapshot).catch(() => void 0) : void 0;
|
|
31836
|
+
const files = summary?.files ?? [];
|
|
31837
|
+
const chunks = [
|
|
31838
|
+
"Cowork write repair context:",
|
|
31839
|
+
"- Do not switch providers for this write repair.",
|
|
31840
|
+
"- Re-read the current workspace state below and repair from it.",
|
|
31841
|
+
"",
|
|
31842
|
+
"Failure:",
|
|
31843
|
+
error,
|
|
31844
|
+
"",
|
|
31845
|
+
files.length ? "Changed files since the failed attempt:" : "No changed files were detected after the failed attempt."
|
|
31846
|
+
];
|
|
31847
|
+
for (const file of files) chunks.push(`- ${file.path} (${file.kind}, +${file.addedLines}/-${file.removedLines})`);
|
|
31848
|
+
if (summary?.diff) chunks.push("", "Diff since original snapshot:", preview(summary.diff, 6e3));
|
|
31849
|
+
if (this.#options.config.cowork.fallback.write.readChangedFilesBeforeRepair) {
|
|
31850
|
+
const fileContents = await readCoworkRepairFiles(cwd, files.map((file) => file.path));
|
|
31851
|
+
if (fileContents.length) chunks.push("", "Current changed file contents:", ...fileContents);
|
|
31852
|
+
}
|
|
31853
|
+
return { text: chunks.join("\n"), hasChangedFiles: files.length > 0 };
|
|
31854
|
+
}
|
|
30470
31855
|
async createCoworkWriterSnapshot(cwd) {
|
|
30471
31856
|
try {
|
|
30472
31857
|
return await createWorkspaceSnapshot(cwd);
|
|
@@ -30493,8 +31878,8 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
30493
31878
|
}
|
|
30494
31879
|
}
|
|
30495
31880
|
async createCoworkIsolatedWorkspace(groupId, memberId) {
|
|
30496
|
-
const root = await mkdtemp(
|
|
30497
|
-
const cwd =
|
|
31881
|
+
const root = await mkdtemp(path34.join(os15.tmpdir(), `demian-cowork-${groupId}-${memberId}-`));
|
|
31882
|
+
const cwd = path34.join(root, "workspace");
|
|
30498
31883
|
await cp(this.#options.cwd, cwd, {
|
|
30499
31884
|
recursive: true,
|
|
30500
31885
|
filter: (source) => shouldCopyIntoCoworkWorkspace(this.#options.cwd, source)
|
|
@@ -30570,11 +31955,28 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
30570
31955
|
if (denied?.includes(target.name)) return { ok: false, error: `Agent ${parent.parentAgent.name} is configured not to cowork with ${target.name}.` };
|
|
30571
31956
|
const maxDepth = parent.parentAgent.delegation?.maxDepth ?? this.#options.config.delegation.maxDepth;
|
|
30572
31957
|
if (parent.agentPath.length > maxDepth) return { ok: false, error: `Max delegation depth (${maxDepth}) exceeded.` };
|
|
31958
|
+
let assignment;
|
|
31959
|
+
try {
|
|
31960
|
+
assignment = resolveCoworkAssignment({
|
|
31961
|
+
member,
|
|
31962
|
+
agent: target,
|
|
31963
|
+
config: this.#options.config.cowork,
|
|
31964
|
+
defaultProvider: this.#options.providerName,
|
|
31965
|
+
availability: this.#coworkProviderAvailability
|
|
31966
|
+
});
|
|
31967
|
+
} catch (error) {
|
|
31968
|
+
return { ok: false, error: errorMessage(error) };
|
|
31969
|
+
}
|
|
30573
31970
|
group.push({
|
|
30574
31971
|
memberId,
|
|
30575
31972
|
input: member,
|
|
30576
31973
|
agent: target,
|
|
30577
|
-
providerProfile:
|
|
31974
|
+
providerProfile: assignment.providerProfile,
|
|
31975
|
+
model: assignment.model,
|
|
31976
|
+
modelProfileName: assignment.modelProfileName,
|
|
31977
|
+
remainingProviders: assignment.remainingProviders,
|
|
31978
|
+
remainingProviderSelections: assignment.remainingProviderSelections,
|
|
31979
|
+
permissionOverlay: assignment.permissionOverlay,
|
|
30578
31980
|
mode,
|
|
30579
31981
|
writeScope,
|
|
30580
31982
|
isolated: mode === "write" && strategy === "multi-patch" && this.#options.config.cowork.writerIsolation === "snapshot-diff",
|
|
@@ -30636,24 +32038,54 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
30636
32038
|
systemPrompt: [agent.systemPrompt, this.callableCatalogPrompt(agent)].filter(Boolean).join("\n\n")
|
|
30637
32039
|
});
|
|
30638
32040
|
}
|
|
30639
|
-
decorateCoworkMemberAgent(agent, mode) {
|
|
30640
|
-
|
|
32041
|
+
decorateCoworkMemberAgent(agent, mode, overlay) {
|
|
32042
|
+
const overlayPermissions = overlay?.rules ?? [];
|
|
32043
|
+
const overlayDefaultDecision = overlay?.defaultDecision ?? agent.defaultDecision;
|
|
32044
|
+
const systemPrompt = [agent.systemPrompt, coworkMemberToolBoundaryPrompt(mode)].join("\n\n");
|
|
32045
|
+
if (mode === "write") {
|
|
32046
|
+
return this.withPermissionPreset({
|
|
32047
|
+
...agent,
|
|
32048
|
+
systemPrompt,
|
|
32049
|
+
permissions: [...agent.permissions, ...overlayPermissions],
|
|
32050
|
+
defaultDecision: overlayDefaultDecision
|
|
32051
|
+
});
|
|
32052
|
+
}
|
|
30641
32053
|
const readTools = /* @__PURE__ */ new Set(["read_file", "grep", "glob", "web_search", "update_plan", "report_progress"]);
|
|
30642
32054
|
const denyTools = ["write_file", "edit_file", "bash", "claudecode.Edit", "claudecode.Write", "claudecode.MultiEdit", "claudecode.Bash"];
|
|
30643
32055
|
return this.withPermissionPreset({
|
|
30644
32056
|
...agent,
|
|
30645
|
-
|
|
32057
|
+
systemPrompt,
|
|
30646
32058
|
tools: agent.tools.filter((tool) => readTools.has(tool)),
|
|
30647
32059
|
permissions: [
|
|
30648
32060
|
...agent.permissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
32061
|
+
...overlayPermissions.filter((rule) => !denyTools.includes(rule.tool)),
|
|
30649
32062
|
...denyTools.map((tool) => ({ tool, decision: "deny", reason: "cowork read-only member" }))
|
|
30650
32063
|
],
|
|
30651
|
-
defaultDecision: "deny"
|
|
32064
|
+
defaultDecision: overlayDefaultDecision === "allow" ? "allow" : "deny"
|
|
30652
32065
|
});
|
|
30653
32066
|
}
|
|
30654
32067
|
withPermissionPreset(agent) {
|
|
30655
32068
|
return applyPermissionPresetToAgent(agent, this.#permissionPreset);
|
|
30656
32069
|
}
|
|
32070
|
+
autoPermissionForBackend(backend, cwd, signal) {
|
|
32071
|
+
if (this.#permissionPreset !== "auto") return void 0;
|
|
32072
|
+
if (backend.kind === "provider") return createAutoPermissionAgent({ provider: backend.provider, model: backend.model, cwd, signal });
|
|
32073
|
+
return this.autoPermissionForExternalRuntime(cwd, signal);
|
|
32074
|
+
}
|
|
32075
|
+
autoPermissionForExternalRuntime(cwd, signal) {
|
|
32076
|
+
if (this.#permissionPreset !== "auto") return void 0;
|
|
32077
|
+
const candidates = /* @__PURE__ */ new Set([this.#options.providerName, this.#options.config.defaultProvider, ...Object.keys(this.#options.config.providers)]);
|
|
32078
|
+
for (const profileName of candidates) {
|
|
32079
|
+
try {
|
|
32080
|
+
const providerConfig = resolveProviderConfig(this.#options.config, profileName);
|
|
32081
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName });
|
|
32082
|
+
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 });
|
|
32083
|
+
if (backend.kind === "provider") return createAutoPermissionAgent({ provider: backend.provider, model: backend.model, cwd, signal });
|
|
32084
|
+
} catch {
|
|
32085
|
+
}
|
|
32086
|
+
}
|
|
32087
|
+
return void 0;
|
|
32088
|
+
}
|
|
30657
32089
|
callableCatalogPrompt(caller) {
|
|
30658
32090
|
const allowed = caller.delegation?.allowedAgents;
|
|
30659
32091
|
const denied = new Set(caller.delegation?.deniedAgents ?? []);
|
|
@@ -30688,10 +32120,13 @@ Writer changed files outside writeScope: ${outOfScope.join(", ")}` : result.erro
|
|
|
30688
32120
|
}
|
|
30689
32121
|
return lines.join("\n");
|
|
30690
32122
|
}
|
|
30691
|
-
resolveInvocationBackend(profileName,
|
|
30692
|
-
const
|
|
32123
|
+
resolveInvocationBackend(profileName, modelSelection, agent) {
|
|
32124
|
+
const modelOverride = typeof modelSelection === "string" ? modelSelection : modelSelection?.model;
|
|
32125
|
+
const modelProfileName = typeof modelSelection === "string" ? void 0 : modelSelection?.modelProfileName;
|
|
32126
|
+
const runtime = resolveProviderRuntimeConfig(this.#options.config, { providerName: profileName, model: modelOverride, modelProfileName });
|
|
30693
32127
|
const providerConfig = runtime.providerConfig;
|
|
30694
|
-
|
|
32128
|
+
const selectedModel = modelOverride ?? (modelProfileName ? runtime.model : void 0);
|
|
32129
|
+
if (this.#options.resolveExecutionBackend) return this.#options.resolveExecutionBackend(profileName, providerConfig, selectedModel);
|
|
30695
32130
|
if (this.#options.resolveProvider) {
|
|
30696
32131
|
const resolved = this.#options.resolveProvider(profileName, providerConfig, runtime.model);
|
|
30697
32132
|
return { kind: "provider", ...resolved };
|
|
@@ -30798,17 +32233,17 @@ function findDependencyCycle(group) {
|
|
|
30798
32233
|
const byId = new Map(group.map((member) => [member.memberId, member]));
|
|
30799
32234
|
const visiting = /* @__PURE__ */ new Set();
|
|
30800
32235
|
const visited = /* @__PURE__ */ new Set();
|
|
30801
|
-
const
|
|
32236
|
+
const path37 = [];
|
|
30802
32237
|
const visit = (id) => {
|
|
30803
|
-
if (visiting.has(id)) return [...
|
|
32238
|
+
if (visiting.has(id)) return [...path37.slice(path37.indexOf(id)), id];
|
|
30804
32239
|
if (visited.has(id)) return void 0;
|
|
30805
32240
|
visiting.add(id);
|
|
30806
|
-
|
|
32241
|
+
path37.push(id);
|
|
30807
32242
|
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
30808
32243
|
const cycle = visit(dep);
|
|
30809
32244
|
if (cycle) return cycle;
|
|
30810
32245
|
}
|
|
30811
|
-
|
|
32246
|
+
path37.pop();
|
|
30812
32247
|
visiting.delete(id);
|
|
30813
32248
|
visited.add(id);
|
|
30814
32249
|
return void 0;
|
|
@@ -30849,7 +32284,7 @@ function scopeStaticPrefix(pattern) {
|
|
|
30849
32284
|
return prefix.replace(/\/+$/, "").split("/").filter(Boolean).join("/") || ".";
|
|
30850
32285
|
}
|
|
30851
32286
|
function shouldCopyIntoCoworkWorkspace(root, source) {
|
|
30852
|
-
const relative =
|
|
32287
|
+
const relative = path34.relative(root, source).split(path34.sep).join("/");
|
|
30853
32288
|
if (!relative) return true;
|
|
30854
32289
|
const parts = relative.split("/");
|
|
30855
32290
|
return !parts.includes(".git") && !parts.includes("node_modules") && !parts.includes(".demian");
|
|
@@ -30980,8 +32415,14 @@ function summarizeCoworkResult(goal, outcomes) {
|
|
|
30980
32415
|
}
|
|
30981
32416
|
return lines.join("\n");
|
|
30982
32417
|
}
|
|
30983
|
-
function coworkMemberSummary(text, audit) {
|
|
32418
|
+
function coworkMemberSummary(text, audit, attempt) {
|
|
30984
32419
|
const lines = [text];
|
|
32420
|
+
if (attempt?.fallbackReason) {
|
|
32421
|
+
lines.push("", `Fallback: ${attempt.fallbackReason}`);
|
|
32422
|
+
}
|
|
32423
|
+
if (attempt?.repairReason) {
|
|
32424
|
+
lines.push("", `Repair: ${attempt.repairReason}`);
|
|
32425
|
+
}
|
|
30985
32426
|
if (audit?.summary?.files.length) {
|
|
30986
32427
|
lines.push("", `Workspace changes: ${audit.summary.files.map((file) => file.path).join(", ")}`);
|
|
30987
32428
|
}
|
|
@@ -31002,6 +32443,16 @@ function externalMainRuntimePrompt() {
|
|
|
31002
32443
|
"- Do not announce that delegate_agent or cowork is unavailable unless the user explicitly requested one of those tools."
|
|
31003
32444
|
].join("\n");
|
|
31004
32445
|
}
|
|
32446
|
+
function coworkMemberToolBoundaryPrompt(mode) {
|
|
32447
|
+
const capability = mode === "write" ? "read/write tools allowed by the delegated writeScope" : "read-only inspection tools";
|
|
32448
|
+
return [
|
|
32449
|
+
"Cowork member tool boundary:",
|
|
32450
|
+
"- You are already running inside a Demian cowork group; do not try to call cowork or delegate_agent from this member.",
|
|
32451
|
+
`- Use only the ${capability} exposed by your current runtime.`,
|
|
32452
|
+
"- 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.",
|
|
32453
|
+
"- Do not report missing cowork/delegate_agent as a blocker; report a blocker only when the delegated workspace capability itself is missing."
|
|
32454
|
+
].join("\n");
|
|
32455
|
+
}
|
|
31005
32456
|
function coworkAuditArtifact(audit) {
|
|
31006
32457
|
return {
|
|
31007
32458
|
type: "workspace-diff-audit",
|
|
@@ -31030,6 +32481,80 @@ function formatCoworkResult(result) {
|
|
|
31030
32481
|
)
|
|
31031
32482
|
].join("\n");
|
|
31032
32483
|
}
|
|
32484
|
+
function pickCoworkNonWriteFallbackPolicy(error, policy) {
|
|
32485
|
+
const kind = coworkFailureKind(error);
|
|
32486
|
+
if (kind === "auth") return policy.onAuthFailure;
|
|
32487
|
+
if (kind === "token-limit") return policy.onTokenLimit;
|
|
32488
|
+
if (kind === "connection") return policy.onConnectionError;
|
|
32489
|
+
return "fail-member";
|
|
32490
|
+
}
|
|
32491
|
+
function coworkFailureKind(error) {
|
|
32492
|
+
const text = error.toLowerCase();
|
|
32493
|
+
if (/\b(401|403)\b|unauthorized|forbidden|auth|api key|apikey|invalid key|invalid_key/.test(text)) return "auth";
|
|
32494
|
+
if (/token|context length|quota|rate limit|too many requests|\b429\b|maximum context|max_tokens|max tokens/.test(text)) return "token-limit";
|
|
32495
|
+
if (/econnreset|econnrefused|etimedout|enotfound|network|fetch failed|connection|socket|timeout|temporarily unavailable|no scripted response/.test(text)) return "connection";
|
|
32496
|
+
return "unknown";
|
|
32497
|
+
}
|
|
32498
|
+
function childExecutionFailureMessage(endReason, finalAnswer) {
|
|
32499
|
+
const answer = finalAnswer?.trim();
|
|
32500
|
+
if (answer) return answer;
|
|
32501
|
+
if (endReason === "max_turns") return "Stopped after reaching maxTurns before producing a verified result.";
|
|
32502
|
+
if (endReason === "tool_loop_limit") return "Stopped after reaching the tool-use safety limit before producing a verified result.";
|
|
32503
|
+
if (endReason === "content_filter") return "Provider safety filter blocked the child agent response.";
|
|
32504
|
+
if (endReason === "cancelled") return "Cancelled.";
|
|
32505
|
+
if (endReason === "error") return "Child agent runtime ended with an error.";
|
|
32506
|
+
return `Child agent ended with ${endReason}.`;
|
|
32507
|
+
}
|
|
32508
|
+
function withCombinedCoworkErrors(result, errors) {
|
|
32509
|
+
if (result.ok || errors.length <= 1) return result;
|
|
32510
|
+
return {
|
|
32511
|
+
...result,
|
|
32512
|
+
error: `${result.error}
|
|
32513
|
+
|
|
32514
|
+
Cowork provider attempts:
|
|
32515
|
+
${errors.join("\n")}`
|
|
32516
|
+
};
|
|
32517
|
+
}
|
|
32518
|
+
function providerSelectionLabel2(selection) {
|
|
32519
|
+
const model = selection.modelProfileName ?? selection.model;
|
|
32520
|
+
return model ? `${selection.provider}:${model}` : selection.provider;
|
|
32521
|
+
}
|
|
32522
|
+
function providerSelectionScope(selection) {
|
|
32523
|
+
return providerSelectionLabel2(selection).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
32524
|
+
}
|
|
32525
|
+
function buildCoworkRepairTask(originalTask, error, attempt) {
|
|
32526
|
+
return [
|
|
32527
|
+
`Repair attempt ${attempt} for a failed cowork write task.`,
|
|
32528
|
+
"",
|
|
32529
|
+
"Original task:",
|
|
32530
|
+
originalTask,
|
|
32531
|
+
"",
|
|
32532
|
+
"Previous failure:",
|
|
32533
|
+
error,
|
|
32534
|
+
"",
|
|
32535
|
+
"Continue from the current workspace state. Do not restart blindly or overwrite unrelated changes. Stay inside the delegated writeScope."
|
|
32536
|
+
].join("\n");
|
|
32537
|
+
}
|
|
32538
|
+
async function readCoworkRepairFiles(cwd, relativePaths) {
|
|
32539
|
+
const chunks = [];
|
|
32540
|
+
let budget = 12e3;
|
|
32541
|
+
for (const relativePath of relativePaths.slice(0, 8)) {
|
|
32542
|
+
if (budget <= 0) break;
|
|
32543
|
+
const normalized = relativePath.split(path34.sep).join("/");
|
|
32544
|
+
const absolutePath = path34.resolve(cwd, normalized);
|
|
32545
|
+
if (!absolutePath.startsWith(path34.resolve(cwd) + path34.sep) && absolutePath !== path34.resolve(cwd)) continue;
|
|
32546
|
+
try {
|
|
32547
|
+
const content = await readFile17(absolutePath, "utf8");
|
|
32548
|
+
const clipped = preview(content, budget);
|
|
32549
|
+
budget -= clipped.length;
|
|
32550
|
+
chunks.push([`File: ${normalized}`, "```", clipped, "```"].join("\n"));
|
|
32551
|
+
} catch (error) {
|
|
32552
|
+
chunks.push(`File: ${normalized}
|
|
32553
|
+
(unavailable: ${errorMessage(error)})`);
|
|
32554
|
+
}
|
|
32555
|
+
}
|
|
32556
|
+
return chunks;
|
|
32557
|
+
}
|
|
31033
32558
|
function sumUsage(outcomes) {
|
|
31034
32559
|
return outcomes.reduce(
|
|
31035
32560
|
(sum, outcome) => ({
|
|
@@ -31067,10 +32592,10 @@ function parseCoworkCommand(input2) {
|
|
|
31067
32592
|
const mergeStrategy = consumeMergeStrategy(tokens);
|
|
31068
32593
|
const task = tokens.join(" ").trim();
|
|
31069
32594
|
const memberHints = agents.length ? [] : inferInlineCoworkMembers(task, mode);
|
|
31070
|
-
const requestedAgents = agents.length ? agents :
|
|
32595
|
+
const requestedAgents = agents.length ? agents : unique3(memberHints.map((member) => member.agent));
|
|
31071
32596
|
if (!task) return { kind: "message", message: coworkUsage() };
|
|
31072
|
-
if (mode && !["read-only", "review", "write"].includes(mode)) {
|
|
31073
|
-
return { kind: "message", message: "Usage: /cowork [--agents a,b] [--mode read-only|review|write] [--parallel|--sequential] [--multi-patch] <task>" };
|
|
32597
|
+
if (mode && !["read-only", "review", "write", "manage"].includes(mode)) {
|
|
32598
|
+
return { kind: "message", message: "Usage: /cowork [--agents a,b] [--mode read-only|review|write|manage] [--parallel|--sequential] [--multi-patch] <task>" };
|
|
31074
32599
|
}
|
|
31075
32600
|
return {
|
|
31076
32601
|
kind: "run",
|
|
@@ -31080,8 +32605,8 @@ function parseCoworkCommand(input2) {
|
|
|
31080
32605
|
}
|
|
31081
32606
|
function coworkUsage() {
|
|
31082
32607
|
return [
|
|
31083
|
-
"Usage: /cowork [--agents a,b] [--mode read-only|review|write] [--parallel|--sequential] [--multi-patch] <task>",
|
|
31084
|
-
"Example: /cowork --agents
|
|
32608
|
+
"Usage: /cowork [--agents a,b] [--mode read-only|review|write|manage] [--parallel|--sequential] [--multi-patch] <task>",
|
|
32609
|
+
"Example: /cowork --agents supervisor,reviewer \uC774 \uBCC0\uACBD\uC0AC\uD56D\uC744 \uBCD1\uB82C \uAC80\uD1A0\uD574 \uC918"
|
|
31085
32610
|
].join("\n");
|
|
31086
32611
|
}
|
|
31087
32612
|
function buildCoworkPrompt(input2) {
|
|
@@ -31101,13 +32626,14 @@ function buildCoworkPrompt(input2) {
|
|
|
31101
32626
|
];
|
|
31102
32627
|
if (input2.agents.length) lines.push(`- Requested cowork agents: ${input2.agents.join(", ")}. Use these exact names in cowork.members[].agent.`);
|
|
31103
32628
|
if (input2.memberHints?.length) {
|
|
31104
|
-
lines.push("- Provider
|
|
32629
|
+
lines.push("- Provider names in the user request were mapped to cowork agents.");
|
|
31105
32630
|
lines.push("Suggested cowork member decomposition:");
|
|
31106
32631
|
for (const member of input2.memberHints) lines.push(` - ${member.agent} (from "${member.alias}"): ${member.task || input2.task}`);
|
|
31107
32632
|
}
|
|
31108
32633
|
if (input2.mode === "read-only") lines.push("- Use read-only cowork members; do not request file edits from members.");
|
|
31109
32634
|
if (input2.mode === "review") lines.push("- Use review/read-only cowork members focused on findings, risks, and missing tests.");
|
|
31110
32635
|
if (input2.mode === "write") lines.push("- At least one cowork member may write, but every write member must have a narrow writeScope.");
|
|
32636
|
+
if (input2.mode === "manage") lines.push("- Use planning/management cowork members focused on decomposition, coordination, and review gates.");
|
|
31111
32637
|
if (input2.execution) lines.push(`- Requested execution mode: ${input2.execution}.`);
|
|
31112
32638
|
if (input2.mergeStrategy === "multi-patch") lines.push("- Use merge.strategy=multi-patch and non-overlapping writeScope values for writer members.");
|
|
31113
32639
|
return lines.join("\n");
|
|
@@ -31134,15 +32660,15 @@ function inferInlineCoworkMembers(task, mode) {
|
|
|
31134
32660
|
}
|
|
31135
32661
|
function coworkAgentForAlias(alias, mode) {
|
|
31136
32662
|
const value = alias.toLowerCase();
|
|
31137
|
-
if (value === "codex") return mode === "review" ? "
|
|
31138
|
-
if (value === "claudecode" || value === "claude-code" || value === "claude_code") return mode === "write" ? "
|
|
32663
|
+
if (value === "codex") return mode === "review" ? "supervisor" : "planner";
|
|
32664
|
+
if (value === "claudecode" || value === "claude-code" || value === "claude_code") return mode === "write" ? "builder" : "reviewer";
|
|
31139
32665
|
return "";
|
|
31140
32666
|
}
|
|
31141
32667
|
function cleanInlineCoworkMemberTask(value) {
|
|
31142
32668
|
const text = value.replace(/^[\s,,;;::-]+/, "").replace(/[\s,,;;]+$/, "").replace(/^(?:너는|너는\s+|는|은|에게)\s*/, "").trim();
|
|
31143
32669
|
return text || void 0;
|
|
31144
32670
|
}
|
|
31145
|
-
function
|
|
32671
|
+
function unique3(values) {
|
|
31146
32672
|
return [...new Set(values)];
|
|
31147
32673
|
}
|
|
31148
32674
|
function consumeExecution(tokens) {
|
|
@@ -31475,13 +33001,13 @@ async function askModel(promptUi, selection) {
|
|
|
31475
33001
|
}
|
|
31476
33002
|
|
|
31477
33003
|
// src/ui/preferences.ts
|
|
31478
|
-
import { mkdir as mkdir14, readFile as
|
|
33004
|
+
import { mkdir as mkdir14, readFile as readFile18, writeFile as writeFile14 } from "node:fs/promises";
|
|
31479
33005
|
import fs10 from "node:fs";
|
|
31480
|
-
import
|
|
33006
|
+
import path35 from "node:path";
|
|
31481
33007
|
var UiPreferenceStore = class {
|
|
31482
33008
|
filePath;
|
|
31483
33009
|
constructor(cwd, filePath) {
|
|
31484
|
-
this.filePath = filePath ?
|
|
33010
|
+
this.filePath = filePath ? path35.resolve(cwd, filePath) : path35.join(cwd, ".demian", "preferences.json");
|
|
31485
33011
|
}
|
|
31486
33012
|
async load() {
|
|
31487
33013
|
if (!fs10.existsSync(this.filePath)) return void 0;
|
|
@@ -31499,13 +33025,13 @@ var UiPreferenceStore = class {
|
|
|
31499
33025
|
model: selection.model,
|
|
31500
33026
|
updatedAt: Date.now()
|
|
31501
33027
|
};
|
|
31502
|
-
await mkdir14(
|
|
33028
|
+
await mkdir14(path35.dirname(this.filePath), { recursive: true });
|
|
31503
33029
|
await writeFile14(this.filePath, `${JSON.stringify(file, null, 2)}
|
|
31504
33030
|
`, { mode: 384 });
|
|
31505
33031
|
}
|
|
31506
33032
|
async #read() {
|
|
31507
33033
|
try {
|
|
31508
|
-
return JSON.parse(await
|
|
33034
|
+
return JSON.parse(await readFile18(this.filePath, "utf8"));
|
|
31509
33035
|
} catch {
|
|
31510
33036
|
return void 0;
|
|
31511
33037
|
}
|
|
@@ -31531,7 +33057,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
31531
33057
|
return 0;
|
|
31532
33058
|
}
|
|
31533
33059
|
if (!flags.noWizard) await runFirstRunWizard().catch(() => void 0);
|
|
31534
|
-
const cwd =
|
|
33060
|
+
const cwd = path36.resolve(flags.cwd ?? process.cwd());
|
|
31535
33061
|
const config = await loadConfig({ cwd, configPath: flags.configPath });
|
|
31536
33062
|
const agentName = flags.agent ?? config.defaultAgent;
|
|
31537
33063
|
const preferenceStore = new UiPreferenceStore(cwd);
|
|
@@ -31677,6 +33203,7 @@ async function runPlainTask(options) {
|
|
|
31677
33203
|
for (const agentDefinition of Object.values(config.agents ?? {})) agentRegistry.register(agentDefinition, { scope: "user", trusted: true });
|
|
31678
33204
|
const agent = agentRegistry.get(agentName);
|
|
31679
33205
|
if (mode === "multi-agent") {
|
|
33206
|
+
const runMaxTurns2 = flags.maxTurns ?? config.maxTurns;
|
|
31680
33207
|
const root = new RootSession({
|
|
31681
33208
|
cwd,
|
|
31682
33209
|
config,
|
|
@@ -31684,7 +33211,7 @@ async function runPlainTask(options) {
|
|
|
31684
33211
|
providerName,
|
|
31685
33212
|
model: selection.model,
|
|
31686
33213
|
hooks: config.hooks,
|
|
31687
|
-
maxTurns:
|
|
33214
|
+
maxTurns: runMaxTurns2,
|
|
31688
33215
|
yes: flags.yes,
|
|
31689
33216
|
dryRun: flags.dryRun,
|
|
31690
33217
|
streaming: flags.streaming ?? config.streaming.enabled,
|
|
@@ -31713,6 +33240,7 @@ async function runPlainTask(options) {
|
|
|
31713
33240
|
return { history: interactiveHistoryFromRunMessages(result2.messages), lastExternalSessionKey: options.lastExternalSessionKey };
|
|
31714
33241
|
}
|
|
31715
33242
|
const runtimeAgent = activeGoal ? withGoalTools(agent) : agent;
|
|
33243
|
+
const runMaxTurns = flags.maxTurns ?? config.maxTurns;
|
|
31716
33244
|
const runtime = resolveProviderRuntimeConfig(config, selection);
|
|
31717
33245
|
const backend = resolveExecutionBackend(runtime.providerConfig, {
|
|
31718
33246
|
model: runtime.model,
|
|
@@ -31747,7 +33275,9 @@ async function runPlainTask(options) {
|
|
|
31747
33275
|
providerName,
|
|
31748
33276
|
agent: runtimeAgent,
|
|
31749
33277
|
hooks: config.hooks,
|
|
31750
|
-
maxTurns:
|
|
33278
|
+
maxTurns: runMaxTurns,
|
|
33279
|
+
countToolUseTurns: false,
|
|
33280
|
+
maxToolUseRounds: relaxedToolUseRoundLimit(runMaxTurns),
|
|
31751
33281
|
yes: flags.yes,
|
|
31752
33282
|
dryRun: flags.dryRun,
|
|
31753
33283
|
streaming: flags.streaming ?? config.streaming.enabled,
|
|
@@ -31921,7 +33451,7 @@ Flags:
|
|
|
31921
33451
|
--agent <name> general, build, plan, execute, or a configured custom agent
|
|
31922
33452
|
--provider <name> openai, anthropic, gemini, groq, azure, lmstudio, ollama-local, ollama-cloud, llamacpp, vllm, codex, claudecode
|
|
31923
33453
|
--model <name> override configured model
|
|
31924
|
-
--max-turns <n> maximum
|
|
33454
|
+
--max-turns <n> maximum high-level task turns; tool-use rounds use a separate safety limit
|
|
31925
33455
|
--cwd <path> workspace directory
|
|
31926
33456
|
--yes, -y auto-allow ask permissions; hard deny and hook block still apply
|
|
31927
33457
|
--dry-run stop before write_file, edit_file, and bash execution
|