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