@gajae-code/coding-agent 0.16.0 → 0.16.1
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 +41 -1
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/model-registry.d.ts +16 -0
- package/dist/types/config/settings-schema.d.ts +17 -7
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -1
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +17 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/rows.d.ts +8 -2
- package/dist/types/sdk/cli/session-cli.d.ts +2 -1
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +18 -17
- package/dist/types/sdk/lifecycle-session.d.ts +1 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +4 -0
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +47 -7
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +14 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +12 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/index.d.ts +6 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +7 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/update-cli.ts +63 -12
- package/src/commands/sdk.ts +22 -7
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/model-preset-registry.ts +177 -18
- package/src/config/model-registry.ts +268 -42
- package/src/config/settings-schema.ts +19 -7
- package/src/config/settings.ts +46 -15
- package/src/coordinator-mcp/projection-scan.ts +6 -6
- package/src/coordinator-mcp/server.ts +21 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/session-state-lock.ts +154 -28
- package/src/gjc-runtime/session-state-sidecar.ts +10 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +5 -5
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +10 -1
- package/src/sdk/broker/ensure.ts +76 -3
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +254 -34
- package/src/sdk/broker/spawn-substrate.ts +45 -2
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +17 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +111 -24
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +33 -3
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +33 -21
- package/src/sdk/host/control/dispatch.ts +2 -2
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +2 -0
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +463 -8
- package/src/sdk/host/session-runtime.ts +383 -54
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle-session.ts +7 -2
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +54 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +152 -59
- package/src/sdk/session.ts +60 -41
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1033 -260
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/session-manager.ts +517 -77
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +156 -33
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/index.ts +6 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -14,7 +14,7 @@ import * as fs from "node:fs/promises";
|
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import type { PromptReconciliationStatus, SdkPromptTerminalOutcome } from "../prompt-status";
|
|
16
16
|
import type { ReceiptState } from "../receipt-state";
|
|
17
|
-
import { TURN_RESULT_CONTENT_MAX_BYTES, type TurnResultContent } from "../turn-result";
|
|
17
|
+
import { reportableTurnResultContent, TURN_RESULT_CONTENT_MAX_BYTES, type TurnResultContent } from "../turn-result";
|
|
18
18
|
import type { PromptCorrelation } from "./prompt-reconciliation";
|
|
19
19
|
|
|
20
20
|
export const RECONCILIATION_STORE_VERSION = 2;
|
|
@@ -25,6 +25,16 @@ export const RECONCILIATION_DIR_NAME = ".sdk-reconciliation";
|
|
|
25
25
|
// durable document can never match a real retry and would make the reserved key
|
|
26
26
|
// invisible (and reusable for an unrelated later abort), so reload rejects it.
|
|
27
27
|
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
28
|
+
const SAFE_FAILURE_CODE_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
29
|
+
const DURABLE_FAILURE_MESSAGE_MAX_BYTES = 4_096;
|
|
30
|
+
|
|
31
|
+
function isSafeFailureDiagnostic(value: unknown): value is { code: string; message: string } {
|
|
32
|
+
if (!isRecord(value) || typeof value.code !== "string" || !SAFE_FAILURE_CODE_RE.test(value.code)) return false;
|
|
33
|
+
return (
|
|
34
|
+
typeof value.message === "string" &&
|
|
35
|
+
new TextEncoder().encode(value.message).length <= DURABLE_FAILURE_MESSAGE_MAX_BYTES
|
|
36
|
+
);
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
export function resolveReconciliationSessionFile(
|
|
30
40
|
sessionFile: string | undefined | null,
|
|
@@ -207,11 +217,7 @@ function isValidRecord(value: unknown): boolean {
|
|
|
207
217
|
const settled = value.status !== "dispatching";
|
|
208
218
|
if (settled !== (typeof value.settledAt === "number" && Number.isFinite(value.settledAt))) return false;
|
|
209
219
|
if ((value.status === "rejected" || value.status === "uncertain") !== (value.error !== undefined)) return false;
|
|
210
|
-
if (
|
|
211
|
-
value.error !== undefined &&
|
|
212
|
-
(!isRecord(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string")
|
|
213
|
-
)
|
|
214
|
-
return false;
|
|
220
|
+
if (value.error !== undefined && !isSafeFailureDiagnostic(value.error)) return false;
|
|
215
221
|
return value.receiptState === undefined && value.outcome === undefined;
|
|
216
222
|
}
|
|
217
223
|
if (kind !== "prompt" && kind !== "skill") return false;
|
|
@@ -274,8 +280,7 @@ function isValidRecord(value: unknown): boolean {
|
|
|
274
280
|
return false;
|
|
275
281
|
}
|
|
276
282
|
if (value.error !== undefined) {
|
|
277
|
-
if (!
|
|
278
|
-
if (typeof value.error.code !== "string" || typeof value.error.message !== "string") return false;
|
|
283
|
+
if (!isSafeFailureDiagnostic(value.error)) return false;
|
|
279
284
|
}
|
|
280
285
|
return [outcome, pendingOutcome].every(candidate => {
|
|
281
286
|
if (candidate === undefined) return true;
|
|
@@ -286,12 +291,13 @@ function isValidRecord(value: unknown): boolean {
|
|
|
286
291
|
candidate.reason as string,
|
|
287
292
|
) && ["agent", "client_cancel"].includes(candidate.provenance as string)
|
|
288
293
|
);
|
|
289
|
-
|
|
290
|
-
candidate.kind
|
|
291
|
-
["prompt_failed", "prompt_deadline_exceeded"].includes(candidate.code as string)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
if (
|
|
295
|
+
candidate.kind !== "failed" ||
|
|
296
|
+
!["prompt_failed", "prompt_deadline_exceeded"].includes(candidate.code as string) ||
|
|
297
|
+
!["agent_failed", "deadline"].includes(candidate.provenance as string)
|
|
298
|
+
)
|
|
299
|
+
return false;
|
|
300
|
+
return isSafeFailureDiagnostic({ code: candidate.code, message: candidate.message });
|
|
295
301
|
});
|
|
296
302
|
}
|
|
297
303
|
/** Terminal scope validation: bounded origin/fence/settlement fields only. */
|
|
@@ -361,8 +367,9 @@ function isValidTerminalScope(value: unknown): boolean {
|
|
|
361
367
|
responseState !== "failed"
|
|
362
368
|
)
|
|
363
369
|
return false;
|
|
364
|
-
if (typeof responsePayloadHash !== "string" || !responsePayloadHash) return false;
|
|
365
|
-
if (replayPayloadHash !== undefined && (typeof replayPayloadHash !== "string" || !replayPayloadHash))
|
|
370
|
+
if (typeof responsePayloadHash !== "string" || !SHA256_RE.test(responsePayloadHash)) return false;
|
|
371
|
+
if (replayPayloadHash !== undefined && (typeof replayPayloadHash !== "string" || !SHA256_RE.test(replayPayloadHash)))
|
|
372
|
+
return false;
|
|
366
373
|
if (typeof idempotencyKeyHash !== "string" || !SHA256_RE.test(idempotencyKeyHash)) return false;
|
|
367
374
|
if (typeof idempotencyInputHash !== "string" || !SHA256_RE.test(idempotencyInputHash)) return false;
|
|
368
375
|
if (terminalPublished !== undefined && typeof terminalPublished !== "boolean") return false;
|
|
@@ -406,12 +413,12 @@ function isValidEvictedTerminalKeyEntry(value: unknown): value is EvictedTermina
|
|
|
406
413
|
return false;
|
|
407
414
|
if (
|
|
408
415
|
value.responsePayloadHash !== undefined &&
|
|
409
|
-
(typeof value.responsePayloadHash !== "string" || !value.responsePayloadHash)
|
|
416
|
+
(typeof value.responsePayloadHash !== "string" || !SHA256_RE.test(value.responsePayloadHash))
|
|
410
417
|
)
|
|
411
418
|
return false;
|
|
412
419
|
if (
|
|
413
420
|
value.replayPayloadHash !== undefined &&
|
|
414
|
-
(typeof value.replayPayloadHash !== "string" || !value.replayPayloadHash)
|
|
421
|
+
(typeof value.replayPayloadHash !== "string" || !SHA256_RE.test(value.replayPayloadHash))
|
|
415
422
|
)
|
|
416
423
|
return false;
|
|
417
424
|
if (value.terminalPublished !== undefined && typeof value.terminalPublished !== "boolean") return false;
|
|
@@ -550,6 +557,14 @@ export function settleProcessRestart(
|
|
|
550
557
|
records: DurableReconciliationRecord[],
|
|
551
558
|
now: number,
|
|
552
559
|
): DurableReconciliationRecord[] {
|
|
560
|
+
const isDeadlineOutcome = (outcome: SdkPromptTerminalOutcome | undefined): boolean =>
|
|
561
|
+
outcome?.kind === "failed" && outcome.provenance === "deadline";
|
|
562
|
+
const providerFailureOutcome = (error: { code: string; message: string }): SdkPromptTerminalOutcome => ({
|
|
563
|
+
kind: "failed",
|
|
564
|
+
code: "prompt_failed",
|
|
565
|
+
message: error.message,
|
|
566
|
+
provenance: "agent_failed",
|
|
567
|
+
});
|
|
553
568
|
return records.map(record => {
|
|
554
569
|
if (record.kind === "steer") {
|
|
555
570
|
if (record.status !== "dispatching") return record;
|
|
@@ -569,29 +584,38 @@ export function settleProcessRestart(
|
|
|
569
584
|
return record;
|
|
570
585
|
}
|
|
571
586
|
if (record.pendingOutcome !== undefined || record.kind === "prompt") {
|
|
587
|
+
const { pendingOutcome, pendingReceiptState, ...settledRecord } = record;
|
|
588
|
+
const providerErrorRecord = record.error;
|
|
589
|
+
const providerError =
|
|
590
|
+
providerErrorRecord !== undefined && providerErrorRecord.code !== "prompt_deadline_exceeded";
|
|
591
|
+
const preserveProviderError = providerError;
|
|
572
592
|
const outcome: SdkPromptTerminalOutcome =
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
message: record.error.message,
|
|
578
|
-
provenance: "agent_failed",
|
|
579
|
-
}
|
|
580
|
-
: (record.pendingOutcome ?? {
|
|
593
|
+
providerError &&
|
|
594
|
+
(pendingOutcome === undefined || isDeadlineOutcome(pendingOutcome) || pendingOutcome.kind === "stopped")
|
|
595
|
+
? providerFailureOutcome(providerErrorRecord)
|
|
596
|
+
: (pendingOutcome ?? {
|
|
581
597
|
kind: "failed",
|
|
582
598
|
code: "prompt_failed",
|
|
583
599
|
message: "Prompt did not complete before process restart.",
|
|
584
600
|
provenance: "agent_failed",
|
|
585
601
|
});
|
|
602
|
+
const preservedError =
|
|
603
|
+
providerError && preserveProviderError
|
|
604
|
+
? providerErrorRecord
|
|
605
|
+
: outcome.kind === "failed"
|
|
606
|
+
? { code: outcome.code, message: outcome.message }
|
|
607
|
+
: undefined;
|
|
608
|
+
const receiptState = reportableTurnResultContent(record.content)
|
|
609
|
+
? "present"
|
|
610
|
+
: (pendingReceiptState ?? (outcome.kind === "stopped" ? "missing" : "unknown"));
|
|
586
611
|
return {
|
|
587
|
-
...
|
|
612
|
+
...settledRecord,
|
|
588
613
|
status: outcome.kind === "stopped" ? "terminal_ok" : "failed",
|
|
589
614
|
terminalAt: now,
|
|
590
615
|
outcome,
|
|
591
|
-
receiptState
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
...(outcome.kind === "failed" ? { error: { code: outcome.code, message: outcome.message } } : {}),
|
|
616
|
+
receiptState,
|
|
617
|
+
|
|
618
|
+
...(preservedError !== undefined ? { error: preservedError } : { error: undefined }),
|
|
595
619
|
};
|
|
596
620
|
}
|
|
597
621
|
return {
|
|
@@ -6,7 +6,7 @@ export interface SlackInboundDispatchReceipt {
|
|
|
6
6
|
interactionId: string;
|
|
7
7
|
retryKey: string;
|
|
8
8
|
eventContext?: string;
|
|
9
|
-
kind: "action" | "command";
|
|
9
|
+
kind: "action" | "command" | "message";
|
|
10
10
|
actionId?: string;
|
|
11
11
|
/** The endpoint generation and SDK effect captured before Socket Mode ACK. */
|
|
12
12
|
endpointGeneration: number;
|
|
@@ -145,13 +145,14 @@ type SlackInboundRouting = {
|
|
|
145
145
|
interactionId: string;
|
|
146
146
|
retryKey: string;
|
|
147
147
|
eventContext?: string;
|
|
148
|
-
kind: "action" | "command";
|
|
148
|
+
kind: "action" | "command" | "message";
|
|
149
149
|
actionId?: string;
|
|
150
150
|
};
|
|
151
151
|
|
|
152
152
|
type SlackInboundEffectPayload =
|
|
153
153
|
| { type: "reply"; id: string; answer: string; idempotencyKey: string; routing: SlackInboundRouting }
|
|
154
|
-
| { type: "command"; content: string; idempotencyKey: string; routing: SlackInboundRouting }
|
|
154
|
+
| { type: "command"; content: string; idempotencyKey: string; routing: SlackInboundRouting }
|
|
155
|
+
| { type: "user_message"; sessionId: string; text: string; idempotencyKey: string; routing: SlackInboundRouting };
|
|
155
156
|
|
|
156
157
|
type SlackRootPublication = {
|
|
157
158
|
conversation: SlackConversation;
|
|
@@ -169,7 +170,11 @@ function messageFromEnvelope(
|
|
|
169
170
|
| undefined {
|
|
170
171
|
if (!payload || typeof payload !== "object") return undefined;
|
|
171
172
|
const eventPayload = payload as EventsPayload;
|
|
172
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
(eventPayload.type !== "events_api" && eventPayload.type !== "event_callback") ||
|
|
175
|
+
!eventPayload.event ||
|
|
176
|
+
eventPayload.event.type !== "message"
|
|
177
|
+
)
|
|
173
178
|
return undefined;
|
|
174
179
|
const eventId = text(eventPayload.event_id);
|
|
175
180
|
const teamId = text(eventPayload.team_id);
|
|
@@ -191,6 +196,10 @@ function nextRecord(record: SlackConversation, update: Partial<SlackConversation
|
|
|
191
196
|
return normalizeSlackConversation({ ...record, ...update, generation: record.generation + 1 });
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
function replyableActionId(actionId: string | undefined): string | undefined {
|
|
200
|
+
return actionId?.startsWith("idle:") ? undefined : actionId;
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
/**
|
|
195
204
|
* Slack Socket Mode notification daemon. Accepted inbound SDK effects are
|
|
196
205
|
* persisted before acknowledgement, including their replay payload and captured
|
|
@@ -449,7 +458,7 @@ export class SlackNotificationDaemon {
|
|
|
449
458
|
const actorId = text(inbound.event.user);
|
|
450
459
|
if (
|
|
451
460
|
inbound.event.bot_id ||
|
|
452
|
-
inbound.event.subtype
|
|
461
|
+
inbound.event.subtype !== undefined ||
|
|
453
462
|
actorId === this.options.botUserId ||
|
|
454
463
|
!actorId ||
|
|
455
464
|
!(await this.#actorAuthorized(actorId))
|
|
@@ -852,7 +861,7 @@ export class SlackNotificationDaemon {
|
|
|
852
861
|
publicationId === undefined
|
|
853
862
|
? undefined
|
|
854
863
|
: await this.#publicationAttempt(
|
|
855
|
-
`root:${sessionId}:${publicationId}`,
|
|
864
|
+
`root:${this.options.teamId}:${this.options.channelId}:${sessionId}:${publicationId}`,
|
|
856
865
|
clientMsgId => `root:${sessionId}:${clientMsgId}`,
|
|
857
866
|
);
|
|
858
867
|
const existing = await this.findSession(sessionId, false);
|
|
@@ -1039,16 +1048,21 @@ export class SlackNotificationDaemon {
|
|
|
1039
1048
|
return await this.#track(this.#close(sessionId, marker, true, endpointGeneration));
|
|
1040
1049
|
}
|
|
1041
1050
|
|
|
1042
|
-
async recoverCleanup(sessionId: string, endpointGeneration: number): Promise<
|
|
1051
|
+
async recoverCleanup(sessionId: string, endpointGeneration: number, authorityId?: string): Promise<boolean> {
|
|
1043
1052
|
const found = await this.findSession(sessionId, true);
|
|
1053
|
+
if (!found) return true;
|
|
1054
|
+
if (found.record.endpointGeneration !== endpointGeneration) return true;
|
|
1055
|
+
if (found.record.state === "closed_marker" || found.record.state === "error") return true;
|
|
1044
1056
|
if (
|
|
1045
|
-
|
|
1046
|
-
found.record.
|
|
1047
|
-
found.record.endpointGeneration !== endpointGeneration
|
|
1057
|
+
(found.record.state === "posting_root" || found.record.state === "resumed_root") &&
|
|
1058
|
+
found.record.attachmentAuthorityId === authorityId
|
|
1048
1059
|
)
|
|
1049
|
-
return;
|
|
1060
|
+
return true;
|
|
1061
|
+
if (found.record.state !== "active") return false;
|
|
1062
|
+
if (!found.record.rootTs) return false;
|
|
1063
|
+
if (found.record.attachmentAuthorityId === authorityId && found.record.cleanupEffectId === undefined) return true;
|
|
1050
1064
|
const effectId = `close-marker-cleanup:${sessionId}:${found.record.clientMsgId ?? found.record.rootTs}`;
|
|
1051
|
-
if (found.record.cleanupEffectId !== effectId) return;
|
|
1065
|
+
if (found.record.cleanupEffectId !== effectId) return false;
|
|
1052
1066
|
const effect = await this.#journal.read(effectId);
|
|
1053
1067
|
if (!effect || (effect.state === "terminal" && !effect.receipt?.messageId && !effect.receipt?.timestamp)) {
|
|
1054
1068
|
await this.store.transact(found.key, current =>
|
|
@@ -1056,9 +1070,10 @@ export class SlackNotificationDaemon {
|
|
|
1056
1070
|
? nextRecord(current, { cleanupEffectId: undefined, updatedAt: this.#now() })
|
|
1057
1071
|
: current,
|
|
1058
1072
|
);
|
|
1059
|
-
return;
|
|
1073
|
+
return false;
|
|
1060
1074
|
}
|
|
1061
1075
|
await this.close(sessionId, "Session closed.", endpointGeneration);
|
|
1076
|
+
return true;
|
|
1062
1077
|
}
|
|
1063
1078
|
|
|
1064
1079
|
async retireAttachment(sessionId: string, endpointGeneration: number): Promise<void> {
|
|
@@ -1209,7 +1224,7 @@ export class SlackNotificationDaemon {
|
|
|
1209
1224
|
publicationId === undefined
|
|
1210
1225
|
? undefined
|
|
1211
1226
|
: await this.#publicationAttempt(
|
|
1212
|
-
`root:${sessionId}:${publicationId}`,
|
|
1227
|
+
`root:${this.options.teamId}:${this.options.channelId}:${sessionId}:${publicationId}`,
|
|
1213
1228
|
clientMsgId => `root:${sessionId}:${clientMsgId}`,
|
|
1214
1229
|
);
|
|
1215
1230
|
return (await this.#resumeWithRootPublication(sessionId, body, endpointGeneration, rootPublication?.clientMsgId))
|
|
@@ -1389,7 +1404,10 @@ export class SlackNotificationDaemon {
|
|
|
1389
1404
|
const interactionId = text(inbound.event.client_msg_id) ?? inbound.eventId;
|
|
1390
1405
|
const retryKey = `${inbound.eventId}:${interactionId}`;
|
|
1391
1406
|
const inboundText = text(inbound.event.text);
|
|
1392
|
-
|
|
1407
|
+
if (!inboundText) return undefined;
|
|
1408
|
+
const command = inboundText.startsWith("/sdk ");
|
|
1409
|
+
const actionId = command ? undefined : replyableActionId(record.pendingActionId);
|
|
1410
|
+
const kind: SlackInboundRouting["kind"] = command ? "command" : actionId ? "action" : "message";
|
|
1393
1411
|
|
|
1394
1412
|
const idempotencyKey = `slack:${inbound.teamId}:${inbound.channelId}:${inbound.rootTs}:${actorId}:${inbound.eventId}:${interactionId}`;
|
|
1395
1413
|
const effectId = `inbound:${inbound.teamId}:${inbound.channelId}:${inbound.rootTs}:${actorId}:${inbound.eventId}:${interactionId}`;
|
|
@@ -1403,21 +1421,26 @@ export class SlackNotificationDaemon {
|
|
|
1403
1421
|
actorId,
|
|
1404
1422
|
retryKey,
|
|
1405
1423
|
eventContext: inbound.eventContext,
|
|
1406
|
-
kind
|
|
1407
|
-
...(
|
|
1424
|
+
kind,
|
|
1425
|
+
...(actionId ? { actionId } : {}),
|
|
1408
1426
|
};
|
|
1409
|
-
const initialPayload: SlackInboundEffectPayload
|
|
1410
|
-
? { type: "command", content: inboundText
|
|
1411
|
-
:
|
|
1412
|
-
? { type: "reply", id:
|
|
1413
|
-
:
|
|
1427
|
+
const initialPayload: SlackInboundEffectPayload = command
|
|
1428
|
+
? { type: "command", content: inboundText, idempotencyKey, routing }
|
|
1429
|
+
: actionId
|
|
1430
|
+
? { type: "reply", id: actionId, answer: inboundText, idempotencyKey, routing }
|
|
1431
|
+
: { type: "user_message", sessionId: record.sessionId, text: inboundText, idempotencyKey, routing };
|
|
1414
1432
|
const existingEffect = await this.#journal.read<SlackInboundEffectPayload>(effectId);
|
|
1415
1433
|
const payload = existingEffect?.payload ?? initialPayload;
|
|
1416
|
-
|
|
1434
|
+
const dispatchKind = payload.routing.kind;
|
|
1417
1435
|
await this.#rescheduleAfterEffectTransition(
|
|
1418
1436
|
this.#journal.enqueue({
|
|
1419
1437
|
id: effectId,
|
|
1420
|
-
kind:
|
|
1438
|
+
kind:
|
|
1439
|
+
dispatchKind === "command"
|
|
1440
|
+
? "sdk.inbound.command"
|
|
1441
|
+
: dispatchKind === "action"
|
|
1442
|
+
? "sdk.inbound.reply"
|
|
1443
|
+
: "sdk.inbound.user_message",
|
|
1421
1444
|
transport: "slack",
|
|
1422
1445
|
sessionId: record.sessionId,
|
|
1423
1446
|
endpointGeneration: endpoint.generation,
|
|
@@ -1437,8 +1460,7 @@ export class SlackNotificationDaemon {
|
|
|
1437
1460
|
candidate =>
|
|
1438
1461
|
candidate.eventId === inbound.eventId ||
|
|
1439
1462
|
candidate.interactionId === interactionId ||
|
|
1440
|
-
candidate.retryKey === retryKey
|
|
1441
|
-
(inbound.eventContext !== undefined && candidate.eventContext === inbound.eventContext),
|
|
1463
|
+
candidate.retryKey === retryKey,
|
|
1442
1464
|
);
|
|
1443
1465
|
if (existing) {
|
|
1444
1466
|
sessionId = current.sessionId;
|
|
@@ -1449,8 +1471,8 @@ export class SlackNotificationDaemon {
|
|
|
1449
1471
|
current.seenEventIds.includes(inbound.eventId) ||
|
|
1450
1472
|
current.seenInteractionIds.includes(interactionId) ||
|
|
1451
1473
|
current.seenRetryKeys.includes(retryKey) ||
|
|
1452
|
-
(
|
|
1453
|
-
(
|
|
1474
|
+
(dispatchKind === "action" && replyableActionId(current.pendingActionId) !== payload.routing.actionId) ||
|
|
1475
|
+
(dispatchKind === "message" && replyableActionId(current.pendingActionId) !== undefined)
|
|
1454
1476
|
)
|
|
1455
1477
|
return current;
|
|
1456
1478
|
sessionId = current.sessionId;
|
|
@@ -1460,8 +1482,8 @@ export class SlackNotificationDaemon {
|
|
|
1460
1482
|
interactionId,
|
|
1461
1483
|
retryKey,
|
|
1462
1484
|
eventContext: inbound.eventContext,
|
|
1463
|
-
kind:
|
|
1464
|
-
...(
|
|
1485
|
+
kind: dispatchKind,
|
|
1486
|
+
...(dispatchKind === "action" ? { actionId: payload.routing.actionId } : {}),
|
|
1465
1487
|
endpointGeneration: endpoint.generation,
|
|
1466
1488
|
...(current.attachmentAuthorityId === undefined
|
|
1467
1489
|
? {}
|
|
@@ -1747,7 +1769,9 @@ export class SlackNotificationDaemon {
|
|
|
1747
1769
|
effect.transport !== "slack" ||
|
|
1748
1770
|
effect.state === "terminal" ||
|
|
1749
1771
|
!effect.sessionId ||
|
|
1750
|
-
(effect.kind !== "sdk.inbound.command" &&
|
|
1772
|
+
(effect.kind !== "sdk.inbound.command" &&
|
|
1773
|
+
effect.kind !== "sdk.inbound.reply" &&
|
|
1774
|
+
effect.kind !== "sdk.inbound.user_message")
|
|
1751
1775
|
)
|
|
1752
1776
|
continue;
|
|
1753
1777
|
const adopted = await this.#adoptOrphanInbound(effect as ChatEffect<SlackInboundEffectPayload>);
|
|
@@ -1794,7 +1818,9 @@ export class SlackNotificationDaemon {
|
|
|
1794
1818
|
text(routing.interactionId) !== undefined &&
|
|
1795
1819
|
routing.retryKey === `${routing.eventId}:${routing.interactionId}` &&
|
|
1796
1820
|
(routing.eventContext === undefined || text(routing.eventContext) !== undefined) &&
|
|
1797
|
-
(routing.kind === "command" ||
|
|
1821
|
+
(routing.kind === "command" ||
|
|
1822
|
+
routing.kind === "message" ||
|
|
1823
|
+
(routing.kind === "action" && text(routing.actionId) !== undefined))
|
|
1798
1824
|
);
|
|
1799
1825
|
}
|
|
1800
1826
|
#matchesInboundEffect(effect: ChatEffect<SlackInboundEffectPayload>, receipt: SlackInboundDispatchReceipt): boolean {
|
|
@@ -1828,7 +1854,11 @@ export class SlackNotificationDaemon {
|
|
|
1828
1854
|
(payload.type === "reply" &&
|
|
1829
1855
|
routing.kind === "action" &&
|
|
1830
1856
|
effect.kind === "sdk.inbound.reply" &&
|
|
1831
|
-
payload.id === routing.actionId)
|
|
1857
|
+
payload.id === routing.actionId) ||
|
|
1858
|
+
(payload.type === "user_message" &&
|
|
1859
|
+
routing.kind === "message" &&
|
|
1860
|
+
effect.kind === "sdk.inbound.user_message" &&
|
|
1861
|
+
payload.sessionId === effect.sessionId))
|
|
1832
1862
|
);
|
|
1833
1863
|
}
|
|
1834
1864
|
#sameInboundReceipt(left: SlackInboundDispatchReceipt, right: SlackInboundDispatchReceipt): boolean {
|
|
@@ -1868,7 +1898,9 @@ export class SlackNotificationDaemon {
|
|
|
1868
1898
|
if (
|
|
1869
1899
|
effect.transport !== "slack" ||
|
|
1870
1900
|
effect.state !== "terminal" ||
|
|
1871
|
-
(effect.kind !== "sdk.inbound.command" &&
|
|
1901
|
+
(effect.kind !== "sdk.inbound.command" &&
|
|
1902
|
+
effect.kind !== "sdk.inbound.reply" &&
|
|
1903
|
+
effect.kind !== "sdk.inbound.user_message")
|
|
1872
1904
|
)
|
|
1873
1905
|
continue;
|
|
1874
1906
|
const payload = effect.payload as SlackInboundEffectPayload;
|
|
@@ -1958,7 +1990,11 @@ export class SlackNotificationDaemon {
|
|
|
1958
1990
|
(payload.type === "reply" &&
|
|
1959
1991
|
routing.kind === "action" &&
|
|
1960
1992
|
effect.kind === "sdk.inbound.reply" &&
|
|
1961
|
-
payload.id === routing.actionId)
|
|
1993
|
+
payload.id === routing.actionId) ||
|
|
1994
|
+
(payload.type === "user_message" &&
|
|
1995
|
+
routing.kind === "message" &&
|
|
1996
|
+
effect.kind === "sdk.inbound.user_message" &&
|
|
1997
|
+
payload.sessionId === effect.sessionId));
|
|
1962
1998
|
let receipt: SlackInboundDispatchReceipt | undefined;
|
|
1963
1999
|
await this.store.transact(key, current => {
|
|
1964
2000
|
if (
|
|
@@ -1974,8 +2010,7 @@ export class SlackNotificationDaemon {
|
|
|
1974
2010
|
candidate =>
|
|
1975
2011
|
candidate.eventId === routing.eventId ||
|
|
1976
2012
|
candidate.interactionId === routing.interactionId ||
|
|
1977
|
-
candidate.retryKey === routing.retryKey
|
|
1978
|
-
(routing.eventContext !== undefined && candidate.eventContext === routing.eventContext),
|
|
2013
|
+
candidate.retryKey === routing.retryKey,
|
|
1979
2014
|
);
|
|
1980
2015
|
if (existing) {
|
|
1981
2016
|
if (this.#matchesInboundEffect(effect, existing)) receipt = existing;
|
|
@@ -1985,8 +2020,7 @@ export class SlackNotificationDaemon {
|
|
|
1985
2020
|
current.seenEventIds.includes(routing.eventId) ||
|
|
1986
2021
|
current.seenInteractionIds.includes(routing.interactionId) ||
|
|
1987
2022
|
current.seenRetryKeys.includes(routing.retryKey) ||
|
|
1988
|
-
(routing.
|
|
1989
|
-
(routing.kind === "action" && current.pendingActionId !== routing.actionId)
|
|
2023
|
+
(routing.kind === "action" && replyableActionId(current.pendingActionId) !== routing.actionId)
|
|
1990
2024
|
)
|
|
1991
2025
|
return current;
|
|
1992
2026
|
receipt = {
|
|
@@ -291,7 +291,7 @@ export const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
|
|
|
291
291
|
* killing a session's mirroring after one transient rejection and must be
|
|
292
292
|
* replaced across this upgrade.
|
|
293
293
|
*/
|
|
294
|
-
export const DAEMON_GENERATION =
|
|
294
|
+
export const DAEMON_GENERATION = 181;
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
297
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
|
package/src/sdk/cli/rows.ts
CHANGED
|
@@ -41,16 +41,20 @@ export interface SdkSessionRowV1 {
|
|
|
41
41
|
ambiguous?: boolean;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export interface
|
|
44
|
+
export interface SdkRingPositionV1 {
|
|
45
45
|
revision: number;
|
|
46
46
|
generation: number;
|
|
47
47
|
seq: number;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export interface SdkCheckpointRecordV1 extends SdkRingPositionV1 {
|
|
51
|
+
idle: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
export interface SdkRetentionGapV1 {
|
|
51
55
|
code: "retention_gap";
|
|
52
56
|
missing?: { from: number; to: number };
|
|
53
|
-
resync?:
|
|
57
|
+
resync?: SdkRingPositionV1;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/** One tail item: a retained transcript entry or a live event-ring event. */
|
|
@@ -58,6 +62,8 @@ export interface SdkTailItemV1 {
|
|
|
58
62
|
/** "transcript" for retained transcript entries, otherwise the event-ring kind. */
|
|
59
63
|
kind: string;
|
|
60
64
|
id?: string;
|
|
65
|
+
/** Durable revision at emission; (revision,generation,seq) is unique per session. generation:seq alone is revision-local. */
|
|
66
|
+
revision?: number;
|
|
61
67
|
generation?: number;
|
|
62
68
|
seq?: number;
|
|
63
69
|
payload: unknown;
|
|
@@ -165,6 +171,7 @@ export function toCheckpointRecordV1(value: unknown): SdkCheckpointRecordV1 | un
|
|
|
165
171
|
const revision = value.revision;
|
|
166
172
|
const generation = value.generation;
|
|
167
173
|
const seq = value.seq;
|
|
174
|
+
const idle = value.idle;
|
|
168
175
|
if (
|
|
169
176
|
typeof revision !== "number" ||
|
|
170
177
|
!Number.isSafeInteger(revision) ||
|
|
@@ -174,10 +181,11 @@ export function toCheckpointRecordV1(value: unknown): SdkCheckpointRecordV1 | un
|
|
|
174
181
|
generation < 0 ||
|
|
175
182
|
typeof seq !== "number" ||
|
|
176
183
|
!Number.isSafeInteger(seq) ||
|
|
177
|
-
seq < 0
|
|
184
|
+
seq < 0 ||
|
|
185
|
+
typeof idle !== "boolean"
|
|
178
186
|
)
|
|
179
187
|
return undefined;
|
|
180
|
-
return { revision, generation, seq };
|
|
188
|
+
return { revision, generation, seq, idle };
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
export function toRetentionGapV1(value: unknown): SdkRetentionGapV1 | undefined {
|
|
@@ -189,15 +197,28 @@ export function toRetentionGapV1(value: unknown): SdkRetentionGapV1 | undefined
|
|
|
189
197
|
if (typeof from === "number" && Number.isSafeInteger(from) && typeof to === "number" && Number.isSafeInteger(to))
|
|
190
198
|
gap.missing = { from, to };
|
|
191
199
|
}
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
if (isRecord(value.resync)) {
|
|
201
|
+
const { revision, generation, seq } = value.resync;
|
|
202
|
+
if (
|
|
203
|
+
typeof revision === "number" &&
|
|
204
|
+
Number.isSafeInteger(revision) &&
|
|
205
|
+
revision >= 0 &&
|
|
206
|
+
typeof generation === "number" &&
|
|
207
|
+
Number.isSafeInteger(generation) &&
|
|
208
|
+
generation >= 0 &&
|
|
209
|
+
typeof seq === "number" &&
|
|
210
|
+
Number.isSafeInteger(seq) &&
|
|
211
|
+
seq >= 0
|
|
212
|
+
)
|
|
213
|
+
gap.resync = { revision, generation, seq };
|
|
214
|
+
}
|
|
194
215
|
return gap;
|
|
195
216
|
}
|
|
196
217
|
|
|
197
218
|
/** Projects a transcript entry or ring event into a tail item with dedupe keys. */
|
|
198
219
|
export function toTailItemV1(
|
|
199
220
|
value: unknown,
|
|
200
|
-
fallback: { kind: string; generation?: number; seq?: number },
|
|
221
|
+
fallback: { kind: string; revision?: number; generation?: number; seq?: number },
|
|
201
222
|
): SdkTailItemV1 {
|
|
202
223
|
if (!isRecord(value)) return { kind: fallback.kind, payload: stripSecretFields(value) };
|
|
203
224
|
const kind = optionalString(value.kind) ?? fallback.kind;
|
|
@@ -207,10 +228,12 @@ export function toTailItemV1(
|
|
|
207
228
|
const item: SdkTailItemV1 = {
|
|
208
229
|
kind,
|
|
209
230
|
...(payloadId !== undefined ? { id: payloadId } : id !== undefined ? { id } : {}),
|
|
231
|
+
...(typeof value.revision === "number" ? { revision: value.revision } : {}),
|
|
210
232
|
...(typeof value.generation === "number" ? { generation: value.generation } : {}),
|
|
211
233
|
...(typeof value.seq === "number" ? { seq: value.seq } : {}),
|
|
212
234
|
payload: stripSecretFields(payload),
|
|
213
235
|
};
|
|
236
|
+
if (fallback.revision !== undefined && item.revision === undefined) item.revision = fallback.revision;
|
|
214
237
|
if (fallback.generation !== undefined && item.generation === undefined) item.generation = fallback.generation;
|
|
215
238
|
if (fallback.seq !== undefined && item.seq === undefined) item.seq = fallback.seq;
|
|
216
239
|
return item;
|
|
@@ -613,14 +613,15 @@ async function workspaceIdentity(target: string): Promise<WorkspaceIdentity> {
|
|
|
613
613
|
/**
|
|
614
614
|
* Resolves the list selection from `--repo` (default: process cwd) under the
|
|
615
615
|
* effective scope. Outside Git, `repo`/`worktree` fail typed and actionable —
|
|
616
|
-
* they never broaden to `all`; `cwd` remains an exact canonical match
|
|
616
|
+
* they never broaden to `all`; `cwd` remains an exact canonical match, and
|
|
617
|
+
* `all` ignores the selection entirely so it works outside any checkout.
|
|
617
618
|
*/
|
|
618
619
|
export async function resolveSessionListSelection(
|
|
619
620
|
scope: SdkSessionListScope,
|
|
620
621
|
repoArg: string | undefined,
|
|
621
622
|
): Promise<SdkSessionListSelection> {
|
|
622
623
|
const selection = await workspaceIdentity(repoArg ?? process.cwd());
|
|
623
|
-
if (scope !== "cwd" && !selection.repoRoot)
|
|
624
|
+
if (scope !== "cwd" && scope !== "all" && !selection.repoRoot)
|
|
624
625
|
throw new SdkSessionCliError(
|
|
625
626
|
"not_a_repository",
|
|
626
627
|
`--scope ${scope} requires a Git repository, but "${selection.canonicalPath}" is outside any Git checkout. ` +
|
|
@@ -969,11 +970,7 @@ function extractCheckpoint(response: unknown): CheckpointExtraction {
|
|
|
969
970
|
if (gap !== undefined) return { gap };
|
|
970
971
|
const record = toCheckpointRecordV1(result.checkpoint ?? result);
|
|
971
972
|
const cursor =
|
|
972
|
-
typeof result.
|
|
973
|
-
? result.cursor
|
|
974
|
-
: typeof result.checkpointToken === "string" && result.checkpointToken
|
|
975
|
-
? result.checkpointToken
|
|
976
|
-
: undefined;
|
|
973
|
+
typeof result.checkpointToken === "string" && result.checkpointToken ? result.checkpointToken : undefined;
|
|
977
974
|
return { record, cursor };
|
|
978
975
|
}
|
|
979
976
|
|
|
@@ -1150,12 +1147,6 @@ function eventGapToRetentionGap(
|
|
|
1150
1147
|
return undefined;
|
|
1151
1148
|
}
|
|
1152
1149
|
|
|
1153
|
-
function publicationSequence(publicationId: string): number | undefined {
|
|
1154
|
-
const parts = publicationId.split(":");
|
|
1155
|
-
const value = Number(parts.at(-1));
|
|
1156
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
1150
|
function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | undefined {
|
|
1160
1151
|
if (frame.name === undefined) return undefined;
|
|
1161
1152
|
// The replay response is an out-of-band control frame, not an event-ring
|
|
@@ -1163,18 +1154,16 @@ function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | und
|
|
|
1163
1154
|
// intentionally has no event `seq`; projecting it as a tail item would make
|
|
1164
1155
|
// the strict event-position validator reject every otherwise valid tail.
|
|
1165
1156
|
if (frame.name === "event_replay_result") return undefined;
|
|
1166
|
-
const seq = frame.
|
|
1157
|
+
const seq = frame.seq;
|
|
1167
1158
|
return toTailItemV1(
|
|
1168
1159
|
{
|
|
1169
1160
|
kind: frame.name,
|
|
1170
|
-
...(
|
|
1171
|
-
...(seq === undefined ? {} : { seq }),
|
|
1161
|
+
...(seq === undefined ? {} : { generation: frame.generation, seq }),
|
|
1172
1162
|
payload: frame.body,
|
|
1173
1163
|
},
|
|
1174
1164
|
{
|
|
1175
1165
|
kind: frame.name,
|
|
1176
|
-
...(
|
|
1177
|
-
...(seq === undefined ? {} : { seq }),
|
|
1166
|
+
...(seq === undefined ? {} : { generation: frame.generation, seq }),
|
|
1178
1167
|
},
|
|
1179
1168
|
);
|
|
1180
1169
|
}
|
|
@@ -1388,7 +1377,9 @@ async function offlineTailReplay(
|
|
|
1388
1377
|
version: SESSION_ROWS_VERSION,
|
|
1389
1378
|
source: "offline",
|
|
1390
1379
|
session: row,
|
|
1391
|
-
items: entries.map((entry, index) =>
|
|
1380
|
+
items: entries.map((entry, index) =>
|
|
1381
|
+
toTailItemV1(entry, { kind: "transcript", revision: index + 1, seq: index }),
|
|
1382
|
+
),
|
|
1392
1383
|
terminal: true,
|
|
1393
1384
|
},
|
|
1394
1385
|
};
|
|
@@ -1496,6 +1487,8 @@ async function runLiveTail(
|
|
|
1496
1487
|
if (attachment.sessionId !== sessionId) return;
|
|
1497
1488
|
const item = tailItemFromRouterFrame(frame);
|
|
1498
1489
|
if (!item) return;
|
|
1490
|
+
if (item.revision === undefined && checkpoint?.revision !== undefined) item.revision = checkpoint.revision;
|
|
1491
|
+
// If checkpoint was undefined, queue for later stamping? For now leave without rev
|
|
1499
1492
|
applyLifecycle(mergeEventTailItems(eventItems, seenEvents, [item], include));
|
|
1500
1493
|
};
|
|
1501
1494
|
|
|
@@ -1537,10 +1530,18 @@ async function runLiveTail(
|
|
|
1537
1530
|
);
|
|
1538
1531
|
throwResponseFailure(response);
|
|
1539
1532
|
const page = extractTranscriptPage(response);
|
|
1533
|
+
const rev = checkpoint?.revision;
|
|
1534
|
+
let nextSeq = transcriptItems.length;
|
|
1540
1535
|
mergeTailItems(
|
|
1541
1536
|
transcriptItems,
|
|
1542
1537
|
seenTranscript,
|
|
1543
|
-
page.items.map(item =>
|
|
1538
|
+
page.items.map(item => {
|
|
1539
|
+
const it = toTailItemV1(item, { kind: "transcript" });
|
|
1540
|
+
if (it.revision === undefined && rev !== undefined) it.revision = rev;
|
|
1541
|
+
if (it.generation === undefined) it.generation = checkpoint?.generation ?? 0;
|
|
1542
|
+
if (it.seq === undefined) it.seq = nextSeq++;
|
|
1543
|
+
return it;
|
|
1544
|
+
}),
|
|
1544
1545
|
include,
|
|
1545
1546
|
);
|
|
1546
1547
|
if (page.complete || page.cursor === undefined) break;
|
|
@@ -1586,9 +1587,20 @@ async function runLiveTail(
|
|
|
1586
1587
|
raw.seq,
|
|
1587
1588
|
);
|
|
1588
1589
|
}
|
|
1589
|
-
const replayItems = rawEvents.map(event =>
|
|
1590
|
+
const replayItems = rawEvents.map(event => {
|
|
1591
|
+
const it = toTailItemV1(event, { kind: "event" });
|
|
1592
|
+
if (it.revision === undefined && checkpoint?.revision !== undefined) it.revision = checkpoint.revision;
|
|
1593
|
+
return it;
|
|
1594
|
+
});
|
|
1590
1595
|
applyLifecycle(mergeEventTailItems(eventItems, seenEvents, replayItems, include));
|
|
1591
1596
|
if (malformed !== undefined) throw malformed;
|
|
1597
|
+
if (checkpoint !== undefined) {
|
|
1598
|
+
const checkpointKey = { generation: checkpoint.generation, seq: checkpoint.seq };
|
|
1599
|
+
if (turnStateKey === undefined || compareTailSeqKeys(checkpointKey, turnStateKey) >= 0) {
|
|
1600
|
+
turnStateKey = checkpointKey;
|
|
1601
|
+
turnIdle = checkpoint.idle;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1592
1604
|
if (liveReason === undefined && args.untilIdle === true && turnIdle) liveReason = "idle";
|
|
1593
1605
|
if (liveReason === undefined) {
|
|
1594
1606
|
const completion = Promise.withResolvers<TailExitReason>();
|