@runfusion/fusion 0.22.0 → 0.23.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.
Files changed (53) hide show
  1. package/dist/bin.js +4546 -1223
  2. package/dist/client/assets/AgentDetailView-C1XceMgi.js +18 -0
  3. package/dist/client/assets/AgentsView-DSGQWObq.css +1 -0
  4. package/dist/client/assets/AgentsView-Deh125ss.js +527 -0
  5. package/dist/client/assets/ChatView-7D_RQDqT.js +1 -0
  6. package/dist/client/assets/{DevServerView-l8RCyL2k.js → DevServerView-C9lzHrcT.js} +1 -1
  7. package/dist/client/assets/{DirectoryPicker-CS1dwqcC.js → DirectoryPicker-aVdFaV37.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-DmthQWDZ.js → DocumentsView-DIpg3NSP.js} +1 -1
  9. package/dist/client/assets/{InsightsView-DvXpMKmH.js → InsightsView-jKjEFAx_.js} +1 -1
  10. package/dist/client/assets/{MemoryView-CPwlKnUI.js → MemoryView-nXlTqebk.js} +1 -1
  11. package/dist/client/assets/{NodesView-BLlfUfsy.js → NodesView-Di2SvOhg.js} +1 -1
  12. package/dist/client/assets/{PiExtensionsManager-j8rPXqmB.js → PiExtensionsManager-Buopv-jb.js} +1 -1
  13. package/dist/client/assets/PluginManager-B9-NbQ8f.js +1 -0
  14. package/dist/client/assets/PluginManager-C1DbPaar.css +1 -0
  15. package/dist/client/assets/{ResearchView-D9DNJYDq.js → ResearchView-_BHXUv2j.js} +1 -1
  16. package/dist/client/assets/{RoadmapsView-Djc_X35v.js → RoadmapsView-DHWjUoc8.js} +1 -1
  17. package/dist/client/assets/{SettingsModal-fxvTFLtR.js → SettingsModal-C89Ikhfm.js} +1 -1
  18. package/dist/client/assets/SettingsModal-DHitIpsa.css +1 -0
  19. package/dist/client/assets/SettingsModal-DR_yirvK.js +31 -0
  20. package/dist/client/assets/{SetupWizardModal-tG_MF_nA.js → SetupWizardModal-BtDMY9pa.js} +1 -1
  21. package/dist/client/assets/{SkillsView-Ddf0YL8z.js → SkillsView-hDpTBdFT.js} +1 -1
  22. package/dist/client/assets/{agentSkills-EwIwBlG8.js → agentSkills-B-w5wFHh.js} +1 -1
  23. package/dist/client/assets/{folder-open-BiJpmnaT.js → folder-open-usZkXdq2.js} +1 -1
  24. package/dist/client/assets/index-Bc6ZdGMz.css +1 -0
  25. package/dist/client/assets/index-D__RMku8.js +694 -0
  26. package/dist/client/assets/{star-BwRZmiuZ.js → star-BAT_ObKE.js} +1 -1
  27. package/dist/client/assets/{upload-D4NwZhPp.js → upload-BC2YKNEV.js} +1 -1
  28. package/dist/client/assets/{users-DNISDtI1.js → users-Dkd4rtrN.js} +1 -1
  29. package/dist/client/index.html +2 -2
  30. package/dist/client/version.json +1 -1
  31. package/dist/droid-cli/package.json +1 -1
  32. package/dist/extension.js +3266 -540
  33. package/dist/pi-claude-cli/package.json +1 -1
  34. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  35. package/dist/plugins/fusion-plugin-dependency-graph/src/DependencyGraphView.css +12 -3
  36. package/dist/plugins/fusion-plugin-dependency-graph/src/__tests__/storage.test.ts +12 -2
  37. package/dist/plugins/fusion-plugin-dependency-graph/src/storage.ts +6 -7
  38. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +176 -7
  39. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  40. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  41. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  42. package/package.json +1 -1
  43. package/skill/fusion/references/engine-tools.md +5 -2
  44. package/dist/client/assets/AgentDetailView-BKKpbp1S.js +0 -18
  45. package/dist/client/assets/AgentsView-BRXFmrcJ.js +0 -527
  46. package/dist/client/assets/AgentsView-Bs03ptrd.css +0 -1
  47. package/dist/client/assets/ChatView-D7L2e_qu.js +0 -1
  48. package/dist/client/assets/PluginManager-DA_T0GHn.css +0 -1
  49. package/dist/client/assets/PluginManager-pW6RMz5z.js +0 -1
  50. package/dist/client/assets/SettingsModal-BWe0KrGY.css +0 -1
  51. package/dist/client/assets/SettingsModal-WGCF_pk8.js +0 -31
  52. package/dist/client/assets/index-D6ebxTPF.css +0 -1
  53. package/dist/client/assets/index-DYDLmOcK.js +0 -694
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion/pi-claude-cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
5
5
  "license": "MIT",
6
6
  "private": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/dependency-graph",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "type": "module",
5
5
  "description": "Dependency graph dashboard view plugin for Fusion",
6
6
  "private": true,
@@ -51,10 +51,12 @@
51
51
 
52
52
  .dependency-graph-edge.is-related {
53
53
  stroke: var(--todo);
54
+ opacity: 1;
54
55
  }
55
56
 
56
57
  .dependency-graph-edge.is-dimmed {
57
- opacity: 0.4;
58
+ stroke: var(--border);
59
+ opacity: 0.35;
58
60
  }
59
61
 
60
62
  .dependency-graph-node {
@@ -76,10 +78,13 @@
76
78
  .dependency-graph-node.is-selected .card {
77
79
  box-shadow: var(--focus-ring-strong);
78
80
  border-color: var(--todo);
81
+ opacity: 1;
79
82
  }
80
83
 
81
84
  .dependency-graph-node.is-related:not(.is-selected) .card {
82
85
  border-color: var(--in-progress);
86
+ box-shadow: var(--shadow-sm);
87
+ opacity: 1;
83
88
  }
84
89
 
85
90
  .dependency-graph-node.is-dimmed {
@@ -87,6 +92,10 @@
87
92
  filter: saturate(0.8);
88
93
  }
89
94
 
95
+ .dependency-graph-node.is-dimmed .card {
96
+ border-color: var(--border);
97
+ }
98
+
90
99
  .dependency-graph-empty {
91
100
  display: flex;
92
101
  align-items: center;
@@ -112,8 +121,8 @@
112
121
  }
113
122
 
114
123
  .dependency-graph-controls .btn {
115
- min-height: 44px;
116
- min-width: 44px;
124
+ min-height: calc(var(--space-xs) * 11);
125
+ min-width: calc(var(--space-xs) * 11);
117
126
  }
118
127
 
119
128
  .dependency-graph-canvas {
@@ -20,12 +20,22 @@ describe("storage", () => {
20
20
  localStorage.clear();
21
21
  });
22
22
 
23
- it("builds project-scoped key", () => {
24
- expect(projectScopedKey("proj_123")).toBe("kb:proj_123:dependency-graph-positions");
23
+ it("builds project-scoped key with canonical base key", () => {
24
+ expect(projectScopedKey("proj_123")).toBe("kb:proj_123:fusion-plugin-dependency-graph:positions");
25
+ });
26
+
27
+ it("falls back to unscoped key when projectId is missing or empty", () => {
28
+ expect(projectScopedKey()).toBe("fusion-plugin-dependency-graph:positions");
29
+ expect(projectScopedKey("")).toBe("fusion-plugin-dependency-graph:positions");
25
30
  });
26
31
 
27
32
  it("persists and restores positions", () => {
28
33
  savePositions("proj_123", { "FN-1": { x: 10, y: 20 } });
29
34
  expect(loadPositions("proj_123")).toEqual({ "FN-1": { x: 10, y: 20 } });
30
35
  });
36
+
37
+ it("returns empty object for invalid JSON", () => {
38
+ localStorage.setItem("kb:proj_123:fusion-plugin-dependency-graph:positions", "not-json");
39
+ expect(loadPositions("proj_123")).toEqual({});
40
+ });
31
41
  });
@@ -1,14 +1,14 @@
1
- const BASE_KEY = "dependency-graph-positions";
1
+ import { getScopedItem, scopedKey, setScopedItem } from "../../../packages/dashboard/app/utils/projectStorage";
2
+
3
+ const BASE_KEY = "fusion-plugin-dependency-graph:positions";
2
4
 
3
5
  export function projectScopedKey(projectId?: string): string {
4
- const suffix = projectId ?? "default";
5
- return `kb:${suffix}:${BASE_KEY}`;
6
+ return scopedKey(BASE_KEY, projectId);
6
7
  }
7
8
 
8
9
  export function loadPositions(projectId?: string): Record<string, { x: number; y: number }> {
9
- if (typeof window === "undefined") return {};
10
10
  try {
11
- const raw = window.localStorage.getItem(projectScopedKey(projectId));
11
+ const raw = getScopedItem(BASE_KEY, projectId);
12
12
  if (!raw) return {};
13
13
  const parsed = JSON.parse(raw) as Record<string, { x: number; y: number }>;
14
14
  return parsed ?? {};
@@ -18,6 +18,5 @@ export function loadPositions(projectId?: string): Record<string, { x: number; y
18
18
  }
19
19
 
20
20
  export function savePositions(projectId: string | undefined, positions: Record<string, { x: number; y: number }>): void {
21
- if (typeof window === "undefined") return;
22
- window.localStorage.setItem(projectScopedKey(projectId), JSON.stringify(positions));
21
+ setScopedItem(BASE_KEY, JSON.stringify(positions), projectId);
23
22
  }
@@ -83,7 +83,7 @@ function hermesProfileHome(profileName) {
83
83
  async function listHermesProfiles(opts) {
84
84
  const binary = resolveBinaryForSpawn(opts?.binaryPath ?? "hermes");
85
85
  const timeoutMs = opts?.timeoutMs ?? 5e3;
86
- return new Promise((resolve, reject) => {
86
+ return new Promise((resolve2, reject) => {
87
87
  let settled = false;
88
88
  const child = spawn(binary, ["profile", "list"], {
89
89
  stdio: ["ignore", "pipe", "pipe"],
@@ -126,7 +126,7 @@ async function listHermesProfiles(opts) {
126
126
  ${combined}`));
127
127
  return;
128
128
  }
129
- resolve(parseProfileListOutput(stdout));
129
+ resolve2(parseProfileListOutput(stdout));
130
130
  });
131
131
  });
132
132
  }
@@ -203,7 +203,7 @@ function buildHermesArgs(prompt, settings, resumeSessionId) {
203
203
  async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
204
204
  const args = buildHermesArgs(prompt, settings, resumeSessionId);
205
205
  const binary = resolveBinaryForSpawn(settings.binaryPath);
206
- return new Promise((resolve, reject) => {
206
+ return new Promise((resolve2, reject) => {
207
207
  let settled = false;
208
208
  const spawnEnv = { ...process.env, PYTHONUNBUFFERED: "1" };
209
209
  if (settings.profile) {
@@ -271,7 +271,7 @@ ${combined}`));
271
271
  return;
272
272
  }
273
273
  try {
274
- resolve(parseHermesOutput(stdout, stderr));
274
+ resolve2(parseHermesOutput(stdout, stderr));
275
275
  } catch (parseErr) {
276
276
  reject(parseErr);
277
277
  }
@@ -279,7 +279,161 @@ ${combined}`));
279
279
  });
280
280
  }
281
281
 
282
+ // ../../plugins/fusion-plugin-hermes-runtime/dist/fusion-skill-install.js
283
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
284
+ import { homedir } from "node:os";
285
+ import { basename, dirname, join, resolve } from "node:path";
286
+ import { fileURLToPath } from "node:url";
287
+ var FUSION_SKILL_NAME = "fusion";
288
+ function resolveHermesHome(profile) {
289
+ const base = process.env.HERMES_HOME ?? join(homedir(), ".hermes");
290
+ if (!profile || profile === "default")
291
+ return base;
292
+ return join(base, "profiles", profile);
293
+ }
294
+ function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
295
+ const here = fileURLToPath(moduleUrl);
296
+ const moduleDir = dirname(here);
297
+ return [
298
+ resolve(moduleDir, "..", "..", "..", "..", "packages", "cli", "skill", FUSION_SKILL_NAME),
299
+ resolve(moduleDir, "..", "..", "..", "skill", FUSION_SKILL_NAME),
300
+ resolve(moduleDir, "..", "..", "skill", FUSION_SKILL_NAME),
301
+ resolve(moduleDir, "..", "..", "..", "..", "skill", FUSION_SKILL_NAME)
302
+ ];
303
+ }
304
+ function resolveBundledFusionSkillSource() {
305
+ const candidates = getFusionSkillSourceCandidates();
306
+ for (const candidate of candidates) {
307
+ if (existsSync(join(candidate, "SKILL.md")))
308
+ return candidate;
309
+ }
310
+ return null;
311
+ }
312
+ function installFusionSkillIntoHermesHome(options = {}) {
313
+ const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
314
+ const targetDir = join(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
315
+ if (!sourceDir) {
316
+ return {
317
+ outcome: "warning",
318
+ sourceDir,
319
+ targetDir,
320
+ reason: "bundled Fusion skill source directory not found"
321
+ };
322
+ }
323
+ try {
324
+ mkdirSync(dirname(targetDir), { recursive: true });
325
+ let replaced = false;
326
+ if (existsSync(targetDir) || isBrokenSymlink(targetDir)) {
327
+ const stat = lstatSync(targetDir);
328
+ if (stat.isSymbolicLink()) {
329
+ const currentTarget = safeReadlink(targetDir);
330
+ if (currentTarget && resolve(dirname(targetDir), currentTarget) === resolve(sourceDir)) {
331
+ return { outcome: "already-installed", sourceDir, targetDir };
332
+ }
333
+ if (!looksLikeFusionSkillTarget(resolve(dirname(targetDir), currentTarget ?? ""))) {
334
+ return {
335
+ outcome: "skipped",
336
+ sourceDir,
337
+ targetDir,
338
+ reason: "existing symlink does not look like a Fusion skill install"
339
+ };
340
+ }
341
+ unlinkSync(targetDir);
342
+ replaced = true;
343
+ } else {
344
+ if (!looksLikePriorFusionInstall(targetDir)) {
345
+ return {
346
+ outcome: "skipped",
347
+ sourceDir,
348
+ targetDir,
349
+ reason: "existing directory does not look like a Fusion skill install"
350
+ };
351
+ }
352
+ rmSync(targetDir, { recursive: true, force: true });
353
+ replaced = true;
354
+ }
355
+ }
356
+ try {
357
+ symlinkSync(sourceDir, targetDir, "dir");
358
+ } catch (error) {
359
+ const symlinkReason = error instanceof Error ? error.message : String(error);
360
+ try {
361
+ cpSync(sourceDir, targetDir, { recursive: true });
362
+ return {
363
+ outcome: replaced ? "replaced" : "installed",
364
+ sourceDir,
365
+ targetDir,
366
+ reason: `symlink failed (${symlinkReason}); copied files instead`
367
+ };
368
+ } catch (copyError) {
369
+ return {
370
+ outcome: "warning",
371
+ sourceDir,
372
+ targetDir,
373
+ reason: copyError instanceof Error ? copyError.message : String(copyError)
374
+ };
375
+ }
376
+ }
377
+ return { outcome: replaced ? "replaced" : "installed", sourceDir, targetDir };
378
+ } catch (error) {
379
+ return {
380
+ outcome: "warning",
381
+ sourceDir,
382
+ targetDir,
383
+ reason: error instanceof Error ? error.message : String(error)
384
+ };
385
+ }
386
+ }
387
+ function safeReadlink(path2) {
388
+ try {
389
+ return readlinkSync(path2);
390
+ } catch {
391
+ return null;
392
+ }
393
+ }
394
+ function isBrokenSymlink(path2) {
395
+ try {
396
+ const stat = lstatSync(path2);
397
+ return stat.isSymbolicLink() && !existsSync(path2);
398
+ } catch {
399
+ return false;
400
+ }
401
+ }
402
+ function looksLikePriorFusionInstall(path2) {
403
+ const skillMd = join(path2, "SKILL.md");
404
+ if (!existsSync(skillMd))
405
+ return false;
406
+ try {
407
+ const body = readFileSync(skillMd, "utf-8");
408
+ return /\bfusion\b/i.test(body) && /\bskill\b/i.test(body);
409
+ } catch {
410
+ return false;
411
+ }
412
+ }
413
+ function looksLikeFusionSkillTarget(path2) {
414
+ if (!path2)
415
+ return false;
416
+ if (basename(path2).toLowerCase() === FUSION_SKILL_NAME)
417
+ return true;
418
+ return existsSync(join(path2, "SKILL.md"));
419
+ }
420
+
282
421
  // ../../plugins/fusion-plugin-hermes-runtime/dist/runtime-adapter.js
422
+ function buildRuntimeContextSection(options) {
423
+ const skillNames = Array.isArray(options.skills) ? options.skills.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
424
+ const skillSelection = options.skillSelection;
425
+ const selectionSkillNames = Array.isArray(skillSelection?.requestedSkillNames) ? skillSelection.requestedSkillNames.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
426
+ const mergedSkills = skillNames.length > 0 ? skillNames : selectionSkillNames;
427
+ const lines = [
428
+ "Fusion runtime context:",
429
+ `- Tool mode: ${options.tools ?? "coding"}`
430
+ ];
431
+ if (mergedSkills.length > 0) {
432
+ lines.push(`- Requested skills: ${mergedSkills.join(", ")}`);
433
+ }
434
+ lines.push("- If fn_* tools are available in your runtime, use them directly for coordination/memory/task actions.");
435
+ return lines.join("\n");
436
+ }
283
437
  var HermesRuntimeAdapter = class {
284
438
  id = "hermes";
285
439
  name = "Hermes Runtime";
@@ -302,13 +456,19 @@ var HermesRuntimeAdapter = class {
302
456
  onToolStart: options.onToolStart,
303
457
  onToolEnd: options.onToolEnd
304
458
  },
459
+ runtimeContext: options.runtimeContext,
460
+ fusedSystemPrompt: [options.systemPrompt.trim(), buildRuntimeContextSection(options).trim()].filter((part) => part.length > 0).join("\n\n"),
305
461
  dispose: () => void 0
306
462
  };
307
463
  return { session, sessionFile: void 0 };
308
464
  }
309
465
  async promptWithFallback(session, prompt, _options) {
310
466
  const resumeId = session.sessionId || void 0;
311
- const result = await invokeHermesCli(prompt, this.settings, resumeId);
467
+ const promptWithContext = resumeId ? prompt : `${session.fusedSystemPrompt}
468
+
469
+ User request:
470
+ ${prompt}`;
471
+ const result = await invokeHermesCli(promptWithContext, this.settings, resumeId);
312
472
  session.sessionId = result.sessionId;
313
473
  session.lastModelDescription = this.describeFromSettings();
314
474
  if (result.body) {
@@ -450,7 +610,13 @@ var plugin = definePlugin({
450
610
  hooks: {
451
611
  onLoad: (ctx) => {
452
612
  const settings = resolveCliSettings(ctx.settings);
453
- ctx.logger.info(`Hermes Runtime Plugin loaded \u2014 binary=${settings.binaryPath} model=${settings.model ?? "(default)"}`);
613
+ const skillInstall = installFusionSkillIntoHermesHome({ profile: settings.profile });
614
+ if (skillInstall.outcome === "warning") {
615
+ ctx.logger.warn(`Hermes Runtime Plugin: Fusion skill auto-install warning: ${skillInstall.reason ?? "unknown"}`);
616
+ } else if (skillInstall.outcome === "skipped") {
617
+ ctx.logger.warn(`Hermes Runtime Plugin: Fusion skill auto-install skipped: ${skillInstall.reason ?? "unknown"}`);
618
+ }
619
+ ctx.logger.info(`Hermes Runtime Plugin loaded \u2014 binary=${settings.binaryPath} model=${settings.model ?? "(default)"} fusionSkill=${skillInstall.outcome}`);
454
620
  ctx.emitEvent("hermes-runtime:loaded", {
455
621
  runtimeId: HERMES_RUNTIME_ID,
456
622
  version: HERMES_RUNTIME_VERSION
@@ -472,9 +638,12 @@ export {
472
638
  index_default as default,
473
639
  hermesRuntimeFactory,
474
640
  hermesRuntimeMetadata,
641
+ installFusionSkillIntoHermesHome,
475
642
  invokeHermesCli,
476
643
  listHermesProfiles,
477
644
  parseHermesOutput,
478
645
  probeHermesBinary,
479
- resolveCliSettings
646
+ resolveBundledFusionSkillSource,
647
+ resolveCliSettings,
648
+ resolveHermesHome
480
649
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/hermes-runtime",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/openclaw-runtime",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/paperclip-runtime",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runfusion/fusion",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
6
6
  "homepage": "https://github.com/Runfusion/Fusion#readme",
@@ -4,6 +4,7 @@ These tools are **not** part of the user-invokable extension surface. They are i
4
4
 
5
5
  - Source files: `packages/engine/src/agent-tools.ts`, `triage.ts`, `executor.ts`, `merger.ts`, `agent-heartbeat.ts`
6
6
  - Availability: only when the engine creates a session for the matching agent role
7
+ - Runtime contract: engine sessions now forward requested skill names (`skillSelection.requestedSkillNames`) into the generic runtime `skills` field so non-pi runtimes can still receive Fusion skill intent.
7
8
  - Important: do not tell users to call these directly from the generic extension tool list
8
9
 
9
10
  ## Shared runtime tools (`agent-tools.ts`)
@@ -28,8 +29,8 @@ These tools are **not** part of the user-invokable extension surface. They are i
28
29
  | `fn_delegate_task` | triage, executor, heartbeat | Create and assign a new task to a specific agent | `agent_id` (string), `description` (string), `dependencies?` (string[]) |
29
30
  | `fn_get_agent_config` | executor, heartbeat | Read full config for a direct-report agent | `agent_id` (string) |
30
31
  | `fn_update_agent_config` | executor, heartbeat | Update config fields for a direct-report, non-ephemeral agent | `agent_id` (string), optional: `soul`, `instructions_text`, `instructions_path`, `heartbeat_procedure_path`, `heartbeat_interval_ms`, `heartbeat_timeout_ms`, `max_concurrent_runs`, `message_response_mode` |
31
- | `fn_send_message` | executor, heartbeat | Send inbox messages to agents/users | `to_id` (string), `content` (string), `type?` (`agent-to-agent` \| `agent-to-user`), `reply_to_message_id?` (string) |
32
- | `fn_read_messages` | executor, heartbeat | Read inbox messages | `unread_only?` (boolean), `limit?` (number) |
32
+ | `fn_send_message` | executor, step-session, heartbeat | Send inbox messages to agents/users | `to_id` (string), `content` (string), `type?` (`agent-to-agent` \| `agent-to-user`), `reply_to_message_id?` (string) |
33
+ | `fn_read_messages` | executor, step-session, heartbeat | Read inbox messages | `unread_only?` (boolean), `limit?` (number) |
33
34
 
34
35
  ## Triage-only runtime tools (`triage.ts`)
35
36
 
@@ -41,6 +42,8 @@ These tools are **not** part of the user-invokable extension surface. They are i
41
42
 
42
43
  ## Executor-only runtime tools (`executor.ts`)
43
44
 
45
+ Note: step-session execution (`step-session-executor.ts`) reuses executor coordination tools (`fn_send_message`, `fn_read_messages`, `fn_list_agents`, `fn_delegate_task`, task-document tools, and memory tools) so spawned/session-sliced execution keeps parity with main executor runs.
46
+
44
47
  | Tool | Purpose | Parameters |
45
48
  |---|---|---|
46
49
  | `fn_task_update` | Update a spec step status (`pending`/`in-progress`/`done`/`skipped`) | `step` (number), `status` (enum) |