gentle-pi 3.6.0 → 4.0.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.
- package/README.md +37 -6
- package/assets/agents/gentle-ai-explore.md +4 -4
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -9
- package/assets/orchestrator-delegation.md +40 -41
- package/assets/orchestrator-memory.md +1 -22
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator.md +12 -24
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/gentle-shell.mjs +254 -29
- package/docs/delegated-verification.md +26 -1
- package/docs/gentle-agents-activity.md +24 -0
- package/docs/gentle-shell.md +95 -30
- package/docs/native-authority-architecture.md +2 -2
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +117 -235
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +26 -0
- package/extensions/child-safety.ts +23 -0
- package/extensions/gentle-agents.ts +509 -275
- package/extensions/gentle-ai.ts +781 -683
- package/extensions/gentle-shell.ts +1375 -88
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +18 -7
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1419 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/quiet-tools.ts +179 -87
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +16 -12
- package/extensions/startup-banner.ts +60 -31
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-protocol.ts +60 -0
- package/lib/agents-runner.ts +105 -98
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/bounded-writer-admission.ts +147 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/codemode-renderer.ts +185 -0
- package/lib/command-palette-catalog.ts +3 -9
- package/lib/command-palette.ts +25 -14
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/foreign-target-grants.ts +32 -0
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-launcher.ts +24 -13
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +227 -0
- package/lib/native-review-cli.ts +53 -108
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +67 -17
- package/lib/review-candidate-view.ts +112 -25
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +156 -11
- package/lib/review-sidebar-state.ts +223 -0
- package/lib/selection-engine.ts +515 -0
- package/lib/session-change-capture.ts +10 -1
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-worktree-registry.ts +14 -2
- package/lib/shell-bar.ts +179 -24
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +78 -14
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +23 -13
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +66 -10
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +379 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +108 -0
- package/lib/visual-customize-view.ts +330 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/yolo-session-policy.ts +240 -0
- package/package.json +20 -8
- package/runtime/gentle-shell-launcher.mjs +23 -12
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/native-review-cli.mjs +53 -108
- package/runtime/review-risk-assessment.mjs +154 -9
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/gentle-ai-installer.mjs +14 -13
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/run-test-suite.mjs +76 -0
- package/scripts/test-packed-runner.mjs +31 -14
- package/scripts/verify-package-files.mjs +11 -22
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +9 -15
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-profiles.test.ts +18 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +40 -0
- package/tests/agents-runner.test.ts +400 -89
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/bounded-writer-admission.test.ts +95 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/card-style-policy.test.ts +55 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-safety.test.ts +82 -0
- package/tests/codemode-rendering.test.ts +491 -0
- package/tests/command-palette.test.ts +39 -3
- package/tests/delegated-key-learnings-contract.test.ts +0 -76
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +170 -2
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/foreign-target-grants.test.ts +58 -0
- package/tests/generic-agent-tools.test.ts +54 -0
- package/tests/gentle-agents.test.ts +1411 -256
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +68 -54
- package/tests/gentle-ai-renderer.test.ts +487 -8
- package/tests/gentle-ai.test.ts +288 -65
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-shell-bin.test.ts +651 -114
- package/tests/gentle-shell-launcher.test.ts +99 -52
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell.test.ts +3839 -187
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +80 -8
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +29 -19
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/nan-provider.test.ts +471 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +6 -120
- package/tests/native-review-parity-runtime.test.ts +100 -3
- package/tests/odd-integration.test.ts +67 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
- package/tests/odd-routing-contract.test.ts +86 -35
- package/tests/orchestrator-budget.test.ts +14 -39
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +252 -115
- package/tests/packed-runner-owned-path.test.ts +46 -0
- package/tests/persona-single-channel.test.ts +6 -6
- package/tests/provider-defect-handoff.test.ts +3 -11
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +409 -184
- package/tests/rdd-aware-verification-contract.test.ts +76 -1
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +176 -12
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +298 -0
- package/tests/review-contract-prompt.test.ts +108 -43
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-routing.test.ts +611 -5
- package/tests/review-controller-workspace-root.test.ts +163 -4
- package/tests/review-host-relay-routing.test.ts +338 -2
- package/tests/review-integration-v2-forward.test.ts +200 -0
- package/tests/review-ledger-contract.test.ts +10 -34
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +498 -6
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/run-test-suite.test.ts +124 -0
- package/tests/runtime-harness.mjs +145 -786
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/selection-engine.test.ts +421 -0
- package/tests/session-change-capture.test.ts +12 -1
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-worktree-registry.test.ts +77 -0
- package/tests/shell-bar.test.ts +382 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +52 -0
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-sidebar-layout.test.ts +325 -21
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +87 -1
- package/tests/shell-usage-view.test.ts +27 -0
- package/tests/shell-usage.test.ts +73 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/startup-banner.test.ts +130 -2
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/telemetry-trigger.test.ts +81 -20
- package/tests/theme-customization.test.ts +72 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +110 -0
- package/tests/visual-customize-view.test.ts +418 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/yolo-customize.test.ts +256 -0
- package/tests/yolo-mode-runtime.test.ts +161 -0
- package/tests/yolo-mode.test.ts +261 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
|
@@ -30,7 +30,7 @@ Use it for:
|
|
|
30
30
|
| Keep docs with the user-visible change | Docs belong with the feature or workflow they explain. |
|
|
31
31
|
| Tell a story | A reviewer should understand why each commit exists from its diff and message. |
|
|
32
32
|
| Future PR-ready | Each commit should be a candidate chained PR when the change grows. |
|
|
33
|
-
| SDD workload guard | If SDD tasks forecast a >400-line change, group commits into chained PR slices before implementation. |
|
|
33
|
+
| SDD workload guard | If SDD tasks forecast a >400-line change, honor the selected `delivery_strategy`. On chaining paths only, group commits into chained PR slices before implementation. For explicit `single-pr` or `exception-ok`, keep one PR, report the review load, and follow the destination repository's documented contribution/size policy; selection alone does not accept an exception. |
|
|
34
34
|
| Budget is not code-golf | Never shrink a diff by deleting comments, blank lines, docs, or tests, or by compressing code, to fit the review budget (400 by default, or the session `review_budget_lines`). Slice by work unit or report the overage. |
|
|
35
35
|
|
|
36
36
|
## Work Unit Checklist
|
|
@@ -67,8 +67,9 @@ When `sdd-tasks` produces a Review Workload Forecast:
|
|
|
67
67
|
|
|
68
68
|
- Low risk: keep work-unit commits inside one PR.
|
|
69
69
|
- Medium risk: commit by work unit and monitor changed lines before PR creation.
|
|
70
|
-
- High risk: follow SDD `delivery_strategy` — ask on `ask-on-risk`, auto-slice on `auto-chain`,
|
|
71
|
-
-
|
|
70
|
+
- High risk: follow SDD `delivery_strategy` — ask on `ask-on-risk`, auto-slice with the cached chain choice on `auto-chain`, follow the destination repository's documented contribution/size policy on over-budget `single-pr`, or record an explicitly accepted policy exception on `exception-ok`. A single-PR selection does not itself accept an exception.
|
|
71
|
+
- `size:exception` is a Gentle-owned repository policy, not a universal label requirement. Never request, create, or add the label for generic users unless the destination policy uses it; preserve its maintainer acceptance and protected-label authorization gates.
|
|
72
|
+
- Splitting is bounded: after one honest slicing pass, if no cohesive work-unit split fits the budget, stop and report the smallest honest count and rationale. Recommend an exception only where destination policy provides it. Do not iterate shrinking the code to reach the number.
|
|
72
73
|
|
|
73
74
|
Each SDD work unit should map cleanly to a commit or PR with:
|
|
74
75
|
|
|
@@ -437,6 +437,24 @@ test("normalizeProfilesFile drops an active marker that points nowhere", () => {
|
|
|
437
437
|
assert.equal(normalized?.drops.droppedProfiles.length, 0);
|
|
438
438
|
});
|
|
439
439
|
|
|
440
|
+
test("normalizeProfilesFile accepts effort as alias for thinking in profile entries (#1403)", () => {
|
|
441
|
+
const normalized = normalizeProfilesFile(
|
|
442
|
+
JSON.parse(
|
|
443
|
+
profilesText({
|
|
444
|
+
hybrid: {
|
|
445
|
+
"example-agent": { model: "anthropic/claude-sonnet-4", effort: "medium" },
|
|
446
|
+
"worker": { effort: "high" },
|
|
447
|
+
},
|
|
448
|
+
}),
|
|
449
|
+
),
|
|
450
|
+
);
|
|
451
|
+
assert.deepEqual(normalized?.file.profiles.hybrid, {
|
|
452
|
+
"example-agent": { model: "anthropic/claude-sonnet-4", thinking: "medium" },
|
|
453
|
+
"worker": { model: undefined, thinking: "high" },
|
|
454
|
+
});
|
|
455
|
+
assert.equal(normalized?.drops.droppedAgents.length, 0);
|
|
456
|
+
});
|
|
457
|
+
|
|
440
458
|
test("bootstrapProfilesFile seeds a current profile, active only when routing is non-empty", () => {
|
|
441
459
|
const empty = bootstrapProfilesFile({});
|
|
442
460
|
assert.deepEqual(empty, {
|
|
@@ -9,7 +9,7 @@ export interface FakeChild {
|
|
|
9
9
|
child: ChildLike;
|
|
10
10
|
written: Array<Record<string, unknown>>;
|
|
11
11
|
emit(event: Record<string, unknown>): void;
|
|
12
|
-
exit(code: number): void;
|
|
12
|
+
exit(code: number | null, signal?: string | null): void;
|
|
13
13
|
fail(message: string): void;
|
|
14
14
|
killed: string[];
|
|
15
15
|
sent: Array<Record<string, unknown>>;
|
|
@@ -62,5 +62,5 @@ export function fakeChild(options: { exitOnKill?: boolean; pid?: number } = {}):
|
|
|
62
62
|
return child;
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
|
-
return { child, written, killed, sent, get disconnects() { return disconnects; }, message: (event) => emitter.emit("message", event), emit: (event) => stdout.write(`${JSON.stringify(event)}\n`), exit: (code) => emitter.emit("exit", code,
|
|
65
|
+
return { child, written, killed, sent, get disconnects() { return disconnects; }, message: (event) => emitter.emit("message", event), emit: (event) => stdout.write(`${JSON.stringify(event)}\n`), exit: (code, signal = null) => emitter.emit("exit", code, signal), fail: (message) => emitter.emit("error", new Error(message)) };
|
|
66
66
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createAgentMessageQueue, STALE_NOTIFICATION_MS } from "../lib/agents-message-delivery.ts";
|
|
4
|
+
|
|
5
|
+
test("delivers live notifications and queries in order for the active session", () => {
|
|
6
|
+
const queue = createAgentMessageQueue();
|
|
7
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "First progress", 1000);
|
|
8
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Which branch?", 1050);
|
|
9
|
+
|
|
10
|
+
assert.equal(queue.pendingCount(), 2);
|
|
11
|
+
const deliverable = queue.takeDeliverable(1100, "s1", () => true);
|
|
12
|
+
assert.equal(deliverable.length, 2);
|
|
13
|
+
|
|
14
|
+
assert.equal(deliverable[0]?.kind, "notification");
|
|
15
|
+
assert.equal(deliverable[0]?.content, "First progress");
|
|
16
|
+
assert.equal(deliverable[0]?.display, false);
|
|
17
|
+
|
|
18
|
+
assert.equal(deliverable[1]?.kind, "query");
|
|
19
|
+
assert.match(deliverable[1]?.content ?? "", /Which branch\?/);
|
|
20
|
+
assert.equal(deliverable[1]?.display, true);
|
|
21
|
+
assert.equal(deliverable[1]?.requestId, "q1");
|
|
22
|
+
|
|
23
|
+
assert.equal(queue.pendingCount(), 0);
|
|
24
|
+
assert.equal(queue.takeDeliverable(1200, "s1", () => true).length, 0, "messages are delivered at most once");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a notification from a task that settled before delivery is dropped silently", () => {
|
|
28
|
+
const queue = createAgentMessageQueue();
|
|
29
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "Late notice", 1000);
|
|
30
|
+
|
|
31
|
+
// The task finishes and is invalidated before the parent turn boundary.
|
|
32
|
+
queue.invalidateTask("task-1");
|
|
33
|
+
|
|
34
|
+
const deliverable = queue.takeDeliverable(1050, "s1", () => false);
|
|
35
|
+
assert.equal(deliverable.length, 0, "notification from settled task must never enter conversation");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("an answered query is consumed and never delivered", () => {
|
|
39
|
+
const queue = createAgentMessageQueue();
|
|
40
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Which target?", 1000);
|
|
41
|
+
|
|
42
|
+
// Parent replies before queue flush.
|
|
43
|
+
queue.consumeQuery("task-1", "q1");
|
|
44
|
+
|
|
45
|
+
const deliverable = queue.takeDeliverable(1050, "s1", () => true);
|
|
46
|
+
assert.equal(deliverable.length, 0, "answered query must not be delivered");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("an expired query is dropped and never delivered", () => {
|
|
50
|
+
const queue = createAgentMessageQueue();
|
|
51
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Need approval", 1000);
|
|
52
|
+
|
|
53
|
+
// Query times out.
|
|
54
|
+
queue.expireQuery("task-1", "q1");
|
|
55
|
+
|
|
56
|
+
const deliverable = queue.takeDeliverable(1050, "s1", () => true);
|
|
57
|
+
assert.equal(deliverable.length, 0, "expired query must not be delivered");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a notification exceeding the stale window is dropped", () => {
|
|
61
|
+
const queue = createAgentMessageQueue();
|
|
62
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "Slow notice", 1000);
|
|
63
|
+
|
|
64
|
+
// Held past the 90-second threshold.
|
|
65
|
+
const deliverable = queue.takeDeliverable(1000 + STALE_NOTIFICATION_MS + 10, "s1", () => true);
|
|
66
|
+
assert.equal(deliverable.length, 0, "stale notification must not be delivered");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("isTaskLive returning false drops messages for non-live tasks", () => {
|
|
70
|
+
const queue = createAgentMessageQueue();
|
|
71
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "Dead task notice", 1000);
|
|
72
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Dead task query", 1000);
|
|
73
|
+
|
|
74
|
+
const deliverable = queue.takeDeliverable(1050, "s1", (id) => id !== "task-1");
|
|
75
|
+
assert.equal(deliverable.length, 0, "non-live task messages must be dropped");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("session mismatch drops foreign session messages", () => {
|
|
79
|
+
const queue = createAgentMessageQueue();
|
|
80
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "session-old" }, "Old session notice", 1000);
|
|
81
|
+
|
|
82
|
+
const deliverable = queue.takeDeliverable(1050, "session-new", () => true);
|
|
83
|
+
assert.equal(deliverable.length, 0, "foreign session message must be dropped");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("deduplicates identical query requests for the same task and requestId", () => {
|
|
87
|
+
const queue = createAgentMessageQueue();
|
|
88
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "First ask", 1000);
|
|
89
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Duplicate ask", 1001);
|
|
90
|
+
|
|
91
|
+
assert.equal(queue.pendingCount(), 1);
|
|
92
|
+
const deliverable = queue.takeDeliverable(1050, "s1", () => true);
|
|
93
|
+
assert.equal(deliverable.length, 1);
|
|
94
|
+
assert.match(deliverable[0]!.content, /First ask/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("dropAll clears everything across session reset", () => {
|
|
98
|
+
const queue = createAgentMessageQueue();
|
|
99
|
+
queue.enqueueNotification({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "Notice", 1000);
|
|
100
|
+
queue.enqueueQuery({ id: "task-1", agent: "scout", parentSessionId: "s1" }, "q1", "Query", 1000);
|
|
101
|
+
assert.equal(queue.pendingCount(), 2);
|
|
102
|
+
|
|
103
|
+
queue.dropAll();
|
|
104
|
+
assert.equal(queue.pendingCount(), 0);
|
|
105
|
+
assert.equal(queue.takeDeliverable(1050, "s1", () => true).length, 0);
|
|
106
|
+
});
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
applyTaskEvent,
|
|
5
5
|
emptyThread,
|
|
6
6
|
normalizeRpcEvent,
|
|
7
|
+
ToolArgumentProgress,
|
|
7
8
|
TASK_EVENT,
|
|
8
9
|
TASK_STATUS,
|
|
9
10
|
taskLabel,
|
|
@@ -67,6 +68,45 @@ test("normalizeRpcEvent maps pi RPC events to task deltas and ignores the rest",
|
|
|
67
68
|
assert.deepEqual(normalizeRpcEvent("garbage"), []);
|
|
68
69
|
});
|
|
69
70
|
|
|
71
|
+
test("argument liveness accepts current and legacy Pi starts without display or usage deltas", () => {
|
|
72
|
+
for (const start of [{ type: "toolcall_start", contentIndex: 0, id: "c1", toolName: "write" },
|
|
73
|
+
{ type: "toolcall_start", contentIndex: 0, partial: { content: [{ type: "toolCall", id: "c1", name: "write", arguments: {} }] } }]) {
|
|
74
|
+
const progress = new ToolArgumentProgress();
|
|
75
|
+
const update = (inner: Record<string, unknown>) => ({ type: "message_update", assistantMessageEvent: inner });
|
|
76
|
+
const delta = update({ type: "toolcall_delta", contentIndex: 0, delta: "private" });
|
|
77
|
+
assert.equal(progress.observe(delta), false, "no active generation");
|
|
78
|
+
progress.observe({ type: "message_start", message: { role: "assistant", timestamp: 1 } });
|
|
79
|
+
assert.equal(progress.observe(delta), false, "no announced block");
|
|
80
|
+
assert.equal(progress.observe(update(start)), false, "announcement alone is not progress");
|
|
81
|
+
assert.equal(progress.observe(delta), true);
|
|
82
|
+
assert.equal(progress.observe(delta), false);
|
|
83
|
+
assert.deepEqual(normalizeRpcEvent(delta), [], "no arguments in display events or provisional totals");
|
|
84
|
+
progress.observe(update({ type: "toolcall_end", contentIndex: 0 }));
|
|
85
|
+
assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: "late" })), false);
|
|
86
|
+
progress.observe(update(start));
|
|
87
|
+
assert.equal(progress.observe(delta), false, "a closed block cannot be reopened");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("argument liveness fails closed on malformed starts, stale generations and fingerprint exhaustion", () => {
|
|
92
|
+
const progress = new ToolArgumentProgress();
|
|
93
|
+
const update = (inner: Record<string, unknown>) => ({ type: "message_update", assistantMessageEvent: inner });
|
|
94
|
+
for (const timestamp of [undefined, "1", -1, 1.5]) progress.observe({ type: "message_start", message: { role: "assistant", timestamp } });
|
|
95
|
+
assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: "x" })), false);
|
|
96
|
+
progress.observe({ type: "message_start", message: { role: "assistant", timestamp: 1 } });
|
|
97
|
+
for (const fields of [{ id: 1, toolName: "write" }, { id: "c", toolName: "" }, { partial: null }]) {
|
|
98
|
+
progress.observe(update({ type: "toolcall_start", contentIndex: 0, ...fields }));
|
|
99
|
+
assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: "x" })), false);
|
|
100
|
+
}
|
|
101
|
+
progress.observe(update({ type: "toolcall_start", contentIndex: 0, id: "c", toolName: "write" }));
|
|
102
|
+
for (let index = 0; index < 4096; index++) assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: String(index) })), true);
|
|
103
|
+
assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: "over-cap" })), false);
|
|
104
|
+
progress.observe({ type: "message_end" });
|
|
105
|
+
progress.observe({ type: "message_start", message: { role: "assistant", timestamp: 1 } });
|
|
106
|
+
progress.observe(update({ type: "toolcall_start", contentIndex: 0, id: "c", toolName: "write" }));
|
|
107
|
+
assert.equal(progress.observe(update({ type: "toolcall_delta", contentIndex: 0, delta: "stale" })), false);
|
|
108
|
+
});
|
|
109
|
+
|
|
70
110
|
test("response observations are opt-in, finalized, field-specific and content-free", () => {
|
|
71
111
|
const message = { role: "assistant", provider: "openai", model: "gpt-4o", responseModel: "gpt-4o-2024-08-06",
|
|
72
112
|
providerThinkingLevel: "high", stopReason: "stop", content: [{ type: "text", text: "private response" }],
|