gentle-pi 4.2.0 → 4.3.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 (202) hide show
  1. package/README.md +336 -137
  2. package/assets/agents/gentle-ai-explore.md +1 -0
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -1
  5. package/assets/orchestrator-delegation-reference.md +163 -0
  6. package/assets/orchestrator-delegation.md +25 -176
  7. package/assets/orchestrator-tracking.md +1 -1
  8. package/assets/orchestrator-verification.md +14 -8
  9. package/assets/orchestrator-writer.md +5 -1
  10. package/assets/orchestrator.md +9 -9
  11. package/bin/gentle-shell.mjs +170 -22
  12. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  13. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  14. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  15. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  16. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  17. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  18. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  19. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  20. package/docs/assets/diagrams/measured-routing.svg +49 -0
  21. package/docs/assets/diagrams/native-review.svg +52 -12
  22. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  23. package/docs/assets/diagrams/permission-modes.svg +51 -0
  24. package/docs/assets/features/audio-notifications.png +0 -0
  25. package/docs/assets/features/command-palette.png +0 -0
  26. package/docs/bundled-install.md +33 -3
  27. package/docs/delegated-verification.md +10 -8
  28. package/docs/gentle-agents-activity.md +11 -5
  29. package/docs/gentle-shell-uninstall.md +8 -1
  30. package/docs/gentle-shell.md +5 -4
  31. package/docs/install-wizard.md +8 -1
  32. package/docs/permission-modes.md +1 -1
  33. package/docs/readme-reference.md +107 -19
  34. package/docs/yolo-mode.md +21 -8
  35. package/extensions/codegraph-tools.ts +68 -6
  36. package/extensions/gentle-agents.ts +99 -12
  37. package/extensions/gentle-ai.ts +284 -52
  38. package/extensions/gentle-herdr-activity.ts +2 -3
  39. package/extensions/gentle-shell.ts +27 -12
  40. package/extensions/gentle-todo.ts +43 -9
  41. package/extensions/quiet-tools.ts +1 -1
  42. package/extensions/session-scratch.ts +365 -0
  43. package/extensions/startup-banner.ts +2 -3
  44. package/lib/agent-home.ts +8 -3
  45. package/lib/agents-protocol.ts +3 -0
  46. package/lib/agents-runner.ts +55 -6
  47. package/lib/agents-view.ts +3 -1
  48. package/lib/bounded-writer-admission.ts +51 -0
  49. package/lib/command-palette-catalog.ts +0 -4
  50. package/lib/commit-slice-signal.ts +100 -0
  51. package/lib/context-backstop.ts +84 -0
  52. package/lib/delivered-test-lint.ts +80 -0
  53. package/lib/destructive-command-guard.ts +120 -9
  54. package/lib/gauge-theme.ts +146 -0
  55. package/lib/gentle-ai-binary.ts +28 -7
  56. package/lib/gentle-shell-bundled.ts +39 -3
  57. package/lib/gentle-shell-import-pi.ts +233 -0
  58. package/lib/gentle-shell-launcher.ts +145 -11
  59. package/lib/gentle-shell-uninstall.ts +18 -1
  60. package/lib/herdr-activity.ts +24 -0
  61. package/lib/native-review-cli.ts +9 -1
  62. package/lib/orchestrator-discovery.ts +5 -1
  63. package/lib/orchestrator-presence.ts +9 -0
  64. package/lib/orchestrator-scope.ts +21 -3
  65. package/lib/provider-contract-bundle.ts +99 -53
  66. package/lib/review-candidate-view-owner.ts +29 -19
  67. package/lib/review-candidate-view.ts +36 -2
  68. package/lib/review-risk-assessment.ts +43 -38
  69. package/lib/review-session-standing-permission.ts +3 -1
  70. package/lib/session-profile-freeze.ts +30 -1
  71. package/lib/session-scratch.ts +474 -0
  72. package/lib/shell-bar.ts +25 -25
  73. package/lib/shell-changes-view.ts +80 -13
  74. package/lib/shell-gauge.ts +49 -6
  75. package/lib/shell-todo.ts +23 -4
  76. package/lib/shell-usage-view.ts +5 -1
  77. package/lib/shell-usage.ts +7 -7
  78. package/lib/verifier-admission.ts +29 -0
  79. package/lib/yolo-temp-scratch.ts +69 -0
  80. package/package.json +1 -2
  81. package/runtime/gentle-ai-binary.mjs +28 -7
  82. package/runtime/gentle-shell-bundled.mjs +38 -2
  83. package/runtime/gentle-shell-import-pi.mjs +234 -0
  84. package/runtime/gentle-shell-launcher.mjs +143 -9
  85. package/runtime/gentle-shell-uninstall.mjs +18 -1
  86. package/runtime/native-review-cli.mjs +8 -0
  87. package/runtime/review-risk-assessment.mjs +43 -38
  88. package/scripts/build-runtime-modules.mjs +1 -0
  89. package/scripts/bundled-engram-migration.mjs +125 -0
  90. package/scripts/gentle-ai-installer.mjs +10 -10
  91. package/scripts/installer-server.mjs +14 -4
  92. package/scripts/main-channel.mjs +3 -2
  93. package/scripts/run-test-suite.mjs +5 -0
  94. package/scripts/verify-package-files.mjs +19 -13
  95. package/skills/gentle-ai/SKILL.md +3 -3
  96. package/skills/work-unit-commits/SKILL.md +5 -3
  97. package/tests/agent-risk-escalation.test.ts +37 -10
  98. package/tests/agents-runner.test.ts +260 -3
  99. package/tests/autonomous-guard.test.ts +122 -0
  100. package/tests/bounded-writer-admission.test.ts +11 -3
  101. package/tests/card-content-policy.test.ts +19 -9
  102. package/tests/child-safety.test.ts +39 -0
  103. package/tests/codegraph-tools.test.ts +123 -2
  104. package/tests/command-palette.test.ts +14 -4
  105. package/tests/commit-slice-signal.test.ts +162 -0
  106. package/tests/context-backstop.test.ts +159 -0
  107. package/tests/delegate-for-reason-contract.test.ts +91 -24
  108. package/tests/delivered-test-lint.test.ts +105 -0
  109. package/tests/destructive-command-guard.test.ts +85 -1
  110. package/tests/engram-safety.test.ts +1 -1
  111. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  112. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  113. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  114. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  115. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  116. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  117. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  118. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  119. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  120. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  121. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  122. package/tests/gauge-theme.test.ts +120 -0
  123. package/tests/gentle-agents.test.ts +282 -3
  124. package/tests/gentle-ai-binary.test.ts +1 -1
  125. package/tests/gentle-ai-child-guards.test.ts +61 -0
  126. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  127. package/tests/gentle-ai-installer.test.ts +51 -51
  128. package/tests/gentle-shell-bin.test.ts +163 -0
  129. package/tests/gentle-shell-bundled.test.ts +169 -6
  130. package/tests/gentle-shell-import-pi.test.ts +262 -0
  131. package/tests/gentle-shell-launcher.test.ts +79 -0
  132. package/tests/gentle-shell-uninstall.test.ts +30 -0
  133. package/tests/gentle-shell.test.ts +42 -9
  134. package/tests/gentle-todo.test.ts +179 -0
  135. package/tests/headless-delivery-question.test.ts +58 -0
  136. package/tests/herdr-activity.test.ts +34 -1
  137. package/tests/installer-server.test.ts +10 -2
  138. package/tests/installer-windows-bootstrap.test.ts +33 -4
  139. package/tests/native-review-capability-contract.test.ts +7 -1
  140. package/tests/native-review-parity.test.ts +22 -1
  141. package/tests/no-self-pause-contract.test.ts +5 -2
  142. package/tests/odd-integration.test.ts +5 -1
  143. package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
  144. package/tests/odd-routing-contract.test.ts +115 -30
  145. package/tests/orchestrator-budget.test.ts +19 -8
  146. package/tests/orchestrator-discovery.test.ts +29 -0
  147. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  148. package/tests/orchestrator-scope.test.ts +66 -0
  149. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  150. package/tests/package-manifest.test.ts +24 -20
  151. package/tests/pi-update-launcher.test.ts +21 -2
  152. package/tests/primary-session-prompt.test.ts +101 -0
  153. package/tests/provider-contract-bundle.test.ts +118 -0
  154. package/tests/provider-contract-mirror.test.ts +24 -0
  155. package/tests/quiet-tool-rendering.test.ts +23 -0
  156. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  157. package/tests/rdd-status-line.test.ts +79 -0
  158. package/tests/review-agent-end-preflight.test.ts +6 -0
  159. package/tests/review-candidate-view.test.ts +49 -0
  160. package/tests/review-contract-prompt.test.ts +5 -2
  161. package/tests/review-controller-native-routing.test.ts +14 -1
  162. package/tests/review-controller.test.ts +1 -1
  163. package/tests/review-host-relay-routing.test.ts +59 -2
  164. package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
  165. package/tests/review-owner-identity.test.ts +132 -0
  166. package/tests/review-risk-assessment.test.ts +163 -76
  167. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  168. package/tests/review-session-standing-permission.test.ts +27 -1
  169. package/tests/run-test-suite.test.ts +15 -0
  170. package/tests/runtime-harness.mjs +16 -6
  171. package/tests/session-changes-shell.test.ts +1 -1
  172. package/tests/session-profile-freeze.test.ts +23 -0
  173. package/tests/session-scratch.test.ts +945 -0
  174. package/tests/session-subject-routing-contract.test.ts +2 -1
  175. package/tests/shell-bar.test.ts +7 -0
  176. package/tests/shell-changes-view.test.ts +137 -1
  177. package/tests/shell-gauge.test.ts +42 -0
  178. package/tests/small-task-inline-contract.test.ts +37 -0
  179. package/tests/standalone-config-home.test.ts +324 -0
  180. package/tests/startup-banner.test.ts +5 -4
  181. package/tests/startup-home-repo.test.ts +34 -0
  182. package/tests/support/orchestrator-modules.ts +1 -0
  183. package/tests/task-size-routing-contract.test.ts +69 -9
  184. package/tests/test-scratch-contract.test.ts +102 -0
  185. package/tests/test-strength-contract.test.ts +36 -1
  186. package/tests/tool-batching-contract.test.ts +64 -0
  187. package/tests/verification-proportionality.test.ts +46 -0
  188. package/tests/verifier-admission.test.ts +137 -0
  189. package/tests/verify-agent-contract.test.ts +8 -8
  190. package/tests/windows-hidden-processes.test.ts +5 -0
  191. package/tests/writer-edit-surface-scope.test.ts +3 -0
  192. package/tests/writer-feature-document-admission.test.ts +156 -0
  193. package/tests/yolo-mode.test.ts +18 -0
  194. package/tests/yolo-temp-scratch.test.ts +71 -0
  195. package/scripts/mirror-odd-routing.mjs +0 -242
  196. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  197. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  198. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  199. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  200. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  201. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  202. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -26,8 +26,9 @@ import { VISUAL_SETTINGS_CHANGED } from "../lib/shell-sidebar.ts";
26
26
  import { resolveVisualSettings } from "../lib/visual-customization-policy.ts";
27
27
  import { createCompletionQueue } from "../lib/agents-completion-delivery.ts";
28
28
  import { createAgentMessageQueue, type PendingAgentMessage } from "../lib/agents-message-delivery.ts";
29
- import { AGENT_MODE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, withPinnedModelProfiles, type AgentDefinition, type AgentMode } from "../lib/agents-config.ts";
30
- import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt } from "../lib/session-profile-freeze.ts";
29
+ import { AGENT_MODE, PROFILE_SOURCE, discoverAgents, formatModelRef, loadAgentsConfig, resolveAgentProfile, withPinnedModelProfiles, type AgentDefinition, type AgentMode, type ProfileSource } from "../lib/agents-config.ts";
30
+ import { freezeSessionProfileAtStartup, inheritedProfileDriftNotice, resolveSessionProfile, sessionProfileRoutingAt, type InheritedProfileOptions, type SessionProfileResolution } from "../lib/session-profile-freeze.ts";
31
+ import { resolveProfilePin, resolveUnversionedProjectProfile } from "../lib/agent-profile-pin.ts";
31
32
  import { resolveBackgroundSubagentsPolicy } from "../lib/background-subagents-policy.ts";
32
33
  import { installBackgroundCacheWarming } from "../lib/background-cache-warming.ts";
33
34
  import { isFinished, MISSING_TOOLS_NOTE_PREFIX, TASK_EVENT, TASK_STATUS, TaskStore, type AskRequest, type TaskRecord } from "../lib/agents-protocol.ts";
@@ -49,6 +50,7 @@ import { discoverOrchestrators } from "../lib/orchestrator-discovery.ts";
49
50
  import { consultPublishedMetadata, unavailableMetadata, type MetadataReceipt } from "../lib/orchestrator-consultation.ts";
50
51
  import { HelperCostPermission } from "../lib/orchestrator-helper-consent.ts";
51
52
  import { OrchestratorStateCache } from "../lib/orchestrator-state.ts";
53
+ import { TODO_PROGRESS_EVENT, type TodoProgress } from "../lib/shell-todo.ts";
52
54
  import { normalizeTaskSubject } from "../lib/orchestrator-alias.ts";
53
55
  import { decodeWorkDescriptor } from "../lib/orchestrator-work.ts";
54
56
  import { validateWorkFilter, searchPublishedWork } from "../lib/orchestrator-work-search.ts";
@@ -186,6 +188,44 @@ function childExtensionRequest(env: NodeJS.ProcessEnv, curated: string[] | undef
186
188
  return curated && curated.length > 0 ? { extensionPaths: [...curated] } : {};
187
189
  }
188
190
 
191
+ // gentle-shell#2180: a short, stable token for the profile layer that supplied
192
+ // the launch's model, recorded next to the model in the TaskRecord so every
193
+ // task card shows which layer won and stale disk edits below a frozen session
194
+ // become visible. Derived from the resolution the request already performed;
195
+ // the only profile reads are the pin/declaration lookups in the paths where
196
+ // `sessionProfileRoutingAt` itself reads the target's own layers without
197
+ // carrying the origin (a foreign target under a session frozen without a
198
+ // profile, and `follow`/child launches), so no layer is resolved twice to route.
199
+ function modelSourceToken(
200
+ source: ProfileSource,
201
+ resolution: SessionProfileResolution,
202
+ routingTarget: InheritedProfileOptions & { foreignRepository?: boolean },
203
+ ): string | undefined {
204
+ if (source === PROFILE_SOURCE.DEFINITION) return "definition";
205
+ // Unresolved means no layer supplied a model at all: the card already shows
206
+ // `default`, and naming a layer next to it would only be noise.
207
+ if (source === PROFILE_SOURCE.UNRESOLVED) return undefined;
208
+ if (source !== PROFILE_SOURCE.PROFILE) return "default";
209
+ if (resolution.kind === "explicit") return "binding";
210
+ if (resolution.kind === "inherited") {
211
+ if (resolution.profile !== undefined) return `frozen:${resolution.profile.origin}`;
212
+ // Frozen without a profile: a foreign target keeps only its own pin
213
+ // layers, so a winning pin there is named from the same read
214
+ // `sessionProfileRoutingAt` just performed; otherwise the session keeps
215
+ // the materialized config routing.
216
+ if (routingTarget.foreignRepository === true) {
217
+ const pin = resolveProfilePin(routingTarget);
218
+ return pin === undefined ? "profile" : `pin:${pin.source}`;
219
+ }
220
+ return "profile";
221
+ }
222
+ // `follow` and live child launches read the target's own defaults
223
+ // (pin → declaration → global active); name the layer the same read chose.
224
+ const pin = resolveProfilePin(routingTarget);
225
+ if (pin !== undefined) return `pin:${pin.source}`;
226
+ return resolveUnversionedProjectProfile(routingTarget.cwd, routingTarget.configHome) !== undefined ? "declaration" : "profile";
227
+ }
228
+
189
229
  export function agentRuntimePaths(home: string, agentHome = join(home, ".pi", "agent")): { sessions: string; transcripts: string } {
190
230
  const root = join(agentHome, "gentle-agents");
191
231
  return { sessions: join(root, "sessions"), transcripts: join(root, "transcripts") };
@@ -549,6 +589,28 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
549
589
  scopeCache.clear();
550
590
  publishActivity();
551
591
  });
592
+ // The todo list is the session's progress: publish it for peers so the model
593
+ // spends no turn on orchestrator_session_id. Merged into the owner's curated
594
+ // state; a publication that cannot be made never fails the todo write.
595
+ // Counts only: a todo title is private work text, never published to peers.
596
+ // The last automatic line survives an owner publication that omits `progress`.
597
+ let automaticProgress: { sessionId: string; text: string } | undefined;
598
+ const unsubscribeTodoProgress = pi.events.on(TODO_PROGRESS_EVENT, (event) => {
599
+ const progress = event as Partial<TodoProgress> | undefined;
600
+ if (!sessions || progress?.sessionId !== sessions.getSessionId() || typeof progress.done !== "number" || typeof progress.total !== "number") return;
601
+ const text = `${progress.done}/${progress.total} done`;
602
+ try {
603
+ const owned = stateCache.get(sessions);
604
+ // A state the owner withdrew stays withdrawn until the owner publishes again.
605
+ if (owned?.state === null) return;
606
+ const { progress: previous, ...curated } = owned?.state ?? {};
607
+ // A cleared list has no progress to report: drop the line, publish nothing new.
608
+ if (progress.total === 0 && previous === undefined) return;
609
+ stateCache.publish(sessions, progress.total === 0 ? curated : { ...curated, progress: text }, (type, data) => pi.appendEntry(type, data));
610
+ automaticProgress = progress.total === 0 ? undefined : { sessionId: progress.sessionId, text };
611
+ publishActivity();
612
+ } catch { /* stale session or over the state budget: keep the previous publication */ }
613
+ });
552
614
  let worktrees: SessionWorktreeRegistry | undefined;
553
615
  let worktreeManager: ExtensionContext["sessionManager"] | undefined;
554
616
  let worktreeAuthority: (() => boolean) | undefined;
@@ -594,6 +656,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
594
656
  clearTaskMetrics();
595
657
  unsubscribeMetrics();
596
658
  unsubscribeScope();
659
+ unsubscribeTodoProgress();
597
660
  scopeCache.clear();
598
661
  });
599
662
  let stopAllConfirmation: Promise<void> | undefined;
@@ -1641,14 +1704,17 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1641
1704
  // included; only `follow` reads the defaults of the target. The profile is
1642
1705
  // resolved here, at task-request creation, so queued and running children
1643
1706
  // keep the routing frozen into their requests even if the session rebinds.
1707
+ // The routing target this launch reads (#1064), reused to name the winning
1708
+ // layer (#2180) so both answers come from one resolution.
1709
+ const routingTarget = {
1710
+ cwd: target ?? parentCwd,
1711
+ configHome: profileConfigHome,
1712
+ resolveWorktree: pinIdentity,
1713
+ foreignRepository: foreign,
1714
+ };
1644
1715
  const config = withPinnedModelProfiles(
1645
1716
  loadAgentsConfig(roots(ctx)),
1646
- sessionProfileRoutingAt(sessionProfile, {
1647
- cwd: target ?? parentCwd,
1648
- configHome: profileConfigHome,
1649
- resolveWorktree: pinIdentity,
1650
- foreignRepository: foreign,
1651
- }),
1717
+ sessionProfileRoutingAt(sessionProfile, routingTarget),
1652
1718
  );
1653
1719
  // Drift is measured against the defaults of the session's own directory,
1654
1720
  // where the profile was frozen, never against a delegated target.
@@ -1663,6 +1729,9 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1663
1729
  if (drift !== undefined) ctx.ui.notify(drift, "info");
1664
1730
  }
1665
1731
  const profile = resolveAgentProfile(agent, config);
1732
+ // #2180: the winning layer is named from the resolution and routing this
1733
+ // request already performed, never by re-resolving them.
1734
+ const modelSource = modelSourceToken(profile.source.model, sessionProfile, routingTarget);
1666
1735
  if (admittedModel !== undefined) {
1667
1736
  const model = profile.model ?? ctx.model;
1668
1737
  const catalogModel = model?.provider ? ctx.modelRegistry?.find(model.provider, model.id) : ctx.modelRegistry?.getAll().find(candidate => candidate.id === model?.id);
@@ -1703,6 +1772,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1703
1772
  } } : {}),
1704
1773
  ...(target === undefined || foreign ? {} : { onLaunch: () => { registry.register(target, "subagent:spawn"); } }),
1705
1774
  model: profile.model,
1775
+ ...(modelSource === undefined ? {} : { modelSource }),
1706
1776
  thinking: profile.thinking,
1707
1777
  sessionDir,
1708
1778
  resumeSessionPath: resume,
@@ -1842,7 +1912,7 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1842
1912
  pi.registerTool({
1843
1913
  name: "orchestrator_session_id",
1844
1914
  label: "Orchestrator session ID",
1845
- description: "Return this host session's stable routing ID, Initial alias (FIRST TASK), Current alias (CURRENT TASK), and separate human Session name. Explicit subjects update Current even when named; Initial stays the first declared branch topic, unknown for legacy history. Before delegation or cross-session coordination, declare a short recognizable subject here. Do not require a subject declaration for small direct tasks; do not query all peers. Names never authenticate. Existing Pi names and human renames are preserved. Use a concise non-sensitive label, not a prompt. For work beyond small direct tasks, publish subject and current/next status here; refresh at task changes/completion. Owner-curated state (2048 UTF-8 bytes total): null withdraws, omission leaves unchanged. Never include credentials, internal instructions, or raw prompts. Historical notes are not consent or an owner reply.",
1915
+ description: "Return this host session's stable routing ID, Initial alias (FIRST TASK), Current alias (CURRENT TASK), and separate human Session name. Explicit subjects update Current even when named; Initial stays the first declared branch topic, unknown for legacy history. Before delegation or cross-session coordination, declare a short recognizable subject here. Do not require a subject declaration for small direct tasks; do not query all peers. Names never authenticate. Existing Pi names and human renames are preserved. Use a concise non-sensitive label, not a prompt. Progress is published automatically from the todo list; publish state here only for objective, decisions, blockers, or work, never just to report progress. Owner-curated state (2048 UTF-8 bytes total): null withdraws, omission leaves unchanged. Never include credentials, internal instructions, or raw prompts. Historical notes are not consent or an owner reply.",
1846
1916
  ...orchestratorToolRenderers("session", expandHint),
1847
1917
  parameters: { type: "object", additionalProperties: false, properties: {
1848
1918
  subject: { type: "string", maxLength: 120, description: "Optional short task subject; updates task aliases independently of human names. Also names an unnamed Pi session." },
@@ -1864,7 +1934,20 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1864
1934
  if (!transport) return text("Error: session messaging is not ready.", { error: "not ready" });
1865
1935
  const { subject, state } = params as { subject?: unknown; state?: unknown };
1866
1936
  const declared = normalizeTaskSubject(subject);
1867
- if (state !== undefined || declared) stateCache.publish(ctx.sessionManager, state, (type, data) => pi.appendEntry(type, data), Date.now(), declared);
1937
+ // An owner object without `progress` keeps the automatic todo progress still on
1938
+ // display; null and an explicit `progress` win, and an over-budget merge does not fail the call.
1939
+ const shown = stateCache.get(ctx.sessionManager)?.state?.progress;
1940
+ const merged = state && typeof state === "object" && !Array.isArray(state) && !Object.hasOwn(state, "progress")
1941
+ && automaticProgress?.sessionId === ctx.sessionManager.getSessionId() && shown === automaticProgress.text
1942
+ ? { ...state, progress: shown } : undefined;
1943
+ const append = (type: string, data: unknown) => pi.appendEntry(type, data);
1944
+ if (merged) {
1945
+ try { stateCache.publish(ctx.sessionManager, merged, append, Date.now(), declared); }
1946
+ catch (error) {
1947
+ if ((error as Error).message !== "invalid-published-state") throw error;
1948
+ stateCache.publish(ctx.sessionManager, state, append, Date.now(), declared);
1949
+ }
1950
+ } else if (state !== undefined || declared) stateCache.publish(ctx.sessionManager, state, append, Date.now(), declared);
1868
1951
  if (activeTransportFor(ctx) !== transport) throw new Error("stale-published-state");
1869
1952
  if (declared && !ctx.sessionManager.getSessionName?.()) pi.setSessionName(declared);
1870
1953
  const alias = sanitizeDisplayLabel(ctx.sessionManager.getSessionName?.() ?? "");
@@ -1977,13 +2060,17 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
1977
2060
  ? `repository: ${fact.root} · clone: ${fact.cloneHash} · Git resolved at: ${fact.resolvedAt} (${fact.source})`
1978
2061
  : "repository: unknown";
1979
2062
  const rows = peers.map(peer => {
1980
- const context = peer.freshness === "recent" ? ` · ${peer.aliases?.currentAlias || peer.label || "unnamed"} · Session name: ${peer.label || "unnamed"} · Current alias (CURRENT TASK): ${peer.aliases?.currentAlias ?? "unknown"} · Initial alias (FIRST TASK): ${peer.aliases?.initialAlias ?? "unknown"} · recorded workspace: ${peer.workspace || "unknown"}` : " · context: unknown";
2063
+ const context = peer.freshness === "recent" ? ` · ${peer.aliases?.currentAlias || peer.label || "unnamed"} · Session name: ${peer.label || "unnamed"} · Current alias (CURRENT TASK): ${peer.aliases?.currentAlias ?? "unknown"} · Initial alias (FIRST TASK): ${peer.aliases?.initialAlias ?? "unknown"} · recorded workspace: ${peer.workspace || "unknown"}` : peer.legacyLabel !== undefined
2064
+ ? ` · ${peer.legacyLabel || "unnamed"} · context: legacy (this session predates discovery publication; restart it to publish its workspace and repository)`
2065
+ : " · context: unknown";
1981
2066
  const tasks = peer.tasks?.map(task => `\n - ${task.label || task.id} [${task.status}] · launch workspace: ${task.workspace || "unknown"} · ${repository(peer.scope?.tasks.find(t => t.id === task.id)?.repository)}`).join("") ?? "";
1982
2067
  const registered = peer.scope?.registered.map(fact => `\n registered: ${repository(fact)}`).join("") ?? "";
1983
2068
  const gaps = peer.scope && !peer.scope.complete ? `\n (scope incomplete: ${peer.scope.omittedTasks} tasks, ${peer.scope.omittedRegistered} registered roots omitted)` : "";
1984
2069
  const catalog = peer.catalog ? `\n recorded catalog (not Git identity): ${JSON.stringify(peer.catalog)}` : "\n recorded catalog: unknown";
1985
2070
  const note = params.recipient_session_id ? `\n owner-curated recorded state (not consent or owner reply; authority none): ${peer.state ? JSON.stringify(peer.state) : "unknown"}` : "";
1986
- return `- ${peer.sessionId}${context} · ${repository(peer.scope?.host)} · metadata: ${peer.freshness}${tasks}${registered}${gaps}${catalog}${note}${peer.omitted ? `\n (${peer.omitted} more tasks omitted)` : ""}`;
2071
+ // A legacy peer never publishes its scope: say so instead of reporting an unknown.
2072
+ const legacy = peer.legacyLabel !== undefined;
2073
+ return `- ${peer.sessionId}${context} · ${legacy ? "repository: not published" : repository(peer.scope?.host)} · metadata: ${legacy ? "legacy" : peer.freshness}${tasks}${registered}${gaps}${catalog}${note}${peer.omitted ? `\n (${peer.omitted} more tasks omitted)` : ""}`;
1987
2074
  });
1988
2075
  return peers.length === 0 ? text("No other sessions are currently advertised. Advertisements have unknown reachability and do not guarantee a live session.") : text(`Advertised sessions (reachability is unknown):\n${rows.join("\n")}`, { gentleAgents: { candidates: peers } });
1989
2076
  } catch {