@ganglion/xacpx 0.24.4-beta.0 → 0.24.5
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/bots/bot-types.d.ts +35 -0
- package/dist/bridge/bridge-main.js +98 -15
- package/dist/bridge/engine/bridge-engine.d.ts +1 -0
- package/dist/bridge/engine/runtime/runtime-permission-policy.d.ts +3 -1
- package/dist/bridge/engine/runtime/runtime-worker-main.js +131 -23
- package/dist/channels/types.d.ts +38 -0
- package/dist/cli.js +671 -76
- package/dist/commands/router-types.d.ts +7 -1
- package/dist/control/session-turn-runner.d.ts +3 -0
- package/dist/control/turn-queue.d.ts +7 -0
- package/dist/control/turn-support.d.ts +4 -1
- package/dist/conversations/conversation-types.d.ts +51 -0
- package/dist/permissions/permission-interaction-broker.d.ts +57 -0
- package/dist/permissions/permission-summary.d.ts +17 -0
- package/dist/permissions/permission-types.d.ts +39 -0
- package/dist/plugin-api.d.ts +1 -1
- package/dist/sessions/session-service.d.ts +9 -4
- package/dist/sessions/transport-engine.d.ts +2 -2
- package/dist/state/types.d.ts +11 -0
- package/dist/transport/types.d.ts +7 -0
- package/dist/util/async.d.ts +6 -0
- package/dist/weixin/agent/interface.d.ts +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type BotRuntimeScope = "bot-direct" | "group-member" | "group-controller";
|
|
2
|
+
export interface BotProfile {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
avatar?: string;
|
|
6
|
+
role?: string;
|
|
7
|
+
instructions?: string;
|
|
8
|
+
agent: string;
|
|
9
|
+
workspace: string;
|
|
10
|
+
cwd?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
effort?: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
interface BotRuntimeBindingBase {
|
|
18
|
+
id: string;
|
|
19
|
+
conversationId: string;
|
|
20
|
+
topicId: string;
|
|
21
|
+
logicalSessionId: string;
|
|
22
|
+
sessionAlias: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export type BotRuntimeBinding = (BotRuntimeBindingBase & {
|
|
27
|
+
scope: "bot-direct";
|
|
28
|
+
botId: string;
|
|
29
|
+
}) | (BotRuntimeBindingBase & {
|
|
30
|
+
scope: "group-member";
|
|
31
|
+
botId: string;
|
|
32
|
+
}) | (BotRuntimeBindingBase & {
|
|
33
|
+
scope: "group-controller";
|
|
34
|
+
});
|
|
35
|
+
export {};
|
|
@@ -9911,6 +9911,45 @@ var init_session_effort = __esm(() => {
|
|
|
9911
9911
|
]);
|
|
9912
9912
|
});
|
|
9913
9913
|
|
|
9914
|
+
// src/util/async.ts
|
|
9915
|
+
function settleWithinTimeout(work, timeoutMs) {
|
|
9916
|
+
return new Promise((resolve3) => {
|
|
9917
|
+
let settled = false;
|
|
9918
|
+
const finish = () => {
|
|
9919
|
+
if (!settled) {
|
|
9920
|
+
settled = true;
|
|
9921
|
+
resolve3();
|
|
9922
|
+
}
|
|
9923
|
+
};
|
|
9924
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
9925
|
+
if (typeof timer.unref === "function") {
|
|
9926
|
+
timer.unref();
|
|
9927
|
+
}
|
|
9928
|
+
work.then(() => {
|
|
9929
|
+
clearTimeout(timer);
|
|
9930
|
+
finish();
|
|
9931
|
+
}, () => {
|
|
9932
|
+
clearTimeout(timer);
|
|
9933
|
+
finish();
|
|
9934
|
+
});
|
|
9935
|
+
});
|
|
9936
|
+
}
|
|
9937
|
+
function raceWithTimeout(work, timeoutMs, onTimeout) {
|
|
9938
|
+
return new Promise((resolve3, reject) => {
|
|
9939
|
+
const timer = setTimeout(() => reject(onTimeout()), timeoutMs);
|
|
9940
|
+
if (typeof timer.unref === "function") {
|
|
9941
|
+
timer.unref();
|
|
9942
|
+
}
|
|
9943
|
+
work.then((value) => {
|
|
9944
|
+
clearTimeout(timer);
|
|
9945
|
+
resolve3(value);
|
|
9946
|
+
}, (error) => {
|
|
9947
|
+
clearTimeout(timer);
|
|
9948
|
+
reject(error);
|
|
9949
|
+
});
|
|
9950
|
+
});
|
|
9951
|
+
}
|
|
9952
|
+
|
|
9914
9953
|
// src/bridge/engine/runtime/physical-session-identity.ts
|
|
9915
9954
|
import { createHash as createHash6 } from "node:crypto";
|
|
9916
9955
|
import { resolve as resolvePath } from "node:path";
|
|
@@ -10266,7 +10305,7 @@ function isEligibleForRuntime(policy, nonInteractivePermissions, interactiveAvai
|
|
|
10266
10305
|
}
|
|
10267
10306
|
return true;
|
|
10268
10307
|
}
|
|
10269
|
-
function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, transport) {
|
|
10308
|
+
function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, transport, options) {
|
|
10270
10309
|
if (!hasPersistedRuntimeBindings)
|
|
10271
10310
|
return;
|
|
10272
10311
|
let parsedPolicy;
|
|
@@ -10274,7 +10313,7 @@ function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, t
|
|
|
10274
10313
|
parsedPolicy = typeof transport.permissionPolicy === "object" && transport.permissionPolicy !== null ? transport.permissionPolicy : parseXacpxPermissionPolicy(transport.permissionPolicy);
|
|
10275
10314
|
}
|
|
10276
10315
|
const nonInteractive = typeof transport.nonInteractivePermissions === "string" ? transport.nonInteractivePermissions : undefined;
|
|
10277
|
-
if (!isEligibleForRuntime(parsedPolicy, nonInteractive, false)) {
|
|
10316
|
+
if (!isEligibleForRuntime(parsedPolicy, nonInteractive, options?.interactionAvailable ?? false)) {
|
|
10278
10317
|
throw new Error('cannot apply permission policy: runtime-ineligible policy (nonInteractive="fail" or escalate without interactive) with persisted runtime bindings');
|
|
10279
10318
|
}
|
|
10280
10319
|
}
|
|
@@ -10286,6 +10325,7 @@ var init_runtime_permission_policy = __esm(() => {
|
|
|
10286
10325
|
// src/bridge/engine/runtime/runtime-permission-resolver.ts
|
|
10287
10326
|
var exports_runtime_permission_resolver = {};
|
|
10288
10327
|
__export(exports_runtime_permission_resolver, {
|
|
10328
|
+
readToolInputFromReq: () => readToolInputFromReq,
|
|
10289
10329
|
configFromRaw: () => configFromRaw,
|
|
10290
10330
|
RuntimePermissionResolver: () => RuntimePermissionResolver
|
|
10291
10331
|
});
|
|
@@ -10344,6 +10384,49 @@ function readRawKindFromReq(req) {
|
|
|
10344
10384
|
}
|
|
10345
10385
|
return;
|
|
10346
10386
|
}
|
|
10387
|
+
function readToolInputFromReq(req) {
|
|
10388
|
+
const raw = getRawObject(req);
|
|
10389
|
+
if (!raw)
|
|
10390
|
+
return;
|
|
10391
|
+
const toolCall = raw.toolCall ?? raw.tool;
|
|
10392
|
+
if (toolCall && typeof toolCall === "object" && !Array.isArray(toolCall)) {
|
|
10393
|
+
const rec = toolCall;
|
|
10394
|
+
if (rec.rawInput !== undefined)
|
|
10395
|
+
return rec.rawInput;
|
|
10396
|
+
if (rec.input !== undefined)
|
|
10397
|
+
return rec.input;
|
|
10398
|
+
const contentText = readTextFromToolContent(rec.content);
|
|
10399
|
+
if (contentText !== undefined)
|
|
10400
|
+
return contentText;
|
|
10401
|
+
}
|
|
10402
|
+
if (raw.rawInput !== undefined)
|
|
10403
|
+
return raw.rawInput;
|
|
10404
|
+
if (raw.input !== undefined)
|
|
10405
|
+
return raw.input;
|
|
10406
|
+
if (raw.subject !== undefined && raw.subject !== null)
|
|
10407
|
+
return raw.subject;
|
|
10408
|
+
if (typeof raw.description === "string" && raw.description.trim().length > 0) {
|
|
10409
|
+
return raw.description;
|
|
10410
|
+
}
|
|
10411
|
+
return;
|
|
10412
|
+
}
|
|
10413
|
+
function readTextFromToolContent(content) {
|
|
10414
|
+
if (!Array.isArray(content))
|
|
10415
|
+
return;
|
|
10416
|
+
const parts = [];
|
|
10417
|
+
for (const block of content.slice(0, 8)) {
|
|
10418
|
+
if (block && typeof block === "object" && !Array.isArray(block)) {
|
|
10419
|
+
const rec = block;
|
|
10420
|
+
if (rec.type === "text" && typeof rec.text === "string" && rec.text.trim().length > 0) {
|
|
10421
|
+
parts.push(rec.text);
|
|
10422
|
+
}
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
if (parts.length === 0)
|
|
10426
|
+
return;
|
|
10427
|
+
return parts.join(`
|
|
10428
|
+
`);
|
|
10429
|
+
}
|
|
10347
10430
|
function inferToolKindForReq(req) {
|
|
10348
10431
|
if (req.inferredKind && typeof req.inferredKind === "string" && req.inferredKind.trim().length > 0) {
|
|
10349
10432
|
return normalizeMatcher(req.inferredKind);
|
|
@@ -12284,10 +12367,8 @@ class RuntimeWorkerClient {
|
|
|
12284
12367
|
if (!handler) {
|
|
12285
12368
|
decision = { outcome: "reject_once" };
|
|
12286
12369
|
} else {
|
|
12287
|
-
const
|
|
12288
|
-
|
|
12289
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("permission UI timeout")), 8000).unref?.())
|
|
12290
|
-
]);
|
|
12370
|
+
const timeoutMs = this.deps?.permissionTimeoutMs ?? 125000;
|
|
12371
|
+
const withTimeout = await raceWithTimeout(handler(payload), timeoutMs, () => new Error("permission UI timeout"));
|
|
12291
12372
|
const outcome = withTimeout?.outcome;
|
|
12292
12373
|
if (outcome !== "allow_once" && outcome !== "allow_always" && outcome !== "reject_once" && outcome !== "reject_always" && outcome !== "cancel") {
|
|
12293
12374
|
decision = { outcome: "reject_once" };
|
|
@@ -14343,6 +14424,7 @@ class RuntimeEngine {
|
|
|
14343
14424
|
const fenceDir = this.options.fenceDir ?? (() => join22(this.durableRoot(), "worker-fences"));
|
|
14344
14425
|
const permissionDeps = {
|
|
14345
14426
|
...options.workerClientDeps ?? {},
|
|
14427
|
+
...options.permissionRequestTimeoutMs !== undefined ? options.workerClientDeps?.permissionTimeoutMs === undefined ? { permissionTimeoutMs: options.permissionRequestTimeoutMs } : {} : {},
|
|
14346
14428
|
spawnEnv: {
|
|
14347
14429
|
...resolveAcpxHostPolicyEnv({
|
|
14348
14430
|
acpxMaxIncomingMessageBytes: options.acpxMaxIncomingMessageBytes,
|
|
@@ -14437,7 +14519,7 @@ class RuntimeEngine {
|
|
|
14437
14519
|
await this.ensureSessionHandle(input, client, agentProcessEnv);
|
|
14438
14520
|
try {
|
|
14439
14521
|
const attachments = await buildRuntimeAttachments(options.media);
|
|
14440
|
-
const outcome = await client.request("prompt", { text, ...attachments.length > 0 ? { attachments } : {} }, {
|
|
14522
|
+
const outcome = await client.request("prompt", { text, ...attachments.length > 0 ? { attachments } : {}, ...options.interactionId ? { interactionId: options.interactionId } : {} }, {
|
|
14441
14523
|
onEvent: (payload) => {
|
|
14442
14524
|
const event = payload;
|
|
14443
14525
|
const sink = options.onEvent;
|
|
@@ -14684,7 +14766,7 @@ class RuntimeEngine {
|
|
|
14684
14766
|
isRuntimeEligible() {
|
|
14685
14767
|
try {
|
|
14686
14768
|
const policy = this.options.permissionPolicy !== undefined ? parseXacpxPermissionPolicy(this.options.permissionPolicy) : undefined;
|
|
14687
|
-
const interactiveAvailable = this.options.
|
|
14769
|
+
const interactiveAvailable = this.options.permissionInteractionCapable === true;
|
|
14688
14770
|
return isEligibleForRuntime(policy, this.options.nonInteractivePermissions, interactiveAvailable);
|
|
14689
14771
|
} catch {
|
|
14690
14772
|
return false;
|
|
@@ -14703,10 +14785,8 @@ class RuntimeEngine {
|
|
|
14703
14785
|
return { outcome: "reject_once" };
|
|
14704
14786
|
if (this.options.onPermissionRequest) {
|
|
14705
14787
|
try {
|
|
14706
|
-
const
|
|
14707
|
-
|
|
14708
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("permission UI timeout")), 8000).unref?.())
|
|
14709
|
-
]);
|
|
14788
|
+
const timeoutMs = this.options.permissionRequestTimeoutMs ?? 125000;
|
|
14789
|
+
const res = await raceWithTimeout(this.options.onPermissionRequest(payload), timeoutMs, () => new Error("permission UI timeout"));
|
|
14710
14790
|
if (this.deleting.has(key) || this.shuttingDown)
|
|
14711
14791
|
return { outcome: "reject_once" };
|
|
14712
14792
|
if (payload.policyGeneration !== this.permissionGeneration)
|
|
@@ -15287,7 +15367,7 @@ class RuntimeEngine {
|
|
|
15287
15367
|
throw new RuntimeError("RUNTIME_INIT_FAILED", `session "${key}" is being deleted`);
|
|
15288
15368
|
}
|
|
15289
15369
|
try {
|
|
15290
|
-
const result = await this.executeRuntimeTurn(input, input.text, { onEvent, media: input.media, toolEventMode: input.toolEventMode, toolEvents: input.toolEvents });
|
|
15370
|
+
const result = await this.executeRuntimeTurn(input, input.text, { onEvent, media: input.media, toolEventMode: input.toolEventMode, toolEvents: input.toolEvents, ...input.interactionId ? { interactionId: input.interactionId } : {} });
|
|
15291
15371
|
return result;
|
|
15292
15372
|
} finally {
|
|
15293
15373
|
try {
|
|
@@ -16726,6 +16806,7 @@ class BridgeServer {
|
|
|
16726
16806
|
case "prompt":
|
|
16727
16807
|
const media = asOptionalPromptMediaInput(params.media);
|
|
16728
16808
|
const resolvedToolEventMode = asOptionalToolEventMode(params.toolEventMode);
|
|
16809
|
+
const promptInteractionId = asOptionalString(params.interactionId);
|
|
16729
16810
|
return await this.engines.prompt({
|
|
16730
16811
|
agent: requireString(params, "agent"),
|
|
16731
16812
|
...agentExecutionSettings(params),
|
|
@@ -16742,7 +16823,8 @@ class BridgeServer {
|
|
|
16742
16823
|
replyMode: asOptionalReplyMode(params.replyMode),
|
|
16743
16824
|
toolEvents: params.toolEvents === true,
|
|
16744
16825
|
...resolvedToolEventMode ? { toolEventMode: resolvedToolEventMode } : {},
|
|
16745
|
-
media
|
|
16826
|
+
media,
|
|
16827
|
+
...promptInteractionId ? { interactionId: promptInteractionId } : {}
|
|
16746
16828
|
}, (event) => {
|
|
16747
16829
|
if (event.type === "prompt.segment") {
|
|
16748
16830
|
writeLine?.(encodeBridgePromptSegmentEvent({
|
|
@@ -17249,9 +17331,10 @@ async function runBridgeMain() {
|
|
|
17249
17331
|
durableRootDir: durableRoot,
|
|
17250
17332
|
queueDir,
|
|
17251
17333
|
fenceDir,
|
|
17334
|
+
permissionInteractionCapable: coreEnv("BRIDGE_PERMISSION_INTERACTION_CAPABLE") === "1",
|
|
17252
17335
|
onPermissionRequest: async (payload) => {
|
|
17253
17336
|
try {
|
|
17254
|
-
const result = await server.requestDaemon("resolvePermissionRequest", payload, { timeoutMs:
|
|
17337
|
+
const result = await server.requestDaemon("resolvePermissionRequest", payload, { timeoutMs: 125000 });
|
|
17255
17338
|
const outcome = result?.outcome;
|
|
17256
17339
|
if (outcome === "allow_once" || outcome === "allow_always" || outcome === "reject_once" || outcome === "reject_always" || outcome === "cancel") {
|
|
17257
17340
|
return { outcome };
|
|
@@ -129,6 +129,7 @@ export interface EnginePromptInput extends EngineSessionInput {
|
|
|
129
129
|
toolEvents?: boolean;
|
|
130
130
|
toolEventMode?: ToolEventMode;
|
|
131
131
|
media?: PromptMediaInput;
|
|
132
|
+
interactionId?: string;
|
|
132
133
|
}
|
|
133
134
|
export interface EngineInjectInput extends EngineSessionInput {
|
|
134
135
|
text: string;
|
|
@@ -10,7 +10,7 @@ export declare function isEligibleForRuntime(policy: XacpxPermissionPolicy | und
|
|
|
10
10
|
* Shared fail-closed gate for permission changes while persisted Runtime
|
|
11
11
|
* bindings exist (watcher reload in main.ts, `/config set` + `/pm` handlers).
|
|
12
12
|
* Pure: takes the already-loaded transport snapshot and the bindings flag, so
|
|
13
|
-
* the two call sites cannot drift on `
|
|
13
|
+
* the two call sites cannot drift on `permissionInteractionCapable` or the
|
|
14
14
|
* error contract. Throws with the canonical message when the change must not
|
|
15
15
|
* apply; returns without effect otherwise (including when no Runtime bindings
|
|
16
16
|
* exist — any permission tuple is then appliable).
|
|
@@ -18,4 +18,6 @@ export declare function isEligibleForRuntime(policy: XacpxPermissionPolicy | und
|
|
|
18
18
|
export declare function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings: boolean, transport: {
|
|
19
19
|
permissionPolicy?: unknown;
|
|
20
20
|
nonInteractivePermissions?: unknown;
|
|
21
|
+
}, options?: {
|
|
22
|
+
interactionAvailable?: boolean;
|
|
21
23
|
}): void;
|
|
@@ -46,6 +46,45 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
48
48
|
|
|
49
|
+
// src/util/async.ts
|
|
50
|
+
function settleWithinTimeout(work, timeoutMs) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
let settled = false;
|
|
53
|
+
const finish = () => {
|
|
54
|
+
if (!settled) {
|
|
55
|
+
settled = true;
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
60
|
+
if (typeof timer.unref === "function") {
|
|
61
|
+
timer.unref();
|
|
62
|
+
}
|
|
63
|
+
work.then(() => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
finish();
|
|
66
|
+
}, () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
finish();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function raceWithTimeout(work, timeoutMs, onTimeout) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const timer = setTimeout(() => reject(onTimeout()), timeoutMs);
|
|
75
|
+
if (typeof timer.unref === "function") {
|
|
76
|
+
timer.unref();
|
|
77
|
+
}
|
|
78
|
+
work.then((value) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
resolve(value);
|
|
81
|
+
}, (error) => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
reject(error);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
49
88
|
// src/runtime/core-env.ts
|
|
50
89
|
function coreEnv(suffix, env = process.env) {
|
|
51
90
|
return env[`${PRIMARY_PREFIX}${suffix}`] ?? env[`${LEGACY_PREFIX}${suffix}`];
|
|
@@ -2799,7 +2838,7 @@ function isEligibleForRuntime(policy, nonInteractivePermissions, interactiveAvai
|
|
|
2799
2838
|
}
|
|
2800
2839
|
return true;
|
|
2801
2840
|
}
|
|
2802
|
-
function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, transport) {
|
|
2841
|
+
function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, transport, options) {
|
|
2803
2842
|
if (!hasPersistedRuntimeBindings)
|
|
2804
2843
|
return;
|
|
2805
2844
|
let parsedPolicy;
|
|
@@ -2807,7 +2846,7 @@ function assertEligibleForRuntimePermissionChange(hasPersistedRuntimeBindings, t
|
|
|
2807
2846
|
parsedPolicy = typeof transport.permissionPolicy === "object" && transport.permissionPolicy !== null ? transport.permissionPolicy : parseXacpxPermissionPolicy(transport.permissionPolicy);
|
|
2808
2847
|
}
|
|
2809
2848
|
const nonInteractive = typeof transport.nonInteractivePermissions === "string" ? transport.nonInteractivePermissions : undefined;
|
|
2810
|
-
if (!isEligibleForRuntime(parsedPolicy, nonInteractive, false)) {
|
|
2849
|
+
if (!isEligibleForRuntime(parsedPolicy, nonInteractive, options?.interactionAvailable ?? false)) {
|
|
2811
2850
|
throw new Error('cannot apply permission policy: runtime-ineligible policy (nonInteractive="fail" or escalate without interactive) with persisted runtime bindings');
|
|
2812
2851
|
}
|
|
2813
2852
|
}
|
|
@@ -2819,6 +2858,7 @@ var init_runtime_permission_policy = __esm(() => {
|
|
|
2819
2858
|
// src/bridge/engine/runtime/runtime-permission-resolver.ts
|
|
2820
2859
|
var exports_runtime_permission_resolver = {};
|
|
2821
2860
|
__export(exports_runtime_permission_resolver, {
|
|
2861
|
+
readToolInputFromReq: () => readToolInputFromReq,
|
|
2822
2862
|
configFromRaw: () => configFromRaw,
|
|
2823
2863
|
RuntimePermissionResolver: () => RuntimePermissionResolver
|
|
2824
2864
|
});
|
|
@@ -2877,6 +2917,49 @@ function readRawKindFromReq(req) {
|
|
|
2877
2917
|
}
|
|
2878
2918
|
return;
|
|
2879
2919
|
}
|
|
2920
|
+
function readToolInputFromReq(req) {
|
|
2921
|
+
const raw = getRawObject(req);
|
|
2922
|
+
if (!raw)
|
|
2923
|
+
return;
|
|
2924
|
+
const toolCall = raw.toolCall ?? raw.tool;
|
|
2925
|
+
if (toolCall && typeof toolCall === "object" && !Array.isArray(toolCall)) {
|
|
2926
|
+
const rec = toolCall;
|
|
2927
|
+
if (rec.rawInput !== undefined)
|
|
2928
|
+
return rec.rawInput;
|
|
2929
|
+
if (rec.input !== undefined)
|
|
2930
|
+
return rec.input;
|
|
2931
|
+
const contentText = readTextFromToolContent(rec.content);
|
|
2932
|
+
if (contentText !== undefined)
|
|
2933
|
+
return contentText;
|
|
2934
|
+
}
|
|
2935
|
+
if (raw.rawInput !== undefined)
|
|
2936
|
+
return raw.rawInput;
|
|
2937
|
+
if (raw.input !== undefined)
|
|
2938
|
+
return raw.input;
|
|
2939
|
+
if (raw.subject !== undefined && raw.subject !== null)
|
|
2940
|
+
return raw.subject;
|
|
2941
|
+
if (typeof raw.description === "string" && raw.description.trim().length > 0) {
|
|
2942
|
+
return raw.description;
|
|
2943
|
+
}
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
function readTextFromToolContent(content) {
|
|
2947
|
+
if (!Array.isArray(content))
|
|
2948
|
+
return;
|
|
2949
|
+
const parts = [];
|
|
2950
|
+
for (const block of content.slice(0, 8)) {
|
|
2951
|
+
if (block && typeof block === "object" && !Array.isArray(block)) {
|
|
2952
|
+
const rec = block;
|
|
2953
|
+
if (rec.type === "text" && typeof rec.text === "string" && rec.text.trim().length > 0) {
|
|
2954
|
+
parts.push(rec.text);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (parts.length === 0)
|
|
2959
|
+
return;
|
|
2960
|
+
return parts.join(`
|
|
2961
|
+
`);
|
|
2962
|
+
}
|
|
2880
2963
|
function inferToolKindForReq(req) {
|
|
2881
2964
|
if (req.inferredKind && typeof req.inferredKind === "string" && req.inferredKind.trim().length > 0) {
|
|
2882
2965
|
return normalizeMatcher(req.inferredKind);
|
|
@@ -6251,6 +6334,7 @@ var init_runtime_mcp = __esm(() => {
|
|
|
6251
6334
|
});
|
|
6252
6335
|
|
|
6253
6336
|
// src/bridge/engine/runtime/runtime-worker-main.ts
|
|
6337
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6254
6338
|
import { createInterface } from "node:readline";
|
|
6255
6339
|
|
|
6256
6340
|
// src/bridge/engine/runtime/worker-eof.ts
|
|
@@ -7467,23 +7551,42 @@ async function initializeRuntime(params) {
|
|
|
7467
7551
|
return { outcome: "reject_once" };
|
|
7468
7552
|
if (ctx.signal.aborted)
|
|
7469
7553
|
return { outcome: "reject_once" };
|
|
7470
|
-
const
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
const
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7554
|
+
const activeInteractionId = state.activeInteractionId;
|
|
7555
|
+
if (!activeInteractionId)
|
|
7556
|
+
return { outcome: "reject_once" };
|
|
7557
|
+
const requestId = randomUUID5();
|
|
7558
|
+
const asRecord = (value) => {
|
|
7559
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
7560
|
+
return;
|
|
7561
|
+
return value;
|
|
7562
|
+
};
|
|
7563
|
+
const rawValue = "raw" in req ? asRecord(req.raw) : undefined;
|
|
7564
|
+
const toolValue = rawValue?.toolCall ?? rawValue?.tool;
|
|
7565
|
+
const toolRecord = asRecord(toolValue);
|
|
7566
|
+
const readField = (holder, key) => {
|
|
7567
|
+
if (!holder)
|
|
7568
|
+
return;
|
|
7569
|
+
const value = holder[key];
|
|
7570
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
7571
|
+
};
|
|
7572
|
+
const toolCallId = readField(toolRecord, "toolCallId") ?? readField(toolRecord, "id") ?? requestId;
|
|
7573
|
+
const title = readField(rawValue, "title") ?? readField(toolRecord, "title");
|
|
7574
|
+
const inferredKind = "inferredKind" in req && typeof req.inferredKind === "string" && req.inferredKind ? req.inferredKind : undefined;
|
|
7575
|
+
const kind = inferredKind ?? readField(rawValue, "kind") ?? readField(toolRecord, "kind");
|
|
7576
|
+
const rawInput = readToolInputFromReq(req);
|
|
7577
|
+
const availableOutcomes = (() => {
|
|
7578
|
+
const options = rawValue?.options;
|
|
7579
|
+
if (!Array.isArray(options))
|
|
7580
|
+
return;
|
|
7581
|
+
const kinds = new Set;
|
|
7582
|
+
for (const option of options) {
|
|
7583
|
+
const record = asRecord(option);
|
|
7584
|
+
if (record && typeof record.kind === "string")
|
|
7585
|
+
kinds.add(record.kind);
|
|
7586
|
+
}
|
|
7587
|
+
const valid = ["allow_once", "allow_always", "reject_once", "reject_always", "cancel"].filter((k) => kinds.has(k));
|
|
7588
|
+
return valid.length > 0 ? valid : undefined;
|
|
7485
7589
|
})();
|
|
7486
|
-
const rawInput = req.raw;
|
|
7487
7590
|
const payload = {
|
|
7488
7591
|
logicalSessionId: state.ensureParams?.logicalSessionId ?? params.logicalSessionId ?? state.ensureParams?.sessionKey ?? params.sessionKey,
|
|
7489
7592
|
sessionKey: state.ensureParams?.sessionKey ?? params.sessionKey,
|
|
@@ -7493,7 +7596,9 @@ async function initializeRuntime(params) {
|
|
|
7493
7596
|
...kind ? { kind } : {},
|
|
7494
7597
|
...rawInput !== undefined ? { rawInput } : {},
|
|
7495
7598
|
policyGeneration: state.permissionGeneration,
|
|
7496
|
-
workerGeneration: state.workerGeneration
|
|
7599
|
+
workerGeneration: state.workerGeneration,
|
|
7600
|
+
interactionId: activeInteractionId,
|
|
7601
|
+
...availableOutcomes ? { availableOutcomes } : {}
|
|
7497
7602
|
};
|
|
7498
7603
|
const pending = new Promise((resolve, reject) => {
|
|
7499
7604
|
state.pendingPermissions.set(requestId, { resolve, reject, generation: state.permissionGeneration, workerGeneration: state.workerGeneration });
|
|
@@ -7510,10 +7615,7 @@ async function initializeRuntime(params) {
|
|
|
7510
7615
|
});
|
|
7511
7616
|
process.stdout.write(encodeWorkerMessage({ id: requestId, event: "permission.request", payload }));
|
|
7512
7617
|
try {
|
|
7513
|
-
const decision = await
|
|
7514
|
-
pending,
|
|
7515
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("host permission timeout")), 9000).unref?.())
|
|
7516
|
-
]);
|
|
7618
|
+
const decision = await raceWithTimeout(pending, 125000, () => new Error("host permission timeout"));
|
|
7517
7619
|
const outcome = decision.outcome;
|
|
7518
7620
|
if (outcome !== "allow_once" && outcome !== "allow_always" && outcome !== "reject_once" && outcome !== "reject_always" && outcome !== "cancel") {
|
|
7519
7621
|
return { outcome: "reject_once" };
|
|
@@ -7601,6 +7703,9 @@ async function runPrompt(requestId, params) {
|
|
|
7601
7703
|
onElicitation
|
|
7602
7704
|
});
|
|
7603
7705
|
state.activeTurn = turn;
|
|
7706
|
+
const promptInteractionId = params.interactionId;
|
|
7707
|
+
if (promptInteractionId)
|
|
7708
|
+
state.activeInteractionId = promptInteractionId;
|
|
7604
7709
|
try {
|
|
7605
7710
|
await turn.promptStarted;
|
|
7606
7711
|
let finalText = "";
|
|
@@ -7615,6 +7720,9 @@ async function runPrompt(requestId, params) {
|
|
|
7615
7720
|
} finally {
|
|
7616
7721
|
if (state.activeTurn === turn)
|
|
7617
7722
|
state.activeTurn = undefined;
|
|
7723
|
+
if (promptInteractionId && state.activeInteractionId === promptInteractionId) {
|
|
7724
|
+
state.activeInteractionId = undefined;
|
|
7725
|
+
}
|
|
7618
7726
|
}
|
|
7619
7727
|
}
|
|
7620
7728
|
async function dispatch(request) {
|
package/dist/channels/types.d.ts
CHANGED
|
@@ -10,6 +10,36 @@ import type { ActiveTurnRegistry } from "../sessions/active-turn-registry.js";
|
|
|
10
10
|
import type { Locale } from "../i18n/index.js";
|
|
11
11
|
import type { ControlService } from "../control/control-service.js";
|
|
12
12
|
export type { ChatAgent };
|
|
13
|
+
export type PermissionOutcome = "allow_once" | "allow_always" | "reject_once" | "reject_always" | "cancel";
|
|
14
|
+
export interface ChannelPermissionRequest {
|
|
15
|
+
requestId: string;
|
|
16
|
+
chatKey: string;
|
|
17
|
+
accountId?: string;
|
|
18
|
+
replyContextToken?: string;
|
|
19
|
+
requester: {
|
|
20
|
+
senderId: string;
|
|
21
|
+
senderName?: string;
|
|
22
|
+
isOwner?: boolean;
|
|
23
|
+
};
|
|
24
|
+
toolCallId: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
kind?: string;
|
|
27
|
+
summary?: string;
|
|
28
|
+
availableOutcomes: PermissionOutcome[];
|
|
29
|
+
expiresAt: number;
|
|
30
|
+
signal: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
export interface ChannelPermissionDecision {
|
|
33
|
+
outcome: PermissionOutcome;
|
|
34
|
+
/**
|
|
35
|
+
* Platform user id that activated the approval control. REQUIRED: every
|
|
36
|
+
* `requestPermission()` implementation must return an authenticated
|
|
37
|
+
* responder; the broker re-verifies it against the bound initiator (I3).
|
|
38
|
+
* A platform that cannot prove responder identity must not implement
|
|
39
|
+
* `requestPermission()` at all.
|
|
40
|
+
*/
|
|
41
|
+
responderId: string;
|
|
42
|
+
}
|
|
13
43
|
export interface OutboundQuota {
|
|
14
44
|
onInbound(chatKey: string): void;
|
|
15
45
|
reserveMidSegment(chatKey: string): boolean;
|
|
@@ -151,6 +181,14 @@ export interface MessageChannelRuntime {
|
|
|
151
181
|
notifyTaskProgress(task: OrchestrationTaskRecord, text: string): Promise<void>;
|
|
152
182
|
sendCoordinatorMessage(input: CoordinatorMessageInput): Promise<void>;
|
|
153
183
|
sendScheduledMessage?(input: ScheduledChannelMessageInput): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Interactive permission UI (plan channel-permission-interaction).
|
|
186
|
+
* Optional so already-published plugins stay compatible; absent means
|
|
187
|
+
* interactive permission is unavailable and the broker fails closed.
|
|
188
|
+
* Implementations MUST verify the responder equals request.requester.senderId
|
|
189
|
+
* and MUST settle exactly once (first terminal decision wins).
|
|
190
|
+
*/
|
|
191
|
+
requestPermission?(request: ChannelPermissionRequest): Promise<ChannelPermissionDecision>;
|
|
154
192
|
/**
|
|
155
193
|
* Preferred render format for `/ssn` native session lists. weixin renders
|
|
156
194
|
* markdown tables poorly and declares "cards"; channels that omit this are
|