@gajae-code/coding-agent 0.12.4 → 0.12.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +4 -1
- package/dist/types/async/job-manager.d.ts +9 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +10 -8
- package/dist/types/extensibility/extensions/runner.d.ts +9 -7
- package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
- package/dist/types/gjc-runtime/team-store.d.ts +0 -4
- package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
- package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +18 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -2
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -3
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
- package/dist/types/sdk/host/host.d.ts +23 -2
- package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
- package/dist/types/session/agent-session.d.ts +5 -4
- package/dist/types/session/artifacts.d.ts +8 -5
- package/dist/types/session/attempt-record-store.d.ts +19 -0
- package/dist/types/session/client-bridge.d.ts +14 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -23
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +7 -5
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -1
- package/dist/types/task/receipt.d.ts +3 -0
- package/dist/types/task/types.d.ts +194 -0
- package/dist/types/task/worktree.d.ts +13 -6
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/output-meta.d.ts +37 -5
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -0
- package/dist/types/utils/git.d.ts +1 -0
- package/package.json +8 -8
- package/scripts/capture-sticky-viewport-showcase.ts +131 -5
- package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
- package/scripts/verify-sticky-viewport-showcase.ts +620 -93
- package/src/async/job-manager.ts +13 -5
- package/src/config/model-profiles.ts +8 -0
- package/src/config/settings-schema.ts +17 -3
- package/src/cursor.ts +20 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +425 -151
- package/src/extensibility/extensions/runner.ts +80 -6
- package/src/extensibility/extensions/wrapper.ts +22 -15
- package/src/extensibility/runtime-skill-discovery.ts +4 -6
- package/src/gjc-runtime/launch-tmux.ts +309 -293
- package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
- package/src/gjc-runtime/psmux-detect.ts +52 -6
- package/src/gjc-runtime/team-launch.ts +50 -2
- package/src/gjc-runtime/team-runtime.ts +716 -251
- package/src/gjc-runtime/team-store.ts +25 -7
- package/src/gjc-runtime/team-workers.ts +161 -1
- package/src/gjc-runtime/tmux-common.ts +16 -4
- package/src/gjc-runtime/tmux-gc.ts +37 -5
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
- package/src/gjc-runtime/tmux-provider-context.ts +451 -0
- package/src/gjc-runtime/tmux-sessions.ts +409 -156
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
- package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/internal-urls/agent-protocol.ts +98 -38
- package/src/internal-urls/artifact-protocol.ts +28 -17
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/local-protocol.ts +39 -18
- package/src/internal-urls/registry-helpers.ts +62 -0
- package/src/modes/acp/acp-agent.ts +51 -13
- package/src/modes/acp/acp-client-bridge.ts +154 -11
- package/src/modes/components/gjc-bundle-settings.ts +2 -0
- package/src/modes/components/plugin-settings.ts +5 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/monitor.md +4 -4
- package/src/sdk/acp/adapter.ts +200 -9
- package/src/sdk/broker/broker.ts +93 -10
- package/src/sdk/broker/lifecycle-ledger.ts +227 -11
- package/src/sdk/broker/lifecycle.ts +692 -98
- package/src/sdk/bus/chat-daemon-control.ts +33 -5
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -3
- package/src/sdk/bus/telegram-daemon.ts +283 -44
- package/src/sdk/bus/topic-registry.ts +133 -10
- package/src/sdk/host/host.ts +187 -9
- package/src/sdk/host/reverse-leases.ts +249 -42
- package/src/sdk/session.ts +8 -6
- package/src/session/agent-session.ts +930 -354
- package/src/session/artifacts.ts +211 -74
- package/src/session/attempt-record-store.ts +106 -0
- package/src/session/client-bridge.ts +15 -0
- package/src/session/internal/managed-session-scope.ts +332 -198
- package/src/session/internal/managed-session-storage.ts +162 -450
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +31 -28
- package/src/session/session-storage.ts +190 -53
- package/src/session/streaming-output.ts +198 -40
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +277 -64
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +26 -2
- package/src/task/render.ts +42 -1
- package/src/task/types.ts +52 -0
- package/src/task/worktree.ts +122 -43
- package/src/tools/bash.ts +818 -239
- package/src/tools/eval.ts +99 -4
- package/src/tools/fetch.ts +77 -15
- package/src/tools/find.ts +7 -3
- package/src/tools/monitor.ts +7 -1
- package/src/tools/output-meta.ts +706 -116
- package/src/tools/search.ts +9 -4
- package/src/tools/subagent-render.ts +5 -2
- package/src/tools/subagent.ts +8 -0
- package/src/tools/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
- package/src/utils/git.ts +2 -0
|
@@ -38,12 +38,15 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
38
38
|
* chat daemons after notification configuration parsing changes. Generation 15
|
|
39
39
|
* applies the current notification configuration directly when starting chat
|
|
40
40
|
* daemon transports. Generation 16 applies Telegram sound-policy configuration
|
|
41
|
-
* through shared notification parsing. Generation 17
|
|
42
|
-
* replacement to exact native filesystem authority
|
|
41
|
+
* through shared notification parsing. Generation 17 bound managed-session
|
|
42
|
+
* replacement to exact native filesystem authority; generation 18 retired that
|
|
43
|
+
* binding, generation 19 bound exact cleanup to parent/link-count authority,
|
|
44
|
+
* generation 20 refreshed retained cleanup semantics, and generation 21 hardens
|
|
45
|
+
* exact Bash process-tree ownership shared by chat daemon cleanup.
|
|
43
46
|
*/
|
|
44
47
|
export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
|
|
45
|
-
discord:
|
|
46
|
-
slack:
|
|
48
|
+
discord: 21,
|
|
49
|
+
slack: 21,
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
export function chatDaemonGeneration(kind: ChatDaemonKind): number {
|
|
@@ -243,6 +246,9 @@ interface ChatDaemonOwnerLockLease {
|
|
|
243
246
|
ino: bigint;
|
|
244
247
|
size: bigint;
|
|
245
248
|
mtimeNs: bigint;
|
|
249
|
+
nlink: bigint;
|
|
250
|
+
parentDev: bigint;
|
|
251
|
+
parentIno: bigint;
|
|
246
252
|
sha256: string;
|
|
247
253
|
}
|
|
248
254
|
|
|
@@ -890,6 +896,7 @@ async function createChatDaemonOwnerLock(
|
|
|
890
896
|
if (isAlreadyExists(error)) return undefined;
|
|
891
897
|
throw error;
|
|
892
898
|
}
|
|
899
|
+
await fs.promises.unlink(temporary);
|
|
893
900
|
return await captureChatDaemonOwnerLockLease(lock);
|
|
894
901
|
} finally {
|
|
895
902
|
await fs.promises.unlink(temporary).catch(() => undefined);
|
|
@@ -901,14 +908,22 @@ async function captureChatDaemonOwnerLockLease(lock: string): Promise<ChatDaemon
|
|
|
901
908
|
const handle = await fs.promises.open(lock, "r");
|
|
902
909
|
try {
|
|
903
910
|
const before = await handle.stat({ bigint: true });
|
|
911
|
+
const parentBefore = await fs.promises.lstat(path.dirname(lock), { bigint: true });
|
|
904
912
|
if (!before.isFile()) return undefined;
|
|
905
913
|
const content = await handle.readFile({ encoding: "utf8" });
|
|
906
914
|
const after = await handle.stat({ bigint: true });
|
|
907
915
|
const pathname = await fs.promises.lstat(lock, { bigint: true });
|
|
916
|
+
const parentAfter = await fs.promises.lstat(path.dirname(lock), { bigint: true });
|
|
908
917
|
if (
|
|
909
918
|
!after.isFile() ||
|
|
910
919
|
!pathname.isFile() ||
|
|
911
920
|
pathname.isSymbolicLink() ||
|
|
921
|
+
before.nlink !== 1n ||
|
|
922
|
+
pathname.nlink !== 1n ||
|
|
923
|
+
!parentBefore.isDirectory() ||
|
|
924
|
+
parentBefore.isSymbolicLink() ||
|
|
925
|
+
parentBefore.dev !== parentAfter.dev ||
|
|
926
|
+
parentBefore.ino !== parentAfter.ino ||
|
|
912
927
|
before.dev !== after.dev ||
|
|
913
928
|
before.ino !== after.ino ||
|
|
914
929
|
before.size !== after.size ||
|
|
@@ -925,6 +940,9 @@ async function captureChatDaemonOwnerLockLease(lock: string): Promise<ChatDaemon
|
|
|
925
940
|
ino: before.ino,
|
|
926
941
|
size: before.size,
|
|
927
942
|
mtimeNs: before.mtimeNs,
|
|
943
|
+
nlink: before.nlink,
|
|
944
|
+
parentDev: parentBefore.dev,
|
|
945
|
+
parentIno: parentBefore.ino,
|
|
928
946
|
sha256: crypto.createHash("sha256").update(content).digest("hex"),
|
|
929
947
|
};
|
|
930
948
|
} finally {
|
|
@@ -942,6 +960,9 @@ async function ownsChatDaemonOwnerLock(lock: string, lease: ChatDaemonOwnerLockL
|
|
|
942
960
|
current.ino === lease.ino &&
|
|
943
961
|
current.size === lease.size &&
|
|
944
962
|
current.mtimeNs === lease.mtimeNs &&
|
|
963
|
+
current.nlink === lease.nlink &&
|
|
964
|
+
current.parentDev === lease.parentDev &&
|
|
965
|
+
current.parentIno === lease.parentIno &&
|
|
945
966
|
current.content === lease.content
|
|
946
967
|
);
|
|
947
968
|
}
|
|
@@ -950,7 +971,14 @@ async function ownsChatDaemonOwnerLock(lock: string, lease: ChatDaemonOwnerLockL
|
|
|
950
971
|
function unlinkExactChatDaemonOwnerLock(lock: string, lease: ChatDaemonOwnerLockLease): boolean {
|
|
951
972
|
try {
|
|
952
973
|
const removed = native.exactUnlink(lock, {
|
|
953
|
-
|
|
974
|
+
dev: lease.dev,
|
|
975
|
+
ino: lease.ino,
|
|
976
|
+
size: lease.size,
|
|
977
|
+
mtimeNs: lease.mtimeNs,
|
|
978
|
+
nlink: lease.nlink,
|
|
979
|
+
parentDev: lease.parentDev,
|
|
980
|
+
parentIno: lease.parentIno,
|
|
981
|
+
sha256: lease.sha256,
|
|
954
982
|
quarantineName: `.gjc-delete-chat-daemon-lock-${crypto.randomUUID()}`,
|
|
955
983
|
});
|
|
956
984
|
if (removed.ok) return true;
|
package/src/sdk/bus/index.ts
CHANGED
|
@@ -27,7 +27,7 @@ import * as fs from "node:fs";
|
|
|
27
27
|
import * as os from "node:os";
|
|
28
28
|
import * as path from "node:path";
|
|
29
29
|
import { promisify } from "node:util";
|
|
30
|
-
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
30
|
+
import { type RunSettlementProof, ThinkingLevel } from "@gajae-code/agent-core";
|
|
31
31
|
import type { ImageContent, TextContent, Tool } from "@gajae-code/ai";
|
|
32
32
|
import { NotificationServer, nativeBuildInfo } from "@gajae-code/natives";
|
|
33
33
|
import { $credentialEnv, logger, postmortem, VERSION } from "@gajae-code/utils";
|
|
@@ -61,7 +61,7 @@ import { registerAskAnswerSource, registerWorkflowGateEmitterListener } from "..
|
|
|
61
61
|
import { acpFinalTextFromMessage } from "../acp/final-text";
|
|
62
62
|
import { ensureBroker } from "../broker/ensure";
|
|
63
63
|
import { SessionIndex } from "../broker/session-index";
|
|
64
|
-
import { SessionSdkHost, shouldHostSdk } from "../host";
|
|
64
|
+
import { ReverseLeaseError, SessionSdkHost, shouldHostSdk } from "../host";
|
|
65
65
|
import { type ControlSurface, dispatchControl } from "../host/control";
|
|
66
66
|
import { CursorRegistry, QueryHandlers, RevisionStore, type SessionSurface } from "../host/query";
|
|
67
67
|
import { projectQ10Models } from "../models.js";
|
|
@@ -116,6 +116,25 @@ export {
|
|
|
116
116
|
runIdentityControlTerminalPath,
|
|
117
117
|
} from "./control-drain-lease";
|
|
118
118
|
|
|
119
|
+
const PROMPT_SETTLEMENT_DIAGNOSTIC_ENTRY_LIMIT = 8;
|
|
120
|
+
const PROMPT_SETTLEMENT_DIAGNOSTIC_MAX_AGE_MS = 86_400_000;
|
|
121
|
+
|
|
122
|
+
export function formatPromptSettlementDiagnostic(
|
|
123
|
+
proof: Extract<RunSettlementProof, { status: "unfenced" }>,
|
|
124
|
+
now = Date.now(),
|
|
125
|
+
): string {
|
|
126
|
+
const pending = proof.pending.slice(0, PROMPT_SETTLEMENT_DIAGNOSTIC_ENTRY_LIMIT).map(entry => ({
|
|
127
|
+
kind: entry.kind,
|
|
128
|
+
labelHash: crypto.createHash("sha256").update(entry.label).digest("hex").slice(0, 16),
|
|
129
|
+
ageMs: Math.max(0, Math.min(PROMPT_SETTLEMENT_DIAGNOSTIC_MAX_AGE_MS, now - entry.registeredAt)),
|
|
130
|
+
}));
|
|
131
|
+
return JSON.stringify({
|
|
132
|
+
reason: proof.reason,
|
|
133
|
+
pending,
|
|
134
|
+
omitted: Math.max(0, proof.pending.length - pending.length),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
// ===========================================================================
|
|
120
139
|
// Session lifecycle control protocol (TypeScript mirror of the Rust wire
|
|
121
140
|
// contract in `crates/gjc-sdk/src/lifecycle.rs`).
|
|
@@ -3239,6 +3258,8 @@ export function createNotificationsExtension(
|
|
|
3239
3258
|
awaitStartup: boolean;
|
|
3240
3259
|
}
|
|
3241
3260
|
| undefined;
|
|
3261
|
+
const MAX_SYNTHETIC_SEAM_ADMISSIONS = 64;
|
|
3262
|
+
let syntheticSeamAdmissions = 0;
|
|
3242
3263
|
let extensionShuttingDown = false;
|
|
3243
3264
|
|
|
3244
3265
|
async function ensureTelegramOwner(
|
|
@@ -3548,8 +3569,64 @@ export function createNotificationsExtension(
|
|
|
3548
3569
|
const revisions = new RevisionStore(id, Date.now, { storageDir: stateRoot });
|
|
3549
3570
|
let host: SessionSdkHost | undefined;
|
|
3550
3571
|
let disposeUiAnswerSource: (() => void) | undefined;
|
|
3551
|
-
|
|
3572
|
+
let fsClientBridge: ClientBridge | undefined;
|
|
3573
|
+
let terminalClientBridge: ClientBridge | undefined;
|
|
3574
|
+
let terminalPublicationFailedLeaseId: string | undefined;
|
|
3575
|
+
const MAX_PENDING_TERMINAL_CREATIONS = 32;
|
|
3576
|
+
let pendingTerminalCreations = 0;
|
|
3577
|
+
let terminalCreationTail = Promise.resolve();
|
|
3578
|
+
const withTerminalCreationAdmission = async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
3579
|
+
if (pendingTerminalCreations >= MAX_PENDING_TERMINAL_CREATIONS)
|
|
3580
|
+
throw new Error("terminal creation admission limit reached");
|
|
3581
|
+
pendingTerminalCreations++;
|
|
3582
|
+
const previous = terminalCreationTail;
|
|
3583
|
+
const release = Promise.withResolvers<void>();
|
|
3584
|
+
terminalCreationTail = previous.then(() => release.promise);
|
|
3585
|
+
try {
|
|
3586
|
+
await previous;
|
|
3587
|
+
return await operation();
|
|
3588
|
+
} finally {
|
|
3589
|
+
pendingTerminalCreations--;
|
|
3590
|
+
release.resolve();
|
|
3591
|
+
}
|
|
3592
|
+
};
|
|
3593
|
+
const providerLeaseIdentities = new Map<
|
|
3594
|
+
string,
|
|
3595
|
+
{ connectionId: string; connectionGeneration: number; fence: string }
|
|
3596
|
+
>();
|
|
3597
|
+
const refreshSdkClientBridge = () => {
|
|
3598
|
+
if (!fsClientBridge && !terminalClientBridge) {
|
|
3599
|
+
ctx.setSdkClientBridge?.(undefined);
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
const bridge: ClientBridge = {
|
|
3603
|
+
capabilities: {
|
|
3604
|
+
readTextFile: fsClientBridge?.capabilities.readTextFile,
|
|
3605
|
+
writeTextFile: fsClientBridge?.capabilities.writeTextFile,
|
|
3606
|
+
terminal: terminalClientBridge?.capabilities.terminal,
|
|
3607
|
+
},
|
|
3608
|
+
deferAgentInitiatedTurns: true,
|
|
3609
|
+
};
|
|
3610
|
+
if (fsClientBridge?.readTextFile) bridge.readTextFile = fsClientBridge.readTextFile;
|
|
3611
|
+
if (fsClientBridge?.writeTextFile) bridge.writeTextFile = fsClientBridge.writeTextFile;
|
|
3612
|
+
if (terminalClientBridge?.quarantineProviderLease)
|
|
3613
|
+
bridge.quarantineProviderLease = terminalClientBridge.quarantineProviderLease;
|
|
3614
|
+
if (terminalClientBridge?.createTerminal) bridge.createTerminal = terminalClientBridge.createTerminal;
|
|
3615
|
+
ctx.setSdkClientBridge?.(bridge);
|
|
3616
|
+
};
|
|
3617
|
+
const installProviderDefinitions = (
|
|
3618
|
+
capability: string,
|
|
3619
|
+
definitions: unknown,
|
|
3620
|
+
leaseId: string,
|
|
3621
|
+
connectionId: string,
|
|
3622
|
+
connectionGeneration: number,
|
|
3623
|
+
) => {
|
|
3552
3624
|
validateProviderDefinitions(capability, definitions);
|
|
3625
|
+
providerLeaseIdentities.set(leaseId, {
|
|
3626
|
+
connectionId,
|
|
3627
|
+
connectionGeneration,
|
|
3628
|
+
fence: `${connectionId}:${connectionGeneration}:${leaseId}`,
|
|
3629
|
+
});
|
|
3553
3630
|
if (capability === "permission") {
|
|
3554
3631
|
ctx.setSdkPermissionProvider?.(async (toolCall, permissionOptions, signal) => {
|
|
3555
3632
|
const result = await host!.reverse.request(
|
|
@@ -3587,6 +3664,179 @@ export function createNotificationsExtension(
|
|
|
3587
3664
|
);
|
|
3588
3665
|
return;
|
|
3589
3666
|
}
|
|
3667
|
+
if (capability === "terminal") {
|
|
3668
|
+
const terminalLeaseId = leaseId;
|
|
3669
|
+
if (!terminalLeaseId) throw new Error("terminal provider lease is unavailable");
|
|
3670
|
+
const terminalProviderIdentity = providerLeaseIdentities.get(terminalLeaseId);
|
|
3671
|
+
if (!terminalProviderIdentity) throw new Error("terminal provider generation is unavailable");
|
|
3672
|
+
if (terminalPublicationFailedLeaseId !== terminalLeaseId) terminalPublicationFailedLeaseId = undefined;
|
|
3673
|
+
const requestTerminal = (
|
|
3674
|
+
method: string,
|
|
3675
|
+
payload: Record<string, unknown>,
|
|
3676
|
+
signal?: AbortSignal,
|
|
3677
|
+
cleanup = false,
|
|
3678
|
+
) =>
|
|
3679
|
+
host!.reverse.request(
|
|
3680
|
+
"terminal",
|
|
3681
|
+
method,
|
|
3682
|
+
payload,
|
|
3683
|
+
signal,
|
|
3684
|
+
terminalLeaseId,
|
|
3685
|
+
cleanup,
|
|
3686
|
+
terminalProviderIdentity.connectionId,
|
|
3687
|
+
);
|
|
3688
|
+
const settleTerminalCleanup = async (
|
|
3689
|
+
method: "terminal.kill" | "terminal.release",
|
|
3690
|
+
terminalId: string,
|
|
3691
|
+
): Promise<boolean> =>
|
|
3692
|
+
await Promise.race([
|
|
3693
|
+
Promise.resolve()
|
|
3694
|
+
.then(() => requestTerminal(method, { terminalId }, AbortSignal.timeout(500), true))
|
|
3695
|
+
.then(
|
|
3696
|
+
() => true,
|
|
3697
|
+
() => false,
|
|
3698
|
+
),
|
|
3699
|
+
Bun.sleep(500).then(() => false),
|
|
3700
|
+
]);
|
|
3701
|
+
terminalClientBridge = {
|
|
3702
|
+
capabilities: { terminal: true },
|
|
3703
|
+
deferAgentInitiatedTurns: true,
|
|
3704
|
+
async quarantineProviderLease(identity, _reason) {
|
|
3705
|
+
if (typeof identity === "string") throw new Error("terminal provider generation is required");
|
|
3706
|
+
if (
|
|
3707
|
+
identity.leaseId !== terminalLeaseId ||
|
|
3708
|
+
identity.connectionId !== terminalProviderIdentity.connectionId ||
|
|
3709
|
+
identity.connectionGeneration !== terminalProviderIdentity.connectionGeneration ||
|
|
3710
|
+
identity.fence !== terminalProviderIdentity.fence
|
|
3711
|
+
)
|
|
3712
|
+
throw new Error("terminal provider lease mismatch");
|
|
3713
|
+
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3714
|
+
host!.quarantineProviderLease(identity, "terminal cleanup ownership uncertain");
|
|
3715
|
+
},
|
|
3716
|
+
async createTerminal(params) {
|
|
3717
|
+
return withTerminalCreationAdmission(async () => {
|
|
3718
|
+
if (terminalPublicationFailedLeaseId === terminalLeaseId)
|
|
3719
|
+
throw new Error("terminal provider quarantined after publication failure");
|
|
3720
|
+
if (params.signal?.aborted) throw new Error("terminal creation aborted");
|
|
3721
|
+
const creationAbort = new AbortController();
|
|
3722
|
+
const creationSignal = params.signal
|
|
3723
|
+
? AbortSignal.any([params.signal, creationAbort.signal])
|
|
3724
|
+
: creationAbort.signal;
|
|
3725
|
+
let created: unknown;
|
|
3726
|
+
let creationTimedOut = false;
|
|
3727
|
+
try {
|
|
3728
|
+
created = await Promise.race([
|
|
3729
|
+
requestTerminal(
|
|
3730
|
+
"terminal.create",
|
|
3731
|
+
{
|
|
3732
|
+
command: params.command,
|
|
3733
|
+
...(params.args ? { args: params.args } : {}),
|
|
3734
|
+
...(params.env ? { env: params.env } : {}),
|
|
3735
|
+
...(params.cwd ? { cwd: params.cwd } : {}),
|
|
3736
|
+
...(typeof params.outputByteLimit === "number"
|
|
3737
|
+
? { outputByteLimit: params.outputByteLimit }
|
|
3738
|
+
: {}),
|
|
3739
|
+
},
|
|
3740
|
+
creationSignal,
|
|
3741
|
+
),
|
|
3742
|
+
Bun.sleep(500).then(() => {
|
|
3743
|
+
creationTimedOut = true;
|
|
3744
|
+
creationAbort.abort();
|
|
3745
|
+
throw new Error("terminal creation did not settle within 500ms");
|
|
3746
|
+
}),
|
|
3747
|
+
]);
|
|
3748
|
+
} catch (error) {
|
|
3749
|
+
if (
|
|
3750
|
+
creationSignal.aborted ||
|
|
3751
|
+
(error instanceof ReverseLeaseError && error.code === "payload_too_large")
|
|
3752
|
+
)
|
|
3753
|
+
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3754
|
+
if (creationTimedOut) throw new Error("terminal creation did not settle within 500ms");
|
|
3755
|
+
throw error;
|
|
3756
|
+
} finally {
|
|
3757
|
+
creationAbort.abort();
|
|
3758
|
+
}
|
|
3759
|
+
const terminalId =
|
|
3760
|
+
created &&
|
|
3761
|
+
typeof created === "object" &&
|
|
3762
|
+
typeof (created as { terminalId?: unknown }).terminalId === "string"
|
|
3763
|
+
? (created as { terminalId: string }).terminalId
|
|
3764
|
+
: undefined;
|
|
3765
|
+
if (!terminalId) {
|
|
3766
|
+
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3767
|
+
throw new Error("terminal provider returned an invalid create response");
|
|
3768
|
+
}
|
|
3769
|
+
const publicationAbort = new AbortController();
|
|
3770
|
+
const publicationSignal = params.signal
|
|
3771
|
+
? AbortSignal.any([params.signal, publicationAbort.signal])
|
|
3772
|
+
: publicationAbort.signal;
|
|
3773
|
+
try {
|
|
3774
|
+
await Promise.race([
|
|
3775
|
+
requestTerminal(
|
|
3776
|
+
"terminal.publish",
|
|
3777
|
+
{ toolCallId: params.toolCallId, terminalId },
|
|
3778
|
+
publicationSignal,
|
|
3779
|
+
),
|
|
3780
|
+
Bun.sleep(500).then(() => {
|
|
3781
|
+
publicationAbort.abort();
|
|
3782
|
+
throw new Error("terminal publication did not settle within 500ms");
|
|
3783
|
+
}),
|
|
3784
|
+
]);
|
|
3785
|
+
} catch (error) {
|
|
3786
|
+
const killed = await settleTerminalCleanup("terminal.kill", terminalId);
|
|
3787
|
+
const released = await settleTerminalCleanup("terminal.release", terminalId);
|
|
3788
|
+
if (!params.signal?.aborted || !killed || !released)
|
|
3789
|
+
terminalPublicationFailedLeaseId = terminalLeaseId;
|
|
3790
|
+
throw error;
|
|
3791
|
+
} finally {
|
|
3792
|
+
publicationAbort.abort();
|
|
3793
|
+
}
|
|
3794
|
+
return {
|
|
3795
|
+
terminalId,
|
|
3796
|
+
providerLeaseId: terminalLeaseId,
|
|
3797
|
+
providerConnectionId: terminalProviderIdentity.connectionId,
|
|
3798
|
+
providerConnectionGeneration: terminalProviderIdentity.connectionGeneration,
|
|
3799
|
+
providerFence: terminalProviderIdentity.fence,
|
|
3800
|
+
async currentOutput() {
|
|
3801
|
+
const output = await requestTerminal("terminal.output", { terminalId });
|
|
3802
|
+
if (
|
|
3803
|
+
!output ||
|
|
3804
|
+
typeof output !== "object" ||
|
|
3805
|
+
typeof (output as { output?: unknown }).output !== "string" ||
|
|
3806
|
+
typeof (output as { truncated?: unknown }).truncated !== "boolean"
|
|
3807
|
+
) {
|
|
3808
|
+
throw new Error("terminal provider returned an invalid output response");
|
|
3809
|
+
}
|
|
3810
|
+
const response = output as { output: string; truncated: boolean; exitStatus?: unknown };
|
|
3811
|
+
return {
|
|
3812
|
+
output: response.output,
|
|
3813
|
+
truncated: response.truncated,
|
|
3814
|
+
exitStatus:
|
|
3815
|
+
response.exitStatus && typeof response.exitStatus === "object"
|
|
3816
|
+
? (response.exitStatus as { exitCode?: number | null; signal?: string | null })
|
|
3817
|
+
: null,
|
|
3818
|
+
};
|
|
3819
|
+
},
|
|
3820
|
+
async waitForExit() {
|
|
3821
|
+
const status = await requestTerminal("terminal.waitForExit", { terminalId });
|
|
3822
|
+
if (!status || typeof status !== "object")
|
|
3823
|
+
throw new Error("terminal provider returned an invalid exit response");
|
|
3824
|
+
const response = status as { exitCode?: number | null; signal?: string | null };
|
|
3825
|
+
return { exitCode: response.exitCode ?? null, signal: response.signal ?? null };
|
|
3826
|
+
},
|
|
3827
|
+
async kill() {
|
|
3828
|
+
await requestTerminal("terminal.kill", { terminalId }, undefined, true);
|
|
3829
|
+
},
|
|
3830
|
+
async release() {
|
|
3831
|
+
await requestTerminal("terminal.release", { terminalId }, undefined, true);
|
|
3832
|
+
},
|
|
3833
|
+
};
|
|
3834
|
+
});
|
|
3835
|
+
},
|
|
3836
|
+
};
|
|
3837
|
+
refreshSdkClientBridge();
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3590
3840
|
if (capability !== "fs") return;
|
|
3591
3841
|
// Only advertise the methods the client actually declared; a read-only client
|
|
3592
3842
|
// must keep using the local write path instead of failing against the bridge.
|
|
@@ -3618,11 +3868,17 @@ export function createNotificationsExtension(
|
|
|
3618
3868
|
};
|
|
3619
3869
|
if (!canRead) delete bridge.readTextFile;
|
|
3620
3870
|
if (!canWrite) delete bridge.writeTextFile;
|
|
3621
|
-
|
|
3871
|
+
fsClientBridge = bridge;
|
|
3872
|
+
refreshSdkClientBridge();
|
|
3622
3873
|
};
|
|
3623
3874
|
const removeProviderDefinitions = (capability: string) => {
|
|
3624
3875
|
if (capability === "permission") ctx.setSdkPermissionProvider?.(undefined);
|
|
3625
|
-
if (capability === "fs")
|
|
3876
|
+
if (capability === "fs") fsClientBridge = undefined;
|
|
3877
|
+
if (capability === "terminal") {
|
|
3878
|
+
terminalClientBridge = undefined;
|
|
3879
|
+
terminalPublicationFailedLeaseId = undefined;
|
|
3880
|
+
}
|
|
3881
|
+
if (capability === "fs" || capability === "terminal") refreshSdkClientBridge();
|
|
3626
3882
|
if (capability === "ui") {
|
|
3627
3883
|
disposeUiAnswerSource?.();
|
|
3628
3884
|
disposeUiAnswerSource = undefined;
|
|
@@ -3630,6 +3886,12 @@ export function createNotificationsExtension(
|
|
|
3630
3886
|
};
|
|
3631
3887
|
|
|
3632
3888
|
const hostCapCache = new Map<string, ReadonlySet<string>>();
|
|
3889
|
+
const MAX_PENDING_PROVIDER_RECLAIMS = 64;
|
|
3890
|
+
const pendingProviderReclaims = new Map<
|
|
3891
|
+
string,
|
|
3892
|
+
Array<{ connectionId: string; connectionGeneration: number; frame: Record<string, unknown> }>
|
|
3893
|
+
>();
|
|
3894
|
+
let pendingProviderReclaimCount = 0;
|
|
3633
3895
|
|
|
3634
3896
|
const configOverrides = new Map<string, unknown>();
|
|
3635
3897
|
const configRevision = { current: 0 };
|
|
@@ -3990,10 +4252,7 @@ export function createNotificationsExtension(
|
|
|
3990
4252
|
submission.phase = "terminalizing";
|
|
3991
4253
|
if (options.fence) {
|
|
3992
4254
|
const seam = ctx as typeof ctx & {
|
|
3993
|
-
abortPromptAndWait?: (
|
|
3994
|
-
handle: string,
|
|
3995
|
-
options: { graceMs: number },
|
|
3996
|
-
) => Promise<{ status: "settled" } | { status: "unfenced" }>;
|
|
4255
|
+
abortPromptAndWait?: (handle: string, options: { graceMs: number }) => Promise<RunSettlementProof>;
|
|
3997
4256
|
};
|
|
3998
4257
|
// Only the handle captured for this correlation may be fenced; a later run
|
|
3999
4258
|
// must never be aborted by an older prompt's cleanup.
|
|
@@ -4006,7 +4265,7 @@ export function createNotificationsExtension(
|
|
|
4006
4265
|
);
|
|
4007
4266
|
return;
|
|
4008
4267
|
}
|
|
4009
|
-
let proof:
|
|
4268
|
+
let proof: RunSettlementProof;
|
|
4010
4269
|
try {
|
|
4011
4270
|
proof = await seam.abortPromptAndWait(submission.executionHandle, {
|
|
4012
4271
|
graceMs: PROMPT_TERMINALIZATION_GRACE_MS,
|
|
@@ -4022,6 +4281,7 @@ export function createNotificationsExtension(
|
|
|
4022
4281
|
return;
|
|
4023
4282
|
}
|
|
4024
4283
|
if (proof?.status !== "settled") {
|
|
4284
|
+
logger.warn(`sdk: prompt resource settlement unfenced: ${formatPromptSettlementDiagnostic(proof)}`);
|
|
4025
4285
|
// No settlement proof: never publish a normal terminal. The durable
|
|
4026
4286
|
// pending claim stays active so restart recovery finalizes it.
|
|
4027
4287
|
failPromptClosed(
|
|
@@ -4185,7 +4445,8 @@ export function createNotificationsExtension(
|
|
|
4185
4445
|
};
|
|
4186
4446
|
|
|
4187
4447
|
const sendSdkFrame = (connectionId: string, frame: Record<string, unknown>) => {
|
|
4188
|
-
|
|
4448
|
+
const cancellation = frame.type === "reverse_cancel";
|
|
4449
|
+
if (!cancellation && (extensionShuttingDown || runtime?.stopping || runtimes.get(id) !== runtime)) {
|
|
4189
4450
|
abandonPromptResponse(connectionId, frame);
|
|
4190
4451
|
return;
|
|
4191
4452
|
}
|
|
@@ -4204,13 +4465,15 @@ export function createNotificationsExtension(
|
|
|
4204
4465
|
abandonPromptResponse(connectionId, frame);
|
|
4205
4466
|
throw error;
|
|
4206
4467
|
}
|
|
4468
|
+
syntheticSeamAdmissions = Math.max(0, syntheticSeamAdmissions - 1);
|
|
4469
|
+
host?.handleDisconnect(connectionId);
|
|
4207
4470
|
return;
|
|
4208
4471
|
}
|
|
4209
4472
|
try {
|
|
4210
4473
|
server.sendTo(connectionId, json);
|
|
4211
4474
|
} catch (error) {
|
|
4212
|
-
logger.warn(`sdk: directed response delivery failed for ${connectionId}: ${String(error)}`);
|
|
4213
4475
|
abandonPromptResponse(connectionId, frame);
|
|
4476
|
+
logger.warn(`sdk: directed response delivery rejected for ${connectionId}: ${String(error)}`);
|
|
4214
4477
|
throw error;
|
|
4215
4478
|
}
|
|
4216
4479
|
};
|
|
@@ -4223,6 +4486,13 @@ export function createNotificationsExtension(
|
|
|
4223
4486
|
connectionCapabilities: connectionId => hostCapCache.get(connectionId),
|
|
4224
4487
|
installProviderDefinitions,
|
|
4225
4488
|
onProviderDefinitionsRemoved: removeProviderDefinitions,
|
|
4489
|
+
onProviderLeaseRegistered: (lease, connectionGeneration) => {
|
|
4490
|
+
providerLeaseIdentities.set(lease.leaseId, {
|
|
4491
|
+
connectionId: lease.connectionId,
|
|
4492
|
+
connectionGeneration,
|
|
4493
|
+
fence: `${lease.connectionId}:${connectionGeneration}:${lease.leaseId}`,
|
|
4494
|
+
});
|
|
4495
|
+
},
|
|
4226
4496
|
onFrame: handler => {
|
|
4227
4497
|
inboundSdkFrame = handler;
|
|
4228
4498
|
return () => {
|
|
@@ -4230,6 +4500,11 @@ export function createNotificationsExtension(
|
|
|
4230
4500
|
};
|
|
4231
4501
|
},
|
|
4232
4502
|
onRequest: options.onSdkRequest,
|
|
4503
|
+
onFrameOverflow: connectionId => {
|
|
4504
|
+
fencedConnections.add(connectionId);
|
|
4505
|
+
controlSurface.cancelPendingPreflightsForConnection(connectionId);
|
|
4506
|
+
host?.handleDisconnect(connectionId);
|
|
4507
|
+
},
|
|
4233
4508
|
beforeControlResponse: async (_connectionId, request, response, sendTerminal) => {
|
|
4234
4509
|
if (typeof request.operation !== "string" || !identityControlOperations.has(request.operation)) return;
|
|
4235
4510
|
const pending = deferredIdentityRotation;
|
|
@@ -4521,6 +4796,47 @@ export function createNotificationsExtension(
|
|
|
4521
4796
|
server.sendTo(connectionId, JSON.stringify({ type: responseType, id, ok: false, error }));
|
|
4522
4797
|
} catch {}
|
|
4523
4798
|
};
|
|
4799
|
+
const queueExpectedProviderReclaim = (connectionId: string, frame: Record<string, unknown>): boolean => {
|
|
4800
|
+
if (frame.type !== "register_provider") return false;
|
|
4801
|
+
const capability = typeof frame.capability === "string" ? frame.capability : undefined;
|
|
4802
|
+
const expectedLeaseId = typeof frame.expectedLeaseId === "string" ? frame.expectedLeaseId : undefined;
|
|
4803
|
+
if (!capability || !expectedLeaseId) return false;
|
|
4804
|
+
const lease = host?.reverse.getLease(capability);
|
|
4805
|
+
if (!lease || lease.connectionId === connectionId || lease.leaseId !== expectedLeaseId) return false;
|
|
4806
|
+
if (pendingProviderReclaimCount >= MAX_PENDING_PROVIDER_RECLAIMS) return false;
|
|
4807
|
+
const queued = pendingProviderReclaims.get(lease.connectionId) ?? [];
|
|
4808
|
+
if (typeof frame.id === "string" && queued.some(entry => entry.frame.id === frame.id)) return true;
|
|
4809
|
+
const currentGeneration = host?.connectionGeneration(connectionId) ?? 0;
|
|
4810
|
+
if (currentGeneration <= 0) return false;
|
|
4811
|
+
queued.push({ connectionId, connectionGeneration: currentGeneration, frame });
|
|
4812
|
+
pendingProviderReclaims.set(lease.connectionId, queued);
|
|
4813
|
+
pendingProviderReclaimCount++;
|
|
4814
|
+
return true;
|
|
4815
|
+
};
|
|
4816
|
+
const purgeProviderReclaimsForClaimant = (connectionId: string): void => {
|
|
4817
|
+
for (const [incumbentId, queued] of pendingProviderReclaims) {
|
|
4818
|
+
const retained = queued.filter(entry => entry.connectionId !== connectionId);
|
|
4819
|
+
pendingProviderReclaimCount = Math.max(0, pendingProviderReclaimCount - (queued.length - retained.length));
|
|
4820
|
+
if (retained.length === 0) pendingProviderReclaims.delete(incumbentId);
|
|
4821
|
+
else pendingProviderReclaims.set(incumbentId, retained);
|
|
4822
|
+
}
|
|
4823
|
+
};
|
|
4824
|
+
const drainProviderReclaims = (closedConnectionId: string): void => {
|
|
4825
|
+
const queued = pendingProviderReclaims.get(closedConnectionId);
|
|
4826
|
+
if (!queued) return;
|
|
4827
|
+
pendingProviderReclaims.delete(closedConnectionId);
|
|
4828
|
+
pendingProviderReclaimCount = Math.max(0, pendingProviderReclaimCount - queued.length);
|
|
4829
|
+
for (const entry of queued) {
|
|
4830
|
+
if (
|
|
4831
|
+
initializedRuntime.inboundFenced ||
|
|
4832
|
+
initializedRuntime.stopping ||
|
|
4833
|
+
runtimes.get(id) !== initializedRuntime
|
|
4834
|
+
)
|
|
4835
|
+
continue;
|
|
4836
|
+
if (!host?.isConnectionOpen(entry.connectionId, entry.connectionGeneration)) continue;
|
|
4837
|
+
inboundSdkFrame?.(entry.connectionId, entry.frame);
|
|
4838
|
+
}
|
|
4839
|
+
};
|
|
4524
4840
|
try {
|
|
4525
4841
|
server.onSdkFrame((err, inbound) => {
|
|
4526
4842
|
if (err || !inbound) return;
|
|
@@ -4528,6 +4844,13 @@ export function createNotificationsExtension(
|
|
|
4528
4844
|
const frame = JSON.parse(inbound.json) as unknown;
|
|
4529
4845
|
if (!frame || typeof frame !== "object") return;
|
|
4530
4846
|
const typedFrame = frame as Record<string, unknown>;
|
|
4847
|
+
if (!host.isConnectionOpen(inbound.connectionId)) {
|
|
4848
|
+
if (host.connectionGeneration(inbound.connectionId) > 0) {
|
|
4849
|
+
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
host.handleConnect(inbound.connectionId);
|
|
4853
|
+
}
|
|
4531
4854
|
if (inbound.connectionId && fencedConnections.has(inbound.connectionId)) {
|
|
4532
4855
|
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4533
4856
|
return;
|
|
@@ -4536,6 +4859,7 @@ export function createNotificationsExtension(
|
|
|
4536
4859
|
sendEndpointStale(inbound.connectionId, typedFrame);
|
|
4537
4860
|
return;
|
|
4538
4861
|
}
|
|
4862
|
+
if (queueExpectedProviderReclaim(inbound.connectionId, typedFrame)) return;
|
|
4539
4863
|
if (typedFrame.type === "ephemeral_turn" || typedFrame.type === "ephemeral_turn_cancel") return;
|
|
4540
4864
|
if (typedFrame.type === "event_replay") {
|
|
4541
4865
|
const capabilities = Array.isArray(typedFrame.capabilities) ? typedFrame.capabilities : [];
|
|
@@ -4563,11 +4887,16 @@ export function createNotificationsExtension(
|
|
|
4563
4887
|
server.onConnectionClose((_err, connectionId) => {
|
|
4564
4888
|
if (!connectionId) return;
|
|
4565
4889
|
controlSurface.cancelPendingPreflightsForConnection(connectionId);
|
|
4566
|
-
|
|
4890
|
+
try {
|
|
4891
|
+
host.handleDisconnect(connectionId);
|
|
4892
|
+
} finally {
|
|
4893
|
+
drainProviderReclaims(connectionId);
|
|
4894
|
+
}
|
|
4567
4895
|
hostCapCache.delete(connectionId);
|
|
4568
4896
|
// The socket is gone, so its fence has nothing left to refuse. Dropping the
|
|
4569
4897
|
// entry keeps the set bounded by live connections instead of growing forever.
|
|
4570
4898
|
fencedConnections.delete(connectionId);
|
|
4899
|
+
purgeProviderReclaimsForClaimant(connectionId);
|
|
4571
4900
|
// Deliberate deviation from the plan's "claim prompt_failed on old-owner
|
|
4572
4901
|
// disconnect": that would break the shipped Q26 reconnect contract, where a
|
|
4573
4902
|
// client may drop its socket and reconcile the still-running prompt without
|
|
@@ -4848,7 +5177,9 @@ export function createNotificationsExtension(
|
|
|
4848
5177
|
!suspendedRuntime.stopping &&
|
|
4849
5178
|
!suspendedRuntime.policySuspended
|
|
4850
5179
|
)
|
|
4851
|
-
|
|
5180
|
+
if (syntheticSeamAdmissions >= MAX_SYNTHETIC_SEAM_ADMISSIONS) return;
|
|
5181
|
+
syntheticSeamAdmissions++;
|
|
5182
|
+
inboundSdkFrame?.(`seam:${inbound.requestId ?? "notification"}`, frame);
|
|
4852
5183
|
});
|
|
4853
5184
|
}
|
|
4854
5185
|
}
|
|
@@ -4857,6 +5188,8 @@ export function createNotificationsExtension(
|
|
|
4857
5188
|
if (inbound.kind === "control_command") {
|
|
4858
5189
|
const frame = sdkInboundFrame(inbound.commandJson);
|
|
4859
5190
|
if (frame) {
|
|
5191
|
+
if (syntheticSeamAdmissions >= MAX_SYNTHETIC_SEAM_ADMISSIONS) return;
|
|
5192
|
+
syntheticSeamAdmissions++;
|
|
4860
5193
|
inboundSdkFrame?.(`seam:${inbound.requestId ?? "notification"}`, frame);
|
|
4861
5194
|
return;
|
|
4862
5195
|
}
|
|
@@ -46,10 +46,14 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
46
46
|
* 33 adds action-bound multi-select state rendering and replay-safe option
|
|
47
47
|
* snapshots. Generation 34 converts non-photo image formats (including WebP)
|
|
48
48
|
* into Telegram-compatible photo uploads when possible. Generation 35 adds
|
|
49
|
-
* user-created topic adoption (forum-topic folder picker). Generation 36
|
|
50
|
-
* managed-session replacement to exact native filesystem authority
|
|
49
|
+
* user-created topic adoption (forum-topic folder picker). Generation 36 bound
|
|
50
|
+
* managed-session replacement to exact native filesystem authority; generation
|
|
51
|
+
* 37 retires that binding (revert of #3489, which stalled POSIX artifact
|
|
52
|
+
* cleanup); generation 38 binds exact cleanup to parent and link-count authority;
|
|
53
|
+
* generation 39 hardens exact Bash process-tree ownership, settlement, and
|
|
54
|
+
* descendant cleanup authority.
|
|
51
55
|
*/
|
|
52
|
-
export const DAEMON_GENERATION =
|
|
56
|
+
export const DAEMON_GENERATION = 39;
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
|