@nuvin/session 0.2.0-rc.7 → 0.2.0-rc.8
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/{chunk-I5U3WU6B.js → chunk-2OTEZX2E.js} +87 -28
- package/dist/chunk-7LEIACBZ.js +85 -0
- package/dist/{chunk-MRVGQ4DK.js → chunk-MPBST5DM.js} +1 -1
- package/dist/client/daemon-client.d.ts.map +1 -1
- package/dist/client/data-client.d.ts +9 -9
- package/dist/client/data-client.d.ts.map +1 -1
- package/dist/client/directory.d.ts +12 -14
- package/dist/client/directory.d.ts.map +1 -1
- package/dist/client/http-data-client.d.ts +5 -0
- package/dist/client/http-data-client.d.ts.map +1 -1
- package/dist/client/index.js +86 -22
- package/dist/client/session-client.d.ts +2 -2
- package/dist/client/session-client.d.ts.map +1 -1
- package/dist/controller/agent-channel.d.ts +2 -2
- package/dist/controller/agent-channel.d.ts.map +1 -1
- package/dist/controller/index.js +108 -49
- package/dist/controller/session-controller.d.ts +4 -11
- package/dist/controller/session-controller.d.ts.map +1 -1
- package/dist/protocol/data-rpc.d.ts +12 -8
- package/dist/protocol/data-rpc.d.ts.map +1 -1
- package/dist/protocol/history-text.d.ts +4 -0
- package/dist/protocol/history-text.d.ts.map +1 -0
- package/dist/protocol/index.d.ts +1 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js +12 -4
- package/dist/protocol/model-identity.d.ts +7 -0
- package/dist/protocol/model-identity.d.ts.map +1 -1
- package/dist/protocol/types.d.ts +51 -16
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/protocol/version.d.ts +1 -1
- package/dist/state/index.d.ts +2 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +9 -1
- package/dist/state/messages.d.ts +3 -3
- package/dist/state/messages.d.ts.map +1 -1
- package/dist/state/questions.d.ts +5 -0
- package/dist/state/questions.d.ts.map +1 -0
- package/dist/state/session.d.ts.map +1 -1
- package/dist/state/tool-key.d.ts +2 -0
- package/dist/state/tool-key.d.ts.map +1 -0
- package/dist/test-utils/index.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-7HGJO7ZX.js +0 -9
package/dist/client/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSessionViewState,
|
|
3
3
|
reduceSessionEvent
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-2OTEZX2E.js";
|
|
5
5
|
import {
|
|
6
6
|
PROTOCOL_VERSION
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-MPBST5DM.js";
|
|
8
8
|
|
|
9
9
|
// src/client/endpoint.ts
|
|
10
10
|
var TerminalEndpointError = class extends Error {
|
|
@@ -17,6 +17,13 @@ var TerminalEndpointError = class extends Error {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// src/client/http-data-client.ts
|
|
20
|
+
var DataRpcClientError = class extends Error {
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = "DataRpcClientError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
20
27
|
function httpBaseFromWsUrl(wsUrl) {
|
|
21
28
|
const u = new URL(wsUrl);
|
|
22
29
|
const scheme = u.protocol === "wss:" ? "https:" : "http:";
|
|
@@ -46,6 +53,9 @@ async function throwFromResponse(response, fallback, terminal, onUnauthorized) {
|
|
|
46
53
|
typeof body?.message === "string" ? body.message : mapped.message
|
|
47
54
|
);
|
|
48
55
|
}
|
|
56
|
+
if (body?.code === "invalid-history-cursor") {
|
|
57
|
+
throw new DataRpcClientError(body.code, body.message ?? fallback);
|
|
58
|
+
}
|
|
49
59
|
throw new Error(
|
|
50
60
|
typeof body?.message === "string" ? body.message : `${fallback} (HTTP ${response.status}).`
|
|
51
61
|
);
|
|
@@ -173,10 +183,10 @@ function createHttpDataClient(options = {}) {
|
|
|
173
183
|
const body = await response.json();
|
|
174
184
|
return { url: body.wsUrl, credential: { grant: body.grant } };
|
|
175
185
|
},
|
|
176
|
-
async fetchTranscript(daemonId,
|
|
177
|
-
const suffix = `/transcript/${encodeURIComponent(
|
|
178
|
-
workspace:
|
|
179
|
-
profile:
|
|
186
|
+
async fetchTranscript(daemonId, identity) {
|
|
187
|
+
const suffix = `/transcript/${encodeURIComponent(identity.sessionId)}${queryString({
|
|
188
|
+
workspace: identity.workspace,
|
|
189
|
+
profile: identity.profile
|
|
180
190
|
})}`;
|
|
181
191
|
const response = await daemonFetch(daemonId, suffix);
|
|
182
192
|
if (response.status === 404) {
|
|
@@ -202,6 +212,35 @@ function createHttpDataClient(options = {}) {
|
|
|
202
212
|
});
|
|
203
213
|
return await response.json();
|
|
204
214
|
},
|
|
215
|
+
async listHistoryWorkspaces(daemonId, opts) {
|
|
216
|
+
const suffix = `/history/workspaces${queryString({
|
|
217
|
+
profile: opts?.profile,
|
|
218
|
+
currentCwd: opts?.currentCwd,
|
|
219
|
+
query: opts?.query
|
|
220
|
+
})}`;
|
|
221
|
+
const response = await daemonFetch(daemonId, suffix, { signal: opts?.signal });
|
|
222
|
+
if (!response.ok)
|
|
223
|
+
await throwFromResponse(response, "Failed to load history workspaces", {
|
|
224
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
225
|
+
});
|
|
226
|
+
return await response.json();
|
|
227
|
+
},
|
|
228
|
+
async listWorkspaceHistory(daemonId, opts) {
|
|
229
|
+
const suffix = `/history/workspace${queryString({
|
|
230
|
+
profile: opts?.profile,
|
|
231
|
+
workspace: opts?.workspace,
|
|
232
|
+
cwd: opts?.cwd,
|
|
233
|
+
query: opts?.query,
|
|
234
|
+
limit: opts?.limit,
|
|
235
|
+
cursor: opts?.cursor
|
|
236
|
+
})}`;
|
|
237
|
+
const response = await daemonFetch(daemonId, suffix, { signal: opts?.signal });
|
|
238
|
+
if (!response.ok)
|
|
239
|
+
await throwFromResponse(response, "Failed to load workspace history", {
|
|
240
|
+
404: { reason: "daemon-revoked", message: "This daemon is no longer registered." }
|
|
241
|
+
});
|
|
242
|
+
return await response.json();
|
|
243
|
+
},
|
|
205
244
|
async listWorkspaces(daemonId, opts) {
|
|
206
245
|
const suffix = `/workspaces${queryString({ profile: opts?.profile })}`;
|
|
207
246
|
const response = await daemonFetch(daemonId, suffix);
|
|
@@ -224,10 +263,10 @@ function createHttpDataClient(options = {}) {
|
|
|
224
263
|
if (!response.ok) await throwFromResponse(response, "Failed to create the workspace");
|
|
225
264
|
return await response.json();
|
|
226
265
|
},
|
|
227
|
-
async deleteSession(daemonId,
|
|
228
|
-
const suffix = `/sessions/${encodeURIComponent(
|
|
229
|
-
workspace:
|
|
230
|
-
profile:
|
|
266
|
+
async deleteSession(daemonId, identity) {
|
|
267
|
+
const suffix = `/sessions/${encodeURIComponent(identity.sessionId)}${queryString({
|
|
268
|
+
workspace: identity.workspace,
|
|
269
|
+
profile: identity.profile
|
|
231
270
|
})}`;
|
|
232
271
|
const response = await daemonFetch(daemonId, suffix, { method: "DELETE" });
|
|
233
272
|
if (response.status === 404) {
|
|
@@ -540,22 +579,24 @@ function createSessionClient(transport) {
|
|
|
540
579
|
})).then(() => {
|
|
541
580
|
});
|
|
542
581
|
},
|
|
543
|
-
decideApproval(toolCallId, decision, comment, grantDir) {
|
|
582
|
+
decideApproval(toolCallId, decision, comment, grantDir, parentToolCallId) {
|
|
544
583
|
return sendCommand((id) => ({
|
|
545
584
|
id,
|
|
546
585
|
type: "decide-approval",
|
|
547
586
|
toolCallId,
|
|
548
587
|
decision,
|
|
549
588
|
...comment !== void 0 ? { comment } : {},
|
|
550
|
-
...grantDir !== void 0 ? { grantDir } : {}
|
|
589
|
+
...grantDir !== void 0 ? { grantDir } : {},
|
|
590
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {}
|
|
551
591
|
})).then(() => {
|
|
552
592
|
});
|
|
553
593
|
},
|
|
554
|
-
persistBashPermission(toolCallId, request) {
|
|
594
|
+
persistBashPermission(toolCallId, request, parentToolCallId) {
|
|
555
595
|
return sendCommand((id) => ({
|
|
556
596
|
id,
|
|
557
597
|
type: "persist-bash-permission",
|
|
558
598
|
toolCallId,
|
|
599
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {},
|
|
559
600
|
request
|
|
560
601
|
})).then(toPersistBashPermissionResult);
|
|
561
602
|
},
|
|
@@ -994,11 +1035,13 @@ function throwCollectedErrors(errors, message) {
|
|
|
994
1035
|
}
|
|
995
1036
|
function bindDaemonDataClient(client, daemonId) {
|
|
996
1037
|
return {
|
|
997
|
-
fetchTranscript: (
|
|
1038
|
+
fetchTranscript: (identity) => client.fetchTranscript(daemonId, identity),
|
|
998
1039
|
fetchHistory: (opts) => client.fetchHistory(daemonId, opts),
|
|
1040
|
+
listHistoryWorkspaces: (opts) => client.listHistoryWorkspaces(daemonId, opts),
|
|
1041
|
+
listWorkspaceHistory: (opts) => client.listWorkspaceHistory(daemonId, opts),
|
|
999
1042
|
listWorkspaces: (opts) => client.listWorkspaces(daemonId, opts),
|
|
1000
1043
|
createWorkspace: (opts) => client.createWorkspace(daemonId, opts),
|
|
1001
|
-
deleteSession: (
|
|
1044
|
+
deleteSession: (identity) => client.deleteSession(daemonId, identity),
|
|
1002
1045
|
listProfiles: () => client.listProfiles(daemonId),
|
|
1003
1046
|
listProviders: (opts) => client.listProviders(daemonId, opts),
|
|
1004
1047
|
addProvider: (input) => client.addProvider(daemonId, input),
|
|
@@ -1198,6 +1241,24 @@ function createDaemonClient(target, deps = {}) {
|
|
|
1198
1241
|
}
|
|
1199
1242
|
|
|
1200
1243
|
// src/client/directory.ts
|
|
1244
|
+
var DAEMON_REQUEST_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1245
|
+
"transcript-unavailable",
|
|
1246
|
+
"transcript-corrupt",
|
|
1247
|
+
"history-state-conflict",
|
|
1248
|
+
"ambiguous-session-identity"
|
|
1249
|
+
]);
|
|
1250
|
+
var DaemonRequestError = class extends Error {
|
|
1251
|
+
code;
|
|
1252
|
+
identity;
|
|
1253
|
+
constructor(message, options) {
|
|
1254
|
+
super(message);
|
|
1255
|
+
this.name = "DaemonRequestError";
|
|
1256
|
+
if (options?.code && DAEMON_REQUEST_ERROR_CODES.has(options.code)) {
|
|
1257
|
+
this.code = options.code;
|
|
1258
|
+
}
|
|
1259
|
+
this.identity = options?.identity;
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1201
1262
|
var toText2 = (data) => {
|
|
1202
1263
|
if (typeof data === "string") return data;
|
|
1203
1264
|
if (data instanceof Buffer) return data.toString("utf8");
|
|
@@ -1323,7 +1384,12 @@ function createServerDirectory(options) {
|
|
|
1323
1384
|
if (!entry || entry.kind === "list") return;
|
|
1324
1385
|
link.pending.delete(message.id);
|
|
1325
1386
|
if (!message.ok) {
|
|
1326
|
-
entry.reject(
|
|
1387
|
+
entry.reject(
|
|
1388
|
+
new DaemonRequestError(message.error?.message ?? "Command failed.", {
|
|
1389
|
+
...message.error?.code ? { code: message.error.code } : {},
|
|
1390
|
+
...message.error?.identity ? { identity: message.error.identity } : {}
|
|
1391
|
+
})
|
|
1392
|
+
);
|
|
1327
1393
|
return;
|
|
1328
1394
|
}
|
|
1329
1395
|
if (entry.kind === "kill") {
|
|
@@ -1524,7 +1590,7 @@ function createServerDirectory(options) {
|
|
|
1524
1590
|
);
|
|
1525
1591
|
});
|
|
1526
1592
|
},
|
|
1527
|
-
resumeSession(server,
|
|
1593
|
+
resumeSession(server, identity) {
|
|
1528
1594
|
return new Promise((resolve, reject) => {
|
|
1529
1595
|
const link = linkFor(server);
|
|
1530
1596
|
if (link instanceof Error) {
|
|
@@ -1538,11 +1604,7 @@ function createServerDirectory(options) {
|
|
|
1538
1604
|
JSON.stringify({
|
|
1539
1605
|
id,
|
|
1540
1606
|
type: "resume-session",
|
|
1541
|
-
|
|
1542
|
-
cwd: resumeOptions.cwd,
|
|
1543
|
-
...resumeOptions.name !== void 0 ? { name: resumeOptions.name } : {},
|
|
1544
|
-
...resumeOptions.workspace !== void 0 ? { workspace: resumeOptions.workspace } : {},
|
|
1545
|
-
...resumeOptions.profile !== void 0 ? { profile: resumeOptions.profile } : {}
|
|
1607
|
+
identity
|
|
1546
1608
|
})
|
|
1547
1609
|
);
|
|
1548
1610
|
});
|
|
@@ -1590,6 +1652,8 @@ function createServerDirectory(options) {
|
|
|
1590
1652
|
};
|
|
1591
1653
|
}
|
|
1592
1654
|
export {
|
|
1655
|
+
DaemonRequestError,
|
|
1656
|
+
DataRpcClientError,
|
|
1593
1657
|
SessionCommandError,
|
|
1594
1658
|
TerminalEndpointError,
|
|
1595
1659
|
adaptWhatwgSocket,
|
|
@@ -18,8 +18,8 @@ export interface SessionClient {
|
|
|
18
18
|
/** Raw frame hook for UI side-effects (e.g. close modals on state-reset). */
|
|
19
19
|
onEvent(listener: (event: ServerEvent) => void): () => void;
|
|
20
20
|
submit(input: AgentInput, opts: ClientSubmitOptions): Promise<void>;
|
|
21
|
-
decideApproval(toolCallId: string, decision: "a" | "n" | "y", comment?: string, grantDir?: string): Promise<void>;
|
|
22
|
-
persistBashPermission(toolCallId: string, request: PersistBashPermissionRequest): Promise<PersistBashPermissionResult>;
|
|
21
|
+
decideApproval(toolCallId: string, decision: "a" | "n" | "y", comment?: string, grantDir?: string, parentToolCallId?: string): Promise<void>;
|
|
22
|
+
persistBashPermission(toolCallId: string, request: PersistBashPermissionRequest, parentToolCallId?: string): Promise<PersistBashPermissionResult>;
|
|
23
23
|
answerQuestion(questionId: string, answers: AskUserAnswers): Promise<void>;
|
|
24
24
|
abort(): Promise<void>;
|
|
25
25
|
/** Cancel a queued (not-yet-running) message by its `turn-status.queued` id. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-client.d.ts","sourceRoot":"","sources":["../../src/client/session-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAa,MAAM,0BAA0B,CAAC;AAEtF,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EAEtB,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EACf,4BAA4B,EAC5B,2BAA2B,EAC3B,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,IAAI,gBAAgB,CAAC;IAC7B,MAAM,IAAI,MAAM,CAAC;IACjB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,cAAc,CACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EACzB,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"session-client.d.ts","sourceRoot":"","sources":["../../src/client/session-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,7 +36,7 @@ export type ToolCallRequest = {
|
|
|
36
36
|
export type AgentEventListener = (payload: AgentEventPayload) => void;
|
|
37
37
|
export type SessionLoadedListener = (loaded: SessionLoadedPayload) => void;
|
|
38
38
|
export type ToolCallDecider = (request: ToolCallRequest) => Promise<ToolRuntimeDispatchDecision> | ToolRuntimeDispatchDecision;
|
|
39
|
-
export type UserQuestionHandler = (request: AskUserQuestionRequest) => Promise<AskUserAnswers> | AskUserAnswers;
|
|
39
|
+
export type UserQuestionHandler = (request: AskUserQuestionRequest, scope?: DelegationScope) => Promise<AskUserAnswers> | AskUserAnswers;
|
|
40
40
|
/**
|
|
41
41
|
* In-process channel between the Agent (constructed in `main()`) and the
|
|
42
42
|
* React UI (`<App />`). Two distinct shapes:
|
|
@@ -54,7 +54,7 @@ export declare class AgentChannel {
|
|
|
54
54
|
publishEvent(event: AgentEvent, scope?: DelegationScope): void;
|
|
55
55
|
publishSessionLoaded(loaded: SessionLoadedPayload): void;
|
|
56
56
|
requestToolDecision(request: ToolCallRequest): Promise<ToolRuntimeDispatchDecision> | ToolRuntimeDispatchDecision;
|
|
57
|
-
requestUserQuestion(request: AskUserQuestionRequest): Promise<AskUserAnswers>;
|
|
57
|
+
requestUserQuestion(request: AskUserQuestionRequest, scope?: DelegationScope): Promise<AskUserAnswers>;
|
|
58
58
|
onEvent(listener: AgentEventListener): () => void;
|
|
59
59
|
onSessionLoaded(listener: SessionLoadedListener): () => void;
|
|
60
60
|
setToolDecider(decider: ToolCallDecider | null): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-channel.d.ts","sourceRoot":"","sources":["../../src/controller/agent-channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,sBAAsB,EACtB,OAAO,EACP,mBAAmB,EACnB,2BAA2B,EAC3B,YAAY,EACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,kCAAkC,EAClC,4BAA4B,EAC5B,yBAAyB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C,gFAAgF;IAChF,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,qBAAqB,CAAC,EAAE,CACtB,OAAO,EAAE,4BAA4B,KAClC,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACjD,QAAQ,EAAE,YAAY,CAAC;IACvB,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;AACtE,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAE3E,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,2BAA2B,CAAC,GAAG,2BAA2B,CAAC;AAExE,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"agent-channel.d.ts","sourceRoot":"","sources":["../../src/controller/agent-channel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,cAAc,EACd,sBAAsB,EACtB,OAAO,EACP,mBAAmB,EACnB,2BAA2B,EAC3B,YAAY,EACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,kCAAkC,EAClC,4BAA4B,EAC5B,yBAAyB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,6BAA6B,CAAC;IAC3C,gFAAgF;IAChF,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,qBAAqB,CAAC,EAAE,CACtB,OAAO,EAAE,4BAA4B,KAClC,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACjD,QAAQ,EAAE,YAAY,CAAC;IACvB,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;AACtE,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAE3E,MAAM,MAAM,eAAe,GAAG,CAC5B,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,2BAA2B,CAAC,GAAG,2BAA2B,CAAC;AAExE,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,CAAC,EAAE,eAAe,KACpB,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAO9C;;;;;;;;;GASG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,eAAe,CAAoC;IAI3D,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI;IAI9D,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAIxD,mBAAmB,CACjB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,2BAA2B,CAAC,GAAG,2BAA2B;IAOrE,mBAAmB,CACjB,OAAO,EAAE,sBAAsB,EAC/B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,cAAc,CAAC;IAS1B,OAAO,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAOjD,eAAe,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAO5D,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI;IAIrD,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAAG,IAAI;CAG9D"}
|
package/dist/controller/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
createMessageStateFromMessages,
|
|
7
7
|
createSessionViewState,
|
|
8
8
|
isToolAutoApprovedForMode,
|
|
9
|
-
reduceSessionEvent
|
|
10
|
-
|
|
9
|
+
reduceSessionEvent,
|
|
10
|
+
scopedToolKey
|
|
11
|
+
} from "../chunk-2OTEZX2E.js";
|
|
11
12
|
|
|
12
13
|
// src/controller/agent-channel.ts
|
|
13
14
|
import { EventEmitter } from "events";
|
|
@@ -32,11 +33,11 @@ var AgentChannel = class {
|
|
|
32
33
|
}
|
|
33
34
|
return this.decider(request);
|
|
34
35
|
}
|
|
35
|
-
requestUserQuestion(request) {
|
|
36
|
+
requestUserQuestion(request, scope) {
|
|
36
37
|
if (!this.questionHandler) {
|
|
37
38
|
return Promise.reject(new Error("UI is not ready to handle user questions."));
|
|
38
39
|
}
|
|
39
|
-
return Promise.resolve(this.questionHandler(request));
|
|
40
|
+
return Promise.resolve(this.questionHandler(request, scope));
|
|
40
41
|
}
|
|
41
42
|
// ---------- UI-side (subscribers) ----------
|
|
42
43
|
onEvent(listener) {
|
|
@@ -86,6 +87,10 @@ function createSessionController(deps) {
|
|
|
86
87
|
let seq = 0;
|
|
87
88
|
let approvalMode = deps.approvalMode ?? "readOnly";
|
|
88
89
|
let abortController = null;
|
|
90
|
+
let activeTurnPromise = null;
|
|
91
|
+
let closePromise = null;
|
|
92
|
+
let closing = false;
|
|
93
|
+
let detached = false;
|
|
89
94
|
let activeTurnId;
|
|
90
95
|
let activePolicyDenial;
|
|
91
96
|
let hostBusyCount = 0;
|
|
@@ -246,7 +251,7 @@ function createSessionController(deps) {
|
|
|
246
251
|
const descriptor = {
|
|
247
252
|
agentId,
|
|
248
253
|
permissionClass,
|
|
249
|
-
summary: findToolSummary(toolCall.id),
|
|
254
|
+
summary: findToolSummary(toolCall.id, parentToolCallId),
|
|
250
255
|
toolCallId: toolCall.id,
|
|
251
256
|
toolName: toolCall.name,
|
|
252
257
|
...dirAccess !== void 0 ? { dirAccess } : {},
|
|
@@ -257,8 +262,13 @@ function createSessionController(deps) {
|
|
|
257
262
|
...request.workflowBashPolicy !== void 0 ? { workflowBashPolicy: request.workflowBashPolicy } : {}
|
|
258
263
|
};
|
|
259
264
|
return new Promise((resolve) => {
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
const toolKey = scopedToolKey(parentToolCallId, toolCall.id);
|
|
266
|
+
approvalResolvers.set(toolKey, {
|
|
267
|
+
resolve,
|
|
268
|
+
toolCallId: toolCall.id,
|
|
269
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {}
|
|
270
|
+
});
|
|
271
|
+
privateApprovals.set(toolKey, {
|
|
262
272
|
approvalKey,
|
|
263
273
|
bashPolicy: hasBashPolicy,
|
|
264
274
|
...!dirAccess && toolCall.name === "Bash" && bashPolicy?.persistence.eligible && request.persistBashPermission !== void 0 ? { persistBashPermission: request.persistBashPermission } : {}
|
|
@@ -266,7 +276,7 @@ function createSessionController(deps) {
|
|
|
266
276
|
emit({ type: "approval-requested", approval: descriptor });
|
|
267
277
|
});
|
|
268
278
|
});
|
|
269
|
-
deps.channel.setQuestionHandler((request) => {
|
|
279
|
+
deps.channel.setQuestionHandler((request, scope) => {
|
|
270
280
|
if (deps.headless) {
|
|
271
281
|
activePolicyDenial = "Questions are unavailable in headless mode.";
|
|
272
282
|
return Promise.reject(new Error(activePolicyDenial));
|
|
@@ -275,31 +285,47 @@ function createSessionController(deps) {
|
|
|
275
285
|
const questionId = `question-${questionCounter}`;
|
|
276
286
|
return new Promise((resolve, reject) => {
|
|
277
287
|
questionResolvers.set(questionId, { resolve, reject });
|
|
278
|
-
|
|
288
|
+
const nickname = nicknameFor(scope?.parentToolCallId);
|
|
289
|
+
emit({
|
|
290
|
+
type: "question-asked",
|
|
291
|
+
question: {
|
|
292
|
+
questionId,
|
|
293
|
+
request,
|
|
294
|
+
...scope?.agentId !== void 0 ? { agentId: scope.agentId } : {},
|
|
295
|
+
...scope?.parentToolCallId !== void 0 ? { parentToolCallId: scope.parentToolCallId } : {},
|
|
296
|
+
...nickname !== void 0 ? { nickname } : {}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
279
299
|
});
|
|
280
300
|
});
|
|
281
|
-
function findToolSummary(toolCallId) {
|
|
282
|
-
const messageId = state.messages.toolMessageIds[toolCallId];
|
|
301
|
+
function findToolSummary(toolCallId, parentToolCallId) {
|
|
302
|
+
const messageId = state.messages.toolMessageIds[scopedToolKey(parentToolCallId, toolCallId)];
|
|
283
303
|
const message = state.messages.messages.find((entry) => entry.id === messageId);
|
|
284
304
|
return message?.role === "tool" ? message.summary : "";
|
|
285
305
|
}
|
|
286
306
|
function nicknameFor(parentToolCallId) {
|
|
287
307
|
if (!parentToolCallId) return void 0;
|
|
288
|
-
const parentMessageId = state.messages.toolMessageIds[parentToolCallId];
|
|
308
|
+
const parentMessageId = state.messages.toolMessageIds[scopedToolKey(void 0, parentToolCallId)];
|
|
289
309
|
const parentMessage = state.messages.messages.find((entry) => entry.id === parentMessageId);
|
|
290
310
|
if (parentMessage?.role !== "tool") return void 0;
|
|
291
311
|
const nickname = parentMessage.input?.nickname;
|
|
292
312
|
return typeof nickname === "string" ? nickname : void 0;
|
|
293
313
|
}
|
|
294
|
-
function findDescriptor(toolCallId) {
|
|
314
|
+
function findDescriptor(toolCallId, parentToolCallId) {
|
|
295
315
|
return [state.approval.active, ...state.approval.pending].find(
|
|
296
|
-
(entry) => entry?.toolCallId === toolCallId
|
|
316
|
+
(entry) => entry?.toolCallId === toolCallId && entry.parentToolCallId === parentToolCallId
|
|
297
317
|
);
|
|
298
318
|
}
|
|
299
319
|
function rejectAllPending(reason) {
|
|
300
|
-
for (const
|
|
301
|
-
resolve({ action: "reject", reason });
|
|
302
|
-
emit({
|
|
320
|
+
for (const pending of approvalResolvers.values()) {
|
|
321
|
+
pending.resolve({ action: "reject", reason });
|
|
322
|
+
emit({
|
|
323
|
+
type: "approval-settled",
|
|
324
|
+
toolCallId: pending.toolCallId,
|
|
325
|
+
...pending.parentToolCallId === void 0 ? {} : { parentToolCallId: pending.parentToolCallId },
|
|
326
|
+
status: "rejected",
|
|
327
|
+
by: "abort"
|
|
328
|
+
});
|
|
303
329
|
}
|
|
304
330
|
approvalResolvers.clear();
|
|
305
331
|
privateApprovals.clear();
|
|
@@ -323,7 +349,7 @@ function createSessionController(deps) {
|
|
|
323
349
|
}
|
|
324
350
|
}
|
|
325
351
|
function startNextQueuedSubmission() {
|
|
326
|
-
if (abortController || hostBusyCount > 0) return;
|
|
352
|
+
if (closing || abortController || hostBusyCount > 0) return;
|
|
327
353
|
const next = queue.shift();
|
|
328
354
|
if (!next) return;
|
|
329
355
|
if (!next.opts.silent) {
|
|
@@ -336,7 +362,7 @@ function createSessionController(deps) {
|
|
|
336
362
|
next.opts.turnId
|
|
337
363
|
);
|
|
338
364
|
}
|
|
339
|
-
void
|
|
365
|
+
void startTrackedTurn(next.input, {
|
|
340
366
|
turnId: next.opts.turnId,
|
|
341
367
|
allowSteering: next.opts.turnId === void 0
|
|
342
368
|
});
|
|
@@ -486,10 +512,28 @@ function createSessionController(deps) {
|
|
|
486
512
|
if (ownedTurn) startNextQueuedSubmission();
|
|
487
513
|
}
|
|
488
514
|
}
|
|
515
|
+
function startTrackedTurn(input, opts) {
|
|
516
|
+
let tracked;
|
|
517
|
+
tracked = runTurn(input, opts).finally(() => {
|
|
518
|
+
if (activeTurnPromise === tracked) activeTurnPromise = null;
|
|
519
|
+
});
|
|
520
|
+
activeTurnPromise = tracked;
|
|
521
|
+
return tracked;
|
|
522
|
+
}
|
|
523
|
+
function detachController() {
|
|
524
|
+
if (detached) return;
|
|
525
|
+
detached = true;
|
|
526
|
+
unsubscribeEvents();
|
|
527
|
+
unsubscribeLoaded();
|
|
528
|
+
deps.channel.setToolDecider(null);
|
|
529
|
+
deps.channel.setQuestionHandler(null);
|
|
530
|
+
listeners.clear();
|
|
531
|
+
}
|
|
489
532
|
return {
|
|
490
533
|
abort() {
|
|
491
534
|
queue.length = 0;
|
|
492
535
|
abortController?.abort(new Error("User aborted the current turn."));
|
|
536
|
+
rejectAllPending("User aborted the current turn.");
|
|
493
537
|
},
|
|
494
538
|
attachHostHooks(hooks) {
|
|
495
539
|
if (hooks.compaction) compaction = hooks.compaction;
|
|
@@ -510,14 +554,21 @@ function createSessionController(deps) {
|
|
|
510
554
|
emit({ type: "info", message });
|
|
511
555
|
},
|
|
512
556
|
close() {
|
|
557
|
+
if (closePromise) return closePromise;
|
|
558
|
+
closing = true;
|
|
559
|
+
const shouldEmitTurnStatus = queue.length > 0 || abortController !== null || hostBusyCount > 0;
|
|
513
560
|
queue.length = 0;
|
|
514
561
|
abortController?.abort(new Error("Session closed."));
|
|
515
562
|
rejectAllPending("Session closed.");
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
563
|
+
if (shouldEmitTurnStatus) emitTurnStatus();
|
|
564
|
+
const active = activeTurnPromise;
|
|
565
|
+
if (!active) {
|
|
566
|
+
detachController();
|
|
567
|
+
closePromise = Promise.resolve();
|
|
568
|
+
return closePromise;
|
|
569
|
+
}
|
|
570
|
+
closePromise = active.then(detachController, detachController);
|
|
571
|
+
return closePromise;
|
|
521
572
|
},
|
|
522
573
|
dequeue(queuedId) {
|
|
523
574
|
const index = queue.findIndex((q) => q.id === queuedId);
|
|
@@ -526,17 +577,18 @@ function createSessionController(deps) {
|
|
|
526
577
|
emitTurnStatus();
|
|
527
578
|
return true;
|
|
528
579
|
},
|
|
529
|
-
decideApproval(toolCallId, decision, comment, grantDir) {
|
|
530
|
-
|
|
580
|
+
decideApproval(toolCallId, decision, comment, grantDir, parentToolCallId) {
|
|
581
|
+
const toolKey = scopedToolKey(parentToolCallId, toolCallId);
|
|
582
|
+
if (persistenceInFlight.has(toolKey)) {
|
|
531
583
|
return false;
|
|
532
584
|
}
|
|
533
|
-
const
|
|
534
|
-
if (!
|
|
535
|
-
approvalResolvers.delete(
|
|
536
|
-
persistenceInFlight.delete(
|
|
537
|
-
const descriptor = findDescriptor(toolCallId);
|
|
538
|
-
const privateApproval = privateApprovals.get(
|
|
539
|
-
privateApprovals.delete(
|
|
585
|
+
const pending = approvalResolvers.get(toolKey);
|
|
586
|
+
if (!pending) return false;
|
|
587
|
+
approvalResolvers.delete(toolKey);
|
|
588
|
+
persistenceInFlight.delete(toolKey);
|
|
589
|
+
const descriptor = findDescriptor(toolCallId, parentToolCallId);
|
|
590
|
+
const privateApproval = privateApprovals.get(toolKey);
|
|
591
|
+
privateApprovals.delete(toolKey);
|
|
540
592
|
if (decision === "a" && descriptor && !descriptor.dirAccess) {
|
|
541
593
|
alwaysApproved.add(
|
|
542
594
|
privateApproval?.approvalKey ?? `${descriptor.agentId}:${descriptor.toolName}`
|
|
@@ -545,9 +597,12 @@ function createSessionController(deps) {
|
|
|
545
597
|
const rejected = decision === "n";
|
|
546
598
|
if (rejected) {
|
|
547
599
|
const baseReason = `User rejected tool execution (${descriptor?.toolName ?? "unknown tool"})`;
|
|
548
|
-
resolve({
|
|
600
|
+
pending.resolve({
|
|
601
|
+
action: "reject",
|
|
602
|
+
reason: comment ? `${baseReason}: ${comment}` : baseReason
|
|
603
|
+
});
|
|
549
604
|
} else if (descriptor?.dirAccess) {
|
|
550
|
-
resolve({
|
|
605
|
+
pending.resolve({
|
|
551
606
|
action: "run",
|
|
552
607
|
grantDir: {
|
|
553
608
|
dir: resolveGrantDir(
|
|
@@ -563,30 +618,32 @@ function createSessionController(deps) {
|
|
|
563
618
|
} : {}
|
|
564
619
|
});
|
|
565
620
|
} else if (descriptor?.toolName === "Bash" && privateApproval?.bashPolicy) {
|
|
566
|
-
resolve({
|
|
621
|
+
pending.resolve({
|
|
567
622
|
action: "run",
|
|
568
623
|
approvalDecision: decision,
|
|
569
624
|
approvalSource: "user"
|
|
570
625
|
});
|
|
571
626
|
} else {
|
|
572
|
-
resolve({ action: "run" });
|
|
627
|
+
pending.resolve({ action: "run" });
|
|
573
628
|
}
|
|
574
629
|
emit({
|
|
575
630
|
type: "approval-settled",
|
|
576
631
|
toolCallId,
|
|
632
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {},
|
|
577
633
|
status: rejected ? "rejected" : "approved",
|
|
578
634
|
by: "client"
|
|
579
635
|
});
|
|
580
636
|
return true;
|
|
581
637
|
},
|
|
582
|
-
async persistBashPermission(toolCallId, request) {
|
|
638
|
+
async persistBashPermission(toolCallId, request, parentToolCallId) {
|
|
639
|
+
const toolKey = scopedToolKey(parentToolCallId, toolCallId);
|
|
583
640
|
const descriptor = state.approval.active;
|
|
584
|
-
const privateApproval = privateApprovals.get(
|
|
641
|
+
const privateApproval = privateApprovals.get(toolKey);
|
|
585
642
|
const handler = privateApproval?.persistBashPermission;
|
|
586
|
-
if (descriptor?.toolCallId !== toolCallId || descriptor.toolName !== "Bash" || descriptor.dirAccess !== void 0 || descriptor.bashPolicy?.persistence.eligible !== true || handler === void 0 || !approvalResolvers.has(
|
|
643
|
+
if (descriptor?.toolCallId !== toolCallId || descriptor.parentToolCallId !== parentToolCallId || descriptor.toolName !== "Bash" || descriptor.dirAccess !== void 0 || descriptor.bashPolicy?.persistence.eligible !== true || handler === void 0 || !approvalResolvers.has(toolKey)) {
|
|
587
644
|
return void 0;
|
|
588
645
|
}
|
|
589
|
-
if (persistenceInFlight.has(
|
|
646
|
+
if (persistenceInFlight.has(toolKey)) {
|
|
590
647
|
return {
|
|
591
648
|
status: "validation-error",
|
|
592
649
|
message: "A persistent Bash permission save is already in progress.",
|
|
@@ -600,7 +657,7 @@ function createSessionController(deps) {
|
|
|
600
657
|
fieldErrors: { server: "Wait for the current operation to finish." }
|
|
601
658
|
};
|
|
602
659
|
}
|
|
603
|
-
persistenceInFlight.add(
|
|
660
|
+
persistenceInFlight.add(toolKey);
|
|
604
661
|
hostBusyCount += 1;
|
|
605
662
|
emitTurnStatus();
|
|
606
663
|
try {
|
|
@@ -608,13 +665,13 @@ function createSessionController(deps) {
|
|
|
608
665
|
if (outcome.status === "validation-error") {
|
|
609
666
|
return outcome;
|
|
610
667
|
}
|
|
611
|
-
const
|
|
612
|
-
if (
|
|
668
|
+
const pending = approvalResolvers.get(toolKey);
|
|
669
|
+
if (pending === void 0 || state.approval.active?.toolCallId !== toolCallId || state.approval.active.parentToolCallId !== parentToolCallId || privateApprovals.get(toolKey) !== privateApproval) {
|
|
613
670
|
return void 0;
|
|
614
671
|
}
|
|
615
|
-
approvalResolvers.delete(
|
|
616
|
-
privateApprovals.delete(
|
|
617
|
-
resolve({
|
|
672
|
+
approvalResolvers.delete(toolKey);
|
|
673
|
+
privateApprovals.delete(toolKey);
|
|
674
|
+
pending.resolve({
|
|
618
675
|
action: "run",
|
|
619
676
|
executionAuthorization: outcome.executionAuthorization,
|
|
620
677
|
approvalSource: "user"
|
|
@@ -622,12 +679,13 @@ function createSessionController(deps) {
|
|
|
622
679
|
emit({
|
|
623
680
|
type: "approval-settled",
|
|
624
681
|
toolCallId,
|
|
682
|
+
...parentToolCallId !== void 0 ? { parentToolCallId } : {},
|
|
625
683
|
status: "approved",
|
|
626
684
|
by: "client"
|
|
627
685
|
});
|
|
628
686
|
return { status: "approved" };
|
|
629
687
|
} finally {
|
|
630
|
-
persistenceInFlight.delete(
|
|
688
|
+
persistenceInFlight.delete(toolKey);
|
|
631
689
|
hostBusyCount = Math.max(0, hostBusyCount - 1);
|
|
632
690
|
emitTurnStatus();
|
|
633
691
|
}
|
|
@@ -695,6 +753,7 @@ function createSessionController(deps) {
|
|
|
695
753
|
startNextQueuedSubmission();
|
|
696
754
|
},
|
|
697
755
|
async submit(input, opts) {
|
|
756
|
+
if (closing) throw new Error("Session is closing.");
|
|
698
757
|
if (abortController || hostBusyCount > 0) {
|
|
699
758
|
queue.push({
|
|
700
759
|
id: `q-${++queueCounter}`,
|
|
@@ -715,7 +774,7 @@ function createSessionController(deps) {
|
|
|
715
774
|
opts.turnId
|
|
716
775
|
);
|
|
717
776
|
}
|
|
718
|
-
await
|
|
777
|
+
await startTrackedTurn(input, {
|
|
719
778
|
turnId: opts.turnId,
|
|
720
779
|
allowSteering: opts.turnId === void 0
|
|
721
780
|
});
|
|
@@ -84,28 +84,21 @@ export interface SessionController {
|
|
|
84
84
|
compaction?: CompactionHooks;
|
|
85
85
|
guardPrompt?: (input: AgentInput) => Promise<GuardResult>;
|
|
86
86
|
}): void;
|
|
87
|
-
/**
|
|
88
|
-
* Settle a pending question by id.
|
|
89
|
-
*
|
|
90
|
-
* Single-active-question is the current protocol invariant: when parallel
|
|
91
|
-
* sub-agents ask concurrently, only the latest question is surfaced as
|
|
92
|
-
* `question.active` in state. All resolvers survive in the internal map, so
|
|
93
|
-
* answering any questionId settles the matching ask correctly.
|
|
94
|
-
*/
|
|
87
|
+
/** Settle a pending question by id. */
|
|
95
88
|
answerQuestion(questionId: string, answers: AskUserAnswers): boolean;
|
|
96
89
|
/** Emit an error line into the replicated transcript. */
|
|
97
90
|
appendError(message: string): void;
|
|
98
91
|
/** Emit an info line into the replicated transcript (slash output, system notices). */
|
|
99
92
|
appendInfo(message: string): void;
|
|
100
|
-
close(): void
|
|
93
|
+
close(): Promise<void>;
|
|
101
94
|
/**
|
|
102
95
|
* Cancel a queued (not-yet-running) submission by its `turn-status.queued` id.
|
|
103
96
|
* Returns true if an item was removed, false if the id was unknown (already
|
|
104
97
|
* drained or never existed) — a race-safe no-op the host still acks ok.
|
|
105
98
|
*/
|
|
106
99
|
dequeue(queuedId: string): boolean;
|
|
107
|
-
decideApproval(toolCallId: string, decision: "a" | "n" | "y", comment?: string, grantDir?: string): boolean;
|
|
108
|
-
persistBashPermission(toolCallId: string, request: PersistBashPermissionRequest): Promise<PersistBashPermissionResult | undefined>;
|
|
100
|
+
decideApproval(toolCallId: string, decision: "a" | "n" | "y", comment?: string, grantDir?: string, parentToolCallId?: string): boolean;
|
|
101
|
+
persistBashPermission(toolCallId: string, request: PersistBashPermissionRequest, parentToolCallId?: string): Promise<PersistBashPermissionResult | undefined>;
|
|
109
102
|
/** Echo a user-style message without starting a turn (slash commands echo their invocation). */
|
|
110
103
|
echoUserMessage(text: string, attachmentLabels?: string[]): void;
|
|
111
104
|
/**
|