gentle-pi 3.5.0 → 3.6.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 +50 -41
- package/assets/orchestrator-delegation.md +2 -0
- package/bin/gentle-shell.mjs +1068 -16
- package/docs/gentle-shell.md +1 -1
- package/docs/readme-reference.md +108 -19
- package/extensions/gentle-ai.ts +27 -48
- package/lib/gentle-shell-launcher.ts +734 -37
- package/lib/inprocess-reviewer.ts +54 -6
- package/lib/native-review-cli.ts +12 -0
- package/package.json +1 -1
- package/runtime/gentle-shell-launcher.mjs +732 -35
- package/runtime/native-review-cli.mjs +12 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-tui-mode-setting.mjs +21 -2
- package/scripts/verify-package-files.mjs +2 -2
- package/tests/agents-rpc-publisher.test.ts +66 -0
- package/tests/gentle-agents.test.ts +46 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +54 -49
- package/tests/gentle-ai.test.ts +147 -2
- package/tests/gentle-shell-bin.test.ts +2389 -6
- package/tests/gentle-shell-launcher.test.ts +1053 -10
- package/tests/inprocess-reviewer.test.ts +179 -0
- package/tests/install-tui-mode-setting.test.ts +22 -4
- package/tests/native-review-capability-contract.test.ts +28 -1
- package/tests/odd-runtime-delegation-gate.test.ts +18 -197
- package/tests/package-manifest.test.ts +6 -6
- package/tests/runtime-harness.mjs +1 -2
- package/lib/odd-runtime-delegation-gate.ts +0 -88
|
@@ -1033,6 +1033,18 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1033
1033
|
// closed START/STATUS fields this row negotiates. riskEvidence and hint
|
|
1034
1034
|
// remain dark; neither is proven to reach Pi's negotiated START path.
|
|
1035
1035
|
"3.5.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 }),
|
|
1036
|
+
// v3.6.0 repeats 3.5.0: the published provider contract bundle is
|
|
1037
|
+
// byte-identical at 1.2.0, both binaries advertise capabilities/v2.6
|
|
1038
|
+
// with only build-identity differences, and no review-integration schema
|
|
1039
|
+
// changed between the v3.5.0 and v3.6.0 tags. riskEvidence and hint
|
|
1040
|
+
// remain dark; neither is proven to reach Pi's negotiated START path.
|
|
1041
|
+
"3.6.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 }),
|
|
1042
|
+
// v3.6.1 repeats 3.6.0: published provider-contract archives are byte-identical
|
|
1043
|
+
// (SHA-256 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c).
|
|
1044
|
+
// The published v3.6.1 binary advertises capabilities/v2.6, and the tag diff
|
|
1045
|
+
// changes no review-integration schema or capability source. riskEvidence and
|
|
1046
|
+
// hint remain dark because neither is proven in Pi's negotiated START path.
|
|
1047
|
+
"3.6.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 }),
|
|
1036
1048
|
});
|
|
1037
1049
|
|
|
1038
1050
|
|
|
@@ -36,7 +36,7 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
36
36
|
// version check below) derives from this constant instead of repeating the
|
|
37
37
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
38
38
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
39
|
-
export const INSTALLER_VERSION = "3.
|
|
39
|
+
export const INSTALLER_VERSION = "3.6.1";
|
|
40
40
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
41
41
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
42
42
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
@@ -45,10 +45,10 @@ export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
|
45
45
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai";
|
|
46
46
|
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v3";
|
|
47
47
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
48
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.
|
|
48
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v3@v3.6.1`
|
|
49
49
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
50
|
-
// tag resolves to commit
|
|
51
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
50
|
+
// tag resolves to commit 623624ed, the published v3.6.1 release head.
|
|
51
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:De+eaJuMPxsaiq8KQzWUKCC5FTaFiA3BqDg9nyh/k9Y=";
|
|
52
52
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
53
53
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
54
54
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -67,7 +67,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
67
67
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
68
68
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
69
69
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
70
|
-
// still holds it. The v3.
|
|
70
|
+
// still holds it. The v3.6.1 digests are pinned from the published release:
|
|
71
71
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
72
72
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
73
73
|
// executables.
|
|
@@ -109,15 +109,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
112
|
-
// distributing unsigned Windows builds in c4b764d0, so v3.
|
|
112
|
+
// distributing unsigned Windows builds in c4b764d0, so v3.6.1 publishes signed
|
|
113
113
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
114
114
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
115
115
|
// upstream ships signed Windows assets.
|
|
116
116
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
117
|
-
"darwin/amd64": asset("gentle-ai_3.
|
|
118
|
-
"darwin/arm64": asset("gentle-ai_3.
|
|
119
|
-
"linux/amd64": asset("gentle-ai_3.
|
|
120
|
-
"linux/arm64": asset("gentle-ai_3.
|
|
117
|
+
"darwin/amd64": asset("gentle-ai_3.6.1_darwin_amd64.tar.gz", "75f9839035eb25e2bc8eb324a365154cd6ecf869cc3db7885cbc9892008ac9bd", "fb9c286d58fddb0234660aa71a4ed0850f7d015f21c5f6c2059fdf70dc3bc0ae", "gentle-ai"),
|
|
118
|
+
"darwin/arm64": asset("gentle-ai_3.6.1_darwin_arm64.tar.gz", "be46c0884f31f485ca9663ee819156e058e1b1e665165a72105f42bb449560a2", "e13779ba1dfa23c31403650d76cceb0d523e56904530426c6bdedca9d7937550", "gentle-ai"),
|
|
119
|
+
"linux/amd64": asset("gentle-ai_3.6.1_linux_amd64.tar.gz", "251db626a3b774665066eeedfb9edbb1bfe60e874fbe98af1df65587b19b3c2f", "7208d5e3185293b70779f174a9deac4962f503a8873d4b3529ad7d444e1308a2", "gentle-ai"),
|
|
120
|
+
"linux/arm64": asset("gentle-ai_3.6.1_linux_arm64.tar.gz", "d6acc74454e6135af9714c497bc4791ed7149ee3c52d16f378cc085817274db5", "e0db95f4a7b9837060ba6de599f2ff15460ceb1b550c65d4225aaccc657ca979", "gentle-ai"),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|
|
@@ -17,6 +17,23 @@ const PI_MANAGED_SEGMENT_SEQUENCES = [
|
|
|
17
17
|
["git", "github.com", "Gentleman-Programming"],
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
// Gentle Shell's default theme (themes/Gentleman-Cute.json, "name":
|
|
21
|
+
// "Gentleman-Cute"): applied by withIsolatedHomeDefaults below whenever a
|
|
22
|
+
// home's settings do not already declare one, never overriding a user's own
|
|
23
|
+
// choice.
|
|
24
|
+
export const DEFAULT_THEME_NAME = "Gentleman-Cute";
|
|
25
|
+
|
|
26
|
+
// Pure merge: returns `value` with fullscreen tuiMode always applied, and —
|
|
27
|
+
// only when `value` does not already declare a "theme" key — the default
|
|
28
|
+
// theme above added too. No filesystem access, so it is unit-testable
|
|
29
|
+
// without a tempdir; installIsolatedTuiModeSetting below is the only caller,
|
|
30
|
+
// applying this to a freshly bootstrapped home's settings.
|
|
31
|
+
export function withIsolatedHomeDefaults(value) {
|
|
32
|
+
const next = { ...value, tuiMode: "fullscreen" };
|
|
33
|
+
if (!("theme" in value)) next.theme = DEFAULT_THEME_NAME;
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
function containsSequence(segments, sequence) {
|
|
21
38
|
for (let start = 0; start + sequence.length <= segments.length; start += 1) {
|
|
22
39
|
if (sequence.every((part, offset) => segments[start + offset] === part)) return true;
|
|
@@ -147,7 +164,9 @@ export async function installTuiModeSetting(options = {}) {
|
|
|
147
164
|
}
|
|
148
165
|
}
|
|
149
166
|
|
|
150
|
-
/** Writes tuiMode: "fullscreen"
|
|
167
|
+
/** Writes tuiMode: "fullscreen" and — when the home has no theme of its own
|
|
168
|
+
* yet — the default Gentle Shell theme (withIsolatedHomeDefaults,
|
|
169
|
+
* DEFAULT_THEME_NAME above) into a directory gentle-shell's own isolated-home
|
|
151
170
|
* bootstrap (T2) just created and owns. Unlike installTuiModeSetting, there is no
|
|
152
171
|
* "physically installed under this home's npm/node_modules" ownership check to
|
|
153
172
|
* satisfy: the caller already knows it created `dir` moments ago as gentle-shell's
|
|
@@ -170,7 +189,7 @@ export async function installIsolatedTuiModeSetting(dir) {
|
|
|
170
189
|
const fd = openSync(staging, "wx", original.stat ? original.stat.mode & 0o777 : 0o600);
|
|
171
190
|
try {
|
|
172
191
|
if (original.stat) fchmodSync(fd, original.stat.mode & 0o777);
|
|
173
|
-
writeFileSync(fd, `${JSON.stringify(
|
|
192
|
+
writeFileSync(fd, `${JSON.stringify(withIsolatedHomeDefaults(original.value), null, 2)}\n`, "utf8");
|
|
174
193
|
fsyncSync(fd);
|
|
175
194
|
} finally { closeSync(fd); }
|
|
176
195
|
assertDirectories([home]);
|
|
@@ -342,7 +342,7 @@ async function main() {
|
|
|
342
342
|
});
|
|
343
343
|
|
|
344
344
|
if (driftedContracts.length > 0) {
|
|
345
|
-
console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v3.
|
|
345
|
+
console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v3.6.1 runtime's vendored Gentle AI contract artifacts:");
|
|
346
346
|
for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
|
|
347
347
|
process.exit(1);
|
|
348
348
|
}
|
|
@@ -387,7 +387,7 @@ async function main() {
|
|
|
387
387
|
process.exit(1);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v3.
|
|
390
|
+
console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v3.6.1 runtime).`);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
@@ -185,6 +185,41 @@ test("projectRpcActivity honors a custom maxThreadItems override", () => {
|
|
|
185
185
|
assert.deepEqual((activity.tasks[0]!.thread.items as { text: string }[]).map((item) => item.text), ["n3", "n4"]);
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
// Regression for the desktop app's Helpers tab showing helpers from every
|
|
189
|
+
// session: `TaskStore` restores finished tasks of every session from disk at
|
|
190
|
+
// startup, so an RPC push scoped to `opts.parentSessionId` must project only
|
|
191
|
+
// the current session's tasks, and its `summary` counts must match.
|
|
192
|
+
test("projectRpcActivity with parentSessionId keeps only that session's tasks and computes the summary from them", () => {
|
|
193
|
+
const store = new TaskStore();
|
|
194
|
+
store.add(task("mine-running", "s1", { status: TASK_STATUS.RUNNING }));
|
|
195
|
+
store.add(task("mine-finished", "s1", { status: TASK_STATUS.COMPLETED, endedAt: 100 }));
|
|
196
|
+
store.add(task("other-running", "s2", { status: TASK_STATUS.RUNNING }));
|
|
197
|
+
store.add(task("other-finished", "s2", { status: TASK_STATUS.COMPLETED, endedAt: 200 }));
|
|
198
|
+
|
|
199
|
+
const activity = projectRpcActivity(store, { parentSessionId: "s1" });
|
|
200
|
+
|
|
201
|
+
assert.deepEqual(
|
|
202
|
+
activity.tasks.map((entry) => entry.summary.id).sort(),
|
|
203
|
+
["mine-finished", "mine-running"],
|
|
204
|
+
"only the requested session's tasks are projected",
|
|
205
|
+
);
|
|
206
|
+
assert.deepEqual(activity.summary, store.summary("s1"), "summary counts are scoped to the same session filter, not the whole store");
|
|
207
|
+
assert.notDeepEqual(activity.summary, store.summary(), "an unfiltered summary would have counted the other session's tasks too");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Tasks restored on demand (e.g. opening an older task from another session
|
|
211
|
+
// in the overlay) can land in the shared store without ever being live in
|
|
212
|
+
// the current session; the RPC projection must still exclude them by id.
|
|
213
|
+
test("projectRpcActivity with parentSessionId excludes a task from another session even when it is the only one in the store", () => {
|
|
214
|
+
const store = new TaskStore();
|
|
215
|
+
store.add(task("not-mine", "other-session"));
|
|
216
|
+
|
|
217
|
+
const activity = projectRpcActivity(store, { parentSessionId: "current-session" });
|
|
218
|
+
|
|
219
|
+
assert.deepEqual(activity.tasks, []);
|
|
220
|
+
assert.deepEqual(activity.summary, { running: 0, queued: 0, waiting: 0, finished: 0 });
|
|
221
|
+
});
|
|
222
|
+
|
|
188
223
|
test("encodeActivityLines returns the activity untouched as a single line when it already fits", () => {
|
|
189
224
|
const activity: RpcActivity = { schema: ACTIVITY_SCHEMA, summary: { running: 0, queued: 0, waiting: 0, finished: 0 }, tasks: [] };
|
|
190
225
|
|
|
@@ -366,6 +401,37 @@ test("createRpcActivityPublisher stop unsubscribes everything and publishes exac
|
|
|
366
401
|
assert.equal(calls.length, 1, "no further frame is published after stop");
|
|
367
402
|
});
|
|
368
403
|
|
|
404
|
+
// `parentSessionId` is a value captured at construction, not a live getter.
|
|
405
|
+
// Honoring a mid-process session switch (a resumed/new/forked session) is
|
|
406
|
+
// the caller's job: stop the old publisher and construct a new one scoped
|
|
407
|
+
// to the new session id, exactly as `extensions/gentle-agents.ts` does on
|
|
408
|
+
// every `session_start`. This locks in that only the newly scoped publisher
|
|
409
|
+
// ever sees the other session's tasks.
|
|
410
|
+
test("createRpcActivityPublisher recreated with a new parentSessionId after a session switch publishes only the new session's tasks", () => {
|
|
411
|
+
const store = new TaskStore();
|
|
412
|
+
store.add(task("session-a-task", "session-a"));
|
|
413
|
+
const scheduler = fakeScheduler();
|
|
414
|
+
const calls: string[][] = [];
|
|
415
|
+
const ui = { setWidget: (_key: string, lines: string[]) => calls.push(lines) };
|
|
416
|
+
|
|
417
|
+
const first = createRpcActivityPublisher({ store, ui, schedule: scheduler.schedule, parentSessionId: "session-a" });
|
|
418
|
+
first.start();
|
|
419
|
+
scheduler.flushAll();
|
|
420
|
+
const firstActivity = JSON.parse(calls.at(-1)![0]!) as RpcActivity;
|
|
421
|
+
assert.deepEqual(firstActivity.tasks.map((entry) => entry.summary.id), ["session-a-task"]);
|
|
422
|
+
|
|
423
|
+
first.stop();
|
|
424
|
+
calls.length = 0;
|
|
425
|
+
store.add(task("session-b-task", "session-b"));
|
|
426
|
+
|
|
427
|
+
const second = createRpcActivityPublisher({ store, ui, schedule: scheduler.schedule, parentSessionId: "session-b" });
|
|
428
|
+
second.start();
|
|
429
|
+
scheduler.flushAll();
|
|
430
|
+
|
|
431
|
+
const secondActivity = JSON.parse(calls.at(-1)![0]!) as RpcActivity;
|
|
432
|
+
assert.deepEqual(secondActivity.tasks.map((entry) => entry.summary.id), ["session-b-task"], "the publisher scoped to the new session must never carry the old session's task");
|
|
433
|
+
});
|
|
434
|
+
|
|
369
435
|
test("createRpcActivityPublisher flush publishes immediately and cancels a pending coalescing timer", () => {
|
|
370
436
|
const store = new TaskStore();
|
|
371
437
|
store.add(task("t1", "s1"));
|
|
@@ -469,6 +469,52 @@ test("gentle-agents notifies once, deduplicated, when the RPC activity publisher
|
|
|
469
469
|
assert.equal(notify.mock.calls[0]?.arguments[1], "warning");
|
|
470
470
|
});
|
|
471
471
|
|
|
472
|
+
// Regression for the desktop app's Helpers tab showing helpers from every
|
|
473
|
+
// session: a resumed session's own finished tasks restore from disk into
|
|
474
|
+
// the shared `TaskStore` (see "resuming a session restores its own
|
|
475
|
+
// finished tasks as history, never another session's" above for the
|
|
476
|
+
// overlay-render side of this), and the RPC activity publisher created on
|
|
477
|
+
// `session_start` must scope its `setWidget` payload to the same session,
|
|
478
|
+
// never surfacing another session's restored task.
|
|
479
|
+
test("gentle-agents' RPC activity payload excludes a restored task from another session", async (t) => {
|
|
480
|
+
const h = fakePi();
|
|
481
|
+
const runtime = deps();
|
|
482
|
+
const scheduler = fakeScheduler();
|
|
483
|
+
runtime.deps.schedule = scheduler.schedule;
|
|
484
|
+
runtime.deps.env = { PATH: "/bin", GENTLE_SHELL_INTERACTIVE_HOST: "1" };
|
|
485
|
+
const historyHome = join(root, "rpc-restore-history-home");
|
|
486
|
+
const own: TaskRecord = { id: "own-1", agent: "explore-a", mode: "background", prompt: "p", label: "p", cwd, parentSessionId: "resumed-session", status: TASK_STATUS.COMPLETED, createdAt: 1, startedAt: 1, endedAt: 100, model: "m", thinking: undefined, sessionPath: null, error: null, result: "done", lastStep: "responded", lastActivityAt: 100, turns: 1, toolCalls: 0, tokens: 0, cost: 0 };
|
|
487
|
+
const other: TaskRecord = { ...own, id: "not-mine", agent: "explore-other", parentSessionId: "other-session" };
|
|
488
|
+
await saveTask(historyDir(historyHome), own, emptyThread());
|
|
489
|
+
await saveTask(historyDir(historyHome), other, emptyThread());
|
|
490
|
+
runtime.deps.home = historyHome;
|
|
491
|
+
gentleAgents(h.pi, {}, runtime.deps);
|
|
492
|
+
const { ctx } = fakeContext();
|
|
493
|
+
Object.assign(ctx, { mode: "rpc", hasUI: true });
|
|
494
|
+
ctx.sessionManager.getSessionId = () => "resumed-session";
|
|
495
|
+
const setWidget = t.mock.method(ctx.ui, "setWidget");
|
|
496
|
+
|
|
497
|
+
await h.fire("session_start", ctx, { reason: "resume" });
|
|
498
|
+
|
|
499
|
+
// The disk history read behind restoreSessionHistory is fire-and-forget
|
|
500
|
+
// real async I/O, unrelated to the fake coalescing scheduler; poll both
|
|
501
|
+
// until the resumed session's own restored task reaches a flushed frame.
|
|
502
|
+
let activity: { tasks: Array<{ summary: { id: string } }> } | undefined;
|
|
503
|
+
for (let attempt = 0; attempt < 40 && !activity; attempt += 1) {
|
|
504
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
505
|
+
scheduler.flushAll();
|
|
506
|
+
const activityCalls = setWidget.mock.calls.filter((call) => call.arguments[0] === "gentle-agents" && Array.isArray(call.arguments[1]));
|
|
507
|
+
if (activityCalls.length === 0) continue;
|
|
508
|
+
const [, lines] = activityCalls.at(-1)!.arguments as unknown as [string, string[]];
|
|
509
|
+
const parsed = JSON.parse(lines[0]!) as { tasks: Array<{ summary: { id: string } }> };
|
|
510
|
+
if (parsed.tasks.some((entry) => entry.summary.id === "own-1")) activity = parsed;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
assert.ok(activity, "the RPC activity payload must eventually include the resumed session's own restored task");
|
|
514
|
+
assert.ok(!activity!.tasks.some((entry) => entry.summary.id === "not-mine"), "another session's restored task must never appear in the RPC activity payload");
|
|
515
|
+
await h.fire("session_shutdown", ctx);
|
|
516
|
+
});
|
|
517
|
+
|
|
472
518
|
test("all nine subagent registrations own their transcript shell", () => {
|
|
473
519
|
const { pi, tools } = fakePi();
|
|
474
520
|
gentleAgents(pi, {}, deps().deps);
|
|
@@ -97,7 +97,7 @@ async function writeWindowsSourceBinary(packageRoot: string): Promise<{ binaryPa
|
|
|
97
97
|
method: "go-sumdb-source-build",
|
|
98
98
|
package: "github.com/gentleman-programming/gentle-ai/v3/cmd/gentle-ai",
|
|
99
99
|
module: "github.com/gentleman-programming/gentle-ai/v3",
|
|
100
|
-
tag: "v3.
|
|
100
|
+
tag: "v3.6.1",
|
|
101
101
|
architecture: process.arch === "x64" ? "x64" : "arm64",
|
|
102
102
|
binarySha256: createHash("sha256").update(binary).digest("hex"),
|
|
103
103
|
moduleChecksum: GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM,
|