@gajae-code/coding-agent 0.11.10 → 0.12.0
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 +86 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +86 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +18 -2
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
- package/dist/types/sdk/host/control/operations.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +51 -19
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +59 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +49 -2
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +91 -7
- package/src/commands/team.ts +1 -1
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +24 -0
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +84 -3
- package/src/config/settings.ts +254 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -921
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +979 -71
- package/src/gjc-runtime/state-runtime.ts +50 -12
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +10 -11
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +597 -67
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +76 -5
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +62 -6
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +127 -29
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/lifecycle.ts +107 -7
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +503 -60
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
- package/src/sdk/bus/telegram-daemon.ts +531 -53
- package/src/sdk/host/control/dispatch.ts +7 -1
- package/src/sdk/host/control/operations.ts +2 -1
- package/src/sdk/host/host.ts +25 -3
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +76 -0
- package/src/sdk/protocol/operation-registry.ts +8 -2
- package/src/sdk/session.ts +25 -2
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +720 -190
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +40 -10
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +1372 -42
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +2 -0
- package/src/task/types.ts +22 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kind-aware invocation reconciliation (prompt | skill) with optional durable store.
|
|
3
|
+
* Preserves Q26 admit/first-terminal/capacity/TTL semantics; indexes and caps are per-kind.
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
PROMPT_RECONCILIATION_ACTIVE_CAPACITY,
|
|
7
|
+
PROMPT_RECONCILIATION_TERMINAL_CAPACITY,
|
|
8
|
+
PROMPT_RECONCILIATION_TERMINAL_TTL_MS,
|
|
9
|
+
type PromptCorrelation,
|
|
10
|
+
type PromptReconciliationStatus,
|
|
11
|
+
sanitizePromptFailure,
|
|
12
|
+
type TurnPromptReconciliation,
|
|
13
|
+
} from "./prompt-reconciliation";
|
|
14
|
+
import type { DurableReconciliationRecord, ReconciliationKind, ReconciliationStore } from "./reconciliation-store";
|
|
15
|
+
|
|
16
|
+
export type { ReconciliationKind };
|
|
17
|
+
|
|
18
|
+
export interface KindCorrelation extends PromptCorrelation {
|
|
19
|
+
kind: ReconciliationKind;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface KindAwareReconciliation {
|
|
23
|
+
admit(kind: ReconciliationKind, clientRef?: string): void;
|
|
24
|
+
releaseAdmission(kind: ReconciliationKind, clientRef?: string): void;
|
|
25
|
+
noteAccepted(
|
|
26
|
+
kind: ReconciliationKind,
|
|
27
|
+
correlation: PromptCorrelation,
|
|
28
|
+
clientRef?: string,
|
|
29
|
+
extra?: { skillName?: string },
|
|
30
|
+
): Promise<void>;
|
|
31
|
+
noteTransition(
|
|
32
|
+
kind: ReconciliationKind,
|
|
33
|
+
correlation: PromptCorrelation | undefined,
|
|
34
|
+
frame: { type: "agent_start" | "agent_end" } | { type: "agent_failed"; error: unknown },
|
|
35
|
+
): Promise<void>;
|
|
36
|
+
lookup(
|
|
37
|
+
kind: ReconciliationKind,
|
|
38
|
+
selector: { commandId?: string; turnId?: string; clientRef?: string },
|
|
39
|
+
): TurnPromptReconciliation;
|
|
40
|
+
cleanup(): void;
|
|
41
|
+
activeCount(kind: ReconciliationKind): number;
|
|
42
|
+
/** Hydrate from durable store (call once at session host start). */
|
|
43
|
+
hydrateFromStore(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createKindAwareReconciliation(
|
|
47
|
+
options: { now?: () => number; store?: ReconciliationStore | null } = {},
|
|
48
|
+
): KindAwareReconciliation {
|
|
49
|
+
const now = options.now ?? Date.now;
|
|
50
|
+
const store = options.store ?? null;
|
|
51
|
+
const records = new Map<string, DurableReconciliationRecord>();
|
|
52
|
+
const clientRefIndex = new Map<string, string>(); // `${kind}\0${clientRef}` -> key
|
|
53
|
+
const reservedClientRefs = new Map<ReconciliationKind, Set<string>>();
|
|
54
|
+
const reservations: Array<{ kind: ReconciliationKind; clientRef?: string }> = [];
|
|
55
|
+
|
|
56
|
+
const keyOf = (kind: ReconciliationKind, correlation: PromptCorrelation) =>
|
|
57
|
+
`${kind}:${correlation.commandId}:${correlation.turnId}`;
|
|
58
|
+
const refKey = (kind: ReconciliationKind, clientRef: string) => `${kind}\0${clientRef}`;
|
|
59
|
+
|
|
60
|
+
const reservedFor = (kind: ReconciliationKind) => {
|
|
61
|
+
let set = reservedClientRefs.get(kind);
|
|
62
|
+
if (!set) {
|
|
63
|
+
set = new Set();
|
|
64
|
+
reservedClientRefs.set(kind, set);
|
|
65
|
+
}
|
|
66
|
+
return set;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const remove = (key: string) => {
|
|
70
|
+
const record = records.get(key);
|
|
71
|
+
if (!record) return;
|
|
72
|
+
records.delete(key);
|
|
73
|
+
if (record.clientRef !== undefined) {
|
|
74
|
+
const rk = refKey(record.kind, record.clientRef);
|
|
75
|
+
if (clientRefIndex.get(rk) === key) clientRefIndex.delete(rk);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const cleanup = () => {
|
|
80
|
+
const at = now();
|
|
81
|
+
for (const [key, record] of records)
|
|
82
|
+
if (record.terminalAt !== undefined && record.terminalAt + PROMPT_RECONCILIATION_TERMINAL_TTL_MS <= at)
|
|
83
|
+
remove(key);
|
|
84
|
+
for (const kind of ["prompt", "skill"] as const) {
|
|
85
|
+
const terminalEntries = [...records.entries()].filter(
|
|
86
|
+
([, record]) => record.kind === kind && record.terminalAt !== undefined,
|
|
87
|
+
);
|
|
88
|
+
if (terminalEntries.length <= PROMPT_RECONCILIATION_TERMINAL_CAPACITY) continue;
|
|
89
|
+
terminalEntries.sort((a, b) => (a[1].terminalAt as number) - (b[1].terminalAt as number));
|
|
90
|
+
for (const [key] of terminalEntries.slice(0, terminalEntries.length - PROMPT_RECONCILIATION_TERMINAL_CAPACITY))
|
|
91
|
+
remove(key);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const activeCount = (kind: ReconciliationKind) => {
|
|
96
|
+
let count = 0;
|
|
97
|
+
for (const record of records.values()) if (record.kind === kind && record.terminalAt === undefined) count++;
|
|
98
|
+
return count;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const reservationCount = (kind: ReconciliationKind) => reservations.filter(r => r.kind === kind).length;
|
|
102
|
+
|
|
103
|
+
const consumeReservation = (kind: ReconciliationKind, clientRef?: string) => {
|
|
104
|
+
const index = reservations.findIndex(r => r.kind === kind && r.clientRef === clientRef);
|
|
105
|
+
if (index === -1) return;
|
|
106
|
+
reservations.splice(index, 1);
|
|
107
|
+
if (clientRef !== undefined && !reservations.some(r => r.kind === kind && r.clientRef === clientRef))
|
|
108
|
+
reservedFor(kind).delete(clientRef);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const persist = async () => {
|
|
112
|
+
if (!store) return;
|
|
113
|
+
await store.transact(() => [...records.values()].map(r => ({ ...r })));
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const admit = (kind: ReconciliationKind, clientRef?: string) => {
|
|
117
|
+
cleanup();
|
|
118
|
+
const reserved = reservedFor(kind);
|
|
119
|
+
if (clientRef !== undefined && (clientRefIndex.has(refKey(kind, clientRef)) || reserved.has(clientRef)))
|
|
120
|
+
throw Object.assign(
|
|
121
|
+
new Error("A submission with this clientRef is already retained; never reuse a clientRef for retry."),
|
|
122
|
+
{ code: "client_ref_conflict" },
|
|
123
|
+
);
|
|
124
|
+
if (activeCount(kind) + reservationCount(kind) >= PROMPT_RECONCILIATION_ACTIVE_CAPACITY)
|
|
125
|
+
throw Object.assign(new Error("Too many active submissions; reconcile or await terminal state."), {
|
|
126
|
+
code: "reconciliation_capacity",
|
|
127
|
+
});
|
|
128
|
+
reservations.push({ kind, clientRef });
|
|
129
|
+
if (clientRef !== undefined) reserved.add(clientRef);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const releaseAdmission = (kind: ReconciliationKind, clientRef?: string) => {
|
|
133
|
+
consumeReservation(kind, clientRef);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const noteAccepted = async (
|
|
137
|
+
kind: ReconciliationKind,
|
|
138
|
+
correlation: PromptCorrelation,
|
|
139
|
+
clientRef?: string,
|
|
140
|
+
extra?: { skillName?: string },
|
|
141
|
+
) => {
|
|
142
|
+
cleanup();
|
|
143
|
+
consumeReservation(kind, clientRef);
|
|
144
|
+
const at = now();
|
|
145
|
+
const key = keyOf(kind, correlation);
|
|
146
|
+
const record: DurableReconciliationRecord = {
|
|
147
|
+
kind,
|
|
148
|
+
commandId: correlation.commandId,
|
|
149
|
+
turnId: correlation.turnId,
|
|
150
|
+
...(clientRef !== undefined ? { clientRef } : {}),
|
|
151
|
+
status: "accepted",
|
|
152
|
+
acceptedAt: at,
|
|
153
|
+
...(extra?.skillName ? { skillName: extra.skillName } : {}),
|
|
154
|
+
};
|
|
155
|
+
records.set(key, record);
|
|
156
|
+
if (clientRef !== undefined) clientRefIndex.set(refKey(kind, clientRef), key);
|
|
157
|
+
await persist();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const noteTransition = async (
|
|
161
|
+
kind: ReconciliationKind,
|
|
162
|
+
correlation: PromptCorrelation | undefined,
|
|
163
|
+
frame: { type: "agent_start" | "agent_end" } | { type: "agent_failed"; error: unknown },
|
|
164
|
+
) => {
|
|
165
|
+
if (!correlation) return;
|
|
166
|
+
const record = records.get(keyOf(kind, correlation));
|
|
167
|
+
if (!record || record.terminalAt !== undefined) return;
|
|
168
|
+
if (frame.type === "agent_start") {
|
|
169
|
+
if (record.status === "accepted") {
|
|
170
|
+
record.status = "in_flight";
|
|
171
|
+
record.startedAt = now();
|
|
172
|
+
await persist();
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
record.terminalAt = now();
|
|
177
|
+
if (frame.type === "agent_failed") {
|
|
178
|
+
record.status = "failed";
|
|
179
|
+
record.error = sanitizePromptFailure(frame.error);
|
|
180
|
+
} else {
|
|
181
|
+
record.status = "terminal_ok";
|
|
182
|
+
}
|
|
183
|
+
cleanup();
|
|
184
|
+
await persist();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const lookup = (
|
|
188
|
+
kind: ReconciliationKind,
|
|
189
|
+
selector: { commandId?: string; turnId?: string; clientRef?: string },
|
|
190
|
+
): TurnPromptReconciliation => {
|
|
191
|
+
cleanup();
|
|
192
|
+
const key =
|
|
193
|
+
selector.clientRef !== undefined
|
|
194
|
+
? clientRefIndex.get(refKey(kind, selector.clientRef))
|
|
195
|
+
: selector.commandId !== undefined && selector.turnId !== undefined
|
|
196
|
+
? keyOf(kind, { commandId: selector.commandId, turnId: selector.turnId })
|
|
197
|
+
: undefined;
|
|
198
|
+
const record = key === undefined ? undefined : records.get(key);
|
|
199
|
+
if (!record) return { status: "unknown" };
|
|
200
|
+
const identity = {
|
|
201
|
+
commandId: record.commandId,
|
|
202
|
+
turnId: record.turnId,
|
|
203
|
+
...(record.clientRef !== undefined ? { clientRef: record.clientRef } : {}),
|
|
204
|
+
acceptedAt: record.acceptedAt,
|
|
205
|
+
};
|
|
206
|
+
if (record.status === "accepted") return { status: "accepted", ...identity };
|
|
207
|
+
if (record.status === "in_flight")
|
|
208
|
+
return { status: "in_flight", ...identity, startedAt: record.startedAt as number };
|
|
209
|
+
const terminal = {
|
|
210
|
+
...identity,
|
|
211
|
+
...(record.startedAt !== undefined ? { startedAt: record.startedAt } : {}),
|
|
212
|
+
terminalAt: record.terminalAt as number,
|
|
213
|
+
};
|
|
214
|
+
if (record.status === "terminal_ok") return { status: "terminal_ok", ...terminal };
|
|
215
|
+
return { status: "failed", ...terminal, error: record.error ?? sanitizePromptFailure(undefined) };
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const hydrateFromStore = async () => {
|
|
219
|
+
if (!store) return;
|
|
220
|
+
const loaded = await store.load();
|
|
221
|
+
records.clear();
|
|
222
|
+
clientRefIndex.clear();
|
|
223
|
+
for (const record of loaded) {
|
|
224
|
+
const key = keyOf(record.kind, record);
|
|
225
|
+
records.set(key, record);
|
|
226
|
+
if (record.clientRef !== undefined) clientRefIndex.set(refKey(record.kind, record.clientRef), key);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
admit,
|
|
232
|
+
releaseAdmission,
|
|
233
|
+
noteAccepted,
|
|
234
|
+
noteTransition,
|
|
235
|
+
lookup,
|
|
236
|
+
cleanup,
|
|
237
|
+
activeCount,
|
|
238
|
+
hydrateFromStore,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type { PromptReconciliationStatus };
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* - Terminal records are retained for TERMINAL_TTL_MS and bounded to
|
|
13
13
|
* TERMINAL_CAPACITY, evicted oldest-terminal-first by terminalAt; only then
|
|
14
14
|
* does a lookup honestly report `unknown`.
|
|
15
|
-
* -
|
|
15
|
+
* - Process-local durability floor is the live session process: restart means `unknown`.
|
|
16
|
+
* Session-scoped durable retention (kind-aware store) is provided by reconciliation-store.ts (#3032).
|
|
16
17
|
* - The clientRef index is session-runtime scoped; a ref conflicts only while
|
|
17
18
|
* retained and must never be reused as a retry mechanism.
|
|
18
19
|
* - Terminal transitions settle once: the first terminal outcome wins.
|
|
@@ -116,6 +116,14 @@ export class RateLimitPool<T = unknown> {
|
|
|
116
116
|
return total;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/** Whether any queued item currently matches the predicate. */
|
|
120
|
+
someQueued(predicate: (item: RateLimitItem<T>) => boolean): boolean {
|
|
121
|
+
for (const queue of this.lanes.values()) {
|
|
122
|
+
if (queue.some(entry => predicate(entry.item))) return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
/** Current available token count (after refill at `now`). */
|
|
120
128
|
availableTokens(nowMs: number = this.now()): number {
|
|
121
129
|
this.refill(nowMs);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped durable store for kind-aware invocation reconciliation (#3032/#3035).
|
|
3
|
+
*
|
|
4
|
+
* Path is always a private sibling of the transcript, never under artifactsDir:
|
|
5
|
+
* <dirname(sessionFile)>/.sdk-reconciliation/<safeSessionId>.json
|
|
6
|
+
*
|
|
7
|
+
* Safe session ids only: /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/
|
|
8
|
+
* Atomic write: temp + fsync + rename + 0600. Corrupt → quarantine + empty.
|
|
9
|
+
* Non-terminal records settle to failed/process_restart on bootstrap (recon incomplete).
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs/promises";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import type { PromptCorrelation, PromptReconciliationStatus } from "./prompt-reconciliation";
|
|
14
|
+
|
|
15
|
+
export const RECONCILIATION_STORE_VERSION = 1;
|
|
16
|
+
export const RECONCILIATION_SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
|
+
export const RECONCILIATION_DIR_NAME = ".sdk-reconciliation";
|
|
18
|
+
|
|
19
|
+
export type ReconciliationKind = "prompt" | "skill";
|
|
20
|
+
|
|
21
|
+
export interface DurableReconciliationRecord extends PromptCorrelation {
|
|
22
|
+
kind: ReconciliationKind;
|
|
23
|
+
clientRef?: string;
|
|
24
|
+
status: PromptReconciliationStatus;
|
|
25
|
+
error?: { code: string; message: string };
|
|
26
|
+
acceptedAt: number;
|
|
27
|
+
startedAt?: number;
|
|
28
|
+
terminalAt?: number;
|
|
29
|
+
/** Skill-only safe token; never skill args bodies. */
|
|
30
|
+
skillName?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ReconciliationStoreDocument {
|
|
34
|
+
version: typeof RECONCILIATION_STORE_VERSION;
|
|
35
|
+
sessionId: string;
|
|
36
|
+
records: DurableReconciliationRecord[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReconciliationStoreFs {
|
|
40
|
+
mkdir(directory: string, options: { recursive: true; mode: number }): Promise<unknown>;
|
|
41
|
+
readFile(file: string, encoding: "utf8"): Promise<string>;
|
|
42
|
+
writeFile(file: string, data: string, options: { mode: number }): Promise<void>;
|
|
43
|
+
rename(from: string, to: string): Promise<void>;
|
|
44
|
+
unlink(file: string): Promise<void>;
|
|
45
|
+
open(
|
|
46
|
+
file: string,
|
|
47
|
+
flags: string,
|
|
48
|
+
): Promise<{
|
|
49
|
+
sync(): Promise<void>;
|
|
50
|
+
close(): Promise<void>;
|
|
51
|
+
writeFile(data: string, encoding: "utf8"): Promise<void>;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const nodeFs: ReconciliationStoreFs = {
|
|
56
|
+
mkdir: fs.mkdir,
|
|
57
|
+
readFile: fs.readFile,
|
|
58
|
+
writeFile: fs.writeFile,
|
|
59
|
+
rename: fs.rename,
|
|
60
|
+
unlink: fs.unlink,
|
|
61
|
+
open: fs.open as ReconciliationStoreFs["open"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function isSafeReconciliationSessionId(sessionId: string): boolean {
|
|
65
|
+
return RECONCILIATION_SESSION_ID_PATTERN.test(sessionId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Derive private store path; throws if sessionId is unsafe (path escape). */
|
|
69
|
+
export function reconciliationStorePath(sessionFile: string, sessionId: string): string {
|
|
70
|
+
if (!isSafeReconciliationSessionId(sessionId))
|
|
71
|
+
throw Object.assign(new Error("Unsafe session id for reconciliation store path."), {
|
|
72
|
+
code: "invalid_input",
|
|
73
|
+
});
|
|
74
|
+
return path.join(path.dirname(sessionFile), RECONCILIATION_DIR_NAME, `${sessionId}.json`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
78
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseDocument(raw: string, expectedSessionId: string): ReconciliationStoreDocument {
|
|
82
|
+
const value = JSON.parse(raw) as unknown;
|
|
83
|
+
if (!isRecord(value) || value.version !== RECONCILIATION_STORE_VERSION)
|
|
84
|
+
throw new Error("invalid reconciliation store version");
|
|
85
|
+
if (value.sessionId !== expectedSessionId) throw new Error("session id mismatch");
|
|
86
|
+
if (!Array.isArray(value.records)) throw new Error("invalid records");
|
|
87
|
+
return value as unknown as ReconciliationStoreDocument;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Settle non-terminal durable records after process death: failed/process_restart.
|
|
92
|
+
* Meaning: reconciliation incomplete — not proof of agent failure.
|
|
93
|
+
*/
|
|
94
|
+
export function settleProcessRestart(
|
|
95
|
+
records: DurableReconciliationRecord[],
|
|
96
|
+
now: number,
|
|
97
|
+
): DurableReconciliationRecord[] {
|
|
98
|
+
return records.map(record => {
|
|
99
|
+
if (record.terminalAt !== undefined) return record;
|
|
100
|
+
return {
|
|
101
|
+
...record,
|
|
102
|
+
status: "failed",
|
|
103
|
+
terminalAt: now,
|
|
104
|
+
error: { code: "process_restart", message: "Reconciliation incomplete after process restart." },
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ReconciliationStore {
|
|
110
|
+
readonly path: string | null;
|
|
111
|
+
readonly sessionId: string;
|
|
112
|
+
/** Serialize mutations; reload not required for single-process host (in-memory + write). */
|
|
113
|
+
transact(mutator: (records: DurableReconciliationRecord[]) => DurableReconciliationRecord[]): Promise<void>;
|
|
114
|
+
load(): Promise<DurableReconciliationRecord[]>;
|
|
115
|
+
/** Snapshot currently held in memory after last load/transact. */
|
|
116
|
+
snapshot(): DurableReconciliationRecord[];
|
|
117
|
+
delete(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function createReconciliationStore(options: {
|
|
121
|
+
sessionFile: string | null | undefined;
|
|
122
|
+
sessionId: string;
|
|
123
|
+
fs?: ReconciliationStoreFs;
|
|
124
|
+
now?: () => number;
|
|
125
|
+
}): ReconciliationStore {
|
|
126
|
+
const fileFs = options.fs ?? nodeFs;
|
|
127
|
+
const now = options.now ?? Date.now;
|
|
128
|
+
const sessionId = options.sessionId;
|
|
129
|
+
const filePath =
|
|
130
|
+
options.sessionFile && isSafeReconciliationSessionId(sessionId)
|
|
131
|
+
? reconciliationStorePath(options.sessionFile, sessionId)
|
|
132
|
+
: null;
|
|
133
|
+
|
|
134
|
+
let memory: DurableReconciliationRecord[] = [];
|
|
135
|
+
let chain: Promise<void> = Promise.resolve();
|
|
136
|
+
|
|
137
|
+
const writeAtomic = async (document: ReconciliationStoreDocument): Promise<void> => {
|
|
138
|
+
if (!filePath) return;
|
|
139
|
+
const directory = path.dirname(filePath);
|
|
140
|
+
await fileFs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
141
|
+
const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
142
|
+
try {
|
|
143
|
+
await fileFs.writeFile(temporary, `${JSON.stringify(document)}\n`, { mode: 0o600 });
|
|
144
|
+
try {
|
|
145
|
+
const handle = await fileFs.open(temporary, "r+");
|
|
146
|
+
try {
|
|
147
|
+
await handle.sync();
|
|
148
|
+
} finally {
|
|
149
|
+
await handle.close();
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
// fsync optional on some fs seams
|
|
153
|
+
}
|
|
154
|
+
await fileFs.rename(temporary, filePath);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await fileFs.unlink(temporary).catch(() => {});
|
|
157
|
+
throw Object.assign(error instanceof Error ? error : new Error("reconciliation persist failed"), {
|
|
158
|
+
code: "reconciliation_persist_failed",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const load = async (): Promise<DurableReconciliationRecord[]> => {
|
|
164
|
+
if (!filePath) {
|
|
165
|
+
memory = [];
|
|
166
|
+
return memory;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const raw = await fileFs.readFile(filePath, "utf8");
|
|
170
|
+
const document = parseDocument(raw, sessionId);
|
|
171
|
+
memory = settleProcessRestart(document.records, now());
|
|
172
|
+
// Persist settled state so restart is sticky.
|
|
173
|
+
if (memory.some((r, i) => r !== document.records[i]))
|
|
174
|
+
await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: memory });
|
|
175
|
+
return memory;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
178
|
+
memory = [];
|
|
179
|
+
return memory;
|
|
180
|
+
}
|
|
181
|
+
// Corrupt → quarantine
|
|
182
|
+
try {
|
|
183
|
+
await fileFs.rename(filePath, `${filePath}.corrupt.${now()}`);
|
|
184
|
+
} catch {
|
|
185
|
+
// ignore
|
|
186
|
+
}
|
|
187
|
+
memory = [];
|
|
188
|
+
return memory;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const transact = async (
|
|
193
|
+
mutator: (records: DurableReconciliationRecord[]) => DurableReconciliationRecord[],
|
|
194
|
+
): Promise<void> => {
|
|
195
|
+
const run = async () => {
|
|
196
|
+
const next = mutator(memory.map(r => ({ ...r })));
|
|
197
|
+
memory = next;
|
|
198
|
+
await writeAtomic({ version: RECONCILIATION_STORE_VERSION, sessionId, records: memory });
|
|
199
|
+
};
|
|
200
|
+
const pending = chain.then(run, run);
|
|
201
|
+
chain = pending.then(
|
|
202
|
+
() => undefined,
|
|
203
|
+
() => undefined,
|
|
204
|
+
);
|
|
205
|
+
await pending;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const deleteStore = async (): Promise<void> => {
|
|
209
|
+
memory = [];
|
|
210
|
+
if (!filePath) return;
|
|
211
|
+
await fileFs.unlink(filePath).catch(error => {
|
|
212
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
path: filePath,
|
|
218
|
+
sessionId,
|
|
219
|
+
transact,
|
|
220
|
+
load,
|
|
221
|
+
snapshot: () => memory.map(r => ({ ...r })),
|
|
222
|
+
delete: deleteStore,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -39,12 +39,14 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
39
39
|
* Generation 28 rejects special files before retained native authority opens.
|
|
40
40
|
* Generation 29 adds serving-epoch compatibility, sidecar heartbeat, root GC,
|
|
41
41
|
* and Bot API cooldown structural fixes (#2956, #2960, #3048).
|
|
42
|
+
* Generation 30 adds opt-in tool activity delivery, closed lifecycle phases,
|
|
43
|
+
* and capability-versioned mixed-host compatibility.
|
|
42
44
|
*/
|
|
43
|
-
export const DAEMON_GENERATION =
|
|
45
|
+
export const DAEMON_GENERATION = 30;
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
|
|
47
|
-
* all builds published before this field existed;
|
|
48
|
-
* convergence and reload of compatible live predecessors.
|
|
49
|
+
* all builds published before this field existed; epoch 2 covered generation 29;
|
|
50
|
+
* bump this to force serving convergence and reload of compatible live predecessors.
|
|
49
51
|
*/
|
|
50
|
-
export const SERVING_EPOCH =
|
|
52
|
+
export const SERVING_EPOCH = 3;
|