@vincemakes/kiso-subagent-ext 0.39.2 → 0.40.0

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.
@@ -98,7 +98,10 @@ export default async function createSubagentExtension() {
98
98
  execute: async (input, ctx) => {
99
99
  const tasks = ((input ?? {}).tasks ?? []).slice(0, 8);
100
100
  if (tasks.length === 0) return { content: "delegate: no tasks", isError: true, errorKind: "precondition" };
101
- const sessionsDir = join(process.env.KISO_HOME ?? join(homedir(), ".kiso"), "sessions");
101
+ // 0.40.0: the parent's own folder (one per project), handed over
102
+ // by the CLI; a pinned KISO_SESSIONS_DIR, or the legacy folder,
103
+ // when no CLI said (a direct load)
104
+ const sessionsDir = delegationConfig().sessionsDir ?? (process.env.KISO_SESSIONS_DIR || join(process.env.KISO_HOME ?? join(homedir(), ".kiso"), "sessions"));
102
105
  // P3: the loop now threads the session id through
103
106
  // ToolContext.sessionId — the discovery heuristic below is
104
107
  // kept ONLY as a fallback for direct tool use / tests.
@@ -149,14 +152,10 @@ export default async function createSubagentExtension() {
149
152
  // Partial success is not overall failure — only ALL failed
150
153
  // makes the whole result an error.
151
154
  // W12: the blob opens with a machine-readable summary line —
152
- // the ONE-LINE shape the TUI's settled row renders (└ N
153
- // tool calls · R roles · F failed · /last for the report).
155
+ // the ONE-LINE shape the TUI's settled row renders verbatim.
154
156
  // The per-section text below is unchanged — the model's
155
157
  // view is preserved, the summary is additive.
156
- const toolCalls = sections.reduce((n, s) => n + (s.toolCalls ?? 0), 0);
157
- const roles = new Set(tasks.map((t) => t.role)).size;
158
- const failed = sections.filter((s) => s.failed).length;
159
- const summary = `summary: ${toolCalls} tool calls · ${roles} role${roles === 1 ? "" : "s"} · ${failed} failed`;
158
+ const summary = delegateSummary(sections, new Set(tasks.map((t) => t.role)).size);
160
159
  return { content: `${summary}\n${sections.map((s) => s.text).join("\n")}`, isError: sections.every((s) => s.failed) };
161
160
  },
162
161
  },
@@ -253,7 +252,7 @@ async function runChild({ childId, role, task, scope, acceptance, model, after,
253
252
  // the fixed UNRESOLVED instruction the result parser reads back.
254
253
  const taskPath = join(manifestDir ?? policyDir, `${childId}.task`);
255
254
  writeFileSync(taskPath, `${task}\n\n${UNRESOLVED_INSTRUCTION}\n`, "utf8");
256
- const { code, stdout, killed } = await runProcess(childId, bin, childCwd, policyDir, taskPath, timeout, signal, model);
255
+ const { code, stdout, killed } = await runProcess(childId, bin, childCwd, policyDir, taskPath, timeout, signal, model, sessionsDir);
257
256
  const extraction = await extractChildResult(sessionsDir, childId, `exit ${code}\n${stdout}`);
258
257
  const status = killed === "timeout" ? "timeout" : killed === "abort" ? "killed" : code !== 0 && extraction.outcome === "missing" ? "spawn-failed" : extraction.outcome;
259
258
  let failed = code !== 0 || killed !== null || extraction.failed;
@@ -347,13 +346,43 @@ async function runChild({ childId, role, task, scope, acceptance, model, after,
347
346
  text += `\n FAILED: collecting the worktree's changes: ${collection.reason}${collection.partialPath !== undefined ? ` (partial patch at ${collection.partialPath})` : ""}\n worktree kept at: ${worktree}`;
348
347
  }
349
348
  }
350
- return { failed, text, toolCalls: extraction.toolCalls };
349
+ return { failed, ...(failed ? { failKind: failKindOf(status, verification) } : {}), text, toolCalls: extraction.toolCalls };
351
350
  } finally {
352
351
  rmSync(policyDir, { recursive: true, force: true });
353
352
  if (worktree !== null && ownsWorktree && !keepWorktree) removeWorktree(parentCwd, worktree);
354
353
  }
355
354
  }
356
355
 
356
+ /** 0.40.0 — why a child failed, in the three words a person acts on: its
357
+ * clock ran out (`timeout`: raise it, or split the task), the parent's
358
+ * acceptance check RAN and failed (`acceptance`: the work is wrong), or
359
+ * anything else (`error`: a crash, an abort, a missing or ambiguous
360
+ * result, a spawn or collection failure — read the report). */
361
+ export function failKindOf(status, verification) {
362
+ if (status === "timeout") return "timeout";
363
+ if (verification !== null && verification !== undefined && verification.passed === false) return "acceptance";
364
+ return "error";
365
+ }
366
+
367
+ const FAIL_KIND_ORDER = ["timeout", "acceptance", "error"];
368
+
369
+ /** 0.40.0 — the settled row's marker: how many tasks ran, and — when any
370
+ * failed — how many of each kind, in a fixed order with zero counts
371
+ * omitted. A failed section without a kind counts as an error: a failure
372
+ * is never silently left out of the count. */
373
+ export function delegateSummary(sections, roles) {
374
+ const n = sections.length;
375
+ const toolCalls = sections.reduce((sum, s) => sum + (s.toolCalls ?? 0), 0);
376
+ const failedSections = sections.filter((s) => s.failed);
377
+ const counts = new Map(FAIL_KIND_ORDER.map((k) => [k, 0]));
378
+ for (const s of failedSections) {
379
+ const k = FAIL_KIND_ORDER.includes(s.failKind) ? s.failKind : "error";
380
+ counts.set(k, counts.get(k) + 1);
381
+ }
382
+ const kinds = FAIL_KIND_ORDER.filter((k) => counts.get(k) > 0).map((k) => `${counts.get(k)} ${k}`);
383
+ return `summary: ${n} task${n === 1 ? "" : "s"} · ${toolCalls} tool calls · ${roles} role${roles === 1 ? "" : "s"} · ${failedSections.length} failed${kinds.length > 0 ? ` (${kinds.join(", ")})` : ""}`;
384
+ }
385
+
357
386
  /** DT-1a: the fixed trailer every task file ends with — the parser reads the section back. */
358
387
  export const UNRESOLVED_INSTRUCTION = 'When you finish, end your reply with a section titled UNRESOLVED listing what you could not do or verify, one item per line starting with "- ", or the single word none.';
359
388
 
@@ -363,9 +392,9 @@ function delegationConfig() {
363
392
  try {
364
393
  const raw = process.env.KISO_DELEGATION_CONFIG_JSON;
365
394
  const parsed = raw === undefined ? {} : JSON.parse(raw);
366
- return { checks: parsed.checks ?? {}, profiles: parsed.profiles ?? [] };
395
+ return { checks: parsed.checks ?? {}, profiles: parsed.profiles ?? [], sessionsDir: typeof parsed.sessionsDir === "string" && parsed.sessionsDir !== "" ? parsed.sessionsDir : undefined };
367
396
  } catch {
368
- return { checks: {}, profiles: [] };
397
+ return { checks: {}, profiles: [], sessionsDir: undefined };
369
398
  }
370
399
  }
371
400
 
@@ -590,7 +619,7 @@ export function childArgs(bin, childId, taskPath, model) {
590
619
  return [bin, ...(model !== undefined ? ["--model", model] : []), "chat", childId, "--task-file", taskPath];
591
620
  }
592
621
 
593
- function runProcess(childId, bin, cwd, policyDir, taskPath, timeout, signal, model) {
622
+ function runProcess(childId, bin, cwd, policyDir, taskPath, timeout, signal, model, sessionsDir) {
594
623
  const depth = Number.parseInt(process.env.KISO_SUBAGENT_DEPTH ?? "0", 10) || 0;
595
624
  const child = spawn(process.execPath, childArgs(bin, childId, taskPath, model), {
596
625
  cwd,
@@ -598,6 +627,9 @@ function runProcess(childId, bin, cwd, policyDir, taskPath, timeout, signal, mod
598
627
  ...process.env,
599
628
  KISO_SUBAGENT_DEPTH: String(depth + 1),
600
629
  KISO_EXTENSIONS_DIR: policyDir,
630
+ // 0.40.0: the child writes its log beside its parent's — the
631
+ // parent reads it back from there — whatever cwd it runs in
632
+ KISO_SESSIONS_DIR: sessionsDir,
601
633
  // Modes: a headless child has no human — the mode tiers'
602
634
  // ask would stall it. Bypass is the neutral tier here; the
603
635
  // role policy dir (allow/deny only — a child must never
@@ -848,7 +880,7 @@ function removeWorktree(parentCwd, worktree) {
848
880
  }
849
881
 
850
882
  function failSection(childId, role, task, reason) {
851
- return { failed: true, text: `[subagent] ${role}: ${task}\n FAILED: ${reason}`, toolCalls: 0 };
883
+ return { failed: true, failKind: "error", text: `[subagent] ${role}: ${task}\n FAILED: ${reason}`, toolCalls: 0 };
852
884
  }
853
885
 
854
886
  const msg = (err) => (err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-subagent-ext",
3
- "version": "0.39.2",
3
+ "version": "0.40.0",
4
4
  "description": "kiso official subagent extension \u2014 child kiso processes with role policies, kernel untouched",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "test": "vitest run"
24
24
  },
25
25
  "devDependencies": {
26
- "@vincemakes/kiso-core": "0.39.2",
26
+ "@vincemakes/kiso-core": "0.40.0",
27
27
  "@types/node": "^26.1.2",
28
28
  "typescript": "^5.7.2",
29
29
  "vitest": "^3.0.0"