@selesai/code 0.13.38 → 0.13.40
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/CHANGELOG.md +7 -0
- package/dist/core/intercom-rpc.d.ts +135 -0
- package/dist/core/intercom-rpc.js +64 -0
- package/dist/core/release-rpc.d.ts +137 -0
- package/dist/core/release-rpc.js +77 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.js +3 -0
- package/dist/core/subagent-rpc.d.ts +110 -0
- package/dist/core/subagent-rpc.js +60 -0
- package/dist/extensions/agent-browser.ts +9 -0
- package/dist/extensions/pi-graft/cli.test.ts +5 -5
- package/dist/extensions/pi-graft/cli.ts +10 -5
- package/dist/extensions/pi-graft/commands.test.ts +5 -5
- package/dist/extensions/pi-graft/graft.integration.test.ts +1 -1
- package/dist/extensions/pi-graft/lifecycle.test.ts +26 -26
- package/dist/extensions/pi-graft/prompt.test.ts +1 -1
- package/dist/extensions/pi-graft/state.test.ts +9 -9
- package/dist/extensions/pi-graft/test-support.ts +1 -1
- package/dist/extensions/pi-graft/tools.test.ts +1 -1
- package/dist/extensions/pi-intercom/README.md +15 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +277 -13
- package/dist/extensions/pi-intercom/broker/client.ts +6 -2
- package/dist/extensions/pi-intercom/broker/mailbox-persistence.test.ts +523 -0
- package/dist/extensions/pi-intercom/broker/paths.test.ts +4 -4
- package/dist/extensions/pi-intercom/broker/paths.ts +4 -2
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +1 -3
- package/dist/extensions/pi-intercom/extension-api.ts +10 -0
- package/dist/extensions/pi-intercom/index.ts +188 -8
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +280 -0
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/release-readiness.test.ts +116 -0
- package/dist/extensions/pi-intercom/release-readiness.ts +93 -0
- package/dist/extensions/pi-subagents/docs/extension-api.md +8 -0
- package/dist/extensions/pi-subagents/docs/observability.md +8 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +51 -1
- package/dist/extensions/pi-subagents/src/extension/observability.ts +164 -0
- package/dist/extensions/pi-subagents/src/extension/release-readiness.ts +367 -0
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +8 -1
- package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +7 -1
- package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +1 -44
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +6 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +13 -3
- package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +269 -7
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +11 -10
- package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +15 -3
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +64 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -1
- package/dist/extensions/pi-subagents/src/shared/owner-record.ts +107 -0
- package/dist/extensions/pi-subagents/src/shared/process-identity.ts +87 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +9 -0
- package/dist/extensions/pi-subagents/test/integration/async-attention-clearing.test.ts +191 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/integration/stop-all-background.test.ts +142 -0
- package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/observability.test.ts +198 -0
- package/dist/extensions/pi-subagents/test/unit/process-identity.test.ts +75 -0
- package/dist/extensions/pi-subagents/test/unit/release-readiness.test.ts +715 -0
- package/dist/extensions/pi-subagents/test/unit/result-takeover.test.ts +332 -0
- package/dist/extensions/tokenin-onboarding.ts +142 -3
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.js +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/rpc/intercom-bridge.d.ts +14 -0
- package/dist/modes/rpc/intercom-bridge.js +75 -0
- package/dist/modes/rpc/intercom-bridge.test.d.ts +1 -0
- package/dist/modes/rpc/intercom-bridge.test.js +65 -0
- package/dist/modes/rpc/release-bridge.d.ts +35 -0
- package/dist/modes/rpc/release-bridge.js +159 -0
- package/dist/modes/rpc/release-bridge.test.d.ts +1 -0
- package/dist/modes/rpc/release-bridge.test.js +347 -0
- package/dist/modes/rpc/rpc-client.d.ts +34 -10
- package/dist/modes/rpc/rpc-client.js +45 -2
- package/dist/modes/rpc/rpc-intercom-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-intercom-transport.test.js +85 -0
- package/dist/modes/rpc/rpc-mode.d.ts +1 -13
- package/dist/modes/rpc/rpc-mode.js +239 -8
- package/dist/modes/rpc/rpc-release-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-release-transport.test.js +329 -0
- package/dist/modes/rpc/rpc-subagent-transport.test.d.ts +1 -0
- package/dist/modes/rpc/rpc-subagent-transport.test.js +60 -0
- package/dist/modes/rpc/rpc-types.d.ts +12 -2
- package/dist/modes/rpc/rpc-types.js +0 -6
- package/dist/modes/rpc/subagent-bridge.d.ts +23 -0
- package/dist/modes/rpc/subagent-bridge.js +93 -0
- package/dist/modes/rpc/test-fixtures/intercom-host.d.ts +1 -0
- package/dist/modes/rpc/test-fixtures/intercom-host.js +13 -0
- package/docs/rpc-subagent.schema.json +491 -0
- package/docs/rpc.md +210 -1
- package/package.json +2 -1
|
@@ -68,8 +68,8 @@ describe("installGraft", () => {
|
|
|
68
68
|
it("runs the compatible global npm install without a shell off Windows", async () => {
|
|
69
69
|
const { pi } = makePi(async () => execReturning(OK));
|
|
70
70
|
const run = await installGraft(pi.exec as never, "/repo");
|
|
71
|
-
expect(GRAFT_INSTALL_SPEC).toBe("@nanonets/graft@^0.
|
|
72
|
-
expect(INSTALL_COMMAND).toBe("npm install -g @nanonets/graft@^0.
|
|
71
|
+
expect(GRAFT_INSTALL_SPEC).toBe("@nanonets/graft@^0.21");
|
|
72
|
+
expect(INSTALL_COMMAND).toBe("npm install -g @nanonets/graft@^0.21");
|
|
73
73
|
const [command, args, options] = pi.exec.mock.calls[0] as [string, string[], { cwd: string; timeout: number }];
|
|
74
74
|
expect(command).toBe(process.platform === "win32" ? "cmd" : "npm");
|
|
75
75
|
expect(args).toContain("install");
|
|
@@ -118,7 +118,7 @@ describe("version handling", () => {
|
|
|
118
118
|
|
|
119
119
|
it("accepts the verified range and rejects the boundaries", () => {
|
|
120
120
|
expect(isCompatibleVersion(MIN_GRAFT_VERSION)).toBe(true);
|
|
121
|
-
expect(isCompatibleVersion("0.
|
|
121
|
+
expect(isCompatibleVersion("0.21.1")).toBe(true);
|
|
122
122
|
expect(isCompatibleVersion("0.99.0")).toBe(true);
|
|
123
123
|
expect(isCompatibleVersion("0.17.9")).toBe(false);
|
|
124
124
|
expect(isCompatibleVersion(MAX_GRAFT_VERSION_EXCLUSIVE)).toBe(false);
|
|
@@ -128,9 +128,9 @@ describe("version handling", () => {
|
|
|
128
128
|
|
|
129
129
|
describe("probeGraft", () => {
|
|
130
130
|
it("accepts a supported version and reports it", async () => {
|
|
131
|
-
const { pi } = makePi(async () => execReturning({ ...OK, stdout: "0.
|
|
131
|
+
const { pi } = makePi(async () => execReturning({ ...OK, stdout: "0.21.1\n" }));
|
|
132
132
|
const probe = await probeGraft(pi.exec as never, "/repo");
|
|
133
|
-
expect(probe).toMatchObject({ kind: "ok", version: "0.
|
|
133
|
+
expect(probe).toMatchObject({ kind: "ok", version: "0.21.1" });
|
|
134
134
|
if (probe.kind === "ok") expect(probe.argv.at(-1)).toBe("--version");
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Selesai model for deep enrichment, otherwise stay structural and local.
|
|
16
16
|
* - Extension-spawned commands default to telemetry opt-out (`DO_NOT_TRACK=1`).
|
|
17
17
|
*
|
|
18
|
-
* Verified against @nanonets/graft 0.
|
|
18
|
+
* Verified against @nanonets/graft 0.21.1 (see {@link MIN_GRAFT_VERSION}).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { truncateHead, type ExecOptions, type ExecResult } from "@selesai/code";
|
|
@@ -27,8 +27,13 @@ export const GRAFT_PACKAGE = "@nanonets/graft";
|
|
|
27
27
|
* Lowest Graft CLI whose documented surface this adapter was verified against:
|
|
28
28
|
* `ask --source/--json`, `skeleton`, `callers --direction/-d`, `grep --in/-i/--fixed`,
|
|
29
29
|
* `map --max-dirs`, `check --json`, `build [--deep]`, and `--version`.
|
|
30
|
+
*
|
|
31
|
+
* The floor sits on the 0.21 minor line this adapter is verified against
|
|
32
|
+
* (0.21.1). Upgrades are deliberate: an executable below the floor reports
|
|
33
|
+
* `incompatible`, and the adapter never overwrites it — the user upgrades with
|
|
34
|
+
* {@link INSTALL_COMMAND}.
|
|
30
35
|
*/
|
|
31
|
-
export const MIN_GRAFT_VERSION = "0.
|
|
36
|
+
export const MIN_GRAFT_VERSION = "0.21.0";
|
|
32
37
|
/** Upper bound is exclusive: a 1.x CLI is allowed to change its contract. */
|
|
33
38
|
export const MAX_GRAFT_VERSION_EXCLUSIVE = "1.0.0";
|
|
34
39
|
|
|
@@ -45,8 +50,8 @@ export const DEEP_BUILD_TIMEOUT_MS = 1_800_000;
|
|
|
45
50
|
/** Global npm installs can be slow on a cold cache. */
|
|
46
51
|
export const INSTALL_TIMEOUT_MS = 180_000;
|
|
47
52
|
|
|
48
|
-
/** Kept on the verified 0.
|
|
49
|
-
export const GRAFT_INSTALL_SPEC = `${GRAFT_PACKAGE}@^0.
|
|
53
|
+
/** Kept on the verified 0.21 minor line; never install a floating latest CLI. */
|
|
54
|
+
export const GRAFT_INSTALL_SPEC = `${GRAFT_PACKAGE}@^0.21`;
|
|
50
55
|
export const INSTALL_COMMAND = `npm install -g ${GRAFT_INSTALL_SPEC}`;
|
|
51
56
|
export const TELEMETRY_ENV = "DO_NOT_TRACK";
|
|
52
57
|
|
|
@@ -399,7 +404,7 @@ export interface AskHit {
|
|
|
399
404
|
}
|
|
400
405
|
|
|
401
406
|
/**
|
|
402
|
-
* `graft ask --json` result, as emitted by `AskResult` upstream (0.
|
|
407
|
+
* `graft ask --json` result, as emitted by `AskResult` upstream (0.21.1).
|
|
403
408
|
*
|
|
404
409
|
* Every field is optional on read: a CLI that changes shape degrades to raw
|
|
405
410
|
* text rather than to a wrong summary, and callers must never treat the parse
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
import { MODE_ENTRY_TYPE, type GraftState } from "./state.ts";
|
|
16
16
|
import { notified, makeCtx, makePi, type CtxStub } from "./test-support.ts";
|
|
17
17
|
|
|
18
|
-
const HEALTHY: GraftState = { name: "fresh-structural", version: "0.
|
|
18
|
+
const HEALTHY: GraftState = { name: "fresh-structural", version: "0.21.1" };
|
|
19
19
|
|
|
20
20
|
function installOf(overrides: Partial<GraftInstallRun> = {}): GraftInstallRun {
|
|
21
21
|
return {
|
|
22
|
-
argv: ["npm", "install", "-g", "@nanonets/graft@^0.
|
|
22
|
+
argv: ["npm", "install", "-g", "@nanonets/graft@^0.21"],
|
|
23
23
|
cwd: "/repo",
|
|
24
24
|
code: 0,
|
|
25
25
|
stdout: "",
|
|
@@ -75,7 +75,7 @@ function makeRuntime(
|
|
|
75
75
|
const recheck = vi.fn(async () => {});
|
|
76
76
|
const installCli = vi.fn(async () => {
|
|
77
77
|
const result = overrides.installResult ?? installOf();
|
|
78
|
-
if (result.code === 0 && !result.killed) state = overrides.postInstallState ?? { name: "unbuilt", version: "0.
|
|
78
|
+
if (result.code === 0 && !result.killed) state = overrides.postInstallState ?? { name: "unbuilt", version: "0.21.1" };
|
|
79
79
|
return result;
|
|
80
80
|
});
|
|
81
81
|
const runBuild = vi.fn(async () => overrides.buildResult ?? runOf());
|
|
@@ -134,7 +134,7 @@ describe("formatDoctor", () => {
|
|
|
134
134
|
it("reports repository, executable, graph, state, mode, telemetry and provider boundary", () => {
|
|
135
135
|
const text = formatDoctor(base);
|
|
136
136
|
expect(text).toContain("/repo (git ✓)");
|
|
137
|
-
expect(text).toContain("graft 0.
|
|
137
|
+
expect(text).toContain("graft 0.21.1");
|
|
138
138
|
expect(text).toContain("structural only");
|
|
139
139
|
expect(text).toContain("fresh-structural");
|
|
140
140
|
expect(text).toContain("nothing is injected");
|
|
@@ -188,7 +188,7 @@ describe("formatStatus", () => {
|
|
|
188
188
|
providerEnv: [],
|
|
189
189
|
});
|
|
190
190
|
expect(text.split("\n")).toHaveLength(4);
|
|
191
|
-
expect(text).toContain("graft: ● structural v0.
|
|
191
|
+
expect(text).toContain("graft: ● structural v0.21.1");
|
|
192
192
|
expect(text).toContain("hybrid");
|
|
193
193
|
expect(text).toContain("root: /repo");
|
|
194
194
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* adapter against the real executable, and it skips itself when no compatible
|
|
7
7
|
* Graft CLI is on PATH — which is the state of ordinary CI.
|
|
8
8
|
*
|
|
9
|
-
* To run it for real: `npm install -g @nanonets/graft@^0.
|
|
9
|
+
* To run it for real: `npm install -g @nanonets/graft@^0.21` then
|
|
10
10
|
* npx vitest run src/extensions/pi-graft/graft.integration.test.ts
|
|
11
11
|
*
|
|
12
12
|
* A structural build is tree-sitter only: no model, no API key, no network. The
|
|
@@ -39,7 +39,7 @@ function writeProjectSettings(value: unknown): void {
|
|
|
39
39
|
function exec(graftOutput = ASK_JSON) {
|
|
40
40
|
return async (command: string, args: string[]) => {
|
|
41
41
|
if (command === "git" && args.includes("--show-toplevel")) return { ...OK, stdout: `${repo}\n` };
|
|
42
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
42
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
43
43
|
return { ...OK, stdout: graftOutput };
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -88,7 +88,7 @@ describe("session start", () => {
|
|
|
88
88
|
it("resolves the Git working tree, probes the executable, and publishes a status line", async () => {
|
|
89
89
|
const session = await bootSession();
|
|
90
90
|
expect(session.commandsRun()).toEqual(["git rev-parse --show-toplevel", "graft --version"]);
|
|
91
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
91
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1");
|
|
92
92
|
await session.reset();
|
|
93
93
|
});
|
|
94
94
|
|
|
@@ -163,7 +163,7 @@ describe("session start", () => {
|
|
|
163
163
|
}
|
|
164
164
|
if (args.includes("--version")) {
|
|
165
165
|
if (!installed) throw new Error("spawn graft ENOENT");
|
|
166
|
-
return { ...OK, stdout: "0.
|
|
166
|
+
return { ...OK, stdout: "0.21.1\n" };
|
|
167
167
|
}
|
|
168
168
|
return { ...OK };
|
|
169
169
|
});
|
|
@@ -171,8 +171,8 @@ describe("session start", () => {
|
|
|
171
171
|
const ctx = makeCtx({ cwd: repo });
|
|
172
172
|
const start = handlerFor(harness, "session_start") as (e: unknown, c: unknown) => Promise<void>;
|
|
173
173
|
await start({ type: "session_start", reason: "startup" }, ctx);
|
|
174
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
175
|
-
expect(harness.commandsRun()).toContain("npm install -g @nanonets/graft@^0.
|
|
174
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
|
|
175
|
+
expect(harness.commandsRun()).toContain("npm install -g @nanonets/graft@^0.21");
|
|
176
176
|
|
|
177
177
|
await start({ type: "session_start", reason: "reload" }, ctx);
|
|
178
178
|
expect(harness.commandsRun().filter((command) => command.startsWith("npm install"))).toHaveLength(1);
|
|
@@ -202,7 +202,7 @@ describe("session start", () => {
|
|
|
202
202
|
writeProjectSettings({});
|
|
203
203
|
const harness = makePi(async (command: string) => {
|
|
204
204
|
if (command === "git") return { code: 128, stdout: "", stderr: "not a git repository", killed: false };
|
|
205
|
-
return { ...OK, stdout: "0.
|
|
205
|
+
return { ...OK, stdout: "0.21.1\n" };
|
|
206
206
|
});
|
|
207
207
|
graftExtension(harness.pi as never);
|
|
208
208
|
const ctx = makeCtx({ cwd: repo });
|
|
@@ -233,14 +233,14 @@ describe("mutation awareness", () => {
|
|
|
233
233
|
it("marks the graph changed after a successful edit", async () => {
|
|
234
234
|
const session = await bootSession();
|
|
235
235
|
await onToolResult(session, "edit", { path: "src/a.ts" });
|
|
236
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.
|
|
236
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
|
|
237
237
|
await session.reset();
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
it("marks the graph changed after a clearly mutating bash command", async () => {
|
|
241
241
|
const session = await bootSession();
|
|
242
242
|
await onToolResult(session, "bash", { command: "rm -rf dist" });
|
|
243
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.
|
|
243
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
|
|
244
244
|
await session.reset();
|
|
245
245
|
});
|
|
246
246
|
|
|
@@ -249,7 +249,7 @@ describe("mutation awareness", () => {
|
|
|
249
249
|
await onToolResult(session, "read", { path: "src/a.ts" });
|
|
250
250
|
await onToolResult(session, "bash", { command: "git status" });
|
|
251
251
|
await onToolResult(session, "edit", { path: "src/a.ts" }, true);
|
|
252
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
252
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1");
|
|
253
253
|
await session.reset();
|
|
254
254
|
});
|
|
255
255
|
});
|
|
@@ -270,7 +270,7 @@ describe("proactive refresh", () => {
|
|
|
270
270
|
writeFileSync(join(bare, ".selesai", "settings.json"), JSON.stringify({ graft: {} }), "utf-8");
|
|
271
271
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
272
272
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
273
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
273
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
274
274
|
if (args.includes("build")) {
|
|
275
275
|
mkdirSync(join(bare, "graft", ".graph"), { recursive: true });
|
|
276
276
|
writeFileSync(join(bare, "graft", ".graph", "wiring.json"), "{}", "utf-8");
|
|
@@ -283,7 +283,7 @@ describe("proactive refresh", () => {
|
|
|
283
283
|
{ type: "session_start", reason: "startup" },
|
|
284
284
|
ctx,
|
|
285
285
|
);
|
|
286
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
286
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
|
|
287
287
|
expect(harness.commandsRun()).toContain("graft build");
|
|
288
288
|
});
|
|
289
289
|
|
|
@@ -294,7 +294,7 @@ describe("proactive refresh", () => {
|
|
|
294
294
|
writeFileSync(join(bare, ".selesai", "settings.json"), JSON.stringify({ graft: {} }), "utf-8");
|
|
295
295
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
296
296
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
297
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
297
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
298
298
|
if (args.includes("build")) {
|
|
299
299
|
mkdirSync(join(bare, "graft", ".graph"), { recursive: true });
|
|
300
300
|
writeFileSync(join(bare, "graft", ".graph", "wiring.json"), "{}", "utf-8");
|
|
@@ -314,7 +314,7 @@ describe("proactive refresh", () => {
|
|
|
314
314
|
{ type: "session_start", reason: "startup" },
|
|
315
315
|
ctx,
|
|
316
316
|
);
|
|
317
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.
|
|
317
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.21.1"));
|
|
318
318
|
const build = harness.exec.mock.calls.find((call) => (call[1] as string[]).includes("build"))!;
|
|
319
319
|
expect(build[1]).toEqual(["build", "--deep"]);
|
|
320
320
|
expect(build[2]).toMatchObject({
|
|
@@ -335,7 +335,7 @@ describe("proactive refresh", () => {
|
|
|
335
335
|
const models: string[] = [];
|
|
336
336
|
const harness = makePi(async (command: string, args: string[], options?: unknown) => {
|
|
337
337
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
338
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
338
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
339
339
|
if (args.includes("build")) {
|
|
340
340
|
const model = (options as { env?: Record<string, string> }).env?.GRAFT_MODEL ?? "";
|
|
341
341
|
models.push(model);
|
|
@@ -359,7 +359,7 @@ describe("proactive refresh", () => {
|
|
|
359
359
|
{ type: "session_start", reason: "startup" },
|
|
360
360
|
ctx,
|
|
361
361
|
);
|
|
362
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.
|
|
362
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● deep v0.21.1"));
|
|
363
363
|
expect(models).toEqual(["deepseek-v4.1-flash", "glm-5.3-flash"]);
|
|
364
364
|
expect(notified(ctx)).toContain("retrying the deep pass");
|
|
365
365
|
});
|
|
@@ -371,7 +371,7 @@ describe("proactive refresh", () => {
|
|
|
371
371
|
writeFileSync(join(bare, ".selesai", "settings.json"), JSON.stringify({ graft: {} }), "utf-8");
|
|
372
372
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
373
373
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
374
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
374
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
375
375
|
if (args.includes("build")) {
|
|
376
376
|
mkdirSync(join(bare, "graft", ".graph"), { recursive: true });
|
|
377
377
|
writeFileSync(join(bare, "graft", ".graph", "wiring.json"), "{}", "utf-8");
|
|
@@ -388,7 +388,7 @@ describe("proactive refresh", () => {
|
|
|
388
388
|
{ type: "session_start", reason: "startup" },
|
|
389
389
|
ctx,
|
|
390
390
|
);
|
|
391
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
391
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
|
|
392
392
|
const build = harness.exec.mock.calls.find((call) => (call[1] as string[]).includes("build"))!;
|
|
393
393
|
expect(build[1]).toEqual(["build"]);
|
|
394
394
|
});
|
|
@@ -403,11 +403,11 @@ describe("proactive refresh", () => {
|
|
|
403
403
|
{ type: "tool_result", toolName: "write", toolCallId: "c1", input: {}, content: [], isError: false },
|
|
404
404
|
session.ctx,
|
|
405
405
|
);
|
|
406
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.
|
|
406
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ◐ changed v0.21.1");
|
|
407
407
|
|
|
408
408
|
await (handlerFor(session, "agent_end") as (e: unknown, c: unknown) => Promise<void>)({}, session.ctx);
|
|
409
|
-
expect(session.ctx.statuses.get("graft")).toBe("graft: ⚙ syncing v0.
|
|
410
|
-
await vi.waitFor(() => expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.
|
|
409
|
+
expect(session.ctx.statuses.get("graft")).toBe("graft: ⚙ syncing v0.21.1");
|
|
410
|
+
await vi.waitFor(() => expect(session.ctx.statuses.get("graft")).toBe("graft: ● structural v0.21.1"));
|
|
411
411
|
expect(session.commandsRun()).toContain("graft build");
|
|
412
412
|
await session.reset();
|
|
413
413
|
});
|
|
@@ -434,7 +434,7 @@ describe("proactive refresh", () => {
|
|
|
434
434
|
writeProjectSettings({ refreshDebounceSeconds: 0 });
|
|
435
435
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
436
436
|
if (command === "git") return { ...OK, stdout: `${repo}\n` };
|
|
437
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
437
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
438
438
|
if (args.includes("build")) return { code: 1, stdout: "", stderr: "✗ index failed", killed: false };
|
|
439
439
|
return { ...OK, stdout: ASK_JSON };
|
|
440
440
|
});
|
|
@@ -449,7 +449,7 @@ describe("proactive refresh", () => {
|
|
|
449
449
|
ctx,
|
|
450
450
|
);
|
|
451
451
|
await (handlerFor(harness, "agent_end") as (e: unknown, c: unknown) => Promise<void>)({}, ctx);
|
|
452
|
-
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ✕ failed v0.
|
|
452
|
+
await vi.waitFor(() => expect(ctx.statuses.get("graft")).toBe("graft: ✕ failed v0.21.1"));
|
|
453
453
|
});
|
|
454
454
|
});
|
|
455
455
|
|
|
@@ -541,7 +541,7 @@ describe("retrieval strategy", () => {
|
|
|
541
541
|
writeProjectSettings({ mode: "push" });
|
|
542
542
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
543
543
|
if (command === "git") return { ...OK, stdout: `${repo}\n` };
|
|
544
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
544
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
545
545
|
return { code: 1, stdout: "", stderr: "✗ graph missing", killed: false };
|
|
546
546
|
});
|
|
547
547
|
graftExtension(harness.pi as never);
|
|
@@ -565,7 +565,7 @@ describe("retrieval strategy", () => {
|
|
|
565
565
|
writeFileSync(join(bare, ".selesai", "settings.json"), JSON.stringify({ graft: { mode: "push" } }), "utf-8");
|
|
566
566
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
567
567
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
568
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
568
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
569
569
|
if (args.includes("build")) {
|
|
570
570
|
mkdirSync(join(bare, "graft", ".graph"), { recursive: true });
|
|
571
571
|
writeFileSync(join(bare, "graft", ".graph", "wiring.json"), "{}", "utf-8");
|
|
@@ -642,7 +642,7 @@ describe("tool readiness", () => {
|
|
|
642
642
|
writeFileSync(join(bare, ".selesai", "settings.json"), JSON.stringify({ graft: {} }), "utf-8");
|
|
643
643
|
const harness = makePi(async (command: string, args: string[]) => {
|
|
644
644
|
if (command === "git") return { ...OK, stdout: `${bare}\n` };
|
|
645
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
645
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
646
646
|
if (args.includes("build")) {
|
|
647
647
|
mkdirSync(join(bare, "graft", ".graph"), { recursive: true });
|
|
648
648
|
writeFileSync(join(bare, "graft", ".graph", "wiring.json"), "{}", "utf-8");
|
|
@@ -671,7 +671,7 @@ describe("tool readiness", () => {
|
|
|
671
671
|
gitCalls += 1;
|
|
672
672
|
return { code: 128, stdout: "", stderr: "not a git repository", killed: false };
|
|
673
673
|
}
|
|
674
|
-
if (args.includes("--version")) return { ...OK, stdout: "0.
|
|
674
|
+
if (args.includes("--version")) return { ...OK, stdout: "0.21.1\n" };
|
|
675
675
|
return { ...OK, stdout: ASK_JSON };
|
|
676
676
|
});
|
|
677
677
|
graftExtension(harness.pi as never);
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./prompt.ts";
|
|
14
14
|
import { DEFAULT_MIN_COVERAGE, type GraftState } from "./state.ts";
|
|
15
15
|
|
|
16
|
-
const FRESH: GraftState = { name: "fresh-structural", version: "0.
|
|
16
|
+
const FRESH: GraftState = { name: "fresh-structural", version: "0.21.1" };
|
|
17
17
|
|
|
18
18
|
function payload(overrides: Partial<AskPayload> = {}): AskPayload {
|
|
19
19
|
return {
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
} from "./state.ts";
|
|
23
23
|
|
|
24
24
|
const UNBUILT: GraftState = { name: "unbuilt", detail: "no graph yet" };
|
|
25
|
-
const FRESH: GraftState = { name: "fresh-structural", version: "0.
|
|
26
|
-
const DEEP: GraftState = { name: "fresh-deep", version: "0.
|
|
25
|
+
const FRESH: GraftState = { name: "fresh-structural", version: "0.21.1", deep: false };
|
|
26
|
+
const DEEP: GraftState = { name: "fresh-deep", version: "0.21.1", deep: true };
|
|
27
27
|
|
|
28
28
|
describe("reduceGraftState", () => {
|
|
29
29
|
it("only claims a repository once root resolution says there is a Git working tree", () => {
|
|
@@ -36,16 +36,16 @@ describe("reduceGraftState", () => {
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it("derives built/fresh/deep from what is actually on disk", () => {
|
|
39
|
-
expect(reduceGraftState(gitState(), { type: "probe-ok", version: "0.
|
|
39
|
+
expect(reduceGraftState(gitState(), { type: "probe-ok", version: "0.21.1", graph: ABSENT_GRAPH })).toEqual({
|
|
40
40
|
name: "unbuilt",
|
|
41
|
-
version: "0.
|
|
41
|
+
version: "0.21.1",
|
|
42
42
|
detail: expect.stringContaining("/graft build"),
|
|
43
43
|
});
|
|
44
44
|
expect(
|
|
45
|
-
reduceGraftState(gitState(), { type: "probe-ok", version: "0.
|
|
45
|
+
reduceGraftState(gitState(), { type: "probe-ok", version: "0.21.1", graph: { built: true, deep: false } }),
|
|
46
46
|
).toEqual(FRESH);
|
|
47
47
|
expect(
|
|
48
|
-
reduceGraftState(gitState(), { type: "probe-ok", version: "0.
|
|
48
|
+
reduceGraftState(gitState(), { type: "probe-ok", version: "0.21.1", graph: { built: true, deep: true } }),
|
|
49
49
|
).toMatchObject({ name: "fresh-deep", deep: true });
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -91,7 +91,7 @@ describe("reduceGraftState", () => {
|
|
|
91
91
|
expect(reduceGraftState(FRESH, { type: "build-started", deep: true })).toMatchObject({
|
|
92
92
|
name: "building",
|
|
93
93
|
deep: true,
|
|
94
|
-
version: "0.
|
|
94
|
+
version: "0.21.1",
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -140,8 +140,8 @@ describe("state predicates and rendering", () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
it("renders a compact status line with the version when it is known", () => {
|
|
143
|
-
expect(statusText(FRESH)).toBe("graft: ● structural v0.
|
|
144
|
-
expect(statusText(DEEP)).toBe("graft: ● deep v0.
|
|
143
|
+
expect(statusText(FRESH)).toBe("graft: ● structural v0.21.1");
|
|
144
|
+
expect(statusText(DEEP)).toBe("graft: ● deep v0.21.1");
|
|
145
145
|
expect(statusText({ name: "unavailable" })).toBe("graft: ✕ no cli");
|
|
146
146
|
expect(statusText({ name: "changed" })).toBe("graft: ◐ changed");
|
|
147
147
|
});
|
|
@@ -190,7 +190,7 @@ export function makeCtx(
|
|
|
190
190
|
*/
|
|
191
191
|
export function healthyExec(overrides: { gitRoot?: string; version?: string; queryOutput?: string } = {}): ExecImpl {
|
|
192
192
|
const gitRoot = overrides.gitRoot ?? "/repo";
|
|
193
|
-
const version = overrides.version ?? "0.
|
|
193
|
+
const version = overrides.version ?? "0.21.1";
|
|
194
194
|
return async (command, args) => {
|
|
195
195
|
if (command === "git" && args.includes("--show-toplevel")) return { ...OK, stdout: `${gitRoot}\n` };
|
|
196
196
|
if (args.includes("--version")) return { ...OK, stdout: `${version}\n` };
|
|
@@ -64,7 +64,7 @@ function makeHarness(
|
|
|
64
64
|
const runtime: GraftToolRuntime = {
|
|
65
65
|
run: run as never,
|
|
66
66
|
settings: () => options.settings ?? {},
|
|
67
|
-
state: () => options.state ?? { name: "fresh-structural", version: "0.
|
|
67
|
+
state: () => options.state ?? { name: "fresh-structural", version: "0.21.1" },
|
|
68
68
|
noteQuery: ((_ctx: unknown, result: GraftRun) => {
|
|
69
69
|
noted.push(result);
|
|
70
70
|
}) as never,
|
|
@@ -143,6 +143,19 @@ See auth.ts:142-156.
|
|
|
143
143
|
|
|
144
144
|
The reply hint (enabled by default) points to `intercom({ action: "reply", ... })`, so recipients do not need raw sender or `replyTo` IDs. Idle recipients get a new turn immediately; busy interactive recipients receive the message through Selesai's steering queue at the next safe model boundary without aborting the active turn. Attachment content is included in the agent-visible body, and messages are rendered inline and stored in Selesai session history.
|
|
145
145
|
|
|
146
|
+
## Desktop / RPC clients
|
|
147
|
+
|
|
148
|
+
RPC clients can use typed `intercom_status`, `intercom_list`, `intercom_send`, `intercom_ask`, `intercom_reply`, and `intercom_pending` commands directly, without a model turn or the TUI-only `/intercom` overlay. They reuse this extension's broker connection, reply tracker, scope, and consent policy. Ordinary/inferred sends honor `confirmSend`; explicit replies and asks skip confirmation as in the tool. Missing/disabled extensions fail explicitly.
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{"id":"peers","type":"intercom_list"}
|
|
152
|
+
{"id":"send","type":"intercom_send","to":"peer-full-id","message":"Build finished."}
|
|
153
|
+
{"id":"ask","type":"intercom_ask","to":"peer-full-id","message":"Approve?","timeoutMs":60000}
|
|
154
|
+
{"id":"reply","type":"intercom_reply","replyTo":"question-id","message":"Approved."}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Structured connection, peer join/leave, presence, incoming message, delivery, and receipt events stream while idle or busy. Command `id` correlates responses; Intercom `messageId`/`replyTo` correlate messages and receipts. An ask timeout does not cancel delivered work. See [the RPC protocol](../../../docs/rpc.md#intercom-desktop--headless-clients) for response shapes, event examples, timeout limits, confirmation dialogs, and typed client helpers.
|
|
158
|
+
|
|
146
159
|
## Workflow: Planner-Worker Coordination
|
|
147
160
|
|
|
148
161
|
The most natural use of pi-intercom is splitting a task between two sessions — one holds the big picture, the other does the hands-on work. When the worker hits an ambiguity ("should I optimize for readability or performance here?"), they ask without losing context.
|
|
@@ -610,3 +623,5 @@ Use pi-messenger for multi-agent swarms working on a shared task. Use pi-interco
|
|
|
610
623
|
- **No attachments UI** — `file`, `snippet`, and `context` attachments are supported in the protocol, but not in the compose overlay
|
|
611
624
|
- **Only connected sessions appear** — The list shows Selesai sessions that have loaded `pi-intercom` and successfully registered with the broker, not every open Selesai process on the machine
|
|
612
625
|
- **Broker lifecycle** — The broker auto-spawns on first use and exits when idle; sessions reconnect automatically if the broker restarts
|
|
626
|
+
|
|
627
|
+
All Intercom events and `intercom_status.data` carry `scope: {version:1,sessionId,epoch}`. `scope.sessionId` is the host parent SDK session ID, not a path or broker endpoint. Existing top-level `sessionId` still identifies broker self (status/connection) or departing peer (leave). The epoch is shared with subagent snapshots and rotates on rebind/reload; discard obsolete scope pairs. Broker reconnect within a binding keeps the epoch.
|