@gajae-code/coding-agent 0.15.4 → 0.15.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 +46 -2
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/update-cli.d.ts +27 -5
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +99 -0
- package/dist/types/config/file-lock.d.ts +49 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -4
- package/dist/types/extensibility/shared-events.d.ts +2 -0
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/sdk/broker/broker.d.ts +3 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/cli/session-cli.d.ts +42 -1
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +19 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +19 -5
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +4 -3
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/index.d.ts +6 -5
- package/dist/types/tools/path-utils.d.ts +5 -6
- package/dist/types/tools/tmux-self-injection-guard.d.ts +15 -0
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -3
- package/src/async/job-manager.ts +671 -39
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +3 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/update-cli.ts +142 -6
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +5 -0
- package/src/config/autorouting-tier-map.ts +33 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +775 -59
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2799 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1028 -123
- package/src/config/settings-schema.ts +11 -0
- package/src/config/settings.ts +6 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/policy.ts +1 -1
- package/src/coordinator-mcp/projection-scan.ts +571 -0
- package/src/coordinator-mcp/server.ts +19 -24
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/extensibility/shared-events.ts +2 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree.ts +44 -7
- package/src/gjc-runtime/ralplan-runtime.ts +14 -1
- package/src/gjc-runtime/session-state-lock.ts +1242 -141
- package/src/gjc-runtime/session-state-sidecar.ts +76 -3
- package/src/gjc-runtime/tmux-sessions.ts +145 -48
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +9 -2
- package/src/modes/acp/acp-agent.ts +11 -5
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/status-line/segments.ts +6 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +23 -6
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/prompt-action-autocomplete.ts +26 -47
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +48 -21
- package/src/sdk/broker/lifecycle.ts +32 -6
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
- package/src/sdk/bus/kind-aware-reconciliation.ts +46 -8
- package/src/sdk/bus/prompt-reconciliation.ts +33 -6
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/cli/session-cli.ts +161 -5
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +3 -0
- package/src/sdk/host/host.ts +7 -0
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +1240 -107
- package/src/sdk/host/session-runtime.ts +1029 -115
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +2 -2
- package/src/sdk/protocol/operation-registry.ts +4 -1
- package/src/sdk/router/session-router.ts +297 -111
- package/src/sdk/session.ts +83 -12
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +887 -95
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +64 -26
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +29 -10
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +142 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +786 -67
- package/src/tools/index.ts +7 -5
- package/src/tools/path-utils.ts +77 -29
- package/src/tools/tmux-self-injection-guard.ts +359 -0
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -46,6 +46,8 @@ export interface BrokerSettings {
|
|
|
46
46
|
heartbeatTtlMs?: number;
|
|
47
47
|
/** Broker-owned migration policy. Client lifecycle frames cannot select it. */
|
|
48
48
|
resolveDirectoryMigration?: (_cwd: string) => Promise<DirectoryMigrationPolicy>;
|
|
49
|
+
/** Host model resolver override for lifecycle tests and embedders. */
|
|
50
|
+
resolveModelPin?: SdkHostModelResolver;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
type ResolvedBrokerSettings = {
|
|
@@ -863,7 +865,8 @@ export class Broker {
|
|
|
863
865
|
#completion!: Promise<void>;
|
|
864
866
|
#resolveCompletion!: () => void;
|
|
865
867
|
#rejectCompletion!: (error: unknown) => void;
|
|
866
|
-
|
|
868
|
+
#resolveModelPin: SdkHostModelResolver;
|
|
869
|
+
#ownsResolveModelPin: boolean;
|
|
867
870
|
constructor(settings: BrokerSettings) {
|
|
868
871
|
this.settings = {
|
|
869
872
|
agentDir: settings.agentDir,
|
|
@@ -874,7 +877,8 @@ export class Broker {
|
|
|
874
877
|
};
|
|
875
878
|
this.index = new SessionIndex(settings.agentDir);
|
|
876
879
|
this.ledger = new LifecycleLedger(settings.agentDir);
|
|
877
|
-
this.#
|
|
880
|
+
this.#ownsResolveModelPin = settings.resolveModelPin === undefined;
|
|
881
|
+
this.#resolveModelPin = settings.resolveModelPin ?? createDefaultSdkHostModelResolver(this.settings.agentDir);
|
|
878
882
|
this.#lock = path.join(settings.agentDir, "sdk", "broker.lock");
|
|
879
883
|
const completion = Promise.withResolvers<void>();
|
|
880
884
|
this.#completion = completion.promise;
|
|
@@ -1023,6 +1027,8 @@ export class Broker {
|
|
|
1023
1027
|
this.#resolveCompletion = completion.resolve;
|
|
1024
1028
|
this.#rejectCompletion = completion.reject;
|
|
1025
1029
|
this.#completionTask = null;
|
|
1030
|
+
if (this.#ownsResolveModelPin)
|
|
1031
|
+
this.#resolveModelPin = createDefaultSdkHostModelResolver(this.settings.agentDir);
|
|
1026
1032
|
// A drained queue refuses every later startup by design, so a restarted broker
|
|
1027
1033
|
// needs a new one or it would admit nothing for the rest of the process.
|
|
1028
1034
|
this.#startupAdmissions = new StartupAdmissionQueue(sdkHostStartupConcurrency());
|
|
@@ -1285,25 +1291,35 @@ export class Broker {
|
|
|
1285
1291
|
if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
|
|
1286
1292
|
this.#heartbeatTimer = null;
|
|
1287
1293
|
this.#completionTask = (async () => {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1294
|
+
try {
|
|
1295
|
+
await this.#transport?.stop();
|
|
1296
|
+
this.#transport = null;
|
|
1297
|
+
if (mode === "lost-root")
|
|
1298
|
+
await Promise.race([Promise.allSettled(this.#admitted), Bun.sleep(BROKER_SETTLEMENT_MS)]);
|
|
1299
|
+
else await Promise.allSettled(this.#admitted);
|
|
1300
|
+
this.#publication?.close();
|
|
1301
|
+
this.#publication = null;
|
|
1302
|
+
if (mode === "owned-root" && this.discovery?.ownerId === this.#owner) {
|
|
1303
|
+
try {
|
|
1304
|
+
const disk = JSON.parse(await fs.readFile(brokerDiscoveryPath(this.settings.agentDir), "utf8")) as {
|
|
1305
|
+
ownerId?: string;
|
|
1306
|
+
};
|
|
1307
|
+
if (disk.ownerId === this.#owner) await fs.unlink(brokerDiscoveryPath(this.settings.agentDir));
|
|
1308
|
+
} catch (e) {
|
|
1309
|
+
if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
|
|
1310
|
+
}
|
|
1311
|
+
await this.#releaseOwnedLock();
|
|
1312
|
+
}
|
|
1313
|
+
} finally {
|
|
1314
|
+
const disposeModelPin = this.#ownsResolveModelPin ? this.#resolveModelPin.dispose?.() : undefined;
|
|
1315
|
+
if (mode === "lost-root" && disposeModelPin !== undefined) {
|
|
1316
|
+
void disposeModelPin.catch(() => undefined);
|
|
1317
|
+
await Promise.race([disposeModelPin, Bun.sleep(BROKER_SETTLEMENT_MS)]);
|
|
1318
|
+
} else {
|
|
1319
|
+
await disposeModelPin;
|
|
1303
1320
|
}
|
|
1304
|
-
|
|
1321
|
+
this.discovery = null;
|
|
1305
1322
|
}
|
|
1306
|
-
this.discovery = null;
|
|
1307
1323
|
})();
|
|
1308
1324
|
void this.#completionTask.then(this.#resolveCompletion, this.#rejectCompletion);
|
|
1309
1325
|
return this.#completionTask;
|
|
@@ -1417,14 +1433,25 @@ export class Broker {
|
|
|
1417
1433
|
|
|
1418
1434
|
try {
|
|
1419
1435
|
const endpointPath = path.join(record.locator.stateRoot, "sdk", `${record.sessionId}.json`);
|
|
1420
|
-
const
|
|
1436
|
+
const before = await fs.lstat(endpointPath, { bigint: true });
|
|
1437
|
+
if (!before.isFile()) return error("endpoint_stale", "session endpoint is not a regular file");
|
|
1438
|
+
const source = await fs.readFile(endpointPath, "utf8");
|
|
1439
|
+
const metadata = await fs.lstat(endpointPath, { bigint: true });
|
|
1440
|
+
if (
|
|
1441
|
+
before.dev !== metadata.dev ||
|
|
1442
|
+
before.ino !== metadata.ino ||
|
|
1443
|
+
before.size !== metadata.size ||
|
|
1444
|
+
before.mtimeNs !== metadata.mtimeNs ||
|
|
1445
|
+
before.ctimeNs !== metadata.ctimeNs
|
|
1446
|
+
)
|
|
1447
|
+
return error("endpoint_stale", "session endpoint changed during read");
|
|
1421
1448
|
const endpoint = JSON.parse(source) as Record<string, unknown>;
|
|
1422
1449
|
if (
|
|
1423
1450
|
endpoint.sessionId !== record.sessionId ||
|
|
1424
1451
|
endpoint.pid !== record.pid ||
|
|
1425
1452
|
endpoint.stale === true ||
|
|
1426
1453
|
record.endpointMtimeMs === undefined ||
|
|
1427
|
-
Math.abs(metadata.
|
|
1454
|
+
Math.abs(Number(metadata.mtimeNs) / 1_000_000 - record.endpointMtimeMs) > 0.001
|
|
1428
1455
|
)
|
|
1429
1456
|
return error("endpoint_stale", "session endpoint is stale");
|
|
1430
1457
|
await this.index.refresh();
|
|
@@ -14,7 +14,10 @@ function nativeLifecycle(): typeof import("@gajae-code/natives") {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
import { $credentialEnv, logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
17
|
-
|
|
17
|
+
import {
|
|
18
|
+
loadAcceptedModelPresetRegistry,
|
|
19
|
+
loadAcceptedModelPresetRegistryAsync,
|
|
20
|
+
} from "../../config/model-preset-registry";
|
|
18
21
|
import {
|
|
19
22
|
isModelProfileError,
|
|
20
23
|
type ModelProfileErrorDetails,
|
|
@@ -609,13 +612,36 @@ function text(value: unknown): string | undefined {
|
|
|
609
612
|
return typeof value === "string" && value ? value : undefined;
|
|
610
613
|
}
|
|
611
614
|
|
|
612
|
-
function
|
|
615
|
+
function validateBrokerModelPresetSync(agentDir: string, requestedProfile: string): string | BrokerResponse {
|
|
616
|
+
const modelsConfigFile = ModelsConfigFile.relocate(path.join(agentDir, "models.yml"));
|
|
617
|
+
modelsConfigFile.invalidate();
|
|
618
|
+
const loaded = modelsConfigFile.tryLoad();
|
|
619
|
+
const accepted = loadAcceptedModelPresetRegistry(agentDir);
|
|
620
|
+
const profiles = mergeModelProfiles(loaded.status === "ok" ? loaded.value.profiles : undefined, accepted.profiles);
|
|
621
|
+
try {
|
|
622
|
+
return validateModelProfileName(
|
|
623
|
+
requestedProfile,
|
|
624
|
+
profiles,
|
|
625
|
+
accepted.error ?? (loaded.status === "error" ? loaded.error : undefined),
|
|
626
|
+
);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
if (isModelProfileError(error)) return fail(error.code, error.message, undefined, error.details);
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function validateBrokerModelPreset(agentDir: string, requestedProfile: string): Promise<string | BrokerResponse> {
|
|
613
634
|
const modelsConfigFile = ModelsConfigFile.relocate(path.join(agentDir, "models.yml"));
|
|
614
635
|
modelsConfigFile.invalidate();
|
|
615
636
|
const loaded = modelsConfigFile.tryLoad();
|
|
616
|
-
const
|
|
637
|
+
const accepted = await loadAcceptedModelPresetRegistryAsync(agentDir);
|
|
638
|
+
const profiles = mergeModelProfiles(loaded.status === "ok" ? loaded.value.profiles : undefined, accepted.profiles);
|
|
617
639
|
try {
|
|
618
|
-
return validateModelProfileName(
|
|
640
|
+
return validateModelProfileName(
|
|
641
|
+
requestedProfile,
|
|
642
|
+
profiles,
|
|
643
|
+
accepted.error ?? (loaded.status === "error" ? loaded.error : undefined),
|
|
644
|
+
);
|
|
619
645
|
} catch (error) {
|
|
620
646
|
if (isModelProfileError(error)) return fail(error.code, error.message, undefined, error.details);
|
|
621
647
|
throw error;
|
|
@@ -623,7 +649,7 @@ function validateBrokerModelPreset(agentDir: string, requestedProfile: string):
|
|
|
623
649
|
}
|
|
624
650
|
|
|
625
651
|
export function validateBrokerModelPresetForTest(agentDir: string, requestedProfile: string): string | BrokerResponse {
|
|
626
|
-
return
|
|
652
|
+
return validateBrokerModelPresetSync(agentDir, requestedProfile);
|
|
627
653
|
}
|
|
628
654
|
|
|
629
655
|
function readinessTimeout(input: Input): number | BrokerResponse {
|
|
@@ -4030,7 +4056,7 @@ async function launchInput(
|
|
|
4030
4056
|
if (input.modelPreset !== undefined && (typeof input.modelPreset !== "string" || input.modelPreset.length === 0))
|
|
4031
4057
|
return fail("invalid_input", "modelPreset must be a non-empty exact profile ID.");
|
|
4032
4058
|
if (modelPreset !== undefined) {
|
|
4033
|
-
const validatedModelPreset = validateBrokerModelPreset(broker.settings.agentDir, modelPreset);
|
|
4059
|
+
const validatedModelPreset = await validateBrokerModelPreset(broker.settings.agentDir, modelPreset);
|
|
4034
4060
|
if (typeof validatedModelPreset !== "string") return validatedModelPreset;
|
|
4035
4061
|
modelPreset = validatedModelPreset;
|
|
4036
4062
|
}
|
|
@@ -132,8 +132,8 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
132
132
|
* would retain the old hot polling loop, so replacement is required.
|
|
133
133
|
*/
|
|
134
134
|
export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
|
|
135
|
-
discord:
|
|
136
|
-
slack:
|
|
135
|
+
discord: 71,
|
|
136
|
+
slack: 74,
|
|
137
137
|
};
|
|
138
138
|
|
|
139
139
|
export function chatDaemonGeneration(kind: ChatDaemonKind): number {
|
|
@@ -114,6 +114,10 @@ function readGeneration(value: unknown): number | undefined {
|
|
|
114
114
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function readSequence(value: unknown): number | undefined {
|
|
118
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
/**
|
|
118
122
|
* What one representation of a frame states about an identity it owns.
|
|
119
123
|
*
|
|
@@ -138,7 +142,7 @@ const ABSENT_IDENTITY: IdentityClaim<never> = { state: "absent" };
|
|
|
138
142
|
*/
|
|
139
143
|
function identityClaim<T>(
|
|
140
144
|
frame: Record<string, unknown> | undefined,
|
|
141
|
-
key: "sessionId" | "generation" | "name" | "kind",
|
|
145
|
+
key: "sessionId" | "generation" | "name" | "kind" | "seq",
|
|
142
146
|
read: (value: unknown) => T | undefined,
|
|
143
147
|
): IdentityClaim<T> {
|
|
144
148
|
if (!frame || !Object.hasOwn(frame, key)) return ABSENT_IDENTITY;
|
|
@@ -232,11 +236,17 @@ function correlateFrame(frame: Record<string, unknown>): CorrelatedFrame | undef
|
|
|
232
236
|
identityClaim(payload, "generation", readGeneration),
|
|
233
237
|
);
|
|
234
238
|
if (!generation.ok) return undefined;
|
|
239
|
+
const seq = reconcileIdentity(
|
|
240
|
+
identityClaim(frame, "seq", readSequence),
|
|
241
|
+
identityClaim(payload, "seq", readSequence),
|
|
242
|
+
);
|
|
243
|
+
if (!seq.ok) return undefined;
|
|
235
244
|
return {
|
|
236
245
|
body,
|
|
237
246
|
name: envelopeName.value ?? bodyName,
|
|
238
247
|
sessionId: sessionId.value,
|
|
239
248
|
generation: generation.value,
|
|
249
|
+
seq: seq.value,
|
|
240
250
|
};
|
|
241
251
|
}
|
|
242
252
|
|
|
@@ -21,6 +21,8 @@ import type {
|
|
|
21
21
|
ReconciliationStore,
|
|
22
22
|
} from "./reconciliation-store";
|
|
23
23
|
|
|
24
|
+
const EMPTY_PROMPT_FAILURE = { code: "prompt_failed", message: "Prompt submission failed." } as const;
|
|
25
|
+
|
|
24
26
|
export type { ReconciliationKind };
|
|
25
27
|
|
|
26
28
|
export interface KindCorrelation extends PromptCorrelation {
|
|
@@ -51,8 +53,13 @@ export interface KindAwareReconciliation {
|
|
|
51
53
|
kind: ReconciliationKind,
|
|
52
54
|
correlation: PromptCorrelation | undefined,
|
|
53
55
|
frame:
|
|
54
|
-
| {
|
|
55
|
-
|
|
56
|
+
| {
|
|
57
|
+
type: "agent_start" | "agent_end";
|
|
58
|
+
content?: TurnResultContent;
|
|
59
|
+
hasActivity?: boolean;
|
|
60
|
+
outcome?: SdkPromptTerminalOutcome;
|
|
61
|
+
}
|
|
62
|
+
| { type: "agent_failed"; error: unknown; content?: TurnResultContent; hasActivity?: boolean },
|
|
56
63
|
): Promise<void>;
|
|
57
64
|
claimPendingOutcome(
|
|
58
65
|
kind: ReconciliationKind,
|
|
@@ -340,8 +347,13 @@ export function createKindAwareReconciliation(
|
|
|
340
347
|
kind: ReconciliationKind,
|
|
341
348
|
correlation: PromptCorrelation | undefined,
|
|
342
349
|
frame:
|
|
343
|
-
| {
|
|
344
|
-
|
|
350
|
+
| {
|
|
351
|
+
type: "agent_start" | "agent_end";
|
|
352
|
+
content?: TurnResultContent;
|
|
353
|
+
hasActivity?: boolean;
|
|
354
|
+
outcome?: SdkPromptTerminalOutcome;
|
|
355
|
+
}
|
|
356
|
+
| { type: "agent_failed"; error: unknown; content?: TurnResultContent; hasActivity?: boolean },
|
|
345
357
|
) => {
|
|
346
358
|
if (!correlation) return;
|
|
347
359
|
await queueMutation(candidate => {
|
|
@@ -355,7 +367,13 @@ export function createKindAwareReconciliation(
|
|
|
355
367
|
// as-is (cleanupRecords is intentionally not re-run). First reason wins: a
|
|
356
368
|
// late generic frame never overwrites a specific one. Persisted so the reason
|
|
357
369
|
// survives reconnect/restart reconciliation.
|
|
358
|
-
if (frame.type === "agent_failed"
|
|
370
|
+
if (frame.type === "agent_failed") {
|
|
371
|
+
const failure = sanitizePromptFailure(frame.error);
|
|
372
|
+
if (
|
|
373
|
+
record.error !== undefined &&
|
|
374
|
+
!(record.error.code === "agent_failed" && failure.code !== "agent_failed")
|
|
375
|
+
)
|
|
376
|
+
return { value: undefined, changed: false };
|
|
359
377
|
record.error = sanitizePromptFailure(frame.error);
|
|
360
378
|
return { value: undefined, changed: true };
|
|
361
379
|
}
|
|
@@ -373,7 +391,9 @@ export function createKindAwareReconciliation(
|
|
|
373
391
|
if (frame.type === "agent_failed") {
|
|
374
392
|
// agent_failed is additive diagnostics; agent_end remains the sole
|
|
375
393
|
// terminal lifecycle boundary for the correlated invocation.
|
|
376
|
-
|
|
394
|
+
const failure = sanitizePromptFailure(frame.error);
|
|
395
|
+
if (record.error === undefined || (record.error.code === "agent_failed" && failure.code !== "agent_failed"))
|
|
396
|
+
record.error = failure;
|
|
377
397
|
delete record.deadlineRecoveryPending;
|
|
378
398
|
delete record.deadlineMaxAt;
|
|
379
399
|
return { value: undefined, changed: true };
|
|
@@ -404,7 +424,17 @@ export function createKindAwareReconciliation(
|
|
|
404
424
|
record.receiptState = record.pendingReceiptState ?? "missing";
|
|
405
425
|
record.pendingReceiptState = undefined;
|
|
406
426
|
} else {
|
|
407
|
-
|
|
427
|
+
if (
|
|
428
|
+
kind === "prompt" &&
|
|
429
|
+
record.error === undefined &&
|
|
430
|
+
frame.type === "agent_end" &&
|
|
431
|
+
frame.outcome?.kind !== "stopped" &&
|
|
432
|
+
!frame.content?.text.trim() &&
|
|
433
|
+
!frame.hasActivity
|
|
434
|
+
) {
|
|
435
|
+
record.status = "failed";
|
|
436
|
+
record.error = EMPTY_PROMPT_FAILURE;
|
|
437
|
+
} else record.status = record.error === undefined ? "terminal_ok" : "failed";
|
|
408
438
|
record.receiptState = frame.content?.text?.trim() ? "present" : "missing";
|
|
409
439
|
}
|
|
410
440
|
cleanupRecords(candidate);
|
|
@@ -445,6 +475,7 @@ export function createKindAwareReconciliation(
|
|
|
445
475
|
: undefined;
|
|
446
476
|
const content =
|
|
447
477
|
typeof arg4 === "function" ? arg6 : typeof arg5 === "object" && arg5 !== null && "code" in arg5 ? arg6 : arg5;
|
|
478
|
+
const sanitizedContent = sanitizeTurnResultContent(content);
|
|
448
479
|
await queueMutation(candidate => {
|
|
449
480
|
if (isCurrent !== undefined && !isCurrent()) return { value: undefined, changed: false };
|
|
450
481
|
const record = candidate.get(keyOf(kind, correlation));
|
|
@@ -455,8 +486,15 @@ export function createKindAwareReconciliation(
|
|
|
455
486
|
if (finalOutcome?.kind === "failed") {
|
|
456
487
|
record.status = "failed";
|
|
457
488
|
record.error = recordError ?? { code: finalOutcome.code, message: finalOutcome.message };
|
|
489
|
+
} else if (
|
|
490
|
+
kind === "prompt" &&
|
|
491
|
+
((finalOutcome === undefined && !sanitizedContent?.text.trim()) ||
|
|
492
|
+
(finalOutcome?.kind !== "stopped" && content !== undefined && !sanitizedContent?.text.trim()))
|
|
493
|
+
) {
|
|
494
|
+
record.status = "failed";
|
|
495
|
+
record.error = EMPTY_PROMPT_FAILURE;
|
|
458
496
|
} else record.status = "terminal_ok";
|
|
459
|
-
record.content =
|
|
497
|
+
record.content = sanitizedContent;
|
|
460
498
|
record.outcome = finalOutcome;
|
|
461
499
|
record.receiptState = record.pendingReceiptState ?? "unknown";
|
|
462
500
|
record.pendingOutcome = undefined;
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure } from "../prompt-failure";
|
|
26
|
+
import type { SdkPromptTerminalOutcome } from "../prompt-status";
|
|
26
27
|
import type { ReceiptState } from "../receipt-state";
|
|
27
28
|
|
|
28
29
|
export { PROMPT_FAILURE_CODE_MAX, sanitizePromptFailure };
|
|
29
30
|
export const PROMPT_RECONCILIATION_ACTIVE_CAPACITY = 128;
|
|
30
31
|
export const PROMPT_RECONCILIATION_TERMINAL_CAPACITY = 256;
|
|
32
|
+
const EMPTY_PROMPT_FAILURE = { code: "prompt_failed", message: "Prompt submission failed." } as const;
|
|
31
33
|
|
|
32
34
|
export type PromptReconciliationStatus = "accepted" | "in_flight" | "terminal_ok" | "failed";
|
|
33
35
|
|
|
@@ -101,8 +103,13 @@ export interface PromptReconciliation {
|
|
|
101
103
|
correlation: PromptCorrelation | undefined,
|
|
102
104
|
frame:
|
|
103
105
|
| { type: "agent_start" }
|
|
104
|
-
| {
|
|
105
|
-
|
|
106
|
+
| {
|
|
107
|
+
type: "agent_end";
|
|
108
|
+
finalText?: string;
|
|
109
|
+
hasActivity?: boolean;
|
|
110
|
+
outcome?: SdkPromptTerminalOutcome;
|
|
111
|
+
}
|
|
112
|
+
| { type: "agent_failed"; error: unknown; finalText?: string; hasActivity?: boolean },
|
|
106
113
|
): void;
|
|
107
114
|
lookup(selector: { commandId?: string; turnId?: string; clientRef?: string }): TurnPromptReconciliation;
|
|
108
115
|
cleanup(): void;
|
|
@@ -190,8 +197,13 @@ export function createPromptReconciliation(options: { now?: () => number } = {})
|
|
|
190
197
|
correlation: PromptCorrelation | undefined,
|
|
191
198
|
frame:
|
|
192
199
|
| { type: "agent_start" }
|
|
193
|
-
| {
|
|
194
|
-
|
|
200
|
+
| {
|
|
201
|
+
type: "agent_end";
|
|
202
|
+
finalText?: string;
|
|
203
|
+
hasActivity?: boolean;
|
|
204
|
+
outcome?: SdkPromptTerminalOutcome;
|
|
205
|
+
}
|
|
206
|
+
| { type: "agent_failed"; error: unknown; finalText?: string; hasActivity?: boolean },
|
|
195
207
|
) => {
|
|
196
208
|
if (!correlation) return;
|
|
197
209
|
const record = records.get(keyOf(correlation));
|
|
@@ -202,8 +214,11 @@ export function createPromptReconciliation(options: { now?: () => number } = {})
|
|
|
202
214
|
// the settled record instead of being dropped; it must not resurrect it,
|
|
203
215
|
// so status, terminalAt, retention order, and clientRefIndex stay as-is.
|
|
204
216
|
// First reason wins: a late generic frame never overwrites a specific one.
|
|
205
|
-
if (frame.type === "agent_failed"
|
|
206
|
-
|
|
217
|
+
if (frame.type === "agent_failed") {
|
|
218
|
+
const failure = sanitizePromptFailure(frame.error);
|
|
219
|
+
if (record.error === undefined || (record.error.code === "agent_failed" && failure.code !== "agent_failed"))
|
|
220
|
+
record.error = failure;
|
|
221
|
+
}
|
|
207
222
|
return;
|
|
208
223
|
}
|
|
209
224
|
if (frame.type === "agent_start") {
|
|
@@ -218,6 +233,18 @@ export function createPromptReconciliation(options: { now?: () => number } = {})
|
|
|
218
233
|
if (frame.type === "agent_failed") {
|
|
219
234
|
record.status = "failed";
|
|
220
235
|
record.error = sanitizePromptFailure(frame.error);
|
|
236
|
+
} else if (record.error !== undefined) {
|
|
237
|
+
// Preserve an actionable provider failure already recorded before a late
|
|
238
|
+
// empty agent_end; the empty frame cannot downgrade or replace its cause.
|
|
239
|
+
record.status = "failed";
|
|
240
|
+
} else if (frame.type === "agent_end" && frame.outcome?.kind === "stopped") {
|
|
241
|
+
record.status = "terminal_ok";
|
|
242
|
+
} else if (!frame.finalText?.trim() && !frame.hasActivity) {
|
|
243
|
+
// A prompt cannot claim successful completion without assistant content.
|
|
244
|
+
// Keep the terminal transition first-wins, but fail closed instead of
|
|
245
|
+
// exposing an empty zero-token turn as terminal_ok.
|
|
246
|
+
record.status = "failed";
|
|
247
|
+
record.error = EMPTY_PROMPT_FAILURE;
|
|
221
248
|
} else {
|
|
222
249
|
record.status = "terminal_ok";
|
|
223
250
|
}
|
|
@@ -74,5 +74,9 @@ export function normalizeSlackConversation(record: SlackConversation): SlackConv
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export function acceptsSlackInbound(record: SlackConversation, rootTs: string, endpointGeneration: number): boolean {
|
|
77
|
-
return
|
|
77
|
+
return (
|
|
78
|
+
(record.state === "active" || record.state === "resumed_root") &&
|
|
79
|
+
record.rootTs === rootTs &&
|
|
80
|
+
record.endpointGeneration === endpointGeneration
|
|
81
|
+
);
|
|
78
82
|
}
|
|
@@ -283,8 +283,15 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
283
283
|
* Generation 177 consumes an exact same-window lean settlement receipt only
|
|
284
284
|
* after its autonomous ask lead-in publishes successfully, preventing idle
|
|
285
285
|
* replay without changing the serving protocol.
|
|
286
|
+
* Generation 178: idle publication waits for positioned identity delivery to
|
|
287
|
+
* cross the native writer barrier before the independent broadcast lane.
|
|
288
|
+
* Generation 179 tolerates a bounded consecutive run of refused notification
|
|
289
|
+
* publications in the router instead of cancelling the subscription on the
|
|
290
|
+
* first refusal, so an owner still running the generation-178 router keeps
|
|
291
|
+
* killing a session's mirroring after one transient rejection and must be
|
|
292
|
+
* replaced across this upgrade.
|
|
286
293
|
*/
|
|
287
|
-
export const DAEMON_GENERATION =
|
|
294
|
+
export const DAEMON_GENERATION = 179;
|
|
288
295
|
|
|
289
296
|
/**
|
|
290
297
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|