@dungle-scrubs/tallow 0.8.25 → 0.8.26

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.
Files changed (52) hide show
  1. package/dist/auth-hardening.d.ts +12 -0
  2. package/dist/auth-hardening.d.ts.map +1 -1
  3. package/dist/auth-hardening.js +30 -7
  4. package/dist/auth-hardening.js.map +1 -1
  5. package/dist/cli.js +5 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +1 -1
  8. package/dist/config.js +1 -1
  9. package/dist/install.js +2 -2
  10. package/dist/install.js.map +1 -1
  11. package/dist/interactive-mode-patch.d.ts.map +1 -1
  12. package/dist/interactive-mode-patch.js +119 -7
  13. package/dist/interactive-mode-patch.js.map +1 -1
  14. package/dist/model-metadata-overrides.d.ts +19 -0
  15. package/dist/model-metadata-overrides.d.ts.map +1 -0
  16. package/dist/model-metadata-overrides.js +38 -0
  17. package/dist/model-metadata-overrides.js.map +1 -0
  18. package/dist/sdk.d.ts +2 -0
  19. package/dist/sdk.d.ts.map +1 -1
  20. package/dist/sdk.js +28 -1
  21. package/dist/sdk.js.map +1 -1
  22. package/extensions/__integration__/teams-runtime.test.ts +22 -1
  23. package/extensions/_shared/__tests__/shell-policy.test.ts +197 -0
  24. package/extensions/_shared/shell-policy.ts +27 -0
  25. package/extensions/background-task-tool/index.ts +2 -1
  26. package/extensions/bash-tool-enhanced/index.ts +2 -1
  27. package/extensions/custom-footer/__tests__/index.test.ts +29 -0
  28. package/extensions/custom-footer/context-display.ts +49 -0
  29. package/extensions/custom-footer/index.ts +10 -23
  30. package/extensions/permissions/index.ts +31 -10
  31. package/extensions/plan-mode-tool/__tests__/index.test.ts +32 -2
  32. package/extensions/plan-mode-tool/index.ts +6 -1
  33. package/extensions/slash-command-bridge/index.ts +30 -1
  34. package/extensions/subagent-tool/__tests__/process-liveness.test.ts +42 -3
  35. package/extensions/subagent-tool/process.ts +132 -21
  36. package/extensions/tasks/__tests__/store.test.ts +26 -2
  37. package/extensions/tasks/commands/register-tasks-extension.ts +2 -2
  38. package/extensions/tasks/index.ts +5 -5
  39. package/extensions/tasks/state/index.ts +90 -36
  40. package/extensions/teams-tool/__tests__/archive-store.test.ts +98 -0
  41. package/extensions/teams-tool/__tests__/peer-messaging.test.ts +26 -0
  42. package/extensions/teams-tool/archive-store.ts +200 -0
  43. package/extensions/teams-tool/sessions/spawn.ts +244 -71
  44. package/extensions/teams-tool/tools/register-extension.ts +146 -105
  45. package/extensions/teams-tool/tools/teammate-tools.ts +43 -1
  46. package/package.json +4 -4
  47. package/skills/tallow-expert/SKILL.md +1 -1
  48. package/templates/agents/architect.md +13 -5
  49. package/templates/agents/debug.md +3 -3
  50. package/templates/agents/explore.md +9 -2
  51. package/templates/agents/refactor.md +2 -2
  52. package/templates/agents/scout.md +3 -2
@@ -23,6 +23,11 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
23
23
  import { Key, Loader, Text, type TUI } from "@mariozechner/pi-tui";
24
24
  import { Type } from "@sinclair/typebox";
25
25
  import { INTEROP_EVENT_NAMES, onInteropEvent } from "../../_shared/interop-events.js";
26
+ import {
27
+ deleteArchivedTeamFromDisk,
28
+ loadAllArchivedTeamsFromDisk,
29
+ writeArchivedTeamToDisk,
30
+ } from "../archive-store.js";
26
31
  import {
27
32
  appendDashboardFeedEvent,
28
33
  bindDashboardSessionTracking,
@@ -66,7 +71,6 @@ import {
66
71
  export function registerTeamsToolExtension(pi: ExtensionAPI): void {
67
72
  setInteropEvents(pi.events);
68
73
  let cwd = process.cwd();
69
- let dashboardCancelInFlight = false;
70
74
  let dashboardEnabled = false;
71
75
  let dashboardTicker: ReturnType<typeof setInterval> | undefined;
72
76
  let dashboardTui: TUI | undefined;
@@ -154,67 +158,82 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
154
158
  }
155
159
 
156
160
  /**
157
- * Abort all teammates that are currently streaming work.
158
- * @returns Number of teammates that received an abort request
161
+ * Refresh the in-memory archived-team index from disk.
162
+ *
163
+ * Team archives must survive process restarts, so the on-disk view is the
164
+ * source of truth. This helper keeps the legacy in-memory map synchronized
165
+ * for the existing store/query helpers.
166
+ *
167
+ * @returns void
159
168
  */
160
- async function abortRunningTeammates(): Promise<number> {
161
- const running: Array<{ teammate: Teammate; team: Team<Teammate> }> = [];
162
- for (const [, team] of getTeams() as Map<string, Team<Teammate>>) {
163
- for (const [, teammate] of team.teammates) {
164
- if (teammate.status !== "working" && !teammate.session.isStreaming) continue;
165
- running.push({ teammate, team });
166
- }
169
+ function refreshArchivedTeamStore(): void {
170
+ const archives = getArchivedTeams();
171
+ archives.clear();
172
+ for (const archived of loadAllArchivedTeamsFromDisk()) {
173
+ archives.set(archived.name, archived);
167
174
  }
168
- if (running.length === 0) return 0;
175
+ }
169
176
 
170
- await Promise.all(
171
- running.map(async ({ teammate }) => {
172
- try {
173
- await teammate.session.abort();
174
- } catch {
175
- // Best-effort abort.
176
- }
177
- })
178
- );
179
-
180
- const touchedTeams = new Set<Team<Teammate>>();
181
- const dashboardActivity = getDashboardActivity();
182
- for (const { teammate, team } of running) {
183
- if (teammate.status === "working") teammate.status = "idle";
184
- dashboardActivity.touch(team.name, teammate.name);
185
- appendDashboardFeedEvent(team.name, "orchestrator", teammate.name, "Cancelled run.");
186
- touchedTeams.add(team);
177
+ /**
178
+ * Move an active team into the archive store and persist it to disk.
179
+ *
180
+ * @param teamName - Active team name to archive
181
+ * @returns void
182
+ */
183
+ function archiveRuntimeTeam(teamName: string): void {
184
+ const archived = archiveTeam(teamName);
185
+ if (!archived) return;
186
+ try {
187
+ writeArchivedTeamToDisk(archived);
188
+ refreshArchivedTeamStore();
189
+ } catch (error) {
190
+ console.error(`Failed to persist archived team ${teamName}: ${error}`);
187
191
  }
188
- for (const team of touchedTeams) refreshTeamView(team);
189
- notifyDashboardChanged();
190
- return running.length;
191
192
  }
192
193
 
193
194
  /**
194
- * Handle Esc inside dashboard: cancel active work first, then close dashboard.
195
- * @param ctx - Extension context
196
- * @returns void
195
+ * Dispose teammate sessions for one team.
196
+ *
197
+ * @param team - Team whose live teammate sessions should be cleaned up
198
+ * @param abortStreaming - Whether currently streaming teammates should be aborted first
199
+ * @returns Number of teammates that were processed
197
200
  */
198
- function handleDashboardEscape(ctx: ExtensionContext): void {
199
- if (dashboardCancelInFlight) return;
200
- void (async () => {
201
- dashboardCancelInFlight = true;
201
+ async function disposeTeamSessions(
202
+ team: Team<Teammate>,
203
+ abortStreaming: boolean
204
+ ): Promise<number> {
205
+ let count = 0;
206
+ for (const [, mate] of team.teammates) {
202
207
  try {
203
- const cancelled = await abortRunningTeammates();
204
- if (cancelled > 0) {
205
- ctx.ui.notify(
206
- `Cancelled ${cancelled} running teammate${cancelled === 1 ? "" : "s"}. Press Esc again to close dashboard.`,
207
- "warning"
208
- );
209
- return;
208
+ if (abortStreaming && mate.session.isStreaming) {
209
+ await mate.session.abort();
210
210
  }
211
- if (!dashboardEnabled) return;
212
- disableDashboard(ctx, false);
213
- ctx.ui.notify("Team dashboard disabled.", "info");
211
+ mate.unsubscribe?.();
212
+ mate.session.dispose();
213
+ } catch (error) {
214
+ console.error(`Failed to clean up teammate ${mate.name}: ${error}`);
214
215
  } finally {
215
- dashboardCancelInFlight = false;
216
+ mate.status = "shutdown";
217
+ count++;
216
218
  }
217
- })();
219
+ }
220
+ return count;
221
+ }
222
+
223
+ /**
224
+ * Handle Escape inside dashboard mode.
225
+ *
226
+ * The dashboard is a view, not an interrupt button. Escape should return to
227
+ * chat without killing teammates; explicit teardown goes through team_shutdown
228
+ * or normal session shutdown.
229
+ *
230
+ * @param ctx - Extension context
231
+ * @returns void
232
+ */
233
+ function handleDashboardEscape(ctx: ExtensionContext): void {
234
+ if (!dashboardEnabled) return;
235
+ disableDashboard(ctx, false);
236
+ ctx.ui.notify("Team dashboard disabled. Teammates keep running.", "info");
218
237
  }
219
238
 
220
239
  /**
@@ -256,7 +275,6 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
256
275
  * @returns void
257
276
  */
258
277
  function disableDashboard(ctx: ExtensionContext, notify = true): void {
259
- dashboardCancelInFlight = false;
260
278
  dashboardEnabled = false;
261
279
  stopDashboardTicker();
262
280
  setDashboardRenderCallback(undefined);
@@ -289,6 +307,7 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
289
307
 
290
308
  pi.on("session_start", async (_event, ctx) => {
291
309
  cwd = ctx.cwd;
310
+ refreshArchivedTeamStore();
292
311
  publishTeamSnapshots();
293
312
  publishDashboardState();
294
313
  });
@@ -301,20 +320,10 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
301
320
  // Archive all teams on session shutdown (preserves tasks for future recovery)
302
321
  pi.on("session_shutdown", async () => {
303
322
  for (const [name, team] of getTeams() as Map<string, Team<Teammate>>) {
304
- for (const [, mate] of team.teammates) {
305
- try {
306
- if (mate.session.isStreaming) await mate.session.abort();
307
- mate.unsubscribe?.();
308
- mate.session.dispose();
309
- } catch (err) {
310
- console.error(`Failed to clean up teammate ${mate.name}: ${err}`);
311
- }
312
- mate.status = "shutdown";
313
- }
323
+ await disposeTeamSessions(team, true);
314
324
  removeTeamView(name);
315
- archiveTeam(name);
325
+ archiveRuntimeTeam(name);
316
326
  }
317
- dashboardCancelInFlight = false;
318
327
  dashboardEnabled = false;
319
328
  stopDashboardTicker();
320
329
  setDashboardRenderCallback(undefined);
@@ -334,21 +343,9 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
334
343
  for (const [name, team] of getTeams() as Map<string, Team<Teammate>>) {
335
344
  const hasActiveWork = [...team.teammates.values()].some((m) => m.status === "working");
336
345
  if (hasActiveWork) continue;
337
-
338
- // All teammates finished — clean up and archive
339
- for (const [, mate] of team.teammates) {
340
- if (mate.status === "idle") {
341
- try {
342
- mate.unsubscribe?.();
343
- mate.session.dispose();
344
- } catch {
345
- // Best-effort cleanup
346
- }
347
- mate.status = "shutdown";
348
- }
349
- }
346
+ await disposeTeamSessions(team, false);
350
347
  removeTeamView(name);
351
- archiveTeam(name);
348
+ archiveRuntimeTeam(name);
352
349
  }
353
350
  });
354
351
 
@@ -388,6 +385,7 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
388
385
  name: Type.String({ description: "Team name (unique)" }),
389
386
  }),
390
387
  async execute(_toolCallId, params) {
388
+ refreshArchivedTeamStore();
391
389
  if (getTeams().has(params.name)) {
392
390
  return {
393
391
  content: [{ type: "text", text: `Team "${params.name}" already exists.` }],
@@ -395,6 +393,20 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
395
393
  isError: true,
396
394
  };
397
395
  }
396
+ if (getArchivedTeams().has(params.name)) {
397
+ return {
398
+ content: [
399
+ {
400
+ type: "text",
401
+ text:
402
+ `Team "${params.name}" already exists as an archive. ` +
403
+ "Use team_resume to continue it or choose a new name.",
404
+ },
405
+ ],
406
+ details: {},
407
+ isError: true,
408
+ };
409
+ }
398
410
  createTeamStore(params.name);
399
411
  appendDashboardFeedEvent(params.name, "orchestrator", "all", `Team "${params.name}" created`);
400
412
  notifyDashboardChanged();
@@ -492,16 +504,27 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
492
504
  description: [
493
505
  "Spawn a teammate with their own agent session, shared task board access, and inter-agent messaging.",
494
506
  "They get standard coding tools plus team coordination tools.",
507
+ "Optionally load a named agent template for prompt/frontmatter defaults.",
495
508
  "After spawning, use team_send to give them initial instructions.",
496
509
  ].join(" "),
497
510
  parameters: Type.Object({
511
+ agent: Type.Optional(
512
+ Type.String({
513
+ description: "Optional agent template name to load from user/project agent directories.",
514
+ })
515
+ ),
498
516
  team: Type.String({ description: "Team name" }),
499
517
  name: Type.String({ description: "Teammate name (unique within team)" }),
500
- role: Type.String({ description: "Role/description (guides their behavior)" }),
518
+ role: Type.Optional(
519
+ Type.String({
520
+ description:
521
+ "Role/description (guides behavior). Optional when agent is provided; otherwise required.",
522
+ })
523
+ ),
501
524
  model: Type.Optional(
502
525
  Type.String({
503
526
  description:
504
- "Explicit model ID (fuzzy matched). When omitted, auto-routes based on role complexity.",
527
+ "Explicit model ID (fuzzy matched). When omitted, auto-routes based on role complexity or agent template.",
505
528
  })
506
529
  ),
507
530
  modelScope: Type.Optional(
@@ -510,10 +533,16 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
510
533
  'Constrain auto-routing to a model family (e.g. "codex", "gemini"). Ignored when explicit model is set.',
511
534
  })
512
535
  ),
536
+ thinkingLevel: Type.Optional(
537
+ Type.String({
538
+ description:
539
+ "Optional teammate thinking level: off, low, medium, or high. Defaults to the parent session when available.",
540
+ })
541
+ ),
513
542
  tools: Type.Optional(
514
543
  Type.Array(Type.String(), {
515
544
  description:
516
- "Standard tool names: read, bash, edit, write, grep, find, ls. Default: all coding tools.",
545
+ "Standard tool names: read, bash, edit, write, grep, find, ls. Default: all coding tools or the agent template allowlist.",
517
546
  })
518
547
  ),
519
548
  }),
@@ -538,19 +567,37 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
538
567
  isError: true,
539
568
  };
540
569
  }
570
+ if (!params.role && !params.agent) {
571
+ return {
572
+ content: [
573
+ {
574
+ type: "text",
575
+ text: "team_spawn requires either a role or an agent template.",
576
+ },
577
+ ],
578
+ details: {},
579
+ isError: true,
580
+ };
581
+ }
582
+
583
+ const inheritedThinkingLevel = (
584
+ ctx as { getThinkingLevel?: () => string | undefined } | undefined
585
+ )?.getThinkingLevel?.();
541
586
 
542
587
  try {
543
- const mate = await spawnTeammateSession(
588
+ const mate = await spawnTeammateSession({
589
+ agentName: params.agent,
544
590
  cwd,
591
+ hints: params.modelScope ? { modelScope: params.modelScope } : undefined,
592
+ modelOverride: params.model,
593
+ name: params.name,
594
+ parentModelId: ctx?.model?.id,
595
+ piEvents: pi.events,
596
+ role: params.role,
545
597
  team,
546
- params.name,
547
- params.role,
548
- params.model,
549
- params.tools,
550
- pi.events,
551
- params.modelScope ? { modelScope: params.modelScope } : undefined,
552
- ctx?.model?.id
553
- );
598
+ thinkingLevel: params.thinkingLevel ?? inheritedThinkingLevel,
599
+ toolNames: params.tools,
600
+ });
554
601
  mate.unsubscribe = bindDashboardSessionTracking(team.name, mate.name, mate.session);
555
602
  const dashboardActivity = getDashboardActivity();
556
603
  dashboardActivity.touch(team.name, mate.name);
@@ -801,23 +848,9 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
801
848
  };
802
849
  }
803
850
 
804
- let count = 0;
805
- for (const [, mate] of team.teammates) {
806
- try {
807
- if (mate.session.isStreaming) await mate.session.abort();
808
- mate.unsubscribe?.();
809
- mate.session.dispose();
810
- mate.status = "shutdown";
811
- count++;
812
- } catch (err) {
813
- console.error(`Failed to clean up teammate ${mate.name}: ${err}`);
814
- mate.status = "shutdown";
815
- count++;
816
- }
817
- }
818
-
851
+ const count = await disposeTeamSessions(team, true);
819
852
  removeTeamView(params.team);
820
- archiveTeam(params.team);
853
+ archiveRuntimeTeam(params.team);
821
854
  return {
822
855
  content: [
823
856
  {
@@ -857,6 +890,8 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
857
890
  ),
858
891
  }),
859
892
  async execute(_toolCallId, params) {
893
+ refreshArchivedTeamStore();
894
+
860
895
  // List mode — show all archived teams
861
896
  if (!params.team) {
862
897
  const archives = getArchivedTeams();
@@ -917,6 +952,12 @@ export function registerTeamsToolExtension(pi: ExtensionAPI): void {
917
952
  }
918
953
  }
919
954
 
955
+ try {
956
+ deleteArchivedTeamFromDisk(params.team);
957
+ } catch (error) {
958
+ console.error(`Failed to delete archived team ${params.team}: ${error}`);
959
+ }
960
+ refreshArchivedTeamStore();
920
961
  notifyDashboardChanged();
921
962
  return {
922
963
  content: [
@@ -9,7 +9,14 @@ import { getIcon } from "../../_icons/index.js";
9
9
  import { appendDashboardFeedEvent, refreshTeamView } from "../dashboard/state.js";
10
10
  import { autoDispatch, wakeTeammate } from "../dispatch/auto-dispatch.js";
11
11
  import type { Teammate } from "../state/types.js";
12
- import { addTeamMessage, getUnread, isTaskReady, markRead, type Team } from "../store.js";
12
+ import {
13
+ addTeamMessage,
14
+ getUnread,
15
+ isTaskReady,
16
+ markRead,
17
+ type Team,
18
+ type TeamTask,
19
+ } from "../store.js";
13
20
 
14
21
  /**
15
22
  * Create the team coordination tools for a specific teammate.
@@ -24,6 +31,23 @@ export function createTeammateTools(
24
31
  myName: string,
25
32
  piEvents?: ExtensionAPI["events"]
26
33
  ): ToolDefinition[] {
34
+ /**
35
+ * Validate that the current teammate owns a claimed task before mutating it.
36
+ *
37
+ * @param action - Mutation being attempted (`complete` or `fail`)
38
+ * @param task - Task being mutated
39
+ * @returns Error text when the mutation should be rejected, otherwise null
40
+ */
41
+ function getTaskOwnershipError(action: "complete" | "fail", task: TeamTask): string | null {
42
+ if (task.status !== "claimed") {
43
+ return `Task #${task.id} is ${task.status}. Claim it before trying to ${action} it.`;
44
+ }
45
+ if (task.assignee !== myName) {
46
+ return `Task #${task.id} is assigned to ${task.assignee ?? "(unassigned)"}. Only the assignee can ${action} it.`;
47
+ }
48
+ return null;
49
+ }
50
+
27
51
  const tasksTool: ToolDefinition = {
28
52
  name: "team_tasks",
29
53
  label: "team_tasks",
@@ -108,6 +132,15 @@ export function createTeammateTools(
108
132
  }
109
133
 
110
134
  if (params.action === "complete") {
135
+ const ownershipError = getTaskOwnershipError("complete", task);
136
+ if (ownershipError) {
137
+ return {
138
+ content: [{ type: "text" as const, text: ownershipError }],
139
+ details: {},
140
+ isError: true,
141
+ };
142
+ }
143
+
111
144
  task.status = "completed";
112
145
  task.result = params.result || "(completed)";
113
146
  piEvents?.emit("task_completed", {
@@ -129,6 +162,15 @@ export function createTeammateTools(
129
162
  }
130
163
 
131
164
  if (params.action === "fail") {
165
+ const ownershipError = getTaskOwnershipError("fail", task);
166
+ if (ownershipError) {
167
+ return {
168
+ content: [{ type: "text" as const, text: ownershipError }],
169
+ details: {},
170
+ isError: true,
171
+ };
172
+ }
173
+
132
174
  task.status = "failed";
133
175
  task.result = params.result || "(failed)";
134
176
  refreshTeamView(team as Team<Teammate>);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dungle-scrubs/tallow",
3
- "version": "0.8.25",
3
+ "version": "0.8.26",
4
4
  "description": "An opinionated coding agent. Built on pi.",
5
5
  "piConfig": {
6
6
  "name": "tallow",
@@ -74,7 +74,7 @@
74
74
  "dependencies": {
75
75
  "@clack/prompts": "^1.1.0",
76
76
  "@dungle-scrubs/synapse": "0.1.6",
77
- "@mariozechner/pi-coding-agent": "^0.58.4",
77
+ "@mariozechner/pi-coding-agent": "^0.60.0",
78
78
  "@mariozechner/pi-tui": "^0.60.0",
79
79
  "@opentelemetry/api": "^1.9.0",
80
80
  "@sinclair/typebox": "0.34.48",
@@ -85,8 +85,8 @@
85
85
  },
86
86
  "devDependencies": {
87
87
  "@biomejs/biome": "2.4.2",
88
- "@mariozechner/pi-agent-core": "^0.58.4",
89
- "@mariozechner/pi-ai": "^0.58.4",
88
+ "@mariozechner/pi-agent-core": "^0.60.0",
89
+ "@mariozechner/pi-ai": "^0.60.0",
90
90
  "@types/node": "25.2.3",
91
91
  "husky": "^9.1.7",
92
92
  "lint-staged": "^16.4.0",
@@ -33,7 +33,7 @@ Relay that answer to the user.
33
33
 
34
34
  | Component | Location |
35
35
  |-----------|----------|
36
- | Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, otel.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
36
+ | Core source | `src/` (agent-runner.ts, atomic-write.ts, auth-hardening.ts, cli-auto-rebuild.ts, cli.ts, compaction-cancel-patch.ts, config.ts, extensions-global.d.ts, fatal-errors.ts, index.ts, install.ts, interactive-mode-patch.ts, model-metadata-overrides.ts, otel.ts, pid-manager.ts, plugins.ts, process-cleanup.ts, project-trust-banner.ts, project-trust-interop.ts, project-trust.ts, runtime-path-provider.ts, runtime-provenance.ts, sdk.ts, session-migration.ts, session-utils.ts, startup-profile.ts, startup-timing.ts, streaming-yield-patch.ts, workspace-transition-interactive.ts, workspace-transition-relay.ts, workspace-transition.ts, yield-to-io.ts) |
37
37
  | Extensions | `extensions/` — extension.json + index.ts each (52 bundled) |
38
38
  | Skills | `skills/` — subdirs with SKILL.md |
39
39
  | Agents | `agents/` — markdown with YAML frontmatter |
@@ -7,17 +7,25 @@ description: High-level architecture agent — plans before coding, thinks in sy
7
7
  # model: claude-sonnet-4-5
8
8
  ---
9
9
 
10
- You are an architecture-focused agent. Before writing any code:
10
+ You are an architecture-focused agent.
11
11
 
12
- 1. **Understand the full request** ask clarifying questions if needed
13
- 2. **Survey the existing codebase** read project structure, key files, dependencies
12
+ Default behavior: **design first, do not implement unless the delegated task explicitly asks for implementation.**
13
+ Subagents often run without interactive confirmation, so never block waiting for a reply.
14
+ If requirements are ambiguous, state the assumptions you made and continue with the best design you can justify.
15
+
16
+ When the task includes implementation:
17
+ 1. **Understand the request** — identify constraints, unknowns, and likely edge cases
18
+ 2. **Survey the codebase** — read project structure, key files, and dependencies
14
19
  3. **Design first** — propose the architecture with:
15
20
  - Component diagram (as text)
16
21
  - Data flow
17
22
  - API boundaries
18
23
  - File/module structure
19
- 4. **Get confirmation** before implementing
20
- 5. **Implement incrementally** — one component at a time, testing as you go
24
+ 4. **Implement incrementally** one component at a time, testing as you go
25
+
26
+ When the task is design-only:
27
+ - Return the proposed architecture, tradeoffs, and a concrete implementation plan
28
+ - Do **not** stop to ask for confirmation
21
29
 
22
30
  Prefer composition over inheritance. Prefer explicit over implicit.
23
31
  Keep modules small and focused. Design for testability.
@@ -8,14 +8,14 @@ description: Debugging agent — methodical root cause analysis
8
8
 
9
9
  You are a debugging specialist. Follow this process strictly:
10
10
 
11
- 1. **Reproduce** — run the failing command/test to see the exact error
11
+ 1. **Reproduce** — run the narrowest command/test that shows the failure
12
12
  2. **Read the error** — full stack trace, error message, exit code
13
13
  3. **Form a hypothesis** — what could cause this specific error?
14
14
  4. **Gather evidence** — read relevant source files, add logging if needed
15
15
  5. **Test the hypothesis** — make the minimal change to verify
16
16
  6. **Fix** — apply the actual fix
17
- 7. **Verify** — run the original failing command to confirm it passes
18
- 8. **Check for regressions** — run the full test suite
17
+ 7. **Verify** — rerun the original reproducer to confirm it passes
18
+ 8. **Broaden only when justified** — run wider regression coverage when the fix touches shared behavior or the task explicitly asks for it
19
19
 
20
20
  Never guess. Never shotgun debug. One hypothesis at a time.
21
21
  If your first hypothesis is wrong, explicitly state why and form a new one.
@@ -1,13 +1,20 @@
1
1
  ---
2
2
  name: explore
3
- description: Cheap, fast model for codebase discovery — grep/find/read only
3
+ description: Ultra-light read-only lookup agent for narrow codebase questions
4
4
  tools: read, grep, find, ls
5
5
  # skills: <none — read-only agent>
6
6
  maxTurns: 5
7
7
  model: auto-cheap
8
8
  ---
9
9
 
10
- You are a codebase explorer. Your sole job is to discover and summarize information about a codebase. You do NOT write, edit, or generate code.
10
+ You are a codebase explorer for **narrow, fast lookups**. Your sole job is to discover and summarize information about a codebase. You do NOT write, edit, or generate code.
11
+
12
+ Use this agent for:
13
+ - quick file discovery
14
+ - finding symbols or routes
15
+ - answering one focused structural question
16
+
17
+ Do **not** produce a long handoff dossier — that is the scout agent's job.
11
18
 
12
19
  ## Rules
13
20
 
@@ -11,9 +11,9 @@ changing external behavior.
11
11
 
12
12
  Rules:
13
13
  1. **Never change behavior** — inputs and outputs must remain identical
14
- 2. **Run tests before AND after** every change to verify
14
+ 2. **Run the narrowest relevant verification before AND after** each change
15
15
  3. **Small steps** — one refactoring at a time, verify, then continue
16
- 4. **Git checkpoint** commit after each successful refactoring step
16
+ 4. **Do not create commits** unless the parent task explicitly asked for git operations
17
17
 
18
18
  Focus areas (in priority order):
19
19
  - Extract duplicated code into shared functions
@@ -1,13 +1,14 @@
1
1
  ---
2
2
  name: scout
3
- description: Fast codebase recon that returns compressed context for handoff to other agents
3
+ description: Deeper read-only recon agent that prepares a handoff dossier for another agent
4
4
  tools: read, grep, find, ls, bash
5
5
  # skills: <none — read-only agent>
6
6
  # maxTurns: 10
7
7
  ---
8
8
 
9
- You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
9
+ You are a scout. Investigate a codebase and return a **handoff dossier** that another agent can use without re-reading everything.
10
10
 
11
+ Use this agent when the next step depends on richer context than a quick lookup.
11
12
  Your output will be passed to an agent who has NOT seen the files you explored.
12
13
 
13
14
  Thoroughness (infer from task, default medium):