gentle-pi 3.2.0 → 3.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 (80) hide show
  1. package/assets/orchestrator-delegation.md +13 -8
  2. package/assets/orchestrator.md +2 -2
  3. package/docs/gentle-shell.md +40 -17
  4. package/docs/readme-reference.md +41 -7
  5. package/docs/review-integration.md +25 -11
  6. package/extensions/gentle-agents.ts +85 -17
  7. package/extensions/gentle-ai.ts +179 -12
  8. package/extensions/gentle-shell.ts +408 -38
  9. package/extensions/gentle-todo.ts +19 -1
  10. package/lib/agents-view.ts +41 -14
  11. package/lib/agents-widget.ts +84 -13
  12. package/lib/command-palette-catalog.ts +1 -0
  13. package/lib/double-esc-cancel-policy.ts +138 -0
  14. package/lib/inprocess-reviewer.ts +260 -0
  15. package/lib/model-routing-authority.ts +1 -1
  16. package/lib/native-review-cli.ts +23 -0
  17. package/lib/odd-runtime-delegation-gate.ts +88 -0
  18. package/lib/review-host-relay.ts +262 -94
  19. package/lib/review-integration-v2.ts +110 -26
  20. package/lib/shell-bar.ts +158 -29
  21. package/lib/shell-card.ts +19 -9
  22. package/lib/shell-changes-view.ts +43 -5
  23. package/lib/shell-changes.ts +92 -5
  24. package/lib/shell-hover.ts +39 -0
  25. package/lib/shell-prompt.ts +10 -1
  26. package/lib/shell-sidebar-layout.ts +111 -15
  27. package/lib/shell-sidebar.ts +16 -0
  28. package/lib/shell-todo.ts +7 -1
  29. package/lib/shell-usage-view.ts +98 -10
  30. package/lib/shell-usage.ts +226 -10
  31. package/package.json +2 -1
  32. package/runtime/native-review-cli.mjs +23 -0
  33. package/runtime/review-integration-v2.mjs +110 -26
  34. package/scripts/gentle-ai-installer.mjs +10 -10
  35. package/scripts/maintainer/provider-relay-matrix.mjs +118 -47
  36. package/scripts/mirror-odd-routing.mjs +242 -0
  37. package/scripts/verify-package-files.mjs +3 -3
  38. package/tests/agents-grouping.test.ts +75 -18
  39. package/tests/agents-view.test.ts +28 -18
  40. package/tests/agents-widget.test.ts +100 -12
  41. package/tests/command-palette.test.ts +1 -0
  42. package/tests/devbinary/pi-host-relay.devtest.ts +176 -138
  43. package/tests/double-esc-cancel-policy.test.ts +194 -0
  44. package/tests/gentle-agents.test.ts +528 -5
  45. package/tests/gentle-ai-binary.test.ts +1 -1
  46. package/tests/gentle-ai-installer.test.ts +47 -47
  47. package/tests/gentle-ai.test.ts +69 -5
  48. package/tests/gentle-shell.test.ts +903 -25
  49. package/tests/gentle-todo.test.ts +17 -4
  50. package/tests/inprocess-reviewer.test.ts +368 -0
  51. package/tests/maintainer/provider-relay.maintest.ts +101 -143
  52. package/tests/native-review-capability-contract.test.ts +32 -1
  53. package/tests/odd-routing-canonical-ratchet.test.ts +293 -0
  54. package/tests/odd-routing-contract.test.ts +57 -0
  55. package/tests/odd-runtime-delegation-gate.test.ts +212 -0
  56. package/tests/orchestrator-rdd-ownership.test.ts +3 -3
  57. package/tests/package-manifest.test.ts +6 -6
  58. package/tests/review-controller-native-routing.test.ts +60 -1
  59. package/tests/review-host-relay-routing.test.ts +77 -0
  60. package/tests/review-host-relay.test.ts +285 -239
  61. package/tests/review-integration-v2-forward.test.ts +61 -0
  62. package/tests/review-integration-v2.test.ts +116 -1
  63. package/tests/review-relay-transport-agent.test.ts +83 -0
  64. package/tests/runtime-harness.mjs +11 -0
  65. package/tests/session-changes-shell.test.ts +27 -0
  66. package/tests/session-worktree-registry.test.ts +41 -0
  67. package/tests/shell-bar.test.ts +224 -6
  68. package/tests/shell-card.test.ts +5 -3
  69. package/tests/shell-changes-view.test.ts +47 -0
  70. package/tests/shell-changes.test.ts +177 -0
  71. package/tests/shell-hover.test.ts +19 -0
  72. package/tests/shell-prompt.test.ts +20 -0
  73. package/tests/shell-sidebar-fullscreen.test.ts +59 -0
  74. package/tests/shell-sidebar-layout.test.ts +243 -5
  75. package/tests/shell-sidebar.test.ts +25 -1
  76. package/tests/shell-todo.test.ts +36 -0
  77. package/tests/shell-usage-view.test.ts +123 -3
  78. package/tests/shell-usage.test.ts +254 -6
  79. package/lib/opaque-pi-reviewer-adapter.ts +0 -284
  80. package/tests/opaque-pi-reviewer-adapter.test.ts +0 -266
@@ -25,7 +25,7 @@ export type ModelConfigFileResult =
25
25
  | { status: "invalid"; path: string }
26
26
  | { status: "valid"; config: AgentModelConfig };
27
27
 
28
- const SAFE_MODEL_ID_PATTERN = /^[A-Za-z0-9._~:@/+%-]+$/;
28
+ export const SAFE_MODEL_ID_PATTERN = /^[A-Za-z0-9._~:@/+%-]+$/;
29
29
  const SAFE_AGENT_NAME_PATTERN = /^[A-Za-z0-9._:@/+%-]+$/;
30
30
 
31
31
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -995,6 +995,29 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
995
995
  // remain dark because neither is proven to reach the negotiated START
996
996
  // path Pi consumes.
997
997
  "3.1.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
998
+ // v3.2.1 changed the ODD orchestrator contract only (gentle-ai #4714
999
+ // follow-up). Ground-truthed by diffing contracts/review-integration/v2 and
1000
+ // contracts/review-provider-contract between the v3.1.0 and v3.2.1 tags
1001
+ // in the gentle-ai source tree: zero bytes changed (provider contract
1002
+ // stays 1.2.0). Neither change touches the closed START/STATUS fields
1003
+ // this row negotiates, so it repeats 3.1.0 exactly. riskEvidence and hint
1004
+ // remain dark because neither is proven to reach the negotiated START
1005
+ // path Pi consumes.
1006
+ "3.2.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
1007
+ // v3.4.0 (gentle-pi never pinned the intervening v3.3.0 tag, so it gets no
1008
+ // row here) added capabilities/v2.6 and status/v8-v9, and extended
1009
+ // `review assess` with review_due/review_due_reason/next_transition
1010
+ // (gentle-ai #4714 follow-up). Ground-truthed by diffing
1011
+ // contracts/review-integration/v2 and contracts/review-provider-contract
1012
+ // between the v3.2.1 and v3.4.0 tags in the gentle-ai source tree: the
1013
+ // provider contract stays byte-identical at 1.2.0, and every
1014
+ // review-integration/v2 change is an additive superset (new optional
1015
+ // schema/fields) that decodeReviewStatusV3 and the capabilities
1016
+ // negotiator already accept without touching the closed START/STATUS
1017
+ // fields this row negotiates, so it repeats 3.2.1 exactly. riskEvidence
1018
+ // and hint remain dark because neither is proven to reach the negotiated
1019
+ // START path Pi consumes.
1020
+ "3.4.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
998
1021
  });
999
1022
 
1000
1023
  export interface NativeReviewProcessDiagnostics {
@@ -0,0 +1,88 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { resolveSessionWorktree } from "./session-worktree-registry.ts";
4
+
5
+ export interface OddDelegationRefusal {
6
+ block: true;
7
+ reason: string;
8
+ }
9
+
10
+ interface SessionState {
11
+ primary: boolean;
12
+ childDepth: number;
13
+ firstSuccessfulPath?: string;
14
+ }
15
+
16
+ function canonicalTarget(path: string): string {
17
+ let candidate = path;
18
+ const missing: string[] = [];
19
+ while (true) {
20
+ try { return resolve(realpathSync(candidate), ...missing); }
21
+ catch {
22
+ const parent = dirname(candidate);
23
+ if (parent === candidate) return path;
24
+ missing.unshift(basename(candidate));
25
+ candidate = parent;
26
+ }
27
+ }
28
+ }
29
+
30
+ function sessionPath(toolName: string, input: unknown, cwd: string): string | undefined {
31
+ if ((toolName !== "edit" && toolName !== "write") || !input || typeof input !== "object") return undefined;
32
+ const path = (input as { path?: unknown }).path;
33
+ if (typeof path !== "string" || path.trim().length === 0) return undefined;
34
+ const root = resolveSessionWorktree(cwd, cwd)?.root;
35
+ if (!root) return undefined;
36
+ const spelling = path.replace(/^@/, "");
37
+ const canonicalCwd = realpathSync(cwd);
38
+ const lexicalTarget = isAbsolute(spelling)
39
+ ? resolve(spelling)
40
+ : resolve(canonicalCwd, spelling);
41
+ const target = canonicalTarget(lexicalTarget);
42
+ const repositoryPath = relative(root, target);
43
+ if (repositoryPath === "" || repositoryPath === ".." || repositoryPath.startsWith(`..${sep}`) || isAbsolute(repositoryPath)) return undefined;
44
+ const canonical = repositoryPath.split(sep).join("/");
45
+ return canonical === "odd/tasks" || canonical.startsWith("odd/tasks/") ? undefined : canonical;
46
+ }
47
+
48
+ export class OddRuntimeDelegationGate {
49
+ private readonly sessions = new Map<string, SessionState>();
50
+
51
+ start(sessionId: string, primary: boolean): void {
52
+ const state = this.sessions.get(sessionId);
53
+ if (primary) this.sessions.set(sessionId, { primary: true, childDepth: 0 });
54
+ else if (state?.primary) state.childDepth += 1;
55
+ else this.sessions.set(sessionId, { primary: false, childDepth: 1 });
56
+ }
57
+
58
+ endChild(sessionId: string): void {
59
+ const state = this.sessions.get(sessionId);
60
+ if (state && state.childDepth > 0) state.childDepth -= 1;
61
+ }
62
+
63
+ beforeTool(
64
+ sessionId: string,
65
+ toolName: string,
66
+ input: unknown,
67
+ cwd: string,
68
+ availableTools: readonly string[],
69
+ ): OddDelegationRefusal | undefined {
70
+ const state = this.sessions.get(sessionId);
71
+ if (!state?.primary || state.childDepth > 0) return undefined;
72
+ const path = sessionPath(toolName, input, cwd);
73
+ if (!path || !state.firstSuccessfulPath || path === state.firstSuccessfulPath) return undefined;
74
+ return {
75
+ block: true,
76
+ reason: availableTools.includes("subagent_run")
77
+ ? `ODD multi-file write refused before mutation: direct edits already changed ${state.firstSuccessfulPath}. Delegate this additional file through subagent_run, preferring gentle-ai-worker and then worker.`
78
+ : "ODD multi-file write refused before mutation: direct edits already changed another file, but subagent_run is not callable. Stop and report that no delegation mechanism is callable.",
79
+ };
80
+ }
81
+
82
+ recordSuccess(sessionId: string, toolName: string, input: unknown, cwd: string): void {
83
+ const state = this.sessions.get(sessionId);
84
+ if (!state?.primary || state.childDepth > 0 || state.firstSuccessfulPath) return;
85
+ const path = sessionPath(toolName, input, cwd);
86
+ if (path) state.firstSuccessfulPath = path;
87
+ }
88
+ }