@cjhyy/code-shell-core 0.9.6 → 0.9.7
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/automation/scheduler.d.ts +3 -0
- package/dist/automation/scheduler.js +21 -0
- package/dist/context/manager.d.ts +8 -2
- package/dist/context/manager.js +20 -4
- package/dist/context/notes.d.ts +39 -0
- package/dist/context/notes.js +314 -0
- package/dist/engine/engine.d.ts +5 -4
- package/dist/engine/engine.js +79 -11
- package/dist/engine/run-tooling.d.ts +3 -0
- package/dist/engine/run-tooling.js +25 -23
- package/dist/engine/run-types.d.ts +4 -0
- package/dist/engine/subagent-spawner.d.ts +3 -0
- package/dist/engine/subagent-spawner.js +48 -17
- package/dist/engine/turn-loop.d.ts +10 -0
- package/dist/engine/turn-loop.js +91 -19
- package/dist/engine/types.d.ts +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/browser.md +4 -2
- package/dist/prompt/sections/context-notes.md +9 -0
- package/dist/protocol/server.d.ts +1 -0
- package/dist/protocol/server.js +207 -19
- package/dist/protocol/types.d.ts +2 -0
- package/dist/session/session-manager.js +8 -7
- package/dist/session/transcript.d.ts +17 -0
- package/dist/session/transcript.js +271 -16
- package/dist/settings/schema.d.ts +9 -0
- package/dist/settings/schema.js +4 -0
- package/dist/themes/paths.js +20 -1
- package/dist/tool-system/browser-bridge.d.ts +3 -1
- package/dist/tool-system/browser-discovery.d.ts +6 -0
- package/dist/tool-system/browser-discovery.js +17 -0
- package/dist/tool-system/builtin/browser-tools.js +12 -8
- package/dist/tool-system/builtin/context-notes.d.ts +12 -0
- package/dist/tool-system/builtin/context-notes.js +188 -0
- package/dist/tool-system/builtin/index.js +47 -0
- package/dist/tool-system/builtin/mcp-tools.d.ts +5 -3
- package/dist/tool-system/builtin/mcp-tools.js +10 -10
- package/dist/tool-system/builtin/tool-search.js +15 -3
- package/dist/tool-system/context.d.ts +9 -0
- package/dist/tool-system/executor.js +5 -3
- package/dist/tool-system/mcp-compat.d.ts +3 -0
- package/dist/tool-system/mcp-compat.js +51 -0
- package/dist/tool-system/mcp-manager.d.ts +27 -26
- package/dist/tool-system/mcp-manager.js +273 -111
- package/dist/tool-system/mcp-workspace.d.ts +18 -0
- package/dist/tool-system/mcp-workspace.js +56 -0
- package/dist/tool-system/permission.d.ts +6 -0
- package/dist/tool-system/permission.js +45 -9
- package/dist/tool-system/plan-mode-allowlist.js +5 -0
- package/dist/tool-system/sandbox/seatbelt.js +71 -2
- package/dist/tool-system/session-tool-host.js +9 -1
- package/dist/types.d.ts +4 -2
- package/package.json +1 -1
package/dist/protocol/server.js
CHANGED
|
@@ -21,8 +21,9 @@ import { sessionMessageOutcome, sessionMessageFailure, sessionMessageResultNotif
|
|
|
21
21
|
import { resolveSessionMessageWorkspace } from "./session-message-workspace.js";
|
|
22
22
|
import { validateWorkspaceContext, workspacePrimaryRoot } from "../workspace/workspace-context.js";
|
|
23
23
|
import { canonicalKey } from "../workspace/canonical-key.js";
|
|
24
|
-
import { getApprovalRouter, getInteractiveApprovalBackend, } from "../tool-system/permission.js";
|
|
24
|
+
import { getApprovalRouter, getInteractiveApprovalBackend, InteractiveApprovalBackend, } from "../tool-system/permission.js";
|
|
25
25
|
import { agentNotificationBus, notificationQueue, notificationEnvelopeToLegacyStreamEvent, } from "../tool-system/builtin/agent-notifications.js";
|
|
26
|
+
import { clearSessionPathApprovals, openSessionPathApprovals } from "../tool-system/path-policy.js";
|
|
26
27
|
import { backgroundShellManager } from "../runtime/background-shell.js";
|
|
27
28
|
import { backgroundJobRegistry } from "../tool-system/builtin/background-jobs.js";
|
|
28
29
|
import { listBackgroundWorkForUI } from "../tool-system/builtin/background-work.js";
|
|
@@ -172,6 +173,7 @@ const COMPACT_STREAM_STRATEGIES = new Set([
|
|
|
172
173
|
// Range archival (archive_range query) emits its boundary through this map;
|
|
173
174
|
// without "range" here it would be silently downgraded to "compacted".
|
|
174
175
|
"range",
|
|
176
|
+
"notes",
|
|
175
177
|
]);
|
|
176
178
|
function toCompactStreamStrategy(strategy) {
|
|
177
179
|
return COMPACT_STREAM_STRATEGIES.has(strategy)
|
|
@@ -322,6 +324,7 @@ export class AgentServer {
|
|
|
322
324
|
panelBridgeEnabled;
|
|
323
325
|
connectionId;
|
|
324
326
|
strictApprovalRouting;
|
|
327
|
+
childHostDisposers = new Map();
|
|
325
328
|
approvalRouter;
|
|
326
329
|
approvalConnectionUnregister = null;
|
|
327
330
|
disconnected = false;
|
|
@@ -674,6 +677,151 @@ export class AgentServer {
|
|
|
674
677
|
session.engine.setAskUser((question, opts) => this.requestAskUserForSession(session, sid, question, opts));
|
|
675
678
|
session.engine.setBrowserBridge(this.makeBrowserBridge(session, sid));
|
|
676
679
|
session.engine.setInjectCredential((credentialId, credentialScope) => this.requestCredentialInjectForSession(session, sid, credentialId, credentialScope));
|
|
680
|
+
session.engine.setChildHostBindings?.((input) => {
|
|
681
|
+
if (input.parentSessionId !== sid)
|
|
682
|
+
throw new Error("child host parent identity mismatch");
|
|
683
|
+
assertSafeSessionId(input.sessionId);
|
|
684
|
+
const route = this.approvalRouter.current(sid);
|
|
685
|
+
if (!route || route.connectionId !== this.connectionId) {
|
|
686
|
+
throw new Error("child host unavailable: parent approval connection no longer owns session");
|
|
687
|
+
}
|
|
688
|
+
const backend = new InteractiveApprovalBackend();
|
|
689
|
+
backend.openSession(input.sessionId);
|
|
690
|
+
let active = false;
|
|
691
|
+
let closed = false;
|
|
692
|
+
let closedFailure = {
|
|
693
|
+
failure: "cancelled",
|
|
694
|
+
reason: "child run ended or was cancelled",
|
|
695
|
+
};
|
|
696
|
+
const scope = {
|
|
697
|
+
sourceSessionId: input.sessionId,
|
|
698
|
+
bindingId: nanoid(),
|
|
699
|
+
route,
|
|
700
|
+
pending: new Set(),
|
|
701
|
+
failure: () => {
|
|
702
|
+
if (closed || input.signal?.aborted)
|
|
703
|
+
return closedFailure;
|
|
704
|
+
if (this.chatManager?.get(sid) !== session)
|
|
705
|
+
return { failure: "session_closed", reason: "parent host session closed" };
|
|
706
|
+
if (this.disconnected || !this.approvalRouter.matches(route))
|
|
707
|
+
return { failure: "owner_lost", reason: "parent host approval ownership changed" };
|
|
708
|
+
return undefined;
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
backend.setRequestGuard((request) => {
|
|
712
|
+
const failure = scope.failure();
|
|
713
|
+
if (failure)
|
|
714
|
+
return { approved: false, ...failure };
|
|
715
|
+
if (request.sessionId !== input.sessionId)
|
|
716
|
+
return {
|
|
717
|
+
approved: false,
|
|
718
|
+
failure: "owner_lost",
|
|
719
|
+
reason: "child approval identity mismatch",
|
|
720
|
+
};
|
|
721
|
+
return undefined;
|
|
722
|
+
});
|
|
723
|
+
backend.setPromptFn((request) => {
|
|
724
|
+
const failure = scope.failure();
|
|
725
|
+
if (failure)
|
|
726
|
+
return Promise.resolve({ approved: false, ...failure });
|
|
727
|
+
if (request.sessionId !== input.sessionId) {
|
|
728
|
+
return Promise.resolve({
|
|
729
|
+
approved: false,
|
|
730
|
+
failure: "owner_lost",
|
|
731
|
+
reason: "child approval identity mismatch",
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return this.requestApprovalFromClient(request, route, scope);
|
|
735
|
+
});
|
|
736
|
+
const dispose = () => {
|
|
737
|
+
if (closed)
|
|
738
|
+
return;
|
|
739
|
+
closed = true;
|
|
740
|
+
input.signal?.removeEventListener("abort", dispose);
|
|
741
|
+
this.childHostDisposers.delete(scope.bindingId);
|
|
742
|
+
backend.clearSession(input.sessionId);
|
|
743
|
+
if (active)
|
|
744
|
+
clearSessionPathApprovals(input.sessionId);
|
|
745
|
+
for (const id of scope.pending) {
|
|
746
|
+
const entry = this.takeSessionApproval(session, id, "cancelled");
|
|
747
|
+
this.clearApprovalTimer(id);
|
|
748
|
+
this.pendingApprovalTargets.delete(id);
|
|
749
|
+
entry?.resolve(entry.metadata.kind === "internal"
|
|
750
|
+
? internalCancellation(closedFailure.failure, closedFailure.reason)
|
|
751
|
+
: {
|
|
752
|
+
approved: false,
|
|
753
|
+
...closedFailure,
|
|
754
|
+
});
|
|
755
|
+
try {
|
|
756
|
+
this.notify(Methods.ApprovalResolved, { sessionId: sid, requestId: id });
|
|
757
|
+
}
|
|
758
|
+
catch {
|
|
759
|
+
/* transport may already be closed */
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
scope.pending.clear();
|
|
763
|
+
// One-way release uses the existing private host channel; it is never
|
|
764
|
+
// a model tool and never inherits a parent tab handoff grant.
|
|
765
|
+
try {
|
|
766
|
+
this.notify(Methods.ApprovalRequest, {
|
|
767
|
+
sessionId: sid,
|
|
768
|
+
requestId: nanoid(12),
|
|
769
|
+
childHost: {
|
|
770
|
+
sourceSessionId: input.sessionId,
|
|
771
|
+
bindingId: scope.bindingId,
|
|
772
|
+
release: true,
|
|
773
|
+
},
|
|
774
|
+
request: {
|
|
775
|
+
toolName: "__browser_action__",
|
|
776
|
+
args: { action: "release" },
|
|
777
|
+
description: "release child browser",
|
|
778
|
+
riskLevel: "low",
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
/* resource owner also closes targets on host teardown */
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
this.childHostDisposers.set(scope.bindingId, {
|
|
787
|
+
route,
|
|
788
|
+
dispose: (reason) => {
|
|
789
|
+
closedFailure = { failure: "owner_lost", reason };
|
|
790
|
+
dispose();
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
input.signal?.addEventListener("abort", dispose, { once: true });
|
|
794
|
+
if (input.signal?.aborted)
|
|
795
|
+
dispose();
|
|
796
|
+
return {
|
|
797
|
+
activate: () => {
|
|
798
|
+
if (!closed && !active) {
|
|
799
|
+
this.notify(Methods.ApprovalRequest, {
|
|
800
|
+
sessionId: sid,
|
|
801
|
+
requestId: nanoid(12),
|
|
802
|
+
childHost: {
|
|
803
|
+
sourceSessionId: input.sessionId,
|
|
804
|
+
bindingId: scope.bindingId,
|
|
805
|
+
activate: true,
|
|
806
|
+
},
|
|
807
|
+
request: {
|
|
808
|
+
toolName: "__browser_action__",
|
|
809
|
+
args: { action: "activate" },
|
|
810
|
+
description: "bind child browser",
|
|
811
|
+
riskLevel: "low",
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
active = true;
|
|
815
|
+
openSessionPathApprovals(input.sessionId);
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
approvalBackend: backend,
|
|
819
|
+
browserBridge: this.makeBrowserBridge(session, sid, scope),
|
|
820
|
+
askUser: (question, opts) => this.requestAskUserForSession(session, sid, question, opts, scope),
|
|
821
|
+
injectCredentialToBrowser: (id, credentialScope) => this.requestCredentialInjectForSession(session, sid, id, credentialScope, scope),
|
|
822
|
+
dispose,
|
|
823
|
+
};
|
|
824
|
+
});
|
|
677
825
|
session.engine.setSessionMessageRouter((input) => this.routeSessionMessage(input));
|
|
678
826
|
if (this.workspaceBridgeEnabled && typeof session.engine.setWorkspaceBridge === "function") {
|
|
679
827
|
session.engine.setWorkspaceBridge(this.makeWorkspaceBridge(session, sid));
|
|
@@ -1591,7 +1739,7 @@ export class AgentServer {
|
|
|
1591
1739
|
}
|
|
1592
1740
|
this.abortController.abort();
|
|
1593
1741
|
for (const [, resolve] of this.pendingApprovals) {
|
|
1594
|
-
resolve({ approved: false, reason: "cancelled" });
|
|
1742
|
+
resolve({ approved: false, failure: "cancelled", reason: "cancelled" });
|
|
1595
1743
|
}
|
|
1596
1744
|
this.pendingApprovals.clear();
|
|
1597
1745
|
this.clearAllApprovalTimers();
|
|
@@ -2795,7 +2943,10 @@ export class AgentServer {
|
|
|
2795
2943
|
/**
|
|
2796
2944
|
* Ask the client to approve a tool operation (legacy single-engine path).
|
|
2797
2945
|
*/
|
|
2798
|
-
requestApprovalFromClient(request, route) {
|
|
2946
|
+
requestApprovalFromClient(request, route, scope) {
|
|
2947
|
+
const failure = scope?.failure();
|
|
2948
|
+
if (failure)
|
|
2949
|
+
return Promise.resolve({ approved: false, ...failure });
|
|
2799
2950
|
return new Promise((resolve) => {
|
|
2800
2951
|
const requestId = nanoid(12);
|
|
2801
2952
|
const effectiveRoute = route ?? {
|
|
@@ -2806,7 +2957,11 @@ export class AgentServer {
|
|
|
2806
2957
|
const sessionId = effectiveRoute.sessionId;
|
|
2807
2958
|
const session = this.chatManager && sessionId ? this.chatManager.get(sessionId) : undefined;
|
|
2808
2959
|
if (this.chatManager && sessionId && !session) {
|
|
2809
|
-
resolve({
|
|
2960
|
+
resolve({
|
|
2961
|
+
approved: false,
|
|
2962
|
+
failure: "session_closed",
|
|
2963
|
+
reason: `session closed: ${sessionId}`,
|
|
2964
|
+
});
|
|
2810
2965
|
return;
|
|
2811
2966
|
}
|
|
2812
2967
|
if (session) {
|
|
@@ -2824,7 +2979,7 @@ export class AgentServer {
|
|
|
2824
2979
|
createdAt: Date.now(),
|
|
2825
2980
|
expiresAt: Date.now() + AgentServer.APPROVAL_TIMEOUT_MS,
|
|
2826
2981
|
surfaceable: !internal,
|
|
2827
|
-
}, (decision) => resolve(decision));
|
|
2982
|
+
}, (decision) => resolve(decision), scope);
|
|
2828
2983
|
}
|
|
2829
2984
|
else {
|
|
2830
2985
|
this.pendingApprovals.set(requestId, resolve);
|
|
@@ -2841,7 +2996,8 @@ export class AgentServer {
|
|
|
2841
2996
|
}
|
|
2842
2997
|
this.pendingApprovalTargets.delete(requestId);
|
|
2843
2998
|
this.approvalTimers.delete(requestId);
|
|
2844
|
-
|
|
2999
|
+
scope?.pending.delete(requestId);
|
|
3000
|
+
resolve({ approved: false, failure: "timed_out", reason: "approval timed out" });
|
|
2845
3001
|
}
|
|
2846
3002
|
}, AgentServer.APPROVAL_TIMEOUT_MS);
|
|
2847
3003
|
this.approvalTimers.set(requestId, timer);
|
|
@@ -2869,7 +3025,10 @@ export class AgentServer {
|
|
|
2869
3025
|
* renderer routes the question to the right chat tab. This intentionally has
|
|
2870
3026
|
* no wall-clock timeout; Stop/cancel drains the pending ask.
|
|
2871
3027
|
*/
|
|
2872
|
-
requestAskUserForSession(session, sessionId, question, opts) {
|
|
3028
|
+
requestAskUserForSession(session, sessionId, question, opts, scope) {
|
|
3029
|
+
const failure = scope?.failure();
|
|
3030
|
+
if (failure)
|
|
3031
|
+
return Promise.resolve(`(question ${failure.failure}: ${failure.reason})`);
|
|
2873
3032
|
return new Promise((resolve) => {
|
|
2874
3033
|
const requestId = nanoid(12);
|
|
2875
3034
|
const routeEnvelope = this.approvalRouteEnvelope(sessionId, requestId);
|
|
@@ -2894,7 +3053,7 @@ export class AgentServer {
|
|
|
2894
3053
|
// chatManager approve handler resolves with the raw decision value.
|
|
2895
3054
|
resolve(typeof decision === "string" ? decision : "");
|
|
2896
3055
|
}
|
|
2897
|
-
});
|
|
3056
|
+
}, scope);
|
|
2898
3057
|
const args = { question };
|
|
2899
3058
|
if (opts?.header !== undefined)
|
|
2900
3059
|
args.header = opts.header;
|
|
@@ -2906,6 +3065,7 @@ export class AgentServer {
|
|
|
2906
3065
|
args.optionsOnly = opts.optionsOnly;
|
|
2907
3066
|
this.notify(Methods.ApprovalRequest, {
|
|
2908
3067
|
...routeEnvelope,
|
|
3068
|
+
...(scope ? { sourceSessionId: scope.sourceSessionId } : {}),
|
|
2909
3069
|
request: {
|
|
2910
3070
|
toolName: "__ask_user__",
|
|
2911
3071
|
args,
|
|
@@ -2953,8 +3113,8 @@ export class AgentServer {
|
|
|
2953
3113
|
* result string we parse back into the typed result. On timeout / malformed
|
|
2954
3114
|
* reply we degrade to a safe error result rather than throwing.
|
|
2955
3115
|
*/
|
|
2956
|
-
makeBrowserBridge(session, sessionId) {
|
|
2957
|
-
const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload);
|
|
3116
|
+
makeBrowserBridge(session, sessionId, scope) {
|
|
3117
|
+
const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload, scope);
|
|
2958
3118
|
return {
|
|
2959
3119
|
requestHumanTakeover: () => call("requestTakeover", {}),
|
|
2960
3120
|
snapshot: () => call("snapshot", {}),
|
|
@@ -2974,7 +3134,10 @@ export class AgentServer {
|
|
|
2974
3134
|
switchTab: (tabId) => call("switchTab", { tabId }),
|
|
2975
3135
|
};
|
|
2976
3136
|
}
|
|
2977
|
-
requestBrowserActionForSession(session, sessionId, action, payload) {
|
|
3137
|
+
requestBrowserActionForSession(session, sessionId, action, payload, scope) {
|
|
3138
|
+
const failure = scope?.failure();
|
|
3139
|
+
if (failure)
|
|
3140
|
+
return Promise.resolve({ ok: false, failure: failure.failure, detail: failure.reason });
|
|
2978
3141
|
return new Promise((resolve) => {
|
|
2979
3142
|
const requestId = nanoid(12);
|
|
2980
3143
|
const routeEnvelope = this.approvalRouteEnvelope(sessionId, requestId);
|
|
@@ -2986,12 +3149,13 @@ export class AgentServer {
|
|
|
2986
3149
|
resolve(parsed.ok
|
|
2987
3150
|
? parsed.value
|
|
2988
3151
|
: { ok: false, failure: parsed.failure, detail: parsed.detail });
|
|
2989
|
-
});
|
|
3152
|
+
}, scope);
|
|
2990
3153
|
const timer = setTimeout(() => {
|
|
2991
3154
|
if (session.pendingApprovals.has(requestId)) {
|
|
2992
3155
|
this.takeSessionApproval(session, requestId, "expired");
|
|
2993
3156
|
this.pendingApprovalTargets.delete(requestId);
|
|
2994
3157
|
this.approvalTimers.delete(requestId);
|
|
3158
|
+
scope?.pending.delete(requestId);
|
|
2995
3159
|
resolve({
|
|
2996
3160
|
ok: false,
|
|
2997
3161
|
failure: "timed_out",
|
|
@@ -3002,6 +3166,9 @@ export class AgentServer {
|
|
|
3002
3166
|
this.approvalTimers.set(requestId, timer);
|
|
3003
3167
|
this.notify(Methods.ApprovalRequest, {
|
|
3004
3168
|
...routeEnvelope,
|
|
3169
|
+
...(scope
|
|
3170
|
+
? { childHost: { sourceSessionId: scope.sourceSessionId, bindingId: scope.bindingId } }
|
|
3171
|
+
: {}),
|
|
3005
3172
|
request: {
|
|
3006
3173
|
toolName: "__browser_action__",
|
|
3007
3174
|
args: { action, ...payload },
|
|
@@ -3017,7 +3184,10 @@ export class AgentServer {
|
|
|
3017
3184
|
* calls restoreCookiesToBrowser and replies with a JSON result string we
|
|
3018
3185
|
* parse into { ok, count?, error? }. Degrades to ok:false on timeout/malformed.
|
|
3019
3186
|
*/
|
|
3020
|
-
requestCredentialInjectForSession(session, sessionId, credentialId, credentialScope = "full") {
|
|
3187
|
+
requestCredentialInjectForSession(session, sessionId, credentialId, credentialScope = "full", scope) {
|
|
3188
|
+
const failure = scope?.failure();
|
|
3189
|
+
if (failure)
|
|
3190
|
+
return Promise.resolve({ ok: false, error: `${failure.failure}: ${failure.reason}` });
|
|
3021
3191
|
return new Promise((resolve) => {
|
|
3022
3192
|
const requestId = nanoid(12);
|
|
3023
3193
|
const routeEnvelope = this.approvalRouteEnvelope(sessionId, requestId);
|
|
@@ -3027,12 +3197,13 @@ export class AgentServer {
|
|
|
3027
3197
|
resolve(parsed.ok
|
|
3028
3198
|
? parsed.value
|
|
3029
3199
|
: { ok: false, error: parsed.detail });
|
|
3030
|
-
});
|
|
3200
|
+
}, scope);
|
|
3031
3201
|
const timer = setTimeout(() => {
|
|
3032
3202
|
if (session.pendingApprovals.has(requestId)) {
|
|
3033
3203
|
this.takeSessionApproval(session, requestId, "expired");
|
|
3034
3204
|
this.pendingApprovalTargets.delete(requestId);
|
|
3035
3205
|
this.approvalTimers.delete(requestId);
|
|
3206
|
+
scope?.pending.delete(requestId);
|
|
3036
3207
|
resolve({
|
|
3037
3208
|
ok: false,
|
|
3038
3209
|
error: `credential inject ${HOST_LOOPBACK_FAILURE_DETAIL.timed_out}`,
|
|
@@ -3042,6 +3213,7 @@ export class AgentServer {
|
|
|
3042
3213
|
this.approvalTimers.set(requestId, timer);
|
|
3043
3214
|
this.notify(Methods.ApprovalRequest, {
|
|
3044
3215
|
...routeEnvelope,
|
|
3216
|
+
...(scope ? { sourceSessionId: scope.sourceSessionId } : {}),
|
|
3045
3217
|
request: {
|
|
3046
3218
|
toolName: "__credential_action__",
|
|
3047
3219
|
args: { action: "injectCookie", credentialId, credentialScope },
|
|
@@ -3331,7 +3503,7 @@ export class AgentServer {
|
|
|
3331
3503
|
}
|
|
3332
3504
|
}
|
|
3333
3505
|
for (const [, resolve] of this.pendingApprovals) {
|
|
3334
|
-
resolve({ approved: false, reason: "server closing" });
|
|
3506
|
+
resolve({ approved: false, failure: "session_closed", reason: "server closing" });
|
|
3335
3507
|
}
|
|
3336
3508
|
this.pendingApprovals.clear();
|
|
3337
3509
|
this.clearAllApprovalTimers();
|
|
@@ -3364,9 +3536,19 @@ export class AgentServer {
|
|
|
3364
3536
|
workerGeneration() {
|
|
3365
3537
|
return this.chatManager?.getLiveSessionSnapshot().generation ?? 0;
|
|
3366
3538
|
}
|
|
3367
|
-
registerSessionApproval(session, metadata, resolve) {
|
|
3539
|
+
registerSessionApproval(session, metadata, resolve, scope) {
|
|
3540
|
+
if (scope) {
|
|
3541
|
+
metadata = { ...metadata, sourceSessionId: scope.sourceSessionId };
|
|
3542
|
+
scope.pending.add(metadata.requestId);
|
|
3543
|
+
}
|
|
3368
3544
|
metadata = this.observeApprovalCreated(metadata);
|
|
3369
|
-
session.pendingApprovals.set(metadata.requestId, {
|
|
3545
|
+
session.pendingApprovals.set(metadata.requestId, {
|
|
3546
|
+
resolve: (decision) => {
|
|
3547
|
+
scope?.pending.delete(metadata.requestId);
|
|
3548
|
+
resolve(decision);
|
|
3549
|
+
},
|
|
3550
|
+
metadata,
|
|
3551
|
+
});
|
|
3370
3552
|
}
|
|
3371
3553
|
takeSessionApproval(session, requestId, status) {
|
|
3372
3554
|
const entry = session.pendingApprovals.get(requestId);
|
|
@@ -3446,7 +3628,7 @@ export class AgentServer {
|
|
|
3446
3628
|
try {
|
|
3447
3629
|
entry.resolve(entry.metadata.kind === "internal"
|
|
3448
3630
|
? internalCancellation(failure, reason)
|
|
3449
|
-
: { approved: false, reason });
|
|
3631
|
+
: { approved: false, failure, reason });
|
|
3450
3632
|
}
|
|
3451
3633
|
catch {
|
|
3452
3634
|
/* a resolver must never break cancel cleanup */
|
|
@@ -3457,6 +3639,12 @@ export class AgentServer {
|
|
|
3457
3639
|
}
|
|
3458
3640
|
failClosedApprovalTargets(targets, reason) {
|
|
3459
3641
|
for (const target of targets) {
|
|
3642
|
+
for (const child of this.childHostDisposers.values()) {
|
|
3643
|
+
if (child.route.connectionId === target.connectionId &&
|
|
3644
|
+
child.route.sessionId === target.sessionId &&
|
|
3645
|
+
child.route.generation === target.generation)
|
|
3646
|
+
child.dispose(reason);
|
|
3647
|
+
}
|
|
3460
3648
|
const session = this.chatManager?.get(target.sessionId);
|
|
3461
3649
|
if (session) {
|
|
3462
3650
|
this.cancelSessionApprovals(session, reason, "owner-lost", "owner_lost");
|
|
@@ -3472,7 +3660,7 @@ export class AgentServer {
|
|
|
3472
3660
|
this.pendingApprovals.delete(requestId);
|
|
3473
3661
|
this.pendingApprovalTargets.delete(requestId);
|
|
3474
3662
|
this.clearApprovalTimer(requestId);
|
|
3475
|
-
resolve?.({ approved: false, reason });
|
|
3663
|
+
resolve?.({ approved: false, failure: "owner_lost", reason });
|
|
3476
3664
|
}
|
|
3477
3665
|
}
|
|
3478
3666
|
}
|
package/dist/protocol/types.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type { SessionMessageTarget } from "../session/session-message.js";
|
|
|
13
13
|
export type PendingApprovalKind = "tool_approval" | "ask_user" | "internal";
|
|
14
14
|
/** Resolver-free metadata retained beside a pending approval callback. */
|
|
15
15
|
export interface PendingApprovalMetadata {
|
|
16
|
+
/** Child task that originated this request; sessionId remains its parent UI route. */
|
|
17
|
+
sourceSessionId?: string;
|
|
16
18
|
sessionId: string;
|
|
17
19
|
requestId: string;
|
|
18
20
|
routeGeneration?: number;
|
|
@@ -160,6 +160,8 @@ const FORK_EVENT_POLICY = {
|
|
|
160
160
|
tool_result: "copy",
|
|
161
161
|
summary: "copy",
|
|
162
162
|
context_transfer: "copy",
|
|
163
|
+
context_note: "copy",
|
|
164
|
+
context_checkpoint: "copy",
|
|
163
165
|
range_archive: "copy",
|
|
164
166
|
content_replace: "copy",
|
|
165
167
|
file_history: "skip",
|
|
@@ -1922,13 +1924,12 @@ export function buildForkTranscript(sourceEvents, state) {
|
|
|
1922
1924
|
forkedFrom: structuredClone(state.forkedFrom),
|
|
1923
1925
|
},
|
|
1924
1926
|
};
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
];
|
|
1927
|
+
const copied = sourceEvents.map((source) => ({
|
|
1928
|
+
...structuredClone(source),
|
|
1929
|
+
id: nanoid(12),
|
|
1930
|
+
}));
|
|
1931
|
+
Transcript.remapContextForkReferences(sourceEvents, copied);
|
|
1932
|
+
return [meta, ...copied];
|
|
1932
1933
|
}
|
|
1933
1934
|
function validateForkToolPairs(events) {
|
|
1934
1935
|
const projectedUses = [];
|
|
@@ -27,6 +27,16 @@ export interface SelectedContextRange {
|
|
|
27
27
|
messages: Message[];
|
|
28
28
|
sourceEventCount: number;
|
|
29
29
|
}
|
|
30
|
+
/** Host-generated replay state. Models supply the note text, never this snapshot. */
|
|
31
|
+
export interface ContextCheckpointSnapshot {
|
|
32
|
+
version: 1;
|
|
33
|
+
noteId: string;
|
|
34
|
+
coveredThroughEventId: string;
|
|
35
|
+
messages: Message[];
|
|
36
|
+
clientMessageIds: Array<[string, number]>;
|
|
37
|
+
}
|
|
38
|
+
/** Complete, ordered provider pairs are required before a checkpoint can commit. */
|
|
39
|
+
export declare function hasCompleteContextToolPairs(messages: readonly Message[]): boolean;
|
|
30
40
|
export type SummaryAppendMetadata = {
|
|
31
41
|
fromTurn: number;
|
|
32
42
|
toTurn: number;
|
|
@@ -59,8 +69,14 @@ export declare class Transcript {
|
|
|
59
69
|
static inMemory(label: string): Transcript;
|
|
60
70
|
/** Rehydrate a process-local fork without serializing its copied history. */
|
|
61
71
|
static fromMemoryEvents(label: string, events: readonly TranscriptEvent[]): Transcript;
|
|
72
|
+
/** Preserve checkpoint provenance when a fork assigns fresh event ids. */
|
|
73
|
+
static remapContextForkReferences(sourceEvents: readonly TranscriptEvent[], copiedEvents: TranscriptEvent[]): void;
|
|
62
74
|
isPersistent(): boolean;
|
|
63
75
|
append(type: TranscriptEventType, data: Record<string, unknown>): TranscriptEvent;
|
|
76
|
+
/** Failed note/checkpoint writes must never change the active replay. */
|
|
77
|
+
appendContextNote(text: string, coveredThroughEventId: string): TranscriptEvent | undefined;
|
|
78
|
+
appendContextCheckpoint(snapshot: ContextCheckpointSnapshot): TranscriptEvent | undefined;
|
|
79
|
+
private appendDurableContextEvent;
|
|
64
80
|
/**
|
|
65
81
|
* Append a chat message to the transcript.
|
|
66
82
|
*
|
|
@@ -148,6 +164,7 @@ export declare class Transcript {
|
|
|
148
164
|
messages: Message[];
|
|
149
165
|
liveIndexByClientMessageId: Map<string, number>;
|
|
150
166
|
};
|
|
167
|
+
private contextReplayEvents;
|
|
151
168
|
getEvents(type?: TranscriptEventType): TranscriptEvent[];
|
|
152
169
|
get turnNumber(): number;
|
|
153
170
|
get eventCount(): number;
|