peaks-loop 4.1.2 → 4.1.3
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/.claude-plugin/marketplace.json +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -1,407 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* a record that already left `queued` is precisely the one that must NOT be
|
|
23
|
-
* re-finalized, so the caller reports the survivors instead of touching one.
|
|
24
|
-
*/
|
|
25
|
-
export function selectFinalizeTarget(candidates) {
|
|
26
|
-
const queued = candidates.filter((candidate) => candidate.status === 'queued');
|
|
27
|
-
if (queued.length === 0)
|
|
28
|
-
return null;
|
|
29
|
-
return [...queued].sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.recordPath.localeCompare(a.recordPath))[0];
|
|
30
|
-
}
|
|
31
|
-
/** Why a candidate was not the chosen one — reported, never guessed at. */
|
|
32
|
-
export function describeFinalizeRejection(candidate, chosen) {
|
|
33
|
-
if (candidate.status !== 'queued' || chosen === null) {
|
|
34
|
-
return 'status is ' + candidate.status;
|
|
35
|
-
}
|
|
36
|
-
return 'superseded by the newer queued record ' + chosen.recordPath;
|
|
37
|
-
}
|
|
1
|
+
import { addJsonOption } from '../cli-helpers.js';
|
|
2
|
+
import { runAwait } from './await-runners.js';
|
|
3
|
+
import { runFinalize } from './finalize-runners.js';
|
|
4
|
+
import { runShare } from './share-runners.js';
|
|
5
|
+
import { runSharedRead } from './shared-read-runners.js';
|
|
6
|
+
// The finalize selection contract is imported by the finalize tests from this
|
|
7
|
+
// entry file, so it stays re-exported here even though its implementation
|
|
8
|
+
// moved to a sibling module.
|
|
9
|
+
export { FINALIZE_SELECTION_RULE, describeFinalizeRejection, selectFinalizeTarget } from './finalize-selection.js';
|
|
10
|
+
const SHARE_DESCRIPTION = 'G8.4: write a shared entry to the cross sub-agent shared channel. ' +
|
|
11
|
+
'Dispatcher-mediated indirect signal: sub-agent A writes, dispatcher ' +
|
|
12
|
+
'stores, sub-agent B (still in flight) reads via `peaks sub-agent ' +
|
|
13
|
+
'shared-read`. Not peer-to-peer; pseudo-swarm property 3 preserved.';
|
|
14
|
+
const SHARED_READ_DESCRIPTION = 'G8.4: read entries from the cross sub-agent shared channel. ' +
|
|
15
|
+
'Returns sibling sub-agent status. Supports --since (ISO8601) ' +
|
|
16
|
+
'and --key (glob pattern with * wildcard).';
|
|
17
|
+
const AWAIT_DESCRIPTION = '2.7.0 slice-dag-dispatcher MVP: wait for a batch of dispatched sub-agents ' +
|
|
18
|
+
'to finish (or hit --timeout). Returns one BatchResult per dispatch. ' +
|
|
19
|
+
'For non-claude-code IDEs, the wait is delegated to the LLM (slice 1.3 will ' +
|
|
20
|
+
'land real per-IDE joins).';
|
|
21
|
+
const FINALIZE_DESCRIPTION = 'D21: signal that a dispatched sub-agent has finished (success/failure/cancellation). Pass --all-stale for crash-recovery sweep.';
|
|
38
22
|
export function registerShareCommand(parent, io) {
|
|
39
23
|
addJsonOption(parent
|
|
40
24
|
.command('share')
|
|
41
|
-
.description(
|
|
42
|
-
'Dispatcher-mediated indirect signal: sub-agent A writes, dispatcher ' +
|
|
43
|
-
'stores, sub-agent B (still in flight) reads via `peaks sub-agent ' +
|
|
44
|
-
'shared-read`. Not peer-to-peer; pseudo-swarm property 3 preserved.')
|
|
25
|
+
.description(SHARE_DESCRIPTION)
|
|
45
26
|
.requiredOption('--batch <batchId>', 'batchId (from `peaks sub-agent dispatch` envelope)')
|
|
46
27
|
.requiredOption('--key <k>', 'entry key (convention: "<role>.<event>")')
|
|
47
28
|
.requiredOption('--value <json>', 'JSON object value (≤ 1KB soft warn, ≥ 64KB rejected)')
|
|
48
29
|
.option('--from <role>', 'sub-agent role string; defaults to dispatch record role if available')
|
|
49
30
|
.option('--request-id <rid>', 'request id (default: "unknown-rid")')
|
|
50
31
|
.option('--session-id <sid>', 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')
|
|
51
|
-
.option('--project <path>', 'target project root (defaults to cwd)')).action((options) =>
|
|
52
|
-
const asJson = options.json === true;
|
|
53
|
-
if (!options.batch || !options.key || !options.value) {
|
|
54
|
-
printResult(io, fail('sub-agent.share', 'MISSING_ARG', '--batch, --key, and --value are required', { ok: false }, ['Re-run with --batch <batchId> --key <key> --value <jsonObject>.']), asJson);
|
|
55
|
-
process.exitCode = 1;
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
let parsedValue;
|
|
59
|
-
try {
|
|
60
|
-
const parsed = JSON.parse(options.value);
|
|
61
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
62
|
-
throw new Error('value must be a JSON object');
|
|
63
|
-
}
|
|
64
|
-
parsedValue = parsed;
|
|
65
|
-
}
|
|
66
|
-
catch (err) {
|
|
67
|
-
printResult(io, fail('sub-agent.share', 'INVALID_VALUE', `value must be a JSON object: ${getErrorMessage(err)}`, { ok: false }, ['Pass --value as a JSON object literal, e.g. --value \'{"reason":"x"}\'.']), asJson);
|
|
68
|
-
process.exitCode = 1;
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const projectRoot = options.project ?? process.cwd();
|
|
73
|
-
const sid = options.sessionId ??
|
|
74
|
-
process.env.PEAKS_SESSION_ID ??
|
|
75
|
-
getCurrentSessionId(projectRoot) ??
|
|
76
|
-
'unknown-sid';
|
|
77
|
-
const rid = options.requestId ?? 'unknown-rid';
|
|
78
|
-
const from = options.from ?? 'unknown-role';
|
|
79
|
-
const result = writeSharedEntry({
|
|
80
|
-
projectRoot,
|
|
81
|
-
sid,
|
|
82
|
-
rid,
|
|
83
|
-
batchId: options.batch,
|
|
84
|
-
key: options.key,
|
|
85
|
-
from,
|
|
86
|
-
value: parsedValue
|
|
87
|
-
});
|
|
88
|
-
if (!result.ok) {
|
|
89
|
-
const code = result.code;
|
|
90
|
-
printResult(io, fail('sub-agent.share', code, result.message, { ok: false, batchId: options.batch }, [
|
|
91
|
-
code === 'VALUE_TOO_LARGE'
|
|
92
|
-
? 'Reduce value size; 1KB is a soft warning, 64KB is a hard reject.'
|
|
93
|
-
: 'See error message; check --batch, --key, --value arguments.'
|
|
94
|
-
]), asJson);
|
|
95
|
-
process.exitCode = 1;
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const warnings = [];
|
|
99
|
-
if (result.lastWriteWins) {
|
|
100
|
-
warnings.push('LAST_WRITE_WINS');
|
|
101
|
-
}
|
|
102
|
-
if (result.softWarning) {
|
|
103
|
-
warnings.push(`VALUE_SIZE_SOFT_WARN: ${result.entry.valueSize} > ${SHARED_CHANNEL_SOFT_VALUE_WARN} bytes`);
|
|
104
|
-
}
|
|
105
|
-
printResult(io, ok('sub-agent.share', {
|
|
106
|
-
envelopeVersion: '2.1.0',
|
|
107
|
-
ok: true,
|
|
108
|
-
batchId: options.batch,
|
|
109
|
-
entryKey: options.key,
|
|
110
|
-
writtenAt: result.entry.at,
|
|
111
|
-
channelSize: result.channelSize,
|
|
112
|
-
lastWriteWins: result.lastWriteWins,
|
|
113
|
-
valueSize: result.entry.valueSize
|
|
114
|
-
}, warnings, [
|
|
115
|
-
'Sub-agents in the same batch can read this entry via `peaks sub-agent shared-read --batch ' +
|
|
116
|
-
options.batch +
|
|
117
|
-
'`.'
|
|
118
|
-
]), asJson);
|
|
119
|
-
try {
|
|
120
|
-
writeLogEntry({
|
|
121
|
-
ts: new Date().toISOString(),
|
|
122
|
-
level: 'info',
|
|
123
|
-
command: 'sub-agent.share',
|
|
124
|
-
msg: 'shared',
|
|
125
|
-
sessionId: sid,
|
|
126
|
-
batchId: options.batch,
|
|
127
|
-
data: {
|
|
128
|
-
batchId: options.batch,
|
|
129
|
-
key: options.key,
|
|
130
|
-
valueSize: result.entry.valueSize,
|
|
131
|
-
lastWriteWins: result.lastWriteWins
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
137
|
-
/* best-effort */
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
const code = error.code ?? 'SHARE_ERROR';
|
|
142
|
-
printResult(io, fail('sub-agent.share', code, getErrorMessage(error), { ok: false, batchId: options.batch }, [shareErrorNextActions(code)]), asJson);
|
|
143
|
-
process.exitCode = 1;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* runner gets an actionable hint instead of a generic "see error
|
|
149
|
-
* message" fallback. Each branch names the most likely next step.
|
|
150
|
-
*/
|
|
151
|
-
function shareErrorNextActions(code) {
|
|
152
|
-
if (code === 'LOCK_TIMEOUT') {
|
|
153
|
-
return 'A concurrent `peaks sub-agent share` is holding the channel lock for >5s; retry, or check for a crashed holder (the .lock file is reaped after 30s).';
|
|
154
|
-
}
|
|
155
|
-
if (code === 'RECORD_NOT_FOUND' || code === 'INVALID_RECORD_PATH') {
|
|
156
|
-
return 'The dispatch record path is missing or outside .peaks/_sub_agents/. Re-run `peaks sub-agent dispatch <role>` to get a fresh record path, then use that here.';
|
|
157
|
-
}
|
|
158
|
-
return 'See error message; check that --batch matches the dispatch envelope and the value is a JSON object ≤ 64KB.';
|
|
32
|
+
.option('--project <path>', 'target project root (defaults to cwd)')).action((options) => runShare(options, io));
|
|
159
33
|
}
|
|
160
34
|
export function registerSharedReadCommand(parent, io) {
|
|
161
35
|
addJsonOption(parent
|
|
162
36
|
.command('shared-read')
|
|
163
|
-
.description(
|
|
164
|
-
'Returns sibling sub-agent status. Supports --since (ISO8601) ' +
|
|
165
|
-
'and --key (glob pattern with * wildcard).')
|
|
37
|
+
.description(SHARED_READ_DESCRIPTION)
|
|
166
38
|
.requiredOption('--batch <batchId>', 'batchId (from `peaks sub-agent dispatch` envelope)')
|
|
167
39
|
.option('--since <iso>', 'only return entries written after this ISO8601 timestamp')
|
|
168
40
|
.option('--key <pattern>', 'glob pattern, e.g. "rd.*" or "*.completed"')
|
|
169
41
|
.option('--request-id <rid>', 'request id (default: "unknown-rid")')
|
|
170
42
|
.option('--session-id <sid>', 'session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')
|
|
171
|
-
.option('--project <path>', 'target project root (defaults to cwd)')).action((options) =>
|
|
172
|
-
const asJson = options.json === true;
|
|
173
|
-
if (!options.batch) {
|
|
174
|
-
printResult(io, fail('sub-agent.shared-read', 'MISSING_BATCH', '--batch is required', { ok: false }, ['Re-run with --batch <batchId>.']), asJson);
|
|
175
|
-
process.exitCode = 1;
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
const projectRoot = options.project ?? process.cwd();
|
|
180
|
-
const sid = options.sessionId ??
|
|
181
|
-
process.env.PEAKS_SESSION_ID ??
|
|
182
|
-
getCurrentSessionId(projectRoot) ??
|
|
183
|
-
'unknown-sid';
|
|
184
|
-
const rid = options.requestId ?? 'unknown-rid';
|
|
185
|
-
const channel = readSharedChannel({
|
|
186
|
-
projectRoot,
|
|
187
|
-
sid,
|
|
188
|
-
rid,
|
|
189
|
-
batchId: options.batch,
|
|
190
|
-
...(options.since !== undefined ? { since: options.since } : {}),
|
|
191
|
-
...(options.key !== undefined ? { keyPattern: options.key } : {})
|
|
192
|
-
});
|
|
193
|
-
printResult(io, ok('sub-agent.shared-read', {
|
|
194
|
-
envelopeVersion: '2.1.0',
|
|
195
|
-
ok: true,
|
|
196
|
-
batchId: options.batch,
|
|
197
|
-
entries: channel.entries,
|
|
198
|
-
totalEntries: Object.keys(channel.entries).length,
|
|
199
|
-
channelSize: JSON.stringify(channel).length,
|
|
200
|
-
updatedAt: channel.updatedAt
|
|
201
|
-
}, [], [
|
|
202
|
-
'Shared channel is dispatcher-mediated; do not attempt to read sibling dispatch records directly.'
|
|
203
|
-
]), asJson);
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
const code = error.code ?? 'SHARED_READ_ERROR';
|
|
207
|
-
printResult(io, fail('sub-agent.shared-read', code, getErrorMessage(error), { ok: false, batchId: options.batch }, [sharedReadErrorNextActions(code)]), asJson);
|
|
208
|
-
process.exitCode = 1;
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
function sharedReadErrorNextActions(code) {
|
|
213
|
-
if (code === 'INVALID_BATCH_ID' || code === 'MISSING_BATCH') {
|
|
214
|
-
return 'Pass --batch <batchId> exactly as returned by the `peaks sub-agent dispatch` envelope.';
|
|
215
|
-
}
|
|
216
|
-
return 'See error message; check that --batch matches the dispatch envelope (typo / wrong batch will return an empty channel, not an error).';
|
|
43
|
+
.option('--project <path>', 'target project root (defaults to cwd)')).action((options) => runSharedRead(options, io));
|
|
217
44
|
}
|
|
218
45
|
export function registerAwaitCommand(parent, io) {
|
|
219
46
|
addJsonOption(parent
|
|
220
47
|
.command('await')
|
|
221
|
-
.description(
|
|
222
|
-
'to finish (or hit --timeout). Returns one BatchResult per dispatch. ' +
|
|
223
|
-
'For non-claude-code IDEs, the wait is delegated to the LLM (slice 1.3 will ' +
|
|
224
|
-
'land real per-IDE joins).')
|
|
48
|
+
.description(AWAIT_DESCRIPTION)
|
|
225
49
|
.requiredOption('--batch <batchId>', 'batchId from a dispatch envelope')
|
|
226
50
|
.option('--timeout <ms>', 'optional cap on how long the join waits (ms; default 60000, max 120000)')
|
|
227
51
|
.option('--project <path>', 'target project root (defaults to cwd)')
|
|
228
|
-
.option('--session-id <sid>', 'override active session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')).action(
|
|
229
|
-
const asJson = options.json === true;
|
|
230
|
-
if (!options.batch) {
|
|
231
|
-
printResult(io, fail('sub-agent.await', 'MISSING_BATCH', '--batch is required', { ok: false }, [
|
|
232
|
-
'Re-run with --batch <batchId> from a dispatch envelope.'
|
|
233
|
-
]), asJson);
|
|
234
|
-
process.exitCode = 1;
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
let timeoutMs;
|
|
238
|
-
if (typeof options.timeout === 'string' && options.timeout.length > 0) {
|
|
239
|
-
const n = Number.parseInt(options.timeout, 10);
|
|
240
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
241
|
-
printResult(io, fail('sub-agent.await', 'INVALID_TIMEOUT', `--timeout must be a positive integer ms (got ${options.timeout})`, { ok: false }, ['Pass an integer like --timeout 60000.']), asJson);
|
|
242
|
-
process.exitCode = 1;
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
timeoutMs = n;
|
|
246
|
-
}
|
|
247
|
-
const projectRoot = options.project ?? process.cwd();
|
|
248
|
-
const sid = options.sessionId ??
|
|
249
|
-
process.env.PEAKS_SESSION_ID ??
|
|
250
|
-
getCurrentSessionId(projectRoot) ??
|
|
251
|
-
'unknown-sid';
|
|
252
|
-
// Lazy-import IDE modules so `peaks sub-agent share` and
|
|
253
|
-
// `peaks sub-agent shared-read` (the high-frequency G8.4 path) do not
|
|
254
|
-
// pay for adapter resolution at module-load time. Slice
|
|
255
|
-
const { detectInstalledIde } = await import('../../services/ide/ide-detector.js');
|
|
256
|
-
const { getAdapter } = await import('../../services/ide/ide-registry.js');
|
|
257
|
-
const ide = detectInstalledIde(projectRoot) ?? 'claude-code';
|
|
258
|
-
const adapter = getAdapter(ide);
|
|
259
|
-
const dispatcher = adapter.subAgentDispatcher;
|
|
260
|
-
if (typeof dispatcher.awaitBatch !== 'function') {
|
|
261
|
-
printResult(io, fail('sub-agent.await', 'IDE_NOT_SUPPORTED', `IDE ${ide} does not support awaitBatch`, { ok: false }, [
|
|
262
|
-
'Every built-in adapter has an awaitBatch since slice 1.3; a dispatcher without one is a custom adapter registered outside the built-in set.'
|
|
263
|
-
]), asJson);
|
|
264
|
-
process.exitCode = 1;
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
// session's dispatch directory. `recordPaths` used to be hardcoded to
|
|
269
|
-
// `[]`, and `awaitBatch` short-circuits on an empty list
|
|
270
|
-
// (await-batch.ts:134) — so `await` reported zero results and exited 0
|
|
271
|
-
// for every batch, forever. A batch we cannot locate is now a failure,
|
|
272
|
-
// not a success with nothing in it.
|
|
273
|
-
const { readRecord } = await import('../../services/dispatch/dispatch-record-writer.js');
|
|
274
|
-
const scan = resolveBatchRecords({
|
|
275
|
-
projectRoot,
|
|
276
|
-
sessionId: sid,
|
|
277
|
-
batchId: options.batch,
|
|
278
|
-
readOne: readRecord
|
|
279
|
-
});
|
|
280
|
-
if (scan.recordPaths.length === 0) {
|
|
281
|
-
const unreadableNote = scan.unreadable.length > 0
|
|
282
|
-
? ` ${scan.unreadable.length} dispatch record(s) there could not be read, so they could not be matched to this batch: ${scan.unreadable.join(', ')}`
|
|
283
|
-
: '';
|
|
284
|
-
printResult(io, fail('sub-agent.await', 'NO_DISPATCH_RECORDS', `No dispatch record with batchId=${options.batch} under ${scan.sessionDir}.${unreadableNote}`, {
|
|
285
|
-
ok: false,
|
|
286
|
-
batchId: options.batch,
|
|
287
|
-
sessionDir: scan.sessionDir,
|
|
288
|
-
unreadableRecords: scan.unreadable
|
|
289
|
-
}, [awaitErrorNextActions('NO_DISPATCH_RECORDS')]), asJson);
|
|
290
|
-
process.exitCode = 1;
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
const input = {
|
|
294
|
-
batchId: options.batch,
|
|
295
|
-
dispatchCount: scan.recordPaths.length,
|
|
296
|
-
recordPaths: scan.recordPaths,
|
|
297
|
-
...(timeoutMs !== undefined ? { timeoutMs } : {})
|
|
298
|
-
};
|
|
299
|
-
const results = await dispatcher.awaitBatch(input);
|
|
300
|
-
const summary = summarizeBatchResults(results);
|
|
301
|
-
printResult(io, ok('sub-agent.await', {
|
|
302
|
-
envelopeVersion: '2.1.0',
|
|
303
|
-
batchId: options.batch,
|
|
304
|
-
ide: dispatcher.label,
|
|
305
|
-
results,
|
|
306
|
-
summary,
|
|
307
|
-
unreadableRecords: scan.unreadable
|
|
308
|
-
}, scan.unreadable.length > 0
|
|
309
|
-
? [
|
|
310
|
-
`${scan.unreadable.length} unreadable dispatch record(s) in this session were skipped and are NOT part of the results: ${scan.unreadable.join(', ')}`
|
|
311
|
-
]
|
|
312
|
-
: [], [
|
|
313
|
-
// four non-Claude IDEs would report `awaitByLlm: <ide> 1.2 fallback`,
|
|
314
|
-
// the slice-1.2 marker that slice 1.3 replaced with a real
|
|
315
|
-
// file-polling await. The text survived because nothing tested it —
|
|
316
|
-
// no adapter produces that note any more (asserted in
|
|
317
|
-
// sub-agent-dispatchers.test.ts), and the emitter that produced it,
|
|
318
|
-
// `awaitByLlmFallback`, has since been removed.
|
|
319
|
-
`Each non-claude-code IDE labels its own results (see the \`note\` field), so a timed-out slot is attributable to the adapter it came from.`
|
|
320
|
-
]), asJson);
|
|
321
|
-
}
|
|
322
|
-
catch (error) {
|
|
323
|
-
const code = error.code ?? 'AWAIT_ERROR';
|
|
324
|
-
printResult(io, fail('sub-agent.await', code, getErrorMessage(error), { ok: false }, [
|
|
325
|
-
awaitErrorNextActions(code)
|
|
326
|
-
]), asJson);
|
|
327
|
-
process.exitCode = 1;
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
function awaitErrorNextActions(code) {
|
|
332
|
-
if (code === 'INVALID_TIMEOUT') {
|
|
333
|
-
return 'Pass --timeout as a positive integer ms (e.g. --timeout 60000). 0 and non-numeric values are rejected.';
|
|
334
|
-
}
|
|
335
|
-
if (code === 'IDE_NOT_SUPPORTED') {
|
|
336
|
-
return 'Switch to claude-code, or rely on LLM-side await for non-claude-code IDEs in slice 1.3.';
|
|
337
|
-
}
|
|
338
|
-
if (code === 'NO_DISPATCH_RECORDS') {
|
|
339
|
-
return 'Check --session-id / --project: records live under .peaks/_sub_agents/<sessionId>/. Use the batchId exactly as printed by the dispatch envelope.';
|
|
340
|
-
}
|
|
341
|
-
return 'See error message; check that --batch matches the dispatch envelope and --timeout is a positive integer ms.';
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Resolve a record's on-disk path through `realpathSync` so the value returned
|
|
345
|
-
* here matches the value `writeInitialDispatchRecord` produces — which itself
|
|
346
|
-
* passes through `assertSafeDispatchRecordPath` and ends up canonicalized. On
|
|
347
|
-
* macOS, `mkdtempSync(join(tmpdir(), prefix))` returns `/var/folders/...`
|
|
348
|
-
* while `process.cwd()` after `chdir` returns `/private/var/folders/...`;
|
|
349
|
-
* without this helper the test fixture's `queuedPath` (canonical) and the
|
|
350
|
-
* envelope's `finalized[].recordPath` (raw) never compare equal. Falls back
|
|
351
|
-
* to the lexical path when the file is missing (race between scan and write).
|
|
352
|
-
*/
|
|
353
|
-
function safeRecordPath(p) {
|
|
354
|
-
try {
|
|
355
|
-
return realpathSyncNative(p);
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return p;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
*
|
|
363
|
-
* Reuses the conventions already in the repo instead of inventing a new one:
|
|
364
|
-
* - records live at `.peaks/_sub_agents/<sid>/dispatch-<rid>-<ts>.json`
|
|
365
|
-
* (`dispatchRecordPath`, src/services/security/safe-settings-path.ts);
|
|
366
|
-
* - a record's batch is its own `batchId` field, written by
|
|
367
|
-
* `writeInitialDispatchRecord`. The `--batch` branch of `finalize` (below)
|
|
368
|
-
* and `findBatchRecords` in heartbeat-watch-command.ts resolve a batch the
|
|
369
|
-
* same way: scan the session dir, filter `dispatch-*.json`, compare field.
|
|
370
|
-
*
|
|
371
|
-
* `unreadable` lists the `dispatch-*.json` candidates whose batch could NOT be
|
|
372
|
-
* determined. The caller must not fold them into "no such batch": a record it
|
|
373
|
-
* cannot read is not evidence that the batch is empty.
|
|
374
|
-
*/
|
|
375
|
-
function resolveBatchRecords(input) {
|
|
376
|
-
const sessionDir = resolve(input.projectRoot, '.peaks', '_sub_agents', input.sessionId);
|
|
377
|
-
const recordPaths = [];
|
|
378
|
-
const unreadable = [];
|
|
379
|
-
if (!existsSync(sessionDir))
|
|
380
|
-
return { sessionDir, recordPaths, unreadable };
|
|
381
|
-
for (const name of readdirSync(sessionDir)) {
|
|
382
|
-
// `active-dispatches.json` (the index) and `batch-<uuid>.counter.json` are
|
|
383
|
-
// not records; neither carries a `version`, so `readRecord` would reject
|
|
384
|
-
// them as "version mismatch". Same filter as the `--batch` branch below.
|
|
385
|
-
if (!name.startsWith('dispatch-') || !name.endsWith('.json'))
|
|
386
|
-
continue;
|
|
387
|
-
const recordPath = safeRecordPath(join(sessionDir, name));
|
|
388
|
-
let batchId;
|
|
389
|
-
try {
|
|
390
|
-
batchId = input.readOne(recordPath).batchId;
|
|
391
|
-
}
|
|
392
|
-
catch {
|
|
393
|
-
unreadable.push(recordPath);
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
if (batchId === input.batchId)
|
|
397
|
-
recordPaths.push(recordPath);
|
|
398
|
-
}
|
|
399
|
-
return { sessionDir, recordPaths, unreadable };
|
|
52
|
+
.option('--session-id <sid>', 'override active session id (default: resolve from .peaks/_runtime/session.json; falls back to PEAKS_SESSION_ID env var; final fallback "unknown-sid")')).action((options) => runAwait(options, io, resolveIdeDispatcher));
|
|
400
53
|
}
|
|
54
|
+
/** Contract: LLM MUST call once per dispatched Task, in post-completion branch. */
|
|
401
55
|
export function registerFinalizeCommand(parent, io) {
|
|
402
56
|
addJsonOption(parent
|
|
403
57
|
.command('finalize')
|
|
404
|
-
.description(
|
|
58
|
+
.description(FINALIZE_DESCRIPTION)
|
|
405
59
|
.option('--batch <batchId>', 'batchId from dispatch envelope')
|
|
406
60
|
.option('--request-id <rid>', 'requestId from dispatch envelope')
|
|
407
61
|
.option('--outcome <state>', 'done | failed | cancelled (default: done)')
|
|
@@ -409,186 +63,23 @@ export function registerFinalizeCommand(parent, io) {
|
|
|
409
63
|
.option('--all-stale', 'bulk-mark every queued record for this session as done')
|
|
410
64
|
.option('--project <path>', 'target project root (defaults to cwd)')
|
|
411
65
|
.option('--session-id <sid>', 'override active session id')).action((options) => {
|
|
412
|
-
void (
|
|
413
|
-
const asJson = options.json === true;
|
|
414
|
-
try {
|
|
415
|
-
const projectRoot = resolve(options.project ?? process.cwd());
|
|
416
|
-
const sessionId = options.sessionId ?? getCurrentSessionId(projectRoot) ?? 'unknown-sid';
|
|
417
|
-
if (!options.allStale && !options.requestId && !options.batch) {
|
|
418
|
-
printResult(io, fail('sub-agent.finalize', 'MISSING_TARGET', 'Pass --request-id or --batch (or --all-stale)', { ok: false }, ['Call finalize after each Task completes.']), asJson);
|
|
419
|
-
process.exitCode = 1;
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const outcome = (options.outcome ?? 'done');
|
|
423
|
-
const writerMod = await import('../../services/dispatch/dispatch-record-writer.js');
|
|
424
|
-
const { readRecord, markCompleted, readActiveDispatchIndex } = writerMod;
|
|
425
|
-
// map outcome -> (status, outcome)
|
|
426
|
-
const outcomeMap = {
|
|
427
|
-
done: { status: 'done', outcome: 'success' },
|
|
428
|
-
failed: { status: 'failed', outcome: 'failed' },
|
|
429
|
-
cancelled: { status: 'cancelled', outcome: 'cancelled' }
|
|
430
|
-
};
|
|
431
|
-
const mapped = outcomeMap[outcome] ?? outcomeMap['done'];
|
|
432
|
-
const finalized = [];
|
|
433
|
-
const skipped = [];
|
|
434
|
-
const errors = [];
|
|
435
|
-
let selection = null;
|
|
436
|
-
/**
|
|
437
|
-
* N2 — one unreadable record must not abort the sweep.
|
|
438
|
-
*
|
|
439
|
-
* Skipping `active-dispatches.json` and `batch-*.counter.json` by
|
|
440
|
-
* FILENAME removed the two non-record files that happened to be in the
|
|
441
|
-
* directory, but a single stale or foreign `dispatch-*.json` (an old
|
|
442
|
-
* `version`, hand-edited JSON, a truncated write) still made
|
|
443
|
-
* `readRecord` throw from OUTSIDE any try/catch — and the throw
|
|
444
|
-
* escaped to the action's outer handler, so `--request-id` AND
|
|
445
|
-
* `--batch` both died with `FINALIZE_ERROR` and exit 1 without
|
|
446
|
-
* touching a single healthy record.
|
|
447
|
-
*
|
|
448
|
-
* A record that cannot be read is now reported in `errors[]` and
|
|
449
|
-
* skipped; every other record is processed as before.
|
|
450
|
-
*/
|
|
451
|
-
const tryReadRecord = (recordPath) => {
|
|
452
|
-
try {
|
|
453
|
-
return readRecord(recordPath);
|
|
454
|
-
}
|
|
455
|
-
catch (e) {
|
|
456
|
-
errors.push({ recordPath, error: getErrorMessage(e) });
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
const applyOutcome = (recordPath, rid) => {
|
|
461
|
-
markCompleted({
|
|
462
|
-
recordPath,
|
|
463
|
-
now: () => new Date(),
|
|
464
|
-
status: mapped.status,
|
|
465
|
-
outcome: mapped.outcome,
|
|
466
|
-
projectRoot
|
|
467
|
-
});
|
|
468
|
-
finalized.push({ recordPath, requestId: rid, status: mapped.status });
|
|
469
|
-
};
|
|
470
|
-
if (options.allStale) {
|
|
471
|
-
const index = readActiveDispatchIndex(projectRoot, sessionId);
|
|
472
|
-
for (const [recordPath, entry] of Object.entries(index)) {
|
|
473
|
-
if (entry.status !== 'queued') {
|
|
474
|
-
skipped.push({ recordPath, reason: 'status is ' + entry.status });
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
try {
|
|
478
|
-
applyOutcome(recordPath, entry.requestId);
|
|
479
|
-
}
|
|
480
|
-
catch (e) {
|
|
481
|
-
errors.push({ recordPath, error: getErrorMessage(e) });
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
else if (options.requestId) {
|
|
486
|
-
const fs2 = await import('node:fs');
|
|
487
|
-
const path2 = await import('node:path');
|
|
488
|
-
const dir = path2.resolve(projectRoot, '.peaks', '_sub_agents', sessionId);
|
|
489
|
-
const candidates = [];
|
|
490
|
-
if (fs2.existsSync(dir)) {
|
|
491
|
-
for (const f of fs2.readdirSync(dir)) {
|
|
492
|
-
// Only dispatch records are readable records. The session
|
|
493
|
-
// directory also holds `active-dispatches.json` (an index)
|
|
494
|
-
// and `batch-<uuid>.counter.json` (batch counters); neither
|
|
495
|
-
// carries a `version` field, so `readRecord` on them throws
|
|
496
|
-
// `Dispatch record version mismatch ... got undefined`. The
|
|
497
|
-
// `--batch` branch below has always used this same filter.
|
|
498
|
-
if (!f.startsWith('dispatch-') || !f.endsWith('.json'))
|
|
499
|
-
continue;
|
|
500
|
-
const p = safeRecordPath(path2.join(dir, f));
|
|
501
|
-
const r = tryReadRecord(p);
|
|
502
|
-
if (r === null || r.requestId !== options.requestId)
|
|
503
|
-
continue;
|
|
504
|
-
candidates.push({
|
|
505
|
-
recordPath: p,
|
|
506
|
-
requestId: r.requestId,
|
|
507
|
-
status: r.status,
|
|
508
|
-
createdAt: r.createdAt
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
if (candidates.length === 0) {
|
|
513
|
-
printResult(io, fail('sub-agent.finalize', 'RECORD_NOT_FOUND', 'No dispatch record for requestId=' + options.requestId, { ok: false }, ['Check --request-id matches the dispatch envelope.']), asJson);
|
|
514
|
-
process.exitCode = 1;
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
// N3: same rule as `--batch` — see `selectFinalizeTarget`.
|
|
518
|
-
const chosen = selectFinalizeTarget(candidates);
|
|
519
|
-
selection = {
|
|
520
|
-
requestId: options.requestId,
|
|
521
|
-
rule: FINALIZE_SELECTION_RULE,
|
|
522
|
-
matched: candidates.length,
|
|
523
|
-
chosen: chosen?.recordPath ?? null,
|
|
524
|
-
rejected: candidates
|
|
525
|
-
.filter((candidate) => candidate !== chosen)
|
|
526
|
-
.map((candidate) => ({
|
|
527
|
-
recordPath: candidate.recordPath,
|
|
528
|
-
status: candidate.status,
|
|
529
|
-
reason: describeFinalizeRejection(candidate, chosen)
|
|
530
|
-
}))
|
|
531
|
-
};
|
|
532
|
-
for (const rejected of selection.rejected) {
|
|
533
|
-
skipped.push({ recordPath: rejected.recordPath, reason: rejected.reason });
|
|
534
|
-
}
|
|
535
|
-
if (chosen !== null) {
|
|
536
|
-
try {
|
|
537
|
-
applyOutcome(chosen.recordPath, chosen.requestId);
|
|
538
|
-
}
|
|
539
|
-
catch (e) {
|
|
540
|
-
errors.push({ recordPath: chosen.recordPath, error: getErrorMessage(e) });
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
const fs2 = await import('node:fs');
|
|
546
|
-
const path2 = await import('node:path');
|
|
547
|
-
const dir = path2.resolve(projectRoot, '.peaks', '_sub_agents', sessionId);
|
|
548
|
-
if (fs2.existsSync(dir)) {
|
|
549
|
-
for (const f of fs2.readdirSync(dir)) {
|
|
550
|
-
if (!f.startsWith('dispatch-') || !f.endsWith('.json'))
|
|
551
|
-
continue;
|
|
552
|
-
const p = safeRecordPath(path2.join(dir, f));
|
|
553
|
-
const r = tryReadRecord(p);
|
|
554
|
-
if (r === null)
|
|
555
|
-
continue;
|
|
556
|
-
if (r.batchId !== options.batch)
|
|
557
|
-
continue;
|
|
558
|
-
if (r.status !== 'queued') {
|
|
559
|
-
skipped.push({ recordPath: p, reason: 'status is ' + r.status });
|
|
560
|
-
continue;
|
|
561
|
-
}
|
|
562
|
-
try {
|
|
563
|
-
applyOutcome(p, r.requestId);
|
|
564
|
-
}
|
|
565
|
-
catch (e) {
|
|
566
|
-
errors.push({ recordPath: p, error: getErrorMessage(e) });
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const hints = [];
|
|
572
|
-
if (errors.length > 0) {
|
|
573
|
-
hints.push('Re-run after fixing; unreadable records are listed in errors[] and were skipped, not fatal.');
|
|
574
|
-
}
|
|
575
|
-
else if (finalized.length === 0 && skipped.length > 0) {
|
|
576
|
-
hints.push("Nothing was finalized: every matching record had already left `queued`. See skipped[] for each record's status.");
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
hints.push('All targeted records transitioned out of queued.');
|
|
580
|
-
}
|
|
581
|
-
if (selection !== null) {
|
|
582
|
-
hints.push(`--request-id selection (${selection.rule}): chose ${selection.chosen ?? '(none)'} of ${selection.matched} matching record(s); ${selection.rejected.length} rejected.`);
|
|
583
|
-
}
|
|
584
|
-
printResult(io, ok('sub-agent.finalize', { finalized, skipped, errors, selection, sessionId, outcome }, errors.length > 0 ? [errors.length + ' failed'] : [], hints), asJson);
|
|
585
|
-
if (errors.length > 0)
|
|
586
|
-
process.exitCode = 1;
|
|
587
|
-
}
|
|
588
|
-
catch (error) {
|
|
589
|
-
printResult(io, fail('sub-agent.finalize', 'FINALIZE_ERROR', getErrorMessage(error), { ok: false }, ['Inspect the error.']), asJson);
|
|
590
|
-
process.exitCode = 1;
|
|
591
|
-
}
|
|
592
|
-
})();
|
|
66
|
+
void runFinalize(options, io);
|
|
593
67
|
});
|
|
594
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the per-IDE sub-agent dispatcher for `await`, importing the IDE
|
|
71
|
+
* modules lazily so `peaks sub-agent share` and `peaks sub-agent shared-read`
|
|
72
|
+
* (the high-frequency G8.4 path) do not pay for adapter resolution at
|
|
73
|
+
* module-load time.
|
|
74
|
+
*
|
|
75
|
+
* This lives here, not in the await runner, because this module is the adapter
|
|
76
|
+
* registry's consumer — `tests/unit/runtime/vendor-neutral-identity-guard.test.ts`
|
|
77
|
+
* pins that this file is in the guard's measured scope.
|
|
78
|
+
*/
|
|
79
|
+
async function resolveIdeDispatcher(projectRoot) {
|
|
80
|
+
const { detectInstalledIde } = await import('../../services/ide/ide-detector.js');
|
|
81
|
+
const { getAdapter } = await import('../../services/ide/ide-registry.js');
|
|
82
|
+
const ide = detectInstalledIde(projectRoot) ?? 'claude-code';
|
|
83
|
+
const adapter = getAdapter(ide);
|
|
84
|
+
return { ide, dispatcher: adapter.subAgentDispatcher };
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-code "what to do next" hints for the failure envelopes of
|
|
3
|
+
* `peaks sub-agent share`, `peaks sub-agent shared-read`, and
|
|
4
|
+
* `peaks sub-agent await`. Extracted from `share-commands.ts` so that file
|
|
5
|
+
* stays under the line cap; each helper is pure — it maps an error code to the
|
|
6
|
+
* single actionable hint the runner places in the envelope, and nothing here
|
|
7
|
+
* prints, reads state, or throws.
|
|
8
|
+
*
|
|
9
|
+
* Each branch names the most likely next step, so a caller gets an actionable
|
|
10
|
+
* hint instead of a generic "see error message" fallback.
|
|
11
|
+
*/
|
|
12
|
+
export declare function shareErrorNextActions(code: string): string;
|
|
13
|
+
export declare function sharedReadErrorNextActions(code: string): string;
|
|
14
|
+
export declare function awaitErrorNextActions(code: string): string;
|