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
@@ -0,0 +1,194 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import test, { after } from "node:test";
6
+ import {
7
+ DOUBLE_ESC_CANCEL_FILE,
8
+ DOUBLE_ESC_CANCEL_SCHEMA,
9
+ DOUBLE_ESC_CANCEL_WINDOW_MS,
10
+ loadDoubleEscCancelPolicy,
11
+ parseDoubleEscCancelPolicyFile,
12
+ resolveDoubleEscCancelPolicy,
13
+ writeDoubleEscCancelPolicy,
14
+ } from "../lib/double-esc-cancel-policy.ts";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Double-esc-cancel policy (issue #1163).
18
+ //
19
+ // Global-only resolver: global file > env var > default off. Mirrors the
20
+ // resolution shape of lib/background-subagents-policy.ts minus the
21
+ // project-file layer, since this preference is deliberately global-only
22
+ // (see the feature scope in odd/tasks/double-esc-cancel.md). Strict schema
23
+ // decode, fail-closed to "off" on any malformed input.
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const scratchRoots: string[] = [];
27
+
28
+ function makeScratch(prefix: string): string {
29
+ const dir = mkdtempSync(join(tmpdir(), prefix));
30
+ scratchRoots.push(dir);
31
+ return dir;
32
+ }
33
+
34
+ after(() => {
35
+ for (const dir of scratchRoots) rmSync(dir, { recursive: true, force: true });
36
+ });
37
+
38
+ function writePolicyFile(dir: string, policy: string): void {
39
+ mkdirSync(dir, { recursive: true });
40
+ writeFileSync(join(dir, DOUBLE_ESC_CANCEL_FILE), JSON.stringify({ schema: DOUBLE_ESC_CANCEL_SCHEMA, policy }));
41
+ }
42
+
43
+ const EMPTY_ENV = {} as Record<string, string | undefined>;
44
+
45
+ test("the confirmation window is 1000ms, deliberately double Pi's own 500ms idle double-Esc window", () => {
46
+ assert.equal(DOUBLE_ESC_CANCEL_WINDOW_MS, 1000);
47
+ });
48
+
49
+ // --- strict decode ---------------------------------------------------------
50
+
51
+ test("strict decode accepts exactly the v1 schema with policy on|off", () => {
52
+ assert.equal(parseDoubleEscCancelPolicyFile(`{"schema":"${DOUBLE_ESC_CANCEL_SCHEMA}","policy":"on"}`), "on");
53
+ assert.equal(parseDoubleEscCancelPolicyFile(`{"schema":"${DOUBLE_ESC_CANCEL_SCHEMA}","policy":"off"}`), "off");
54
+ });
55
+
56
+ test("strict decode rejects malformed shapes", () => {
57
+ for (const raw of [
58
+ "not json",
59
+ "[]",
60
+ "null",
61
+ '{"policy":"on"}',
62
+ '{"schema":"gentle-pi.double-esc-cancel/v2","policy":"on"}',
63
+ `{"schema":"${DOUBLE_ESC_CANCEL_SCHEMA}","policy":"ON"}`,
64
+ `{"schema":"${DOUBLE_ESC_CANCEL_SCHEMA}","policy":true}`,
65
+ `{"schema":"${DOUBLE_ESC_CANCEL_SCHEMA}","policy":"on","extra":1}`,
66
+ ]) {
67
+ assert.equal(parseDoubleEscCancelPolicyFile(raw), undefined, `must reject: ${raw}`);
68
+ }
69
+ });
70
+
71
+ // --- cascade: global file > env > default off -------------------------------
72
+
73
+ test("default is off with no file and no env", () => {
74
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
75
+ assert.equal(loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV }), "off");
76
+ });
77
+
78
+ test("the resolver reads GENTLE_PI_CONFIG_HOME from the given env, never from process.env", () => {
79
+ const root = makeScratch("gp-esc-envhome-");
80
+ writePolicyFile(root, "on");
81
+ const result = resolveDoubleEscCancelPolicy({ env: { GENTLE_PI_CONFIG_HOME: root } });
82
+ assert.equal(result.policy, "on");
83
+ assert.equal(result.globalFile, join(root, "double-esc-cancel.json"));
84
+ });
85
+
86
+ test("global file overrides env", () => {
87
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
88
+ writePolicyFile(configHome, "on");
89
+ assert.equal(
90
+ loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: { GENTLE_PI_DOUBLE_ESC_CANCEL: "off" } }),
91
+ "on",
92
+ );
93
+ });
94
+
95
+ test("env var applies only when no policy file exists, and only exact on|off", () => {
96
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
97
+ assert.equal(
98
+ loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: { GENTLE_PI_DOUBLE_ESC_CANCEL: "on" } }),
99
+ "on",
100
+ );
101
+ for (const invalid of ["1", "true", "ON", "yes", ""]) {
102
+ assert.equal(
103
+ loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: { GENTLE_PI_DOUBLE_ESC_CANCEL: invalid } }),
104
+ "off",
105
+ `env value "${invalid}" must fail closed to off`,
106
+ );
107
+ }
108
+ });
109
+
110
+ test("a malformed global file fails closed to off instead of falling through to env", () => {
111
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
112
+ mkdirSync(configHome, { recursive: true });
113
+ writeFileSync(join(configHome, DOUBLE_ESC_CANCEL_FILE), "{malformed");
114
+ assert.equal(
115
+ loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: { GENTLE_PI_DOUBLE_ESC_CANCEL: "on" } }),
116
+ "off",
117
+ );
118
+ });
119
+
120
+ // --- resolver attribution ---------------------------------------------------
121
+
122
+ test("the resolver attributes the global file when it decides", () => {
123
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
124
+ writePolicyFile(configHome, "on");
125
+ const resolution = resolveDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV });
126
+ assert.deepEqual(resolution, {
127
+ policy: "on",
128
+ source: "global_file",
129
+ malformed: false,
130
+ globalFile: join(configHome, DOUBLE_ESC_CANCEL_FILE),
131
+ globalFileExists: true,
132
+ envValue: undefined,
133
+ });
134
+ });
135
+
136
+ test("the resolver attributes the environment variable when no file exists", () => {
137
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
138
+ const resolution = resolveDoubleEscCancelPolicy({
139
+ gentlePiConfigHome: configHome,
140
+ env: { GENTLE_PI_DOUBLE_ESC_CANCEL: "on" },
141
+ });
142
+ assert.equal(resolution.source, "environment");
143
+ assert.equal(resolution.policy, "on");
144
+ assert.equal(resolution.globalFileExists, false);
145
+ });
146
+
147
+ test("the resolver attributes the built-in default when nothing else decides", () => {
148
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
149
+ const resolution = resolveDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV });
150
+ assert.equal(resolution.source, "default");
151
+ assert.equal(resolution.policy, "off");
152
+ assert.equal(resolution.malformed, false);
153
+ });
154
+
155
+ test("the resolver attributes a malformed global file to that file and does not fall through", () => {
156
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
157
+ mkdirSync(configHome, { recursive: true });
158
+ writeFileSync(join(configHome, DOUBLE_ESC_CANCEL_FILE), "{malformed");
159
+ const resolution = resolveDoubleEscCancelPolicy({
160
+ gentlePiConfigHome: configHome,
161
+ env: { GENTLE_PI_DOUBLE_ESC_CANCEL: "on" },
162
+ });
163
+ assert.equal(resolution.source, "global_file");
164
+ assert.equal(resolution.policy, "off");
165
+ assert.equal(resolution.malformed, true);
166
+ });
167
+
168
+ test("loadDoubleEscCancelPolicy delegates to the resolver so the two can never disagree", () => {
169
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
170
+ writePolicyFile(configHome, "on");
171
+ assert.equal(
172
+ loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV }),
173
+ resolveDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV }).policy,
174
+ );
175
+ });
176
+
177
+ // --- writer -------------------------------------------------------------------
178
+
179
+ test("writeDoubleEscCancelPolicy writes the strict v1 shape to the global file, creating the config home", () => {
180
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai", "nested");
181
+ const path = writeDoubleEscCancelPolicy("on", { gentlePiConfigHome: configHome });
182
+ assert.equal(path, join(configHome, DOUBLE_ESC_CANCEL_FILE));
183
+ assert.equal(loadDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV }), "on");
184
+ });
185
+
186
+ test("writeDoubleEscCancelPolicy(off) round-trips through the resolver", () => {
187
+ const configHome = join(makeScratch("gp-esc-home-"), "gentle-ai");
188
+ writeDoubleEscCancelPolicy("on", { gentlePiConfigHome: configHome });
189
+ writeDoubleEscCancelPolicy("off", { gentlePiConfigHome: configHome });
190
+ const resolution = resolveDoubleEscCancelPolicy({ gentlePiConfigHome: configHome, env: EMPTY_ENV });
191
+ assert.equal(resolution.policy, "off");
192
+ assert.equal(resolution.source, "global_file");
193
+ assert.equal(resolution.malformed, false);
194
+ });