@fitchmultz/pi-subagents 0.46.1 → 0.47.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/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/dist/agents/agent-management.js +8 -2
- package/dist/agents/agents.js +8 -8
- package/dist/extension/build-info.js +1 -1
- package/dist/extension/index.js +11 -5
- package/dist/extension/result-snapshot.js +0 -18
- package/dist/history/ingest.js +40 -31
- package/dist/history/prune.js +1 -1
- package/dist/history/run-projection.js +1 -2
- package/dist/history/search-queries.js +8 -2
- package/dist/history/store.js +20 -4
- package/dist/history/text-chunks.js +1 -2
- package/dist/history/transcript-queries.js +12 -6
- package/dist/intercom/result-intercom.js +0 -74
- package/dist/pi-intercom/bridge-config.js +35 -26
- package/dist/pi-intercom/bridge-files.js +47 -0
- package/dist/pi-intercom/bridge-mailbox.js +324 -0
- package/dist/pi-intercom/bridge-presence.js +100 -0
- package/dist/pi-intercom/bridge-routes.js +27 -3
- package/dist/pi-intercom/bridge-session.js +60 -64
- package/dist/pi-intercom/bridge-wake.js +158 -0
- package/dist/pi-intercom/bridge.js +55 -7
- package/dist/pi-intercom/broker/broker.js +33 -16
- package/dist/pi-intercom/broker/framing.js +67 -17
- package/dist/pi-intercom/broker/snapshot-reader.js +29 -8
- package/dist/pi-intercom/broker/snapshots.js +144 -41
- package/dist/pi-intercom/session-targets.js +0 -1
- package/dist/runs/background/async-run-discovery.js +4 -6
- package/dist/runs/background/completion-dedupe.js +4 -55
- package/dist/runs/background/notify.js +1 -9
- package/dist/runs/background/result-watcher.js +1 -1
- package/dist/runs/background/runner-child-observer.js +12 -7
- package/dist/runs/background/runner-monitor.js +2 -0
- package/dist/runs/background/runner-parallel.js +2 -3
- package/dist/runs/shared/child-observation-spool.js +8 -3
- package/dist/runs/shared/run-restoration.js +4 -5
- package/dist/runs/shared/supervisor-questions.js +7 -4
- package/dist/shared/journal-frames.js +1 -1
- package/dist/slash/command-discovery.js +13 -12
- package/dist/tui/agent-history.js +0 -14
- package/dist/tui/agent-view.js +0 -1
- package/dist/tui/conversation-history.js +10 -1
- package/dist/tui/history-cards.js +8 -22
- package/docs/intercom-bridge.md +39 -17
- package/docs/intercom.md +5 -1
- package/docs/performance.md +72 -3
- package/package.json +1 -1
- package/scripts/intercom-bridge-credentials.mjs +54 -12
- package/scripts/intercom-remote.mjs +25 -1
- package/scripts/package-smoke.mjs +1 -1
- package/src/agents/agent-management.ts +8 -4
- package/src/agents/agents.ts +17 -12
- package/src/extension/index.ts +12 -5
- package/src/extension/result-snapshot.ts +1 -27
- package/src/history/ingest.ts +57 -49
- package/src/history/prune.ts +1 -1
- package/src/history/run-projection.ts +2 -4
- package/src/history/search-queries.ts +9 -2
- package/src/history/store.ts +21 -5
- package/src/history/text-chunks.ts +7 -2
- package/src/history/transcript-queries.ts +16 -10
- package/src/intercom/result-intercom.ts +0 -103
- package/src/pi-intercom/bridge-config.ts +46 -36
- package/src/pi-intercom/bridge-files.ts +59 -0
- package/src/pi-intercom/bridge-mailbox.ts +395 -0
- package/src/pi-intercom/bridge-presence.ts +128 -0
- package/src/pi-intercom/bridge-routes.ts +41 -4
- package/src/pi-intercom/bridge-session.ts +73 -71
- package/src/pi-intercom/bridge-wake.ts +188 -0
- package/src/pi-intercom/bridge.ts +58 -7
- package/src/pi-intercom/broker/broker.ts +34 -20
- package/src/pi-intercom/broker/framing.ts +71 -20
- package/src/pi-intercom/broker/snapshot-reader.ts +47 -9
- package/src/pi-intercom/broker/snapshots.ts +184 -51
- package/src/pi-intercom/session-targets.ts +0 -2
- package/src/runs/background/async-run-discovery.ts +4 -10
- package/src/runs/background/completion-dedupe.ts +6 -69
- package/src/runs/background/notify.ts +2 -12
- package/src/runs/background/result-watcher.ts +1 -1
- package/src/runs/background/runner-child-observer.ts +19 -11
- package/src/runs/background/runner-monitor.ts +2 -0
- package/src/runs/background/runner-parallel.ts +2 -3
- package/src/runs/shared/child-observation-spool.ts +8 -3
- package/src/runs/shared/run-restoration.ts +4 -4
- package/src/runs/shared/supervisor-questions.ts +10 -10
- package/src/shared/journal-frames.ts +1 -1
- package/src/slash/command-discovery.ts +13 -12
- package/src/tui/agent-history.ts +1 -18
- package/src/tui/agent-view.ts +0 -1
- package/src/tui/conversation-history.ts +15 -1
- package/src/tui/history-cards.ts +8 -26
|
@@ -9,7 +9,7 @@ import { createAsyncRunDiscovery } from "../background/async-status.js";
|
|
|
9
9
|
import { parseDetails, parseForegroundResumeRun, parseOwnedRun, parseSubagentExecutionResult, } from "../background/run-schemas.js";
|
|
10
10
|
import { collectInvocationAgentNames } from "../../shared/settings.js";
|
|
11
11
|
import { normalizeSubagentParamsLike, } from "../foreground/subagent-params.js";
|
|
12
|
-
import { getRunMetadataDir, migrateSupervisorQuestionSteps, readRunJson, saveQuestionOwner, saveRunStatus, } from "./supervisor-questions.js";
|
|
12
|
+
import { getRunMetadataDir, migrateSupervisorQuestionSteps, readQuestionOwner, readRunJson, saveQuestionOwner, saveRunStatus, } from "./supervisor-questions.js";
|
|
13
13
|
import { ASYNC_DIR, SLASH_RESULT_TYPE, } from "../../shared/types.js";
|
|
14
14
|
import { OWNED_RUN_ENTRY, rememberOwnedRun, saveForegroundRun } from "./run-persistence.js";
|
|
15
15
|
import { resetOwnedRuns, setOwnedRun, setForegroundRun, suspendRunChanges, } from "./run-state-owner.js";
|
|
@@ -181,15 +181,14 @@ function nativeLaunchCwd(run, details) {
|
|
|
181
181
|
: run;
|
|
182
182
|
}
|
|
183
183
|
function repairQuestionOwner(run) {
|
|
184
|
-
let
|
|
184
|
+
let sessionId;
|
|
185
185
|
try {
|
|
186
|
-
|
|
186
|
+
sessionId = readQuestionOwner(run.runId)?.sessionId;
|
|
187
187
|
}
|
|
188
188
|
catch {
|
|
189
189
|
// A genuine receipt can repair unusable supplemental metadata.
|
|
190
190
|
}
|
|
191
|
-
|
|
192
|
-
if (typeof sessionId !== "string" || sessionId.trim() === "") {
|
|
191
|
+
if (sessionId === undefined || sessionId.trim() === "") {
|
|
193
192
|
saveQuestionOwner(run.runId, run.ownerSessionId);
|
|
194
193
|
}
|
|
195
194
|
}
|
|
@@ -139,7 +139,7 @@ export function createSupervisorQuestion(input, root = QUESTIONS_DIR) {
|
|
|
139
139
|
writeAtomicJson(path.join(questionDir(question, root), "question.json"), question);
|
|
140
140
|
return question;
|
|
141
141
|
}
|
|
142
|
-
export function listRunQuestions(runDir) {
|
|
142
|
+
export function listRunQuestions(runDir, ownerSessionId) {
|
|
143
143
|
let entries;
|
|
144
144
|
try {
|
|
145
145
|
entries = fs.readdirSync(path.join(runDir, "questions"), { withFileTypes: true });
|
|
@@ -154,13 +154,16 @@ export function listRunQuestions(runDir) {
|
|
|
154
154
|
.filter((entry) => entry.isDirectory())
|
|
155
155
|
.flatMap((entry) => {
|
|
156
156
|
const question = readRunJson(path.join(runDir, "questions", entry.name, "question.json"), parseSupervisorQuestion);
|
|
157
|
-
return question
|
|
157
|
+
return question &&
|
|
158
|
+
(ownerSessionId === undefined || question.ownerSessionId === ownerSessionId)
|
|
159
|
+
? [readQuestionState(question, path.dirname(runDir))]
|
|
160
|
+
: [];
|
|
158
161
|
});
|
|
159
162
|
}
|
|
160
163
|
/** Known run IDs need neither a global directory walk nor prefix resolution. */
|
|
161
164
|
export function listOwnedRunQuestions(ownerSessionId, runId) {
|
|
162
165
|
migrateSupervisorQuestions(ownerSessionId, runId);
|
|
163
|
-
return listRunQuestions(getRunMetadataDir(runId)
|
|
166
|
+
return listRunQuestions(getRunMetadataDir(runId), ownerSessionId);
|
|
164
167
|
}
|
|
165
168
|
export function pendingSupervisorQuestion(input) {
|
|
166
169
|
if (input.sessionFile === undefined || input.sessionFile === "") {
|
|
@@ -211,7 +214,7 @@ function* supervisorQuestionSteps(ownerSessionId, runId, root = QUESTIONS_DIR) {
|
|
|
211
214
|
for (const entry of runs) {
|
|
212
215
|
yield;
|
|
213
216
|
try {
|
|
214
|
-
questions.push(...listRunQuestions(path.join(root, entry.name)
|
|
217
|
+
questions.push(...listRunQuestions(path.join(root, entry.name), ownerSessionId));
|
|
215
218
|
}
|
|
216
219
|
catch (error) {
|
|
217
220
|
if (runId !== undefined) {
|
|
@@ -137,7 +137,7 @@ export function readJsonProjection(file, select) {
|
|
|
137
137
|
const projection = new JsonProjection(select, undefined, () => Infinity);
|
|
138
138
|
try {
|
|
139
139
|
const end = fs.fstatSync(fd).size;
|
|
140
|
-
const bytes = Buffer.allocUnsafe(64 * 1024);
|
|
140
|
+
const bytes = Buffer.allocUnsafe(Math.min(64 * 1024, end));
|
|
141
141
|
for (let offset = 0; offset < end;) {
|
|
142
142
|
const count = fs.readSync(fd, bytes, 0, Math.min(bytes.length, end - offset), offset);
|
|
143
143
|
if (count === 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { discoverAgents,
|
|
1
|
+
import { discoverAgents, discoverChains } from "../agents/agents.js";
|
|
2
2
|
import { resolveExecutionCwd } from "../shared/execution-cwd.js";
|
|
3
3
|
function projectTrusted(state) {
|
|
4
4
|
return state.lastUiContext ? state.lastUiContext.isProjectTrusted() : true;
|
|
@@ -30,21 +30,19 @@ function completionPosition(prefix) {
|
|
|
30
30
|
}
|
|
31
31
|
export function makeAgentCompletions(pi, state, multiAgent) {
|
|
32
32
|
return (prefix) => {
|
|
33
|
+
const position = multiAgent ? completionPosition(prefix) : { word: prefix, before: "" };
|
|
34
|
+
if (!position || (!multiAgent && prefix.includes(" "))) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
33
37
|
const cwd = completionCwd(pi, state);
|
|
34
38
|
if (cwd === null || cwd.length === 0) {
|
|
35
39
|
return null;
|
|
36
40
|
}
|
|
37
41
|
const agents = discoverAgents(cwd, "both", { projectTrusted: projectTrusted(state) }).agents;
|
|
38
42
|
if (!multiAgent) {
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
:
|
|
42
|
-
.filter((agent) => agent.name.startsWith(prefix))
|
|
43
|
-
.map((agent) => ({ value: agent.name, label: agent.name }));
|
|
44
|
-
}
|
|
45
|
-
const position = completionPosition(prefix);
|
|
46
|
-
if (!position) {
|
|
47
|
-
return null;
|
|
43
|
+
return agents
|
|
44
|
+
.filter((agent) => agent.name.startsWith(prefix))
|
|
45
|
+
.map((agent) => ({ value: agent.name, label: agent.name }));
|
|
48
46
|
}
|
|
49
47
|
const { word: lastWord, before: beforeLastWord } = position;
|
|
50
48
|
if (lastWord === "->") {
|
|
@@ -57,7 +55,7 @@ export function makeAgentCompletions(pi, state, multiAgent) {
|
|
|
57
55
|
}
|
|
58
56
|
export function discoverSavedChains(cwd, trusted) {
|
|
59
57
|
const chains = new Map();
|
|
60
|
-
for (const chain of
|
|
58
|
+
for (const chain of discoverChains(cwd, { projectTrusted: trusted }).chains) {
|
|
61
59
|
const existing = chains.get(chain.name);
|
|
62
60
|
const projectOverride = existing?.source === "user" && chain.source === "project";
|
|
63
61
|
const jsonOverride = existing?.source === chain.source &&
|
|
@@ -71,8 +69,11 @@ export function discoverSavedChains(cwd, trusted) {
|
|
|
71
69
|
}
|
|
72
70
|
export function makeChainCompletions(pi, state) {
|
|
73
71
|
return (prefix) => {
|
|
72
|
+
if (prefix.includes(" ")) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
74
75
|
const cwd = completionCwd(pi, state);
|
|
75
|
-
if (
|
|
76
|
+
if (cwd === null || cwd.length === 0) {
|
|
76
77
|
return null;
|
|
77
78
|
}
|
|
78
79
|
return discoverSavedChains(cwd, projectTrusted(state))
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import { stripAcceptanceReport } from "../runs/shared/acceptance-reports.js";
|
|
2
|
-
import { displayHistory } from "./history-display.js";
|
|
3
2
|
import { readableText } from "./history-text.js";
|
|
4
3
|
export { readableText } from "./history-text.js";
|
|
5
4
|
export { indexedHistory } from "./indexed-history.js";
|
|
6
|
-
/** Read native entries, not a compacted model context: earlier messages remain inspectable. */
|
|
7
|
-
export function historyItems(entries) {
|
|
8
|
-
const visible = [];
|
|
9
|
-
for (const entry of entries) {
|
|
10
|
-
if (entry.type === "message" ||
|
|
11
|
-
entry.type === "custom_message" ||
|
|
12
|
-
entry.type === "compaction" ||
|
|
13
|
-
entry.type === "branch_summary") {
|
|
14
|
-
visible.push(entry.type === "message" ? { ...entry, native: entry.message } : entry);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return displayHistory(visible);
|
|
18
|
-
}
|
|
19
5
|
function finalMatch(history, text) {
|
|
20
6
|
if (history.finalId !== undefined) {
|
|
21
7
|
return history.finalId;
|
package/dist/tui/agent-view.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { errorText } from "./text-values.js";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
3
|
import { historyStatus, pageNotices, taskNotices, outgoingHistory, } from "./conversation-notices.js";
|
|
3
4
|
import { getSingleResultOutput } from "../shared/utils.js";
|
|
4
5
|
import { withFinalResult } from "./agent-history.js";
|
|
@@ -142,9 +143,17 @@ export class ConversationHistory {
|
|
|
142
143
|
const items = history.items.map((item) => item.id === `result:${task.run.runId}`
|
|
143
144
|
? { ...item, load: () => this.controller.savedResult(this.key, item.id) }
|
|
144
145
|
: item);
|
|
146
|
+
const previous = task.page, sameSource = previous !== undefined &&
|
|
147
|
+
previous.sourceId === result.page.sourceId &&
|
|
148
|
+
previous.generation === result.page.generation &&
|
|
149
|
+
previous.sessionId === result.page.sessionId &&
|
|
150
|
+
previous.sourceState === result.page.sourceState &&
|
|
151
|
+
isDeepStrictEqual(previous.entries.map((entry) => entry.ref), result.page.entries.map((entry) => entry.ref));
|
|
145
152
|
this.seenSource ||= result.page.count > 0;
|
|
146
153
|
this.controller.applyHistoryPage(this.key, { ...history, items }, result.page, this.seenSource);
|
|
147
|
-
|
|
154
|
+
if (!sameSource) {
|
|
155
|
+
this.source.clearCards();
|
|
156
|
+
}
|
|
148
157
|
}
|
|
149
158
|
assignment() {
|
|
150
159
|
const task = this.controller.task(this.key);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
2
|
import { AssistantMessageComponent, UserMessageComponent, ToolExecutionComponent, createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLsToolDefinition, createPowerShellToolDefinition, createReadToolDefinition, createWriteToolDefinition, getMarkdownTheme, renderDiff, } from "@earendil-works/pi-coding-agent";
|
|
2
3
|
import { Container, Spacer, Text, } from "@earendil-works/pi-tui";
|
|
3
4
|
import { formatModelThinking } from "../shared/formatters.js";
|
|
@@ -5,27 +6,9 @@ import { stripAcceptanceReport } from "../runs/shared/acceptance-reports.js";
|
|
|
5
6
|
import { readableText } from "./agent-history.js";
|
|
6
7
|
import { short } from "./view-model.js";
|
|
7
8
|
import { hasText } from "./text-values.js";
|
|
8
|
-
function plainItem(item) {
|
|
9
|
-
return (item.load === undefined &&
|
|
10
|
-
item.assistant === undefined &&
|
|
11
|
-
item.previewAssistant === undefined &&
|
|
12
|
-
item.call === undefined &&
|
|
13
|
-
item.result === undefined);
|
|
14
|
-
}
|
|
15
|
-
function plainSignature(item) {
|
|
16
|
-
return JSON.stringify([
|
|
17
|
-
item.kind,
|
|
18
|
-
item.title,
|
|
19
|
-
item.text,
|
|
20
|
-
item.details,
|
|
21
|
-
item.diff,
|
|
22
|
-
item.model,
|
|
23
|
-
item.timestamp,
|
|
24
|
-
item.messageId,
|
|
25
|
-
]);
|
|
26
|
-
}
|
|
27
9
|
function sameDisplay(left, right) {
|
|
28
|
-
|
|
10
|
+
// Detail loaders stay fresh in contentItems; only the native display component is reused.
|
|
11
|
+
return isDeepStrictEqual({ ...left, load: undefined }, { ...right, load: undefined });
|
|
29
12
|
}
|
|
30
13
|
function previewStop(reason) {
|
|
31
14
|
if (reason === "error" || reason === "aborted" || reason === "length") {
|
|
@@ -132,8 +115,11 @@ export class HistoryCards {
|
|
|
132
115
|
}
|
|
133
116
|
card(item, expanded, detail) {
|
|
134
117
|
const signature = `${expanded}:${detail}`, cached = this.components.get(item.id);
|
|
135
|
-
if (cached?.signature === signature &&
|
|
136
|
-
|
|
118
|
+
if (cached?.signature === signature && cached.item === item) {
|
|
119
|
+
return cached.component;
|
|
120
|
+
}
|
|
121
|
+
if (cached?.signature === signature && sameDisplay(cached.item, item)) {
|
|
122
|
+
this.components.set(item.id, { ...cached, item });
|
|
137
123
|
return cached.component;
|
|
138
124
|
}
|
|
139
125
|
const component = this.create(item, { expanded, detail });
|
package/docs/intercom-bridge.md
CHANGED
|
@@ -10,9 +10,9 @@ Optional HTTPS messaging proxy and locally controlled Pi-session launcher for a
|
|
|
10
10
|
|
|
11
11
|
**Auth/authority:** TLS 1.2+ with client certificates issued by a private CA **and** an explicit SHA-256 certificate fingerprint allowlist. Each allowlisted certificate fixes the peer name and Mac project directory; remote payloads cannot change identity, cwd, socket, shell commands, or presence. Certificates last seven days. TLS verifies the server CA and loopback SAN; never use `curl -k` or disable certificate checks. The proxy exposes register/list/send/ask/reply, inbox/ack receipt plumbing, and optional name-only start plus own-launched sessions/stop operations. No broker admin, topic operations, file reads, or shell endpoint.
|
|
12
12
|
|
|
13
|
-
**Socket discovery:** reuse the actual `IntercomClient`, `getBrokerSocketPath()` and framing code. With the same Mac uid, `PI_CODING_AGENT_DIR` (default `~/.pi/agent`) and `TMPDIR` as Pi, the preferred path is `$TMPDIR/pi-intercom-<sha256(uid:agentDir)[0:16]>/broker.sock`; long paths fall back to `/tmp`. The client's existing owned-socket legacy fallback is retained. No hardcoded `/var/folders` path, chmod,
|
|
13
|
+
**Socket discovery:** reuse the actual `IntercomClient`, `getBrokerSocketPath()` and framing code. With the same Mac uid, `PI_CODING_AGENT_DIR` (default `~/.pi/agent`) and `TMPDIR` as Pi, the preferred path is `$TMPDIR/pi-intercom-<sha256(uid:agentDir)[0:16]>/broker.sock`; long paths fall back to `/tmp`. The client's existing owned-socket legacy fallback is retained. No hardcoded `/var/folders` path, socket chmod, or broker restart. Without `stateDir`, an unavailable broker returns an error; start a normal Pi session to bring it up. Durable presence uses Intercom's safe acquisition to start a detached broker only when none is running, including before any Pi session starts after reboot. Acquisition maintains the normal PID-file/spawn-lock bookkeeping, reuses a healthy broker and refuses to replace a live unhealthy broker that may own accepted work.
|
|
14
14
|
|
|
15
|
-
**Delivery/lifecycle:** one broker peer per authorized certificate
|
|
15
|
+
**Delivery/lifecycle:** one broker peer per authorized certificate. Without `stateDir`, registration is lazy and has a five-minute inactivity lease; poll inbox to maintain presence. With `stateDir`, an authenticated identity has durable presence restored after helper restart and refreshed without remote polling until expiry, revocation or helper shutdown. Send returns broker acceptance, not proof a model consumed it. Ask steers the recipient and waits at most two minutes for both the exact recipient ID and exact `replyTo`. One outgoing ask per identity. Revocation/expiry/shutdown/client disconnect terminate waits. With a local `stateDir`, inbox intake is committed to private SQLite storage before publication or satisfying an outgoing ask. Unacked messages and unanswered inbound asks survive helper restart, idle-lease retirement and broker outage. Inbox reads are non-destructive, bounded cursor pages; ack deletes only ordinary inbox payloads, and successful reply retires an inbound ask. Sender departure does not discard a reply obligation: reply still targets the saved exact sender ID and returns `peer_offline` without deletion while that ID is offline. Without `stateDir`, the legacy 256-message/4 MiB volatile inbox remains; overflow and lease/shutdown loss are reported. Outgoing HTTP ask waits themselves are not resumed after helper restart; persistent mode retains received answers in inbox for recovery and ack.
|
|
16
16
|
|
|
17
17
|
**Audit:** JSONL metadata on stdout: request/connection identity, certificate fingerprint, operation, peer/message IDs and outcome. `session_closed` confirms broker disconnection and removal of the retiring session; cancellation still rejects requests immediately. Message/attachment bodies and keys are not logged. Protect logs (0700 directory, 0600 files); rotate them with your normal log tooling. Never publish private config/keys or raw inbox/list output as smoke evidence.
|
|
18
18
|
|
|
@@ -45,6 +45,24 @@ The last JSON line contains paths to the issued client `cert`/`key`, trusted `ca
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### Enable durable mailboxes
|
|
49
|
+
|
|
50
|
+
Create a dedicated owned **0700** directory and add its absolute path as top-level `stateDir` in the private configuration:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -m 700 "$BRIDGE/mailboxes"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{ "stateDir": "/Users/mitch/.pi/intercom-bridge/mailboxes" }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This opt-in does not enable Pi-session launches or require a Pi SDK. It does enable unattended broker acquisition for durable presence: after reboot or broker exit, the helper can start a broker without a running Pi session. It never restarts or replaces a live broker. Each certificate fingerprint owns a separate **0600** SQLite database. Directory symlinks, database/SQLite-sidecar symlinks, wrong ownership/modes and hard-linked files are rejected. SQLite uses full synchronous commits and a bounded cache; no mailbox-wide hydration is needed. Protect the directory and any backups: databases contain private message bodies, though audit logs do not. `stateDir` changes require helper restart, not SIGHUP.
|
|
61
|
+
|
|
62
|
+
There is no automatic retention limit on unacked messages or unanswered asks. Pages contain at most 256 messages and 4 MiB of serialized envelopes (plus small response metadata); read additional pages with `nextCursor` while `hasMore` is true. Message IDs remain as payload-free tombstones after completion to suppress duplicate/replayed IDs, including after restart. Provision and monitor disk space. Failed disk admission sets durable `overflow`, `lostMessages` and `storageError` when the loss ledger can still be committed; if that also fails, the helper reports `mailbox_storage_failed` (HTTP 507), closes the affected broker peer and logs metadata with `lossRecorded:false`. A failing disk cannot guarantee durable loss bookkeeping. Broker acceptance alone never proves mailbox persistence or remote consumption.
|
|
63
|
+
|
|
64
|
+
Persistent inbox/ack calls remain available while the broker is down; they do not require broker registration. Authenticate once with `register` or another successful connection to establish the private presence proof. Durable presence then reconnects independently, including after helper restart, without remote polling. Revoked/expired certificates still cannot read stored mail or restore presence. Mailboxes are fingerprint-scoped: rotating a certificate does not transfer old private mail to its replacement. Retain the old database for local recovery rather than automatically authorizing a new credential to read it.
|
|
65
|
+
|
|
48
66
|
`cwd` controls presence/project association, **not** an authorization restriction. `list` intentionally returns all connected peer identities, omitting topics/subscriptions. Target full IDs or the returned `target` when names collide. Port `0` requests an ephemeral port, reported in the listening log; a fixed port is easier for SSH.
|
|
49
67
|
|
|
50
68
|
Start the helper in a dedicated terminal (or supervise this exact command with launchd):
|
|
@@ -70,7 +88,7 @@ launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.fitchmultz.pi-i
|
|
|
70
88
|
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.fitchmultz.pi-intercom-bridge.plist"
|
|
71
89
|
```
|
|
72
90
|
|
|
73
|
-
The installed agent has no `KeepAlive` auto-restart; stopping its helper really stops access. Remove its plist to disable future login starts permanently.
|
|
91
|
+
The installed agent has no `KeepAlive` auto-restart; stopping its helper really stops access. Remove its plist to disable future login starts permanently. With `stateDir` enabled, unacked mail survives helper restart. Inspect/drain inbox before restarting a legacy volatile-mode helper.
|
|
74
92
|
|
|
75
93
|
### Tunnel and provision Grok
|
|
76
94
|
|
|
@@ -172,7 +190,7 @@ node "$REMOTE" reply --reply-to QUESTION_ID_FROM_INBOX --message 'Proceed with t
|
|
|
172
190
|
node "$REMOTE" ack ORDINARY_MESSAGE_ID_FROM_INBOX
|
|
173
191
|
```
|
|
174
192
|
|
|
175
|
-
Poll inbox at a reasonable cadence (for example every 5–15 seconds) while Grok is available. Fetch is non-destructive, so retrying a fetch cannot lose messages; persist/consume then ack ordinary messages. Inbound asks remain in the inbox until `reply` succeeds; ack does not discard a reply obligation. Use `send` for most coordination and `ask` only when Grok really needs a blocking answer. The CLI has a hard request deadline (ask timeout + 15 seconds), validates TLS, emits JSON, and exits 1 on errors / 2 on invalid arguments. It never retries mutations automatically.
|
|
193
|
+
Poll inbox at a reasonable cadence (for example every 5–15 seconds) while Grok is available. Fetch is non-destructive, so retrying a fetch cannot lose messages; persist/consume then ack ordinary messages. In persistent mode, follow `hasMore` using `inbox --after NEXT_CURSOR --limit 256`; do not treat the first page as the entire inbox. Restart a drain from cursor `0` to revisit unacked messages. Inbound asks remain in the inbox until `reply` succeeds; ack does not discard a reply obligation. Use `send` for most coordination and `ask` only when Grok really needs a blocking answer. The CLI has a hard request deadline (ask timeout + 15 seconds), validates TLS, emits JSON, and exits 1 on errors / 2 on invalid arguments. It never retries mutations automatically.
|
|
176
194
|
|
|
177
195
|
Curl works too, with bounded waiting and the same TLS files:
|
|
178
196
|
|
|
@@ -188,18 +206,22 @@ curl --fail-with-body --max-time 135 \
|
|
|
188
206
|
|
|
189
207
|
All routes require mTLS and the certificate allowlist. Unknown routes/fields, wrong methods, non-JSON bodies, unsafe control characters, and requests larger than 64 KiB are rejected. No CORS; browser-origin requests are rejected. Success is `{ "ok": true, ... }`; application failures are `{ "ok": false, "error": { "code": "...", "message": "..." } }` with a non-2xx status. Certificate/allowlist admission can terminate TLS before HTTP; the CLI reports that as a TLS/transport error. No remote-controlled broker frame or command is passed through.
|
|
190
208
|
|
|
191
|
-
| Route | Request
|
|
192
|
-
| ------------------- |
|
|
193
|
-
| `POST /v1/register` | `{}`
|
|
194
|
-
| `GET /v1/list` | none
|
|
195
|
-
| `POST /v1/send` | `{to,message}`
|
|
196
|
-
| `POST /v1/ask` | `{to,message,timeoutMs?}`
|
|
197
|
-
| `GET /v1/inbox` |
|
|
198
|
-
| `POST /v1/reply` | `{replyTo,message}`
|
|
199
|
-
| `POST /v1/ack` | `{ids:[...]}`
|
|
200
|
-
| `POST /v1/start` | `{name}`
|
|
201
|
-
| `GET /v1/sessions` | none
|
|
202
|
-
| `POST /v1/stop` | `{sessionId}`
|
|
209
|
+
| Route | Request | Success fields |
|
|
210
|
+
| ------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
211
|
+
| `POST /v1/register` | `{}` | `sessionId`, `name` (also automatic on other operations) |
|
|
212
|
+
| `GET /v1/list` | none | `sessionId`, `sessions` (metadata + unambiguous targets) |
|
|
213
|
+
| `POST /v1/send` | `{to,message}` | `id`, `accepted`, `delivered` |
|
|
214
|
+
| `POST /v1/ask` | `{to,message,timeoutMs?}` | send receipt and `reply:{from,message}` |
|
|
215
|
+
| `GET /v1/inbox` | `?after=CURSOR&limit=N` (optional) | `messages:[{from,message}]`, `nextCursor`, `hasMore`, `overflow`, `lostMessages`, `storageError` |
|
|
216
|
+
| `POST /v1/reply` | `{replyTo,message}` | send receipt; target derived from the received ask |
|
|
217
|
+
| `POST /v1/ack` | `{ids:[...]}` | `acked`, `retained` |
|
|
218
|
+
| `POST /v1/start` | `{name}` | `session:{sessionId,peerId,name,status,pid,sessionFile,error?}`, `reused` |
|
|
219
|
+
| `GET /v1/sessions` | none | `sessions` (only current helper/identity-owned launch records) |
|
|
220
|
+
| `POST /v1/stop` | `{sessionId}` | `session` with observed `exited` status; journal retained |
|
|
221
|
+
|
|
222
|
+
Inbox `after` is an opaque decimal sequence cursor (`0` starts a drain), not a message ID or offset. `limit` is 1–256, default 256; unknown/repeated query keys and invalid cursors are rejected. Ack accepts at most 256 IDs per request and is idempotent; it returns asks in `retained`. Persistent duplicate IDs never increment loss counts or replace the original envelope. In persistent mode an answer returned by `ask` is also retained in inbox; ack its `reply.message.id` after consuming it.
|
|
223
|
+
|
|
224
|
+
The storage API also supports opt-in durable notification intents for a local delivery worker: `Mailbox(stateDir, fingerprint, true)`, `pendingNotifications(after, limit)` and `notificationAccepted(messageId)`. Pending envelopes expose local `receivedAt` and `acknowledged` metadata. Intake and notification intent commit in one SQLite insert. Remote ack/reply hides the inbox row but preserves the notification envelope until the worker marks it accepted; notification acceptance never acknowledges unread mail. This API does not post webhooks or enable any wake service on its own.
|
|
203
225
|
|
|
204
226
|
Timeout does **not** retract a delivered ask; a late answer can arrive in inbox. Offline/missing peers, broker outages, ambiguous/self targets, a second concurrent ask, invalid/revoked credentials, timeout, and resource limits have explicit errors. HTTP send/list deadlines are bounded by the real client (8/5 seconds); broker registration by 10 seconds. A lost HTTP response or acknowledgement timeout means **delivery unknown**—inspect inbox/local peer before retrying to avoid duplicate instructions.
|
|
205
227
|
|
|
@@ -230,7 +252,7 @@ Confirm the reload/revocation audit record, that Grok disappears from the local
|
|
|
230
252
|
|
|
231
253
|
## Verification / smoke transcript
|
|
232
254
|
|
|
233
|
-
The process-boundary tests use a private real broker, real TLS certificates, actual HTTPS requests and the remote CLI. Launch tests use the actually installed Pi dependency graph with a selected-host native faux provider (no paid inference or external credentials); they prove native persistence, zero pre-startup inference, real Intercom wake/tool reply, ownership/caps, startup failure/abort/deadline, safe dialog cancellation, presence-lease independence, short-lived certificate expiry, rotation/revocation and child exit before helper shutdown completes:
|
|
255
|
+
The process-boundary tests use a private real broker, real TLS certificates, actual HTTPS requests and the remote CLI. Mailbox coverage includes cold SIGKILL restart, sender reconnection, idle-lease retirement, broker-outage reads/acks, more than both former count/byte caps, bounded cursor drains, deduplication, failed storage admission, private-file enforcement and notification/ack races across process restarts. Launch tests use the actually installed Pi dependency graph with a selected-host native faux provider (no paid inference or external credentials); they prove native persistence, zero pre-startup inference, real Intercom wake/tool reply, ownership/caps, startup failure/abort/deadline, safe dialog cancellation, presence-lease independence, short-lived certificate expiry, rotation/revocation and child exit before helper shutdown completes:
|
|
234
256
|
|
|
235
257
|
```bash
|
|
236
258
|
node --test test/integration/intercom-bridge.test.ts test/integration/intercom-bridge-launch.test.ts
|
package/docs/intercom.md
CHANGED
|
@@ -183,7 +183,11 @@ The reply hint (enabled by default) points to `intercom({ action: "reply", ... }
|
|
|
183
183
|
|
|
184
184
|
If Esc clears a steered or follow-up message before native handoff, it is re-delivered after the actual agent run settles. Messages already in Pi's session history are not reinserted, even after compaction or reload; messages still in Pi's native queues remain there for its next continuation or prompt. Pi 1.0's turn-boundary pending messages expose the next queued batch, not the whole custom queue; its coarse `hasPendingMessages()` reports only UI text queues. An empty boundary proves all queues cleared, while a nonempty boundary retains admitted messages until their actual history receipts arrive. Selectively removing individual custom items while other queue items remain is not observable through these public APIs: such items remain pending rather than risk a duplicate. Full queue clearing, retained-queue abort, reload and fresh-process recovery are supported. A rejected overlapping prompt does not start receiver retries while the original run is active.
|
|
185
185
|
|
|
186
|
-
Official Pi 1.0 runs user preparation hooks before marking a prompt busy. During that pre-admission window, an incoming custom message can legitimately start a separate model request; custom-triggered runs do not run `before_agent_start`. Both inputs and their receipts are preserved, but one combined request is not guaranteed.
|
|
186
|
+
Official Pi 1.1.0 runs user preparation hooks before marking a prompt busy. During that pre-admission window, an incoming custom message can legitimately start a separate model request; custom-triggered runs do not run `before_agent_start`. Both inputs and their receipts are preserved, but one combined request is not guaranteed.
|
|
187
|
+
|
|
188
|
+
Newer hosts with discriminated `before_agent_start` events also prepare custom-triggered runs, including Intercom and background-command wakeups. A user event has `messageType:"user"` and `prompt`; a custom event has `messageType:"custom"` and `message`, without `prompt`. These hosts mark preparation busy, so Intercom steers arrivals into the starting run instead of launching a separate run. Native steering determines when queued content reaches the model; both inputs and their receipts remain preserved exactly once. Child role/output and peer-awareness sections apply to custom starts without treating notification content as user commands.
|
|
189
|
+
|
|
190
|
+
Intercom follows the host's live idle state; it does not reserve rejected input or emulate a core admission hook. Reload also does not cancel active work: abort explicitly when cancellation is intended.
|
|
187
191
|
|
|
188
192
|
An omitted `ask` still honors recipient availability; use explicit steer only when the sender must remain alive for a busy recipient's reply. The recipient should incorporate relevant context and continue its active task unless the message explicitly replaces it. Use `delivery:"queue"` only when delay is intentional; `queueMode:"replace"` keeps only the latest undelivered thread update.
|
|
189
193
|
|
package/docs/performance.md
CHANGED
|
@@ -1,4 +1,73 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
## 0.46.2 measurements
|
|
4
|
+
|
|
5
|
+
The baseline is `e00b241` (0.46.1). These synthetic workloads exercise the real storage, SQLite, discovery, native-card and broker owners. Counts describe work removed, not a guaranteed reduction in end-to-end latency, retained memory or provider cost.
|
|
6
|
+
|
|
7
|
+
| Workload | Counted operation | 0.46.1 | 0.46.2 |
|
|
8
|
+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------: | --------------------: |
|
|
9
|
+
| 10,000 text deltas | Empty text-spool truncations | 10,000 | 0 |
|
|
10
|
+
| Same deltas | 64 KiB scratch allocations / allocated bytes | 10,000 / 655,360,000 | 0 / 0 |
|
|
11
|
+
| 1,000 one-window native records, cold index | SQL prepares | 14,015 | 19 |
|
|
12
|
+
| Same records | Explicit main-database commits / changed-main autocommit statements | 2,002 / 1,002 | 1,002 / 2 |
|
|
13
|
+
| 1,000 narrowed prune calls beside 1,000 linked sources | Child rows visited | 1,000,000 | 1,000 |
|
|
14
|
+
| 1,000 pending human-message IDs with no later reply | Assistant-tail candidates / statements | 499,500 / 1,000 | 999 / 1 |
|
|
15
|
+
| 200 attempts sharing 1,000 common matching records, relevance search | Final entry-predicate evaluations | 200,000 | 1,000 |
|
|
16
|
+
| 217 profiles, completion after task text or inside an unfinished quote | Profile-file reads | 217 | 0 |
|
|
17
|
+
| Same profiles, management list | Profile-file reads | 434 | 217 |
|
|
18
|
+
| Same profiles and 10 saved chains, saved-chain completion | Profile-file / chain-file reads | 217 / 10 | 0 / 10 |
|
|
19
|
+
| Three identical 100-record history pages | Native cards constructed, including assignment | 303 | 101 |
|
|
20
|
+
| One 1 MiB Intercom frame, 4 KiB fragments | Bytes copied by frame assembly | 135,790,596 | 1,048,576 |
|
|
21
|
+
| Same frame, 1 KiB fragments | Bytes copied by frame assembly | 538,443,780 | 1,048,576 |
|
|
22
|
+
| 1,000 streamed topic rows | Topic text validations | 500,500 | 1,000 |
|
|
23
|
+
| Real broker restoring 1,000 topics | JSON encodings / serialized bytes | 505,504 / 112,612,468 | 7,003 / 873,268 |
|
|
24
|
+
| One socket message write | JSON encodings | 2 | 1 |
|
|
25
|
+
| 10,000 thinking deltas, 1,000 retained workflow steps | Step-status reads in event projection | 10,000,000 | 1,000 |
|
|
26
|
+
| Inert root extension, emitted static local import graph | Modules / JavaScript source bytes / TUI modules | 287 / 3,248,709 / 47 | 11 / 20,587 / 0 |
|
|
27
|
+
| 1,000 foreign async owners, 23-byte supplemental records | Allocated owner-read scratch bytes | 65,536,000 | 23,000 |
|
|
28
|
+
| Same owner census | Opens / reads / closes | 1,000 / 1,000 / 1,000 | 1,000 / 1,000 / 1,000 |
|
|
29
|
+
| 128 foreign questions beside one selected answered question | Foreign question-state opens | 512 | 0 |
|
|
30
|
+
|
|
31
|
+
### Method and retained contracts
|
|
32
|
+
|
|
33
|
+
Measurements used Node **24.21.0**, SQLite **3.53.4** and synthetic private files on macOS/APFS. Discovery used 100 user, 100 trusted-project and 17 builtin profiles. Repeated count probes produced identical results. The history counts begin at cold `SourceIngest` construction after database/source setup; they are SQL transaction and statement boundaries, **not measured fsync counts**. A 100-record control also falls from 1,415 prepares to 19 and from 202 explicit commits plus 102 changed-main autocommit statements to 102 plus 2.
|
|
34
|
+
|
|
35
|
+
Every streaming delta still reaches the observer and schedules status publication; exact wire bytes, malformed-record diagnostics, member ordering, surrogate handling and completed-text output remain covered. Tool aggregation still scans steps on actual tool transitions and coalesced status writes. The event-projection probe has no intervening status publication; its 10,000 scheduling requests are retained, rather than suppressed to obtain the count.
|
|
36
|
+
|
|
37
|
+
Reply lookup reads the latest matching human copy for each requested ID, then answers all IDs from one bounded assistant-tail lookup; physical order and terminal/time boundaries remain authoritative. Empty, missing and latest-singleton requests retain zero assistant-candidate scans.
|
|
38
|
+
|
|
39
|
+
Recovery right-sizes the existing fixed-snapshot streaming JSON buffer to the file's size, up to the unchanged **64 KiB** ceiling. It retains fatal UTF-8 decoding, complete JSON/object validation, positional reads on one opened descriptor and large discarded-field handling. The 1,000-owner probe still reads all 1,000 records and performs the same 3,000 streaming-parser writes; it removes oversized scratch allocation, not filesystem discovery or parsing. A separate native-startup keeper beside 2,048 35-byte foreign owners reduces observed read-buffer bytes from 134,217,728 to 71,680.
|
|
40
|
+
|
|
41
|
+
Owner-scoped question access still reads and validates all 129 question records in the probe, using each question's embedded owner before opening answer, delivery, revival or cancellation state. The selected question retains all four state opens; missing, corrupt or disagreeing supplemental owner files do not hide its question. Unfiltered readers retain full state validation, selected corruption still fails, and later publications are read afresh. There is no archive cap, persistent negative-owner cache or question-state cache.
|
|
42
|
+
|
|
43
|
+
Search qualification used 100,000 documents across 100 owned sources, including 99,000 out-of-scope documents and 200 attempts sharing the selected source. All 12 rare/common × child/shared-agent/global × relevance/newest cases retained independent native-`bm25` ordering and exact first/second-page output. Relevance uses hit-driven attribution; newest retains its previous fixed join order. Scoped eligible-ID materialization remains intentional: replacing it with a correlated entry lookup made common scoped queries about three times slower and was rejected. Contended medians for shared-source relevance were 62.00→29.25 ms (common) and 6.23→3.12 ms (rare); small timing differences in other cases are treated as variation, not gains. Common FTS postings still require scanning, and a bounded page does not bound all search work.
|
|
44
|
+
|
|
45
|
+
SQLite statement reuse is bounded to 128 SQL shapes. Records with at most 32 text windows stage and publish their entry, documents and cursor in one transaction; longer records keep bounded staging transactions. No browse transaction spans a pump yield. Native cold-kill coverage brackets both sides of publication at the 32/33-window boundary. WAL, `synchronous=FULL`, generation checks, the separate writer lock, strict source validation and detail budgets remain intact.
|
|
46
|
+
|
|
47
|
+
Settled pages reuse only their native display components. Equal refreshes advance the comparison item so subsequent frames retain the identity fast path. Selected detail loaders remain fresh; source generation/reference changes and theme invalidation rebuild cards, while unchanged refreshes retain expanded thinking and reading position. Completion and list work still observes current profile edits, overrides, disabled profiles and project trust rather than keeping a persistent discovery cache.
|
|
48
|
+
|
|
49
|
+
Intercom retains the 1 MiB encoded-frame limit, request-ID and escaped-envelope checks, unknown fields, duplicate ordering, first-match subscriptions, stale-revision behavior and validation before state commit or publication. Legacy identity-only lists do not reconstruct discarded peer topic arrays. Replacing or removing the largest cached topic record still scans cached numeric sizes; a requested full snapshot necessarily remains linear in its returned rows.
|
|
50
|
+
|
|
51
|
+
Dynamic output schemas deliberately retain native compilation. Object identity or JSON content alone cannot safely cache nested mutations, recursive references and host format-registry changes. Authoritative receipt/accounting checks, native publication/fsync boundaries, fallback polling, child isolation, fork/fresh choices and acceptance verification are not removed for a better benchmark.
|
|
52
|
+
|
|
53
|
+
The regression owners include `test/integration/{storage-owner-boundaries,session-resume-cost,async-job-tracker,history-index,agent-interaction,slash-commands,pi-intercom-broker,async-execution,process-lifecycle,parent-usage}.test.ts` and `test/unit/{supervisor-questions,compact-child-tools,agent-disabled,pi-intercom-framing}.test.ts`. These protect observable contracts and meaningful operation counts; there are no timing thresholds in CI.
|
|
54
|
+
|
|
55
|
+
### Startup qualification
|
|
56
|
+
|
|
57
|
+
Both production-only packages were loaded through the real official **Pi 1.1.0** and maintained fork **c329e624e73ba2086bd3ce15e091d667879dca9c** SDKs and bundled CLIs, using physical Node 24.21.0 and private HOME/agent/temp directories. SDK preflights asserted error-free registration and expected tools for both package entries, the inert root child, and the actual leaf route (prompt runtime plus both package entries and supervisor metadata). The compiled root deliberately imports the host SDK before deferred imports so production installs retain Pi's host-peer mapping; it does not install another SDK.
|
|
58
|
+
|
|
59
|
+
Fresh-process offline `--list-models zzznomatch` samples below used the same pruned artifacts, one warmup and nine rotated/interleaved runs per case on an Apple M5 Pro. Values are **minimum / median / maximum milliseconds**. Parent loads both entries; root-child loads only the inert global root; leaf loads the real non-delegating child route. No-op/native controls had minima 154.6/143.6 ms on official and 306.5/284.0 ms on the fork.
|
|
60
|
+
|
|
61
|
+
| Host | Route | 0.46.1 | 0.46.2 |
|
|
62
|
+
| -------------- | ---------- | ---------------------- | ---------------------- |
|
|
63
|
+
| Official 1.1.0 | parent | 421.1 / 479.4 / 652.1 | 408.4 / 527.5 / 1138.0 |
|
|
64
|
+
| Official 1.1.0 | root-child | 382.4 / 497.8 / 1105.7 | 252.7 / 301.9 / 498.8 |
|
|
65
|
+
| Official 1.1.0 | leaf | 414.4 / 509.0 / 687.9 | 283.2 / 406.7 / 715.7 |
|
|
66
|
+
| Fork c329e624e | parent | 615.2 / 717.2 / 1416.8 | 629.9 / 676.2 / 796.5 |
|
|
67
|
+
| Fork c329e624e | root-child | 592.5 / 635.8 / 732.1 | 379.3 / 417.5 / 688.3 |
|
|
68
|
+
| Fork c329e624e | leaf | 610.4 / 666.0 / 1160.6 | 464.1 / 481.0 / 607.8 |
|
|
69
|
+
|
|
70
|
+
The machine was contended and observed ranges overlap. These samples do **not** establish an isolated startup-latency gain or regression; operation counts and the smaller local import graph are the repeatable evidence. This measures extension-factory loading, not TUI readiness, model/provider requests or full-session latency. An additional production SDK/CLI/broker/history smoke passed on each host, and the previous fork revision `9007fefef` was qualified separately. No timing threshold is added to CI.
|
|
2
71
|
|
|
3
72
|
## Current behavior
|
|
4
73
|
|
|
@@ -32,7 +101,7 @@ Best-effort timeout samples use a separate `run-timing.sqlite` metadata database
|
|
|
32
101
|
The changed boundaries are exercised through real extension functions and registered SDK tools with synthetic isolated data, not personal transcripts or provider inference:
|
|
33
102
|
|
|
34
103
|
- 20 and 227 genuinely owned runs beside 8,000 unrelated records: Agents startup does no parent-thread child transcript reads or global question listing; only the selected conversation is formatted.
|
|
35
|
-
- Native extension startup beside 2,048 foreign owner directories services input during discovery and restores only the current parent's saved runs and unanswered questions.
|
|
104
|
+
- Native extension startup beside 2,048 foreign owner directories and questions services input during discovery, uses snapshot-sized owner scratch, never opens foreign question state and restores only the current parent's saved runs and unanswered questions.
|
|
36
105
|
- Native Intercom reload restores all 1,024 historical receipts while servicing input. Cleared-queue recovery also services input before its 100 followers finish appending, retaining each message once and one final wake-up across an in-flight reload or a cleared busy handoff. New results and explicit steers are staged before acknowledgement and handed to native steering for the next tool boundary; ordinary queued and passive arrivals wait.
|
|
37
106
|
- 125 owned assignments: global filtering finds an item beyond the first two picker pages; all later pages remain reachable without UI-thread source access.
|
|
38
107
|
- 65 retained runs: off-page reads/stats and migration scans are zero while displayed controls, questions, review and continuation identities remain fresh.
|
|
@@ -77,7 +146,7 @@ Cold recovery still enumerates genuine owner handles and verifies relevant publi
|
|
|
77
146
|
|
|
78
147
|
Pi 1.0 still eagerly loads native session bodies at its own startup; extension installation alone cannot remove that host cost. Explicit fork context still copies and publishes each sibling, with required fsync. An explicit full individual record must fit its detail budget and the consumer's heap.
|
|
79
148
|
|
|
80
|
-
Full-archive backfill/capacity, sustained contention, power-loss durability and cross-platform performance are not qualified by these macOS/APFS synthetic regressions.
|
|
149
|
+
Full-archive backfill/capacity, sustained contention, power-loss durability and cross-platform performance are not qualified by these macOS/APFS synthetic regressions. The measurement workloads do not delete or migrate native journals, inspect personal transcripts or make inference requests. Installed extension updates are separate from these workloads. Intentional tool/queue boundaries, provider response time, host rendering and other extensions remain separate possible costs.
|
|
81
150
|
|
|
82
151
|
## Cached-input traffic and delegation
|
|
83
152
|
|