@north-light/crouter 0.3.220 → 0.3.221
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/dist/api/client.d.ts +9 -0
- package/dist/api/client.js +10 -0
- package/dist/api/dto/chat-inventory.d.ts +43 -0
- package/dist/api/dto/chat-inventory.js +11 -0
- package/dist/api/dto/profiles.d.ts +19 -5
- package/dist/api/dto/profiles.js +2 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/build-root.d.ts +2 -6
- package/dist/build-root.js +51 -4
- package/dist/builtin-memory/internal/agent-shaping.md +3 -1
- package/dist/builtin-memory/internal/memory-loading.md +4 -0
- package/dist/builtin-memory/plan/roadmap.md +7 -1
- package/dist/builtin-memory/spec/guide.md +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +7 -2
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +1 -1
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +1 -1
- package/dist/clients/attach/overlays/file-review.js +2 -2
- package/dist/clients/attach/session/keys.d.ts +1 -1
- package/dist/clients/attach/session/profile-files.js +1 -1
- package/dist/clients/attach/viewer.js +690 -690
- package/dist/clients/inbox/review/launch.d.ts +8 -4
- package/dist/clients/inbox/review/launch.js +55 -5
- package/dist/clients/inbox/review/review-client.d.ts +1 -0
- package/dist/clients/inbox/review/review-client.js +4 -0
- package/dist/clients/inbox/review-adapter.d.ts +1 -8
- package/dist/clients/inbox/review-adapter.js +4 -52
- package/dist/commands/memory/lint.js +2 -1
- package/dist/commands/memory/read.js +1 -0
- package/dist/commands/memory.js +1 -1
- package/dist/commands/pkg/market-manage.js +165 -75
- package/dist/commands/pkg/plugin-inspect.js +19 -2
- package/dist/commands/pkg/plugin-manage.d.ts +8 -3
- package/dist/commands/pkg/plugin-manage.js +72 -24
- package/dist/commands/profile/default.js +6 -10
- package/dist/commands/profile/list.js +5 -3
- package/dist/commands/profile/new.js +21 -8
- package/dist/commands/profile/project.js +25 -19
- package/dist/commands/profile/show.js +3 -3
- package/dist/commands/surface-inbox.js +1 -0
- package/dist/commands/sys/__tests__/migrate.test.js +16 -5
- package/dist/commands/sys/doctor.js +35 -5
- package/dist/commands/sys/migrate.js +38 -19
- package/dist/commands/sys/setup-core.js +1 -1
- package/dist/commands/sys/sync-project-guidance.js +1 -1
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +1 -1
- package/dist/core/__tests__/fixtures/c5-command-boundary-ext.js +24 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -18
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -1
- package/dist/core/__tests__/inline-memory-refs.test.js +36 -2
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +217 -0
- package/dist/core/__tests__/serial/broker-sdk-wiring.test.js +102 -2
- package/dist/core/bootstrap.js +6 -0
- package/dist/core/canvas/browse/app.js +5 -2
- package/dist/core/canvas/browse/model.d.ts +25 -15
- package/dist/core/canvas/browse/model.js +86 -65
- package/dist/core/canvas/render-source.d.ts +6 -0
- package/dist/core/canvas/render-source.js +7 -1
- package/dist/core/canvas/render.js +10 -2
- package/dist/core/command-hooks/artifact.d.ts +10 -0
- package/dist/core/command-hooks/artifact.js +129 -0
- package/dist/core/command-hooks/catalog.d.ts +14 -0
- package/dist/core/command-hooks/catalog.js +38 -0
- package/dist/core/command-hooks/compose.d.ts +15 -0
- package/dist/core/command-hooks/compose.js +99 -0
- package/dist/core/command-hooks/discovery.d.ts +87 -0
- package/dist/core/command-hooks/discovery.js +174 -0
- package/dist/core/command-hooks/help.d.ts +5 -0
- package/dist/core/command-hooks/help.js +18 -0
- package/dist/core/command-hooks/index.d.ts +6 -0
- package/dist/core/command-hooks/index.js +6 -0
- package/dist/core/command-hooks/report.d.ts +23 -0
- package/dist/core/command-hooks/report.js +19 -0
- package/dist/core/command-hooks/schema.d.ts +27 -0
- package/dist/core/command-hooks/schema.js +68 -0
- package/dist/core/command-hooks/transport/exec-invoke.d.ts +22 -0
- package/dist/core/command-hooks/transport/exec-invoke.js +274 -0
- package/dist/core/command-plugins/presence.d.ts +2 -0
- package/dist/core/command-plugins/presence.js +17 -0
- package/dist/core/command-plugins/transport/exec-invoke.d.ts +5 -0
- package/dist/core/command-plugins/transport/exec-invoke.js +58 -5
- package/dist/core/command.d.ts +8 -1
- package/dist/core/command.js +12 -10
- package/dist/core/help.d.ts +7 -1
- package/dist/core/io.d.ts +9 -1
- package/dist/core/io.js +44 -2
- package/dist/core/memory/inline-ref-inventory.d.ts +2 -1
- package/dist/core/memory/inline-ref-inventory.js +15 -8
- package/dist/core/memory-resolver.d.ts +13 -1
- package/dist/core/memory-resolver.js +25 -19
- package/dist/core/profiles/manifest.d.ts +13 -2
- package/dist/core/profiles/manifest.js +84 -18
- package/dist/core/profiles/select.js +9 -9
- package/dist/core/render.js +11 -0
- package/dist/core/runtime/advertised-command-invocation.d.ts +20 -0
- package/dist/core/runtime/advertised-command-invocation.js +233 -0
- package/dist/core/runtime/bearings.js +1 -1
- package/dist/core/runtime/broker/event-projection.js +7 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +1 -1
- package/dist/core/runtime/broker/frame-dispatch.js +15 -10
- package/dist/core/runtime/broker/read-ops.d.ts +4 -0
- package/dist/core/runtime/broker/read-ops.js +6 -2
- package/dist/core/runtime/broker-extension-render.js +1 -1
- package/dist/core/runtime/broker-inventory.d.ts +5 -0
- package/dist/core/runtime/broker-inventory.js +191 -0
- package/dist/core/runtime/broker-protocol.d.ts +9 -2
- package/dist/core/runtime/broker.js +10 -1
- package/dist/core/runtime/command-surface.d.ts +33 -0
- package/dist/core/runtime/command-surface.js +81 -0
- package/dist/core/runtime/node-read.js +5 -0
- package/dist/core/scope.d.ts +26 -1
- package/dist/core/scope.js +52 -12
- package/dist/core/substrate/on-read.d.ts +7 -1
- package/dist/core/substrate/on-read.js +13 -4
- package/dist/core/substrate/render.js +14 -5
- package/dist/core/substrate/schema.d.ts +11 -1
- package/dist/core/substrate/schema.js +11 -2
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -4
- package/dist/daemon/api/handlers/chat-inventory.d.ts +2 -0
- package/dist/daemon/api/handlers/chat-inventory.js +25 -0
- package/dist/daemon/api/handlers/profiles.js +7 -1
- package/dist/daemon/api/map.d.ts +2 -1
- package/dist/daemon/api/map.js +3 -2
- package/dist/daemon/api/server.js +2 -0
- package/dist/hook-authoring.d.ts +75 -0
- package/dist/hook-authoring.js +358 -0
- package/dist/hook-process.d.ts +7 -0
- package/dist/hook-process.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/migrations/002-profile-project-memory.d.ts +2 -0
- package/dist/migrations/002-profile-project-memory.js +71 -0
- package/dist/migrations/profile-manifests.d.ts +30 -0
- package/dist/migrations/profile-manifests.js +70 -0
- package/dist/migrations/registry.js +10 -5
- package/dist/migrations/types.d.ts +28 -1
- package/dist/migrations/types.js +15 -9
- package/dist/pi-extensions/__tests__/canvas-structured-output.test.js +21 -4
- package/dist/pi-extensions/canvas-structured-output.js +85 -2
- package/dist/types.d.ts +15 -6
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
- package/dist/clients/attach/__tests__/file-review-focus.test.js +0 -49
- /package/dist/{clients/attach/__tests__/file-review-focus.test.d.ts → core/__tests__/profile-project-memory-delivery.test.d.ts} +0 -0
package/dist/core/render.js
CHANGED
|
@@ -32,6 +32,13 @@ function scalarStr(v) {
|
|
|
32
32
|
return JSON.stringify(v);
|
|
33
33
|
return String(v);
|
|
34
34
|
}
|
|
35
|
+
function fencedJson(value) {
|
|
36
|
+
const json = JSON.stringify(value, null, 2);
|
|
37
|
+
let fence = '```';
|
|
38
|
+
while (json.includes(fence))
|
|
39
|
+
fence += '`';
|
|
40
|
+
return `${fence}json\n${json}\n${fence}`;
|
|
41
|
+
}
|
|
35
42
|
/** A string field is prose when it is named as such, spans lines, or is long
|
|
36
43
|
* enough that it reads as a sentence rather than an identifier. */
|
|
37
44
|
function isProse(name, v) {
|
|
@@ -122,6 +129,10 @@ export function renderError(p) {
|
|
|
122
129
|
// the value the caller supplied.
|
|
123
130
|
if (p.http_status !== undefined)
|
|
124
131
|
lines.push(`http status: ${p.http_status}`);
|
|
132
|
+
if ('primaryCompleted' in p && p.primaryCompleted === true) {
|
|
133
|
+
lines.push('primaryCompleted: true');
|
|
134
|
+
lines.push(`Primary result (the command already completed):\n${fencedJson(p.result)}`);
|
|
135
|
+
}
|
|
125
136
|
lines.push(`Next: ${p.next}`);
|
|
126
137
|
return `<error code="${attrEsc(p.error)}">\n${lines.join('\n')}\n</error>`;
|
|
127
138
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BrokerSession } from './broker/read-ops.js';
|
|
2
|
+
export interface AdvertisedCommandInvocationMetadata {
|
|
3
|
+
crouterAdvertisedCommandInvocation: {
|
|
4
|
+
raw: string;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
/** Restore extension-owned details while projecting persisted invocation
|
|
8
|
+
* metadata onto the runtime custom message. */
|
|
9
|
+
export declare function hydratePersistedAdvertisedCommandMessages(messages: readonly unknown[]): void;
|
|
10
|
+
/** Add live invocation metadata without changing extension-owned content or
|
|
11
|
+
* details. EventProjection calls this before relaying a custom message. */
|
|
12
|
+
export declare function hydrateLiveAdvertisedCommandMessage(message: unknown): void;
|
|
13
|
+
/** Bind the imminent message_end persistence call to the exact queued app
|
|
14
|
+
* message rather than to a payload-equality search across pending queues. */
|
|
15
|
+
export declare function prepareAdvertisedCommandPersistence(session: BrokerSession, message: unknown): void;
|
|
16
|
+
/** Install persistence/reload plumbing once on a newly built broker session. */
|
|
17
|
+
export declare function installAdvertisedCommandInvocationContract(session: BrokerSession): void;
|
|
18
|
+
/** Preserve a chat-advertised extension command's exact submitted text on the
|
|
19
|
+
* first displayed custom message its handler persists. */
|
|
20
|
+
export declare function promptWithAdvertisedCommandInvocation<T>(session: BrokerSession, raw: string, runPrompt: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { registrationChatSurface } from './command-surface.js';
|
|
3
|
+
const advertisedCommandInvocation = new AsyncLocalStorage();
|
|
4
|
+
const advertisedCustomMessage = new AsyncLocalStorage();
|
|
5
|
+
const invocationAwareSessions = new WeakSet();
|
|
6
|
+
const persistedRuntimeMessages = new WeakSet();
|
|
7
|
+
const endingPersistence = new WeakMap();
|
|
8
|
+
function persistenceDetails(target) {
|
|
9
|
+
return {
|
|
10
|
+
crouterPersistenceEnvelope: true,
|
|
11
|
+
crouterAdvertisedCommandInvocation: { raw: target.raw },
|
|
12
|
+
...(target.details === undefined
|
|
13
|
+
? {}
|
|
14
|
+
: { extensionDetails: target.details }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function persistedDetails(value) {
|
|
18
|
+
if (typeof value !== 'object' || value === null)
|
|
19
|
+
return null;
|
|
20
|
+
const details = value;
|
|
21
|
+
return details.crouterPersistenceEnvelope === true &&
|
|
22
|
+
typeof details.crouterAdvertisedCommandInvocation?.raw === 'string'
|
|
23
|
+
? details
|
|
24
|
+
: null;
|
|
25
|
+
}
|
|
26
|
+
function matchesTarget(target, message) {
|
|
27
|
+
const contentMatches = message.content === target.content ||
|
|
28
|
+
(target.content == null &&
|
|
29
|
+
Array.isArray(message.content) &&
|
|
30
|
+
message.content.length === 0);
|
|
31
|
+
return message.customType === target.customType &&
|
|
32
|
+
contentMatches &&
|
|
33
|
+
message.details === target.details &&
|
|
34
|
+
message.display === true;
|
|
35
|
+
}
|
|
36
|
+
function hydratePersistedMessage(message) {
|
|
37
|
+
if (typeof message !== 'object' || message === null)
|
|
38
|
+
return;
|
|
39
|
+
const record = message;
|
|
40
|
+
if (record['role'] !== 'custom')
|
|
41
|
+
return;
|
|
42
|
+
const persisted = persistedDetails(record['details']);
|
|
43
|
+
if (persisted === null)
|
|
44
|
+
return;
|
|
45
|
+
record['details'] = persisted.extensionDetails;
|
|
46
|
+
record['crouterAdvertisedCommandInvocation'] =
|
|
47
|
+
persisted.crouterAdvertisedCommandInvocation;
|
|
48
|
+
}
|
|
49
|
+
/** Restore extension-owned details while projecting persisted invocation
|
|
50
|
+
* metadata onto the runtime custom message. */
|
|
51
|
+
export function hydratePersistedAdvertisedCommandMessages(messages) {
|
|
52
|
+
for (const message of messages)
|
|
53
|
+
hydratePersistedMessage(message);
|
|
54
|
+
}
|
|
55
|
+
/** Add live invocation metadata without changing extension-owned content or
|
|
56
|
+
* details. EventProjection calls this before relaying a custom message. */
|
|
57
|
+
export function hydrateLiveAdvertisedCommandMessage(message) {
|
|
58
|
+
if (typeof message !== 'object' || message === null)
|
|
59
|
+
return;
|
|
60
|
+
const record = message;
|
|
61
|
+
if (record['role'] !== 'custom')
|
|
62
|
+
return;
|
|
63
|
+
if (typeof record['crouterAdvertisedCommandInvocation']?.raw === 'string') {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const target = advertisedCustomMessage.getStore();
|
|
67
|
+
if (target === undefined ||
|
|
68
|
+
target.relayed ||
|
|
69
|
+
!matchesTarget(target, record)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
target.relayed = true;
|
|
73
|
+
record['crouterAdvertisedCommandInvocation'] = { raw: target.raw };
|
|
74
|
+
if (target.persisted)
|
|
75
|
+
persistedRuntimeMessages.add(record);
|
|
76
|
+
}
|
|
77
|
+
/** Bind the imminent message_end persistence call to the exact queued app
|
|
78
|
+
* message rather than to a payload-equality search across pending queues. */
|
|
79
|
+
export function prepareAdvertisedCommandPersistence(session, message) {
|
|
80
|
+
if (typeof message !== 'object' ||
|
|
81
|
+
message === null ||
|
|
82
|
+
persistedRuntimeMessages.has(message)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const record = message;
|
|
86
|
+
if (record['role'] !== 'custom' || record['display'] !== true)
|
|
87
|
+
return;
|
|
88
|
+
const metadata = record['crouterAdvertisedCommandInvocation'];
|
|
89
|
+
if (typeof metadata?.raw !== 'string')
|
|
90
|
+
return;
|
|
91
|
+
endingPersistence.set(session.sessionManager, {
|
|
92
|
+
raw: metadata.raw,
|
|
93
|
+
customType: String(record['customType'] ?? ''),
|
|
94
|
+
content: record['content'],
|
|
95
|
+
details: record['details'],
|
|
96
|
+
persisted: false,
|
|
97
|
+
relayed: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Install persistence/reload plumbing once on a newly built broker session. */
|
|
101
|
+
export function installAdvertisedCommandInvocationContract(session) {
|
|
102
|
+
if (invocationAwareSessions.has(session))
|
|
103
|
+
return;
|
|
104
|
+
invocationAwareSessions.add(session);
|
|
105
|
+
const pendingPersistence = [];
|
|
106
|
+
const manager = session.sessionManager;
|
|
107
|
+
const appendCustomMessageEntry = manager.appendCustomMessageEntry.bind(manager);
|
|
108
|
+
manager.appendCustomMessageEntry = (customType, content, display, details) => {
|
|
109
|
+
const current = advertisedCustomMessage.getStore();
|
|
110
|
+
const ending = endingPersistence.get(manager);
|
|
111
|
+
const target = ending !== undefined &&
|
|
112
|
+
matchesTarget(ending, { customType, content, display, details })
|
|
113
|
+
? ending
|
|
114
|
+
: current !== undefined &&
|
|
115
|
+
!current.persisted &&
|
|
116
|
+
matchesTarget(current, { customType, content, display, details })
|
|
117
|
+
? current
|
|
118
|
+
: pendingPersistence.find((candidate) => !candidate.persisted &&
|
|
119
|
+
matchesTarget(candidate, { customType, content, display, details }));
|
|
120
|
+
if (target === undefined) {
|
|
121
|
+
return appendCustomMessageEntry(customType, content, display, details);
|
|
122
|
+
}
|
|
123
|
+
target.persisted = true;
|
|
124
|
+
if (target === ending) {
|
|
125
|
+
endingPersistence.delete(manager);
|
|
126
|
+
const queuedIndex = pendingPersistence.findIndex((candidate) => candidate.raw === target.raw &&
|
|
127
|
+
matchesTarget(candidate, { customType, content, display, details }));
|
|
128
|
+
if (queuedIndex !== -1)
|
|
129
|
+
pendingPersistence.splice(queuedIndex, 1);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const pendingIndex = pendingPersistence.indexOf(target);
|
|
133
|
+
if (pendingIndex !== -1)
|
|
134
|
+
pendingPersistence.splice(pendingIndex, 1);
|
|
135
|
+
}
|
|
136
|
+
return appendCustomMessageEntry(customType, content, display, persistenceDetails(target));
|
|
137
|
+
};
|
|
138
|
+
const buildSessionContext = manager.buildSessionContext.bind(manager);
|
|
139
|
+
manager.buildSessionContext = () => {
|
|
140
|
+
const context = buildSessionContext();
|
|
141
|
+
hydratePersistedAdvertisedCommandMessages(context.messages);
|
|
142
|
+
return context;
|
|
143
|
+
};
|
|
144
|
+
hydratePersistedAdvertisedCommandMessages(session.messages);
|
|
145
|
+
// `.agent` is optional on a session — the fake-engine fixture has none, and
|
|
146
|
+
// every other consumer (rebind, frame-dispatch) guards the same way.
|
|
147
|
+
const agent = session.agent;
|
|
148
|
+
for (const method of agent === undefined ? [] : ['steer', 'followUp']) {
|
|
149
|
+
const original = agent?.[method];
|
|
150
|
+
if (typeof original !== 'function' || agent === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
agent[method] = (message, ...args) => {
|
|
153
|
+
const target = advertisedCustomMessage.getStore();
|
|
154
|
+
if (target !== undefined &&
|
|
155
|
+
typeof message === 'object' &&
|
|
156
|
+
message !== null &&
|
|
157
|
+
matchesTarget(target, message)) {
|
|
158
|
+
hydrateLiveAdvertisedCommandMessage(message);
|
|
159
|
+
if (!target.persisted && !pendingPersistence.includes(target)) {
|
|
160
|
+
pendingPersistence.push(target);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return original.call(agent, message, ...args);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const sendCustomMessage = session.sendCustomMessage.bind(session);
|
|
167
|
+
session.sendCustomMessage = (message, options) => {
|
|
168
|
+
const invocation = advertisedCommandInvocation.getStore();
|
|
169
|
+
if (invocation === undefined ||
|
|
170
|
+
!invocation.active ||
|
|
171
|
+
invocation.claimed ||
|
|
172
|
+
message.display !== true) {
|
|
173
|
+
return sendCustomMessage(message, options);
|
|
174
|
+
}
|
|
175
|
+
invocation.claimed = true;
|
|
176
|
+
const target = {
|
|
177
|
+
raw: invocation.raw,
|
|
178
|
+
customType: message.customType,
|
|
179
|
+
content: message.content,
|
|
180
|
+
details: message.details,
|
|
181
|
+
persisted: false,
|
|
182
|
+
relayed: false,
|
|
183
|
+
};
|
|
184
|
+
return advertisedCustomMessage.run(target, () => {
|
|
185
|
+
const result = sendCustomMessage(message, options);
|
|
186
|
+
if (options?.deliverAs === 'nextTurn') {
|
|
187
|
+
const pending = session._pendingNextTurnMessages;
|
|
188
|
+
if (Array.isArray(pending)) {
|
|
189
|
+
for (let index = pending.length - 1; index >= 0; index -= 1) {
|
|
190
|
+
const queued = pending[index];
|
|
191
|
+
if (typeof queued !== 'object' ||
|
|
192
|
+
queued === null ||
|
|
193
|
+
!matchesTarget(target, queued)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
hydrateLiveAdvertisedCommandMessage(queued);
|
|
197
|
+
pendingPersistence.push(target);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/** Preserve a chat-advertised extension command's exact submitted text on the
|
|
207
|
+
* first displayed custom message its handler persists. */
|
|
208
|
+
export function promptWithAdvertisedCommandInvocation(session, raw, runPrompt) {
|
|
209
|
+
if (!raw.startsWith('/'))
|
|
210
|
+
return runPrompt();
|
|
211
|
+
const spaceIndex = raw.indexOf(' ');
|
|
212
|
+
const commandName = spaceIndex === -1 ? raw.slice(1) : raw.slice(1, spaceIndex);
|
|
213
|
+
let command;
|
|
214
|
+
try {
|
|
215
|
+
command = session.extensionRunner?.getCommand(commandName);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return runPrompt();
|
|
219
|
+
}
|
|
220
|
+
if (!registrationChatSurface(command).gatewayCapable)
|
|
221
|
+
return runPrompt();
|
|
222
|
+
installAdvertisedCommandInvocationContract(session);
|
|
223
|
+
const invocation = { raw, claimed: false, active: true };
|
|
224
|
+
return advertisedCommandInvocation.run(invocation, async () => {
|
|
225
|
+
try {
|
|
226
|
+
return await runPrompt();
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
// Unawaited descendants are no longer part of the command outcome.
|
|
230
|
+
invocation.active = false;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -164,7 +164,7 @@ export function profileNote(nodeId) {
|
|
|
164
164
|
catch {
|
|
165
165
|
return '';
|
|
166
166
|
}
|
|
167
|
-
const projects = entry.manifest.projects;
|
|
167
|
+
const projects = entry.manifest.projects.map((project) => project.path);
|
|
168
168
|
if (projects.length === 0)
|
|
169
169
|
return '';
|
|
170
170
|
return (`Your working dir is \`${meta.cwd}\`. Your profile **${entry.manifest.name}** also has purview ` +
|
|
@@ -3,6 +3,7 @@ import { emitEvent } from '../../events/emit.js';
|
|
|
3
3
|
import { releaseKnownStreamWait, resolveStreamWatchdogMs, StreamWatchdog } from '../stream-watchdog.js';
|
|
4
4
|
import { withoutYieldAbort } from '../session-cycles.js';
|
|
5
5
|
import { DEFAULT_WORKING_ACTIVITY, randomWorkingActivity } from '../../../shared/working-activity.js';
|
|
6
|
+
import { hydrateLiveAdvertisedCommandMessage, prepareAdvertisedCommandPersistence, } from '../advertised-command-invocation.js';
|
|
6
7
|
const MESSAGE_UPDATE_COALESCE_MS = 75;
|
|
7
8
|
/** Projects one live engine event stream to broker viewers and owns its relay timers. */
|
|
8
9
|
export class EventProjection {
|
|
@@ -67,6 +68,12 @@ export class EventProjection {
|
|
|
67
68
|
relayEvent(event, generation) {
|
|
68
69
|
this.deps.faultRetry.onEvent(event);
|
|
69
70
|
const type = event.type;
|
|
71
|
+
if (type === 'message_start') {
|
|
72
|
+
hydrateLiveAdvertisedCommandMessage(event.message);
|
|
73
|
+
}
|
|
74
|
+
else if (type === 'message_end') {
|
|
75
|
+
prepareAdvertisedCommandPersistence(this.deps.currentSession(), event.message);
|
|
76
|
+
}
|
|
70
77
|
if (type === 'message_start' && event.message.role === 'user') {
|
|
71
78
|
try {
|
|
72
79
|
this.workingActivity = randomWorkingActivity(readConfig('user').working_gerunds);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentSessionServices, ModelRegistry, PromptOptions } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import type { ImageContent } from '@earendil-works/pi-ai';
|
|
3
|
+
export { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
|
|
3
4
|
import { type BrokerSdkConfig } from '../launch.js';
|
|
4
5
|
import { type ClientToBroker } from '../broker-protocol.js';
|
|
5
6
|
import type { BrokerClient, BrokerClientRegistry } from './client-registry.js';
|
|
@@ -237,4 +238,3 @@ export declare function createFrameDispatchContext(deps: FrameDispatchDeps): {
|
|
|
237
238
|
};
|
|
238
239
|
/** The socket listener calls this explicit dispatcher for every decoded client frame. */
|
|
239
240
|
export declare function handleFrame(ctx: FrameDispatchContext, client: BrokerClient, frame: ClientToBroker): void;
|
|
240
|
-
export {};
|
|
@@ -9,6 +9,9 @@ import { operationIdContext } from '../../events/operation-id.js';
|
|
|
9
9
|
import { buildRefInventory } from '../../memory/inline-ref-inventory.js';
|
|
10
10
|
import { buildGuidance } from '../../memory/inline-ref-guidance.js';
|
|
11
11
|
import { clearSessionCache } from '../../substrate/session-cache.js';
|
|
12
|
+
import { captureSessionPromptTemplateChatSurfaces } from '../command-surface.js';
|
|
13
|
+
import { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
|
|
14
|
+
export { promptWithAdvertisedCommandInvocation } from '../advertised-command-invocation.js';
|
|
12
15
|
import { configuredLadderProviders, isProviderPinnedModelToken, nextLadderModel, normalizeModel, } from '../launch.js';
|
|
13
16
|
import { clearFault, readFault } from '../fault.js';
|
|
14
17
|
import { isManagedProvider, readSubscriptionPool, } from '../../subscription-state.js';
|
|
@@ -451,9 +454,7 @@ export function createFrameDispatchContext(deps) {
|
|
|
451
454
|
// escalated by m-C — so it gets a fresh operation id. A true
|
|
452
455
|
// (mid-stream) followUp rides the current turn's operation, like
|
|
453
456
|
// steer.
|
|
454
|
-
return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
|
|
455
|
-
.prompt(frame.text, route.options)
|
|
456
|
-
.catch(relayError))));
|
|
457
|
+
return settle(trackPromptDispatch(operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options)).catch(relayError))));
|
|
457
458
|
}
|
|
458
459
|
// C1: pi's followUp() takes images as a POSITIONAL 2nd arg (`followUp(text, images?)`).
|
|
459
460
|
return settle(currentSession()
|
|
@@ -470,10 +471,9 @@ export function createFrameDispatchContext(deps) {
|
|
|
470
471
|
// ATOMICALLY clears its own pending entry if prompt() settles
|
|
471
472
|
// without ever reaching that drain (an early throw/return leaves
|
|
472
473
|
// nothing stranded to leak into a later, unrelated turn).
|
|
473
|
-
return settle(trackPromptDispatch(promptWithNextTurnGuidance(currentSession(), guidance, () => operationIdContext.fresh(() => currentSession().prompt(frame.text, route.options))).catch(relayError)));
|
|
474
|
+
return settle(trackPromptDispatch(promptWithNextTurnGuidance(currentSession(), guidance, () => operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options)))).catch(relayError)));
|
|
474
475
|
}
|
|
475
|
-
return settle(trackPromptDispatch(operationIdContext.fresh(() => currentSession()
|
|
476
|
-
.prompt(frame.text, route.options)
|
|
476
|
+
return settle(trackPromptDispatch(operationIdContext.fresh(() => promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, route.options))
|
|
477
477
|
.catch(relayError)
|
|
478
478
|
.then(() => sendGuidance(guidance, deliverAs)))));
|
|
479
479
|
}
|
|
@@ -511,12 +511,10 @@ export function createFrameDispatchContext(deps) {
|
|
|
511
511
|
// and executes it immediately even while streaming. It also expands a
|
|
512
512
|
// matching skill/template before applying the same steer behavior.
|
|
513
513
|
if (resolveSteerCall(frame.text, currentSession()) === 'prompt') {
|
|
514
|
-
void currentSession()
|
|
515
|
-
.prompt(frame.text, {
|
|
514
|
+
void promptWithAdvertisedCommandInvocation(currentSession(), frame.text, () => currentSession().prompt(frame.text, {
|
|
516
515
|
images: frame.images,
|
|
517
516
|
streamingBehavior: 'steer',
|
|
518
|
-
})
|
|
519
|
-
.catch(relayError);
|
|
517
|
+
})).catch(relayError);
|
|
520
518
|
break;
|
|
521
519
|
}
|
|
522
520
|
const guidance = resolveFrameGuidance(frame.text);
|
|
@@ -1223,8 +1221,15 @@ export function createFrameDispatchContext(deps) {
|
|
|
1223
1221
|
agent.steeringMode = 'all';
|
|
1224
1222
|
agent.followUpMode = 'all';
|
|
1225
1223
|
}
|
|
1224
|
+
// `session.reload()` has replaced pi's loaded template objects;
|
|
1225
|
+
// capture their crouter-only metadata before any observer refresh.
|
|
1226
|
+
captureSessionPromptTemplateChatSurfaces(currentSession());
|
|
1226
1227
|
memoizedRefInventory = null;
|
|
1227
1228
|
clearSessionCache();
|
|
1229
|
+
registry.forEachHelloed((observer) => {
|
|
1230
|
+
if (observer.role === 'observer')
|
|
1231
|
+
registry.sendFrame(observer, { type: 'resources_reloaded' });
|
|
1232
|
+
});
|
|
1228
1233
|
ackTo(client, 'reload');
|
|
1229
1234
|
})
|
|
1230
1235
|
.catch(engineErrorTo(client));
|
|
@@ -9,6 +9,10 @@ export type CommandDisclosure = {
|
|
|
9
9
|
source: string;
|
|
10
10
|
template?: string;
|
|
11
11
|
expansion?: DeterministicCommandExpansion;
|
|
12
|
+
/** Whether this command can complete its outcome from a non-terminal chat
|
|
13
|
+
* surface. Opt-in and fail-closed; builtins are never chat-capable. */
|
|
14
|
+
gatewayCapable: boolean;
|
|
15
|
+
argumentHint?: string;
|
|
12
16
|
};
|
|
13
17
|
export declare const modelKey: (model: {
|
|
14
18
|
provider: string;
|
|
@@ -3,6 +3,7 @@ import { boundaryRootedTree } from '../session-visibility.js';
|
|
|
3
3
|
import { isUnknownModel } from '../model-selection.js';
|
|
4
4
|
import { BUILTIN_SLASH_COMMANDS, piSessionsRoot } from '../pi-vendored.js';
|
|
5
5
|
import { isDeterministicCommandExpansion } from '../command-expansion.js';
|
|
6
|
+
import { registrationChatSurface, templateChatSurface } from '../command-surface.js';
|
|
6
7
|
function registryOf(session) {
|
|
7
8
|
return new ModelRegistry(session.modelRuntime);
|
|
8
9
|
}
|
|
@@ -10,8 +11,10 @@ const toModelRef = (model) => model === undefined || isUnknownModel(model) ? nul
|
|
|
10
11
|
export const modelKey = (model) => `${model.provider}/${model.id}`;
|
|
11
12
|
export function buildCommandList(session) {
|
|
12
13
|
const out = [];
|
|
14
|
+
// Builtins are pi's own terminal controls. They stay in the terminal
|
|
15
|
+
// disclosure and are never offered to a chat surface.
|
|
13
16
|
for (const builtin of BUILTIN_SLASH_COMMANDS)
|
|
14
|
-
out.push({ name: builtin.name, description: builtin.description, source: 'builtin' });
|
|
17
|
+
out.push({ name: builtin.name, description: builtin.description, source: 'builtin', gatewayCapable: false });
|
|
15
18
|
try {
|
|
16
19
|
for (const command of session.extensionRunner.getRegisteredCommands()) {
|
|
17
20
|
const expansion = command.expansion;
|
|
@@ -20,6 +23,7 @@ export function buildCommandList(session) {
|
|
|
20
23
|
description: command.description ?? '',
|
|
21
24
|
source: 'command',
|
|
22
25
|
...(isDeterministicCommandExpansion(expansion) ? { expansion } : {}),
|
|
26
|
+
...registrationChatSurface(command),
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -28,7 +32,7 @@ export function buildCommandList(session) {
|
|
|
28
32
|
}
|
|
29
33
|
try {
|
|
30
34
|
for (const template of session.promptTemplates)
|
|
31
|
-
out.push({ name: template.name, description: template.description, source: 'template', template: template.content });
|
|
35
|
+
out.push({ name: template.name, description: template.description, source: 'template', template: template.content, ...templateChatSurface(template) });
|
|
32
36
|
}
|
|
33
37
|
catch {
|
|
34
38
|
/* ignore */
|
|
@@ -73,7 +73,7 @@ function profileNote(node) {
|
|
|
73
73
|
const entry = loadProfileManifest(node.profile_id);
|
|
74
74
|
if (entry.manifest.projects.length === 0)
|
|
75
75
|
return '';
|
|
76
|
-
return `Your working dir is \`${node.cwd}\`. Your profile **${entry.manifest.name}** also has purview over: ${entry.manifest.projects.join(', ')}.`;
|
|
76
|
+
return `Your working dir is \`${node.cwd}\`. Your profile **${entry.manifest.name}** also has purview over: ${entry.manifest.projects.map((project) => project.path).join(', ')}.`;
|
|
77
77
|
}
|
|
78
78
|
catch {
|
|
79
79
|
return '';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ChatInventoryDTO, ChatInventoryMemoryRefDTO } from '../../api/dto/chat-inventory.js';
|
|
2
|
+
export declare function gatewayMemoryRows(refs: unknown): ChatInventoryMemoryRefDTO[];
|
|
3
|
+
/** Collect the chat inventories for one node. Always resolves — every failure
|
|
4
|
+
* mode is expressed in the returned document, not thrown. */
|
|
5
|
+
export declare function readChatInventory(nodeId: string): Promise<ChatInventoryDTO>;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// broker-inventory.ts — one short-lived OBSERVER round trip against a live
|
|
2
|
+
// node's broker that collects both chat-surface inventories at once: the
|
|
3
|
+
// command disclosure (`get_commands`) and the inline memory-reference
|
|
4
|
+
// inventory (`list_memory_refs`).
|
|
5
|
+
//
|
|
6
|
+
// It is modelled on `broker-request.ts`'s `oneShotControllerRequest` — the
|
|
7
|
+
// sanctioned host→live-broker dance — but is a different shape: two frames on
|
|
8
|
+
// one connection, settling on two independent answers, and never writable.
|
|
9
|
+
// Both read-ops are explicitly observer-safe, so this connection never claims
|
|
10
|
+
// controller and can never drive the engine.
|
|
11
|
+
//
|
|
12
|
+
// The two answers arrive by different routes because the frames differ.
|
|
13
|
+
// `get_commands` is uncorrelated and answers with an `ack`, which surfaces on
|
|
14
|
+
// the client's `frame` event. `list_memory_refs` answers with a correlated
|
|
15
|
+
// `data` frame, and `BrokerClient` swallows EVERY `data` frame into its
|
|
16
|
+
// pending-by-id resolver rather than re-emitting it — so that half must ride
|
|
17
|
+
// `client.request()`, which mints the id and owns the reply.
|
|
18
|
+
//
|
|
19
|
+
// A read NEVER revives. A dormant node, a refused connection, or an expired
|
|
20
|
+
// budget answers `broker_live:false` with empty arrays; one answer arriving
|
|
21
|
+
// without the other yields that part plus an empty peer. Nothing here is fatal,
|
|
22
|
+
// because a chat surface's behavior for "empty because it failed" and "empty
|
|
23
|
+
// because it is empty" is identical.
|
|
24
|
+
import { randomUUID } from 'node:crypto';
|
|
25
|
+
import { getNode } from '../canvas/canvas.js';
|
|
26
|
+
import { BrokerClient } from '../broker-client/index.js';
|
|
27
|
+
import { isDeterministicCommandExpansion } from './command-expansion.js';
|
|
28
|
+
import { isBrokerLive } from './model-swap.js';
|
|
29
|
+
/** Same budget as `broker-request.ts`: this is local engine work, so it is
|
|
30
|
+
* generous slack rather than a tuned deadline. One budget covers BOTH answers —
|
|
31
|
+
* whichever part has not arrived when it expires is returned empty. */
|
|
32
|
+
const READ_TIMEOUT_MS = 15_000;
|
|
33
|
+
/** The transcript window this reader asks for in its welcome. Any hello triggers
|
|
34
|
+
* `sendWelcome`, and an untailed welcome makes the broker build and encode the
|
|
35
|
+
* whole conversation on its serialized frame loop for a payload this reader
|
|
36
|
+
* throws away — on every activation and reconnect, worst on the longest
|
|
37
|
+
* conversations. One entry is the smallest legal request. */
|
|
38
|
+
const SNAPSHOT_TAIL = 1;
|
|
39
|
+
const MEMORY_KINDS = new Set(['knowledge', 'preference']);
|
|
40
|
+
const MEMORY_SCOPES = new Set(['node', 'project', 'profile', 'user', 'builtin']);
|
|
41
|
+
function nonEmptyString(value) {
|
|
42
|
+
return typeof value === 'string' && value !== '';
|
|
43
|
+
}
|
|
44
|
+
/** Keep the rows a chat surface can actually use and drop the rest. Malformed
|
|
45
|
+
* rows are discarded individually so one bad registration cannot take down the
|
|
46
|
+
* whole inventory.
|
|
47
|
+
*
|
|
48
|
+
* This validation lives here rather than reusing the attach viewer's parser:
|
|
49
|
+
* `src/core` does not import `src/clients`, and adding that import would invert
|
|
50
|
+
* the layering. */
|
|
51
|
+
function commandRows(detail) {
|
|
52
|
+
if (detail === undefined)
|
|
53
|
+
return [];
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = JSON.parse(detail);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
if (!Array.isArray(parsed))
|
|
62
|
+
return [];
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const row of parsed) {
|
|
65
|
+
if (typeof row !== 'object' || row === null)
|
|
66
|
+
continue;
|
|
67
|
+
const record = row;
|
|
68
|
+
if (record['gatewayCapable'] !== true)
|
|
69
|
+
continue;
|
|
70
|
+
const source = record['source'];
|
|
71
|
+
if (source !== 'command' && source !== 'template')
|
|
72
|
+
continue;
|
|
73
|
+
if (!nonEmptyString(record['name']) || typeof record['description'] !== 'string')
|
|
74
|
+
continue;
|
|
75
|
+
const expansion = record['expansion'];
|
|
76
|
+
const template = record['template'];
|
|
77
|
+
const argumentHint = record['argumentHint'];
|
|
78
|
+
out.push({
|
|
79
|
+
name: record['name'],
|
|
80
|
+
description: record['description'],
|
|
81
|
+
source,
|
|
82
|
+
...(nonEmptyString(argumentHint) ? { argument_hint: argumentHint } : {}),
|
|
83
|
+
...(isDeterministicCommandExpansion(expansion) ? { expansion } : {}),
|
|
84
|
+
...(typeof template === 'string' ? { template } : {}),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
export function gatewayMemoryRows(refs) {
|
|
90
|
+
if (!Array.isArray(refs))
|
|
91
|
+
return [];
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const row of refs) {
|
|
94
|
+
if (typeof row !== 'object' || row === null)
|
|
95
|
+
continue;
|
|
96
|
+
const record = row;
|
|
97
|
+
if (record['gatewayVisible'] !== true)
|
|
98
|
+
continue;
|
|
99
|
+
if (!nonEmptyString(record['name']) || typeof record['shortForm'] !== 'string')
|
|
100
|
+
continue;
|
|
101
|
+
if (typeof record['kind'] !== 'string' || !MEMORY_KINDS.has(record['kind']))
|
|
102
|
+
continue;
|
|
103
|
+
if (typeof record['scope'] !== 'string' || !MEMORY_SCOPES.has(record['scope']))
|
|
104
|
+
continue;
|
|
105
|
+
out.push({
|
|
106
|
+
name: record['name'],
|
|
107
|
+
kind: record['kind'],
|
|
108
|
+
scope: record['scope'],
|
|
109
|
+
shortForm: record['shortForm'],
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
const empty = (nodeId) => ({
|
|
115
|
+
node_id: nodeId,
|
|
116
|
+
broker_live: false,
|
|
117
|
+
commands: [],
|
|
118
|
+
memory_refs: [],
|
|
119
|
+
});
|
|
120
|
+
/** Collect the chat inventories for one node. Always resolves — every failure
|
|
121
|
+
* mode is expressed in the returned document, not thrown. */
|
|
122
|
+
export function readChatInventory(nodeId) {
|
|
123
|
+
const meta = getNode(nodeId);
|
|
124
|
+
if (meta === null || !isBrokerLive(meta))
|
|
125
|
+
return Promise.resolve(empty(nodeId));
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
const client = new BrokerClient(nodeId);
|
|
128
|
+
const clientId = randomUUID();
|
|
129
|
+
let settled = false;
|
|
130
|
+
let connected = false;
|
|
131
|
+
let commands;
|
|
132
|
+
let memoryRefs;
|
|
133
|
+
const timer = setTimeout(() => finish(), READ_TIMEOUT_MS);
|
|
134
|
+
if (typeof timer.unref === 'function')
|
|
135
|
+
timer.unref();
|
|
136
|
+
/** Settle once with whatever arrived. A part that never answered stays
|
|
137
|
+
* empty; a connection that never established reports a dormant broker. */
|
|
138
|
+
function finish() {
|
|
139
|
+
if (settled)
|
|
140
|
+
return;
|
|
141
|
+
settled = true;
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
try {
|
|
144
|
+
client.close();
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* best-effort */
|
|
148
|
+
}
|
|
149
|
+
resolve({
|
|
150
|
+
node_id: nodeId,
|
|
151
|
+
broker_live: connected,
|
|
152
|
+
commands: commands ?? [],
|
|
153
|
+
memory_refs: memoryRefs ?? [],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function settleWhenComplete() {
|
|
157
|
+
if (commands !== undefined && memoryRefs !== undefined)
|
|
158
|
+
finish();
|
|
159
|
+
}
|
|
160
|
+
client.on('error', () => finish());
|
|
161
|
+
client.on('close', () => finish());
|
|
162
|
+
client.on('connect', () => {
|
|
163
|
+
connected = true;
|
|
164
|
+
client.send({ type: 'hello', role: 'observer', client_id: clientId, snapshot_tail: SNAPSHOT_TAIL });
|
|
165
|
+
client.send({ type: 'get_commands' });
|
|
166
|
+
client.request({ type: 'list_memory_refs' }).then((frame) => {
|
|
167
|
+
memoryRefs = frame.kind === 'list_memory_refs' ? gatewayMemoryRows(frame.refs) : [];
|
|
168
|
+
settleWhenComplete();
|
|
169
|
+
}, () => {
|
|
170
|
+
memoryRefs = [];
|
|
171
|
+
settleWhenComplete();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
client.on('frame', (frame) => {
|
|
175
|
+
// `get_commands` carries no id, so its ack is matched on `for` alone.
|
|
176
|
+
if (frame.type === 'ack' && frame.for === 'get_commands') {
|
|
177
|
+
commands = frame.ok ? commandRows(frame.detail) : [];
|
|
178
|
+
settleWhenComplete();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// The memory half's correlated error is consumed by its own request
|
|
182
|
+
// rejection, so an error reaching here is uncorrelated and can only be
|
|
183
|
+
// attributed to the uncorrelated half.
|
|
184
|
+
if (frame.type === 'error' && frame.id === undefined) {
|
|
185
|
+
commands ??= [];
|
|
186
|
+
settleWhenComplete();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
client.connect();
|
|
190
|
+
});
|
|
191
|
+
}
|