@quantiya/codevibe-claude-plugin 2.0.75 → 2.0.77

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.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/node_modules/@quantiya/codevibe-core/dist/appsync/appsync-client.d.ts +41 -10
  3. package/node_modules/@quantiya/codevibe-core/dist/appsync/queries.d.ts +4 -0
  4. package/node_modules/@quantiya/codevibe-core/dist/appsync/session-freshness.d.ts +12 -0
  5. package/node_modules/@quantiya/codevibe-core/dist/index.js +643 -562
  6. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/brainstorm-context.d.ts +11 -0
  7. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.d.ts +11 -1
  8. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +55632 -53046
  9. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/conductor-reply.d.ts +37 -0
  10. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/context-items.d.ts +64 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +44 -2
  12. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/resume-modal.d.ts +19 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/slash-router.d.ts +2 -0
  14. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +48 -2
  15. package/node_modules/@quantiya/codevibe-core/dist/session/pending-session-create.d.ts +31 -0
  16. package/node_modules/@quantiya/codevibe-core/dist/session-resume/ResumePicker.d.ts +21 -0
  17. package/node_modules/@quantiya/codevibe-core/dist/session-resume/carry-over.d.ts +81 -0
  18. package/node_modules/@quantiya/codevibe-core/dist/session-resume/clean-exit.d.ts +33 -0
  19. package/node_modules/@quantiya/codevibe-core/dist/session-resume/cli-resume.d.ts +41 -0
  20. package/node_modules/@quantiya/codevibe-core/dist/session-resume/completeness-copy.d.ts +52 -0
  21. package/node_modules/@quantiya/codevibe-core/dist/session-resume/create-outcome.d.ts +43 -0
  22. package/node_modules/@quantiya/codevibe-core/dist/session-resume/e2e-fault.d.ts +28 -0
  23. package/node_modules/@quantiya/codevibe-core/dist/session-resume/hosted-loader.d.ts +88 -0
  24. package/node_modules/@quantiya/codevibe-core/dist/session-resume/local-completeness.d.ts +10 -0
  25. package/node_modules/@quantiya/codevibe-core/dist/session-resume/local-records.d.ts +121 -0
  26. package/node_modules/@quantiya/codevibe-core/dist/session-resume/picker-rows.d.ts +17 -0
  27. package/node_modules/@quantiya/codevibe-core/dist/session-resume/publish-retire.d.ts +40 -0
  28. package/node_modules/@quantiya/codevibe-core/dist/session-resume/relaunch.d.ts +72 -0
  29. package/node_modules/@quantiya/codevibe-core/dist/session-resume/restore-conversation.d.ts +27 -0
  30. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-candidates.d.ts +52 -0
  31. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-chosen.d.ts +103 -0
  32. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-create.d.ts +121 -0
  33. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-entry.d.ts +87 -0
  34. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-header.d.ts +25 -0
  35. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-io.d.ts +34 -0
  36. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-ledger.d.ts +107 -0
  37. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-messages.d.ts +68 -0
  38. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-rows.d.ts +24 -0
  39. package/node_modules/@quantiya/codevibe-core/dist/session-resume/resume-target.d.ts +65 -0
  40. package/node_modules/@quantiya/codevibe-core/dist/session-resume/shell-resume-io.d.ts +25 -0
  41. package/node_modules/@quantiya/codevibe-core/dist/session-resume/shell-resume.d.ts +34 -0
  42. package/node_modules/@quantiya/codevibe-core/dist/session-resume/source-choice.d.ts +37 -0
  43. package/node_modules/@quantiya/codevibe-core/dist/session-resume/successor-carry-over.d.ts +54 -0
  44. package/node_modules/@quantiya/codevibe-core/dist/session-resume/will-retire.d.ts +24 -0
  45. package/node_modules/@quantiya/codevibe-core/dist/substrate/sandbox-exec.d.ts +7 -0
  46. package/node_modules/@quantiya/codevibe-core/dist/types/session.d.ts +67 -0
  47. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  48. package/package.json +2 -2
@@ -0,0 +1,121 @@
1
+ /**
2
+ * P61 — create the successor S (SESSION-RESUME-DESIGN.md §12.2.2, §8.3, §10;
3
+ * Phase B implementation design §3, B-3, B-15, B-16).
4
+ *
5
+ * Order, under P's resume lock: mint S → prepare S's keys → write the
6
+ * carry-over to S's log and prove it by readback ending in the marker →
7
+ * seal the ledger → `createSession` → classify. Cleanup follows §12.2.4
8
+ * exactly. Every effect is injected, so each outcome class is testable.
9
+ */
10
+ import type { ContextItem, ContextItemDraft, VerifiedContextItemsResult } from '../orchestration-shell/context-items';
11
+ import type { CreateSessionInput, Session } from '../types/session';
12
+ import type { EncryptedSessionKey } from '../types/encryption';
13
+ import { type ResumeCreateOutcome } from './create-outcome';
14
+ import { type CarryOverPlan } from './carry-over';
15
+ import { type ResumeLedgerRecord } from './resume-ledger';
16
+ export interface ResumeCreateDeps {
17
+ mintSessionId(): string;
18
+ /** `creationRequestId` for the idempotent create path. */
19
+ mintRequestId(): string;
20
+ prepareEncryption(sessionId: string): Promise<{
21
+ sessionKey: string;
22
+ encryptedKeys: EncryptedSessionKey[];
23
+ } | null>;
24
+ encryptContent(plaintext: string, sessionKey: string): string;
25
+ deviceId(): Promise<string>;
26
+ append(sessionId: string, draft: ContextItemDraft): Promise<ContextItem | null>;
27
+ flush(sessionId: string): Promise<void>;
28
+ readVerified(sessionId: string): Promise<VerifiedContextItemsResult>;
29
+ sealLedger(record: Omit<ResumeLedgerRecord, 'schema' | 'sealedAt'>): Promise<ResumeLedgerRecord>;
30
+ /** INV-5 rebuild: replace the existing ledger's marker, only while it is still this resume (P61B-S2-R1-F1). */
31
+ resealLedger(previous: ResumeLedgerRecord, marker: ResumeLedgerRecord['marker']): Promise<ResumeLedgerRecord>;
32
+ dropLedger(record: ResumeLedgerRecord): Promise<void>;
33
+ /**
34
+ * INV-6 on the `SessionIdRetired` path (P61B-S2-R1-F4): record S's publish obligation before its
35
+ * ledger is dropped, so it survives the drop and every later resume entry point retries it.
36
+ */
37
+ writeRetireNote(note: {
38
+ successorSessionId: string;
39
+ predecessorSessionId: string;
40
+ marker: {
41
+ seq: string;
42
+ itemSha256: string;
43
+ };
44
+ }): Promise<void>;
45
+ /** Remove S's prepared local directories by exact path (never P's). */
46
+ removeSuccessorDirectory(successorSessionId: string): Promise<void>;
47
+ createSession(input: CreateSessionInput): Promise<Session>;
48
+ /** Post-admission response check shared with the plain create path; throws on mismatch. */
49
+ checkAdmitted(session: Session, input: CreateSessionInput): 'exact' | 'resume';
50
+ /** End an S that must not run (B-15). Best effort. */
51
+ retireSession(sessionId: string): Promise<void>;
52
+ /**
53
+ * Write S's owner record (§12.5) before S's first context append — the
54
+ * carry-over — so this process is S's recorded single writer from the start.
55
+ */
56
+ claimSuccessorOwnership(successorSessionId: string): Promise<void>;
57
+ }
58
+ export type ResumeCreateResult = {
59
+ kind: 'admitted';
60
+ session: Session;
61
+ sessionKey: string;
62
+ marker: ContextItem;
63
+ ledger: ResumeLedgerRecord;
64
+ } | {
65
+ kind: 'refused';
66
+ outcome: Exclude<ResumeCreateOutcome, {
67
+ kind: 'ambiguous';
68
+ }>;
69
+ }
70
+ /** Unknown outcome: S's directory and the ledger stay for replay at the next resume entry point. */
71
+ | {
72
+ kind: 'ambiguous';
73
+ outcome: Extract<ResumeCreateOutcome, {
74
+ kind: 'ambiguous';
75
+ }> | {
76
+ kind: 'ambiguous';
77
+ cause: 'response-mismatch';
78
+ };
79
+ }
80
+ /** The server did not resume P: a placeholder for a blocked caller (B-16), or a backend that ignored the predecessor (B-15). */
81
+ | {
82
+ kind: 'not-resumed';
83
+ cause: 'not-admitted' | 'predecessor-ignored' | 'ledger-pending';
84
+ } | {
85
+ kind: 'encryption-unavailable';
86
+ } | {
87
+ kind: 'carry-over-failed';
88
+ }
89
+ /**
90
+ * `/resume` parent (§5.3 step 1): S's carry-over is verified and the ledger
91
+ * sealed; the create is left to the relaunched child, which replays it.
92
+ */
93
+ | {
94
+ kind: 'prepared';
95
+ ledger: ResumeLedgerRecord;
96
+ marker: ContextItem;
97
+ };
98
+ export declare function createResumedSession(args: {
99
+ userId: string;
100
+ predecessorSessionId: string;
101
+ predecessorDirectory: string;
102
+ /** P's last activity, kept in the ledger for a replay's header (P61B-S1-E2). */
103
+ predecessorEndedAt?: string;
104
+ cwd: string;
105
+ plan: CarryOverPlan;
106
+ sourceComplete: boolean;
107
+ /** §8.4 step 2: the carry-over was rebuilt from P's own predecessor. */
108
+ rebuiltFrom?: string;
109
+ /** Stop after sealing the ledger (the `/resume` parent). */
110
+ prepareOnly?: boolean;
111
+ deps: ResumeCreateDeps;
112
+ }): Promise<ResumeCreateResult>;
113
+ /**
114
+ * Send (or replay, byte-for-byte) the sealed create and apply §12.2.4. Used
115
+ * for a new resume and for every interrupted one (§6.2 step 2).
116
+ */
117
+ export declare function sendResumeCreate(args: {
118
+ ledger: ResumeLedgerRecord;
119
+ marker: ContextItem;
120
+ deps: Pick<ResumeCreateDeps, 'createSession' | 'checkAdmitted' | 'dropLedger' | 'removeSuccessorDirectory' | 'retireSession' | 'writeRetireNote'>;
121
+ }): Promise<ResumeCreateResult>;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * P61 resume entry points (`--continue`, `--resume [<id>]`, the `/resume`
3
+ * child): design §5.1, §6.2 steps 1–4, §12.2.3. Every effect is injected.
4
+ *
5
+ * 1. Read the lists and match the target (`collectResumeCandidates`).
6
+ * 2. Finish interrupted resumes on this computer: retry retire notes, replay
7
+ * sealed ledgers. The targeted one starts S's shell; the others are
8
+ * published and retired (INV-6). A definitive refusal of the targeted one
9
+ * stops with its copy; it never falls through to another session. Each
10
+ * replay first makes this process S's recorded owner (INV-1/INV-2) and
11
+ * applies INV-5: S's verified local carry-over must end in its marker, or
12
+ * it is rebuilt from P, or the replay fails closed.
13
+ * 3. Retire exactly the `willRetire` rows of the step-1 read.
14
+ * 4. Resolve the target (picker, non-TTY list, `--continue`, id) and resume it.
15
+ */
16
+ import type { ContextItem, ReadOnlyContextLogResult } from '../orchestration-shell/context-items';
17
+ import { type ResumeCandidateDeps } from './resume-candidates';
18
+ import { type ResumeChosenDeps } from './resume-chosen';
19
+ import type { ResumeCreateResult } from './resume-create';
20
+ import type { ResumeLedgerRecord, ResumeRetireNote } from './resume-ledger';
21
+ import { type UnlockedResumeRow } from './resume-rows';
22
+ import { type ResumeEntryPoint } from './resume-target';
23
+ import type { RetireCandidateRow } from './will-retire';
24
+ import type { OwnerTakeover, ResumeLockHandle } from './local-records';
25
+ import { type SuccessorLogFailure, type SuccessorRebuildResult } from './successor-carry-over';
26
+ import type { Session } from '../types/session';
27
+ export interface ResumeStart {
28
+ session: Session;
29
+ sessionKey: string;
30
+ predecessorSessionId: string;
31
+ predecessorDirectory: string | null;
32
+ predecessorEndedAtMs: number | null;
33
+ /** S's carried items, marker excluded, for `RESTORE_CONVERSATION`. */
34
+ restoredItems: ContextItem[];
35
+ markerSeq: string;
36
+ rulesRestored: number;
37
+ warnings: string[];
38
+ /** P's resume lock; released once S's shell has started. */
39
+ lock: ResumeLockHandle;
40
+ }
41
+ export type ResumeEntryResult = {
42
+ kind: 'start';
43
+ start: ResumeStart;
44
+ } | {
45
+ kind: 'exit';
46
+ code: number;
47
+ };
48
+ export interface ResumeEntryDeps {
49
+ candidates: ResumeCandidateDeps;
50
+ chosen: ResumeChosenDeps;
51
+ /** §6.2 step 3 on the step-1 rows (`sweepOrphanSessionsFromRows`). */
52
+ retireRows(rows: RetireCandidateRow[], ids: ReadonlySet<string>): Promise<string[]>;
53
+ listRetireNotes(): Promise<ResumeRetireNote[]>;
54
+ /** Re-drain and retire one noted S (INV-6); true when it is now retired. */
55
+ retryRetireNote(note: ResumeRetireNote): Promise<boolean>;
56
+ /**
57
+ * Drop P's ledger when it still names the note's S (P61B-S2-R2-F1). True when no such ledger remains;
58
+ * false when one may (it cannot be read now).
59
+ */
60
+ settleNotedLedger(note: ResumeRetireNote): Promise<boolean>;
61
+ /** The `/resume` child's adopted predecessor (§12.6), whose lock step 2a must not take; null otherwise. */
62
+ handOffSessionId?: string | null;
63
+ /** Re-send one sealed create byte-for-byte, under P's lock (taken by the caller). */
64
+ replayLedger(record: ResumeLedgerRecord, marker: ContextItem): Promise<ResumeCreateResult>;
65
+ /** Publish then retire an admitted, non-targeted S. */
66
+ /** `carriedItems`: S's verified carry-over, for its picker preview (P61B-S1-L12). */
67
+ publishThenRetire(record: ResumeLedgerRecord, carriedItems: readonly ContextItem[]): Promise<'retired' | 'noted'>;
68
+ /** A read-only read of S's local log (B-8): never writes S's directory. */
69
+ readSuccessorLog(successorSessionId: string): Promise<ReadOnlyContextLogResult>;
70
+ /** Make this process S's recorded owner (`takeOverOwnerRecord`); P names the resume, for a `/resume` hand-off. */
71
+ takeOverSuccessorOwnership(successorSessionId: string, predecessorSessionId: string): Promise<OwnerTakeover>;
72
+ /** Mark S's owner record finished (`markOwnerFinished`) once this launch is done with a non-targeted S. */
73
+ markSuccessorFinished(successorSessionId: string): Promise<boolean>;
74
+ /** INV-5: set S's log aside and rebuild its carry-over from P (`rebuildSuccessorCarryOver`). */
75
+ rebuildSuccessor(args: {
76
+ record: ResumeLedgerRecord;
77
+ reason: SuccessorLogFailure;
78
+ interactive: boolean;
79
+ }): Promise<SuccessorRebuildResult>;
80
+ /** Picker (terminal only). Returns the chosen session ID, or null when cancelled. */
81
+ pick(rows: UnlockedResumeRow[]): Promise<string | null>;
82
+ print(text: string): void;
83
+ printError(text: string): void;
84
+ }
85
+ /** The plain-text list (no terminal): full IDs, directory, last prompt, then `tail`. */
86
+ export declare function listText(rows: UnlockedResumeRow[], tail: string): string;
87
+ export declare function runResumeEntryPoint(entry: ResumeEntryPoint, deps: ResumeEntryDeps): Promise<ResumeEntryResult>;
@@ -0,0 +1,25 @@
1
+ /** Display cap for one restored reply (§5.4); the agents still get the full text. */
2
+ export declare const RESTORED_REPLY_MAX_LINES = 40;
3
+ export declare const RESTORED_REPLY_MAX_CHARS = 4000;
4
+ export declare const RESTORED_REPLY_SHORTENED = "\u2026 (shortened here; the agents still have it)";
5
+ export declare const NO_REPLY_RECORDED = "(no reply was recorded)";
6
+ export declare function shortId(sessionId: string): string;
7
+ export declare function endedAgo(endedAtMs: number, nowMs: number): string;
8
+ export declare function displayPath(directory: string, home?: string): string;
9
+ export declare function buildResumeHeader(args: {
10
+ predecessorSessionId: string;
11
+ successorSessionId: string;
12
+ endedAtMs: number | null;
13
+ nowMs: number;
14
+ recordedDirectory: string | null;
15
+ cwd: string;
16
+ rulesRestored: number;
17
+ warnings: readonly string[];
18
+ home?: string;
19
+ }): {
20
+ top: string;
21
+ warnings: string[];
22
+ bottom: string;
23
+ };
24
+ /** §5.4 display shortening for a restored reply (display only). */
25
+ export declare function shortenRestoredReply(text: string): string;
@@ -0,0 +1,34 @@
1
+ import type { AppSyncClient } from '../appsync/appsync-client';
2
+ import type { Logger } from '../logger';
3
+ import { type CreateSessionInput, type Session } from '../types/session';
4
+ import { type ResumeLockHandle } from './local-records';
5
+ import { toPickerRows } from './picker-rows';
6
+ import type { ResumeEntryDeps } from './resume-entry';
7
+ import type { ContinueLabel } from './completeness-copy';
8
+ /**
9
+ * A failed resume create is classified by `classifyResumeCreateFailure`; an unconfirmed one prints §10's
10
+ * "Couldn't confirm the resume" copy and keeps the ledger for replay, but nothing recorded WHY (E2E r18 and
11
+ * container-a-r3, both transient backend contention). Logs the failure's class and the server's short
12
+ * message (metadata only, no session content), then rethrows the same error, so classification is unchanged.
13
+ */
14
+ export declare function logResumeCreateFailures(create: (input: CreateSessionInput) => Promise<Session>, logger: Pick<Logger, 'warn'>): (input: CreateSessionInput) => Promise<Session>;
15
+ export declare function createResumeEntryDeps(args: {
16
+ client: AppSyncClient;
17
+ userId: string;
18
+ deviceId: string;
19
+ cwd: string;
20
+ isTTY: boolean;
21
+ logger: Logger;
22
+ ownSessionId?: string;
23
+ pick: (rows: ReturnType<typeof toPickerRows>) => Promise<string | null>;
24
+ choose: (prompt: string, continueLabel?: ContinueLabel) => Promise<boolean>;
25
+ print: (text: string) => void;
26
+ printError: (text: string) => void;
27
+ nowMs?: () => number;
28
+ /**
29
+ * `/resume` child (§12.6): P's lock adopted from the live parent. It serves
30
+ * the acquire for that P (the parent still holds it, waiting on this
31
+ * process), and lets S's owner record pass from the parent to this process.
32
+ */
33
+ adoptedLock?: ResumeLockHandle | null;
34
+ }): ResumeEntryDeps;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * P61 session resume — the resume ledger and retire notes
3
+ * (SESSION-RESUME-DESIGN.md §12.2, INV-5/INV-6/INV-7/INV-10/INV-11; Phase B
4
+ * implementation design §3, §5).
5
+ *
6
+ * The ledger lives in its own directory, never under `pending-session-creates/`,
7
+ * so a plain launch can neither replay a resume nor be blocked by one (INV-10;
8
+ * code map finding 9). It is sealed with the device-bound at-rest key because
9
+ * it carries S's session key. A ledger sealed for another signed-in user is
10
+ * never read for replay, modified or deleted (INV-11).
11
+ */
12
+ import type { CreateSessionInput } from '../types/session';
13
+ declare const LEDGER_SCHEMA = "codevibe.resume_ledger.v1";
14
+ declare const RETIRE_SCHEMA = "codevibe.resume_retire_note.v1";
15
+ export declare function resumeLedgerDir(): string;
16
+ export declare function resumeRetireDir(): string;
17
+ export declare function resumeLedgerPath(predecessorSessionId: string): string;
18
+ export interface ResumeLedgerRecord {
19
+ schema: typeof LEDGER_SCHEMA;
20
+ userId: string;
21
+ predecessorSessionId: string;
22
+ successorSessionId: string;
23
+ /** The exact create, replayed byte-for-byte (carries creationRequestId and resumedFromSessionId). */
24
+ createInput: CreateSessionInput;
25
+ /** S's session key (base64). */
26
+ sessionKey: string;
27
+ /** P's recorded directory, decrypted at resume time (the `--continue` match key). */
28
+ predecessorDirectory: string;
29
+ /** S's carry-over marker, which ends S's verified log. */
30
+ marker: {
31
+ seq: string;
32
+ itemSha256: string;
33
+ };
34
+ /**
35
+ * P's last activity when the resume began (`lastHeartbeatAt`, else `createdAt`), so a replay's header
36
+ * can say "ended <when>" as the fresh path does (§5.4; P61B-S1-E2). Absent on older ledgers.
37
+ */
38
+ predecessorEndedAt?: string;
39
+ sealedAt: string;
40
+ }
41
+ /** P already has a sealed ledger: an interrupted resume of P must be finished first (P61B-S1-L7). */
42
+ export declare class ResumeLedgerExistsError extends Error {
43
+ constructor();
44
+ }
45
+ export declare class ResumeLedgerKeychainUnavailableError extends Error {
46
+ constructor();
47
+ }
48
+ /**
49
+ * Seal the ledger for one predecessor (design §12.2.2 step 3: after the
50
+ * carry-over is verified, before `createSession`). Throws when the record
51
+ * cannot be sealed: the resume then aborts with nothing claimed.
52
+ */
53
+ export declare function sealResumeLedger(record: Omit<ResumeLedgerRecord, 'schema' | 'sealedAt'>): Promise<ResumeLedgerRecord>;
54
+ /** The ledger on disk is no longer the resume being rebuilt: nothing was written (P61B-S2-R1-F1). */
55
+ export declare class ResumeLedgerChangedError extends Error {
56
+ constructor();
57
+ }
58
+ /**
59
+ * Replace P's ledger after an INV-5 rebuild re-authored S's carry-over, which changes only the
60
+ * marker (P61B-S2-R1-F1: `sealResumeLedger` refuses every existing ledger, and an interrupted resume
61
+ * always has one). The ledger on disk must still be this exact resume — same user, P, S, key and
62
+ * create input, so the replay stays byte-for-byte — or nothing is written. The replacement is atomic
63
+ * and read back before it counts. The caller holds P's resume lock and is S's recorded owner.
64
+ */
65
+ export declare function resealResumeLedger(previous: ResumeLedgerRecord, marker: ResumeLedgerRecord['marker'], signedInUserId: string): Promise<ResumeLedgerRecord>;
66
+ export type ResumeLedgerRead = {
67
+ state: 'ok';
68
+ record: ResumeLedgerRecord;
69
+ file: string;
70
+ } | {
71
+ state: 'foreign';
72
+ file: string;
73
+ } | {
74
+ state: 'keychain-unavailable';
75
+ file: string;
76
+ } | {
77
+ state: 'quarantined';
78
+ file: string;
79
+ };
80
+ /** Read one ledger file, fail-closed and INV-11-safe. */
81
+ export declare function readResumeLedgerFile(file: string, signedInUserId: string): Promise<ResumeLedgerRead | null>;
82
+ /** Every ledger on this computer (§6.2 step 1 target matching). */
83
+ export declare function listResumeLedgers(signedInUserId: string): Promise<ResumeLedgerRead[]>;
84
+ /**
85
+ * Drop a ledger after its outcome is definitive. Removes only the exact file
86
+ * whose content still names this successor for this user.
87
+ */
88
+ export declare function dropResumeLedger(record: ResumeLedgerRecord, signedInUserId: string): Promise<void>;
89
+ export interface ResumeRetireNote {
90
+ schema: typeof RETIRE_SCHEMA;
91
+ userId: string;
92
+ successorSessionId: string;
93
+ predecessorSessionId: string;
94
+ marker: {
95
+ seq: string;
96
+ itemSha256: string;
97
+ };
98
+ /** S's picker preview (P61B-S1-L12); absent on older notes. */
99
+ encryptedLastPrompt?: string;
100
+ notedAt: string;
101
+ }
102
+ export declare function resumeRetireNotePath(successorSessionId: string): string;
103
+ /** Record an admitted S whose publish-then-retire failed; later entry points retry it. */
104
+ export declare function writeResumeRetireNote(note: Omit<ResumeRetireNote, 'schema' | 'notedAt'>): Promise<void>;
105
+ export declare function listResumeRetireNotes(signedInUserId: string): Promise<ResumeRetireNote[]>;
106
+ export declare function clearResumeRetireNote(note: Pick<ResumeRetireNote, 'userId' | 'successorSessionId'>, signedInUserId: string): Promise<void>;
107
+ export {};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * P61 §10 — the exact refusal and failure copy. Pure: callers pass what they
3
+ * already know (the `getResumeDetails` row, the create result).
4
+ */
5
+ import type { ResumeDetails } from '../types/session';
6
+ import type { ResumeCreateResult } from './resume-create';
7
+ export declare const OLD_BACKEND_MESSAGE = "Resume isn't available on this CodeVibe server yet.";
8
+ /**
9
+ * P61B-S1-L13: a backend without Phase A has no resume queries, so step 1's read fails GraphQL
10
+ * validation before anything runs, and the §10 old-backend copy applies. AppSync's answer, observed
11
+ * against Development on 2026-10-15 (`tools/probe_undefined_query_field.js`, Phase B assumption
12
+ * PB-13): "Validation error of type FieldUndefined: Field '<query>' in type 'Query' is undefined".
13
+ */
14
+ export declare function isResumeUnavailableOnServer(error: unknown): boolean;
15
+ /** The text for a resume that threw instead of returning a §10 refusal. */
16
+ export declare function resumeErrorMessage(error: unknown, prefix: string): string;
17
+ /** The plain create path's session-cap copy (`cli.ts` `classifyOrchestrationSessionBootstrapError`); §10: a resume's
18
+ * cap refusal uses "the existing create-path messages". One constant, so the two paths cannot drift. */
19
+ export declare const SESSION_LIMIT_MESSAGE = "Your active-session limit is reached. Close an active session in the CodeVibe mobile app or exit another desktop agent, then try again.";
20
+ export declare const ACCOUNT_DELETION_MESSAGE = "Your CodeVibe account is being deleted, so sessions can't be resumed.";
21
+ export declare function noMatchMessage(id: string): string;
22
+ export declare function companionMessage(id: string, agentType: string): string;
23
+ export declare function alreadyResumedMessage(id: string, successorSessionId: string, successorResumable: boolean): string;
24
+ export declare function windowClosedMessage(id: string): string;
25
+ /**
26
+ * DD-1 (amended 2026-10-15, Hendry: "Fix copy + mobile follow-up"). A computer
27
+ * signed in when the session started always holds its key. The phone apps
28
+ * derive keys for the sessions they list only while ACTIVE and keep them in
29
+ * memory, so the dependable way for a phone to share an ENDED session's key is
30
+ * an app that has stayed open since the session was running (E2E-7 r4). It is
31
+ * not the only way: the iOS app also caches the key of a session it opens from
32
+ * a notification, in any status (DD-1, A-5; P61B-S1-L4, P61B-S2-R1-L2).
33
+ */
34
+ export declare function noKeyMessage(id: string): string;
35
+ /**
36
+ * P61B-S1-L5: the rebuild of an interrupted resume (INV-5). This computer held P's key when it
37
+ * prepared S, so a missing key now means its device identity changed since (`codevibe reset-device`
38
+ * deletes it; the next launch mints a new one). The way forward is the same as `noKeyMessage`.
39
+ */
40
+ export declare function resetIdentityNoKeyMessage(id: string): string;
41
+ /**
42
+ * The refusal for a chosen session decided from its `getResumeDetails` row,
43
+ * before anything is prepared. `still-active` defers to the §6.2 owner-record
44
+ * table; `null` means resumable.
45
+ */
46
+ export declare function refusalFromDetails(id: string, details: ResumeDetails | null, successorResumable?: boolean): {
47
+ kind: 'message';
48
+ text: string;
49
+ } | {
50
+ kind: 'still-active';
51
+ } | null;
52
+ /** §10 copy for a create result; null for an admitted S. */
53
+ export declare function createResultMessage(predecessorSessionId: string, result: ResumeCreateResult): string | null;
54
+ /** §10, `SessionIdRetired` on a ledger replay. */
55
+ export declare function admittedThenRetiredMessage(predecessorSessionId: string, successorSessionId: string): string;
56
+ /**
57
+ * §6.2 "When P is chosen but is still ACTIVE", by P's owner record here and
58
+ * `willRetire`. `retire-and-continue` means retire P now (as the sweep does),
59
+ * then resume; `continue` means step 3 already retired it.
60
+ */
61
+ export declare function stillActiveDecision(id: string, ownerState: 'finished' | 'dead' | 'live' | 'absent' | 'unreadable', retiring: boolean): {
62
+ kind: 'refuse';
63
+ text: string;
64
+ } | {
65
+ kind: 'retire-and-continue';
66
+ } | {
67
+ kind: 'continue';
68
+ };
@@ -0,0 +1,24 @@
1
+ import type { ResumeDetails } from '../types/session';
2
+ import type { TargetSession } from './resume-target';
3
+ export type UnlockFailure = 'no-envelope-for-this-computer' | 'keychain-unavailable';
4
+ export interface UnlockedResumeRow {
5
+ details: ResumeDetails;
6
+ sessionKey: string | null;
7
+ /** Why the row could not be unlocked here (null when unlocked). */
8
+ failure: UnlockFailure | null;
9
+ directory: string | null;
10
+ lastPrompt: string | null;
11
+ /** §5.2 / amendment A-1 ordering: `max(lastHeartbeatAt, createdAt)` in ms, or null. */
12
+ activityMs: number | null;
13
+ }
14
+ export declare function resumeRowActivityMs(details: Pick<ResumeDetails, 'lastHeartbeatAt' | 'createdAt'>): number | null;
15
+ export declare function unlockResumeRow(details: ResumeDetails): Promise<UnlockedResumeRow>;
16
+ /** The target-matching view of an unlocked row (§12.2.3). */
17
+ export declare function toTargetSession(row: UnlockedResumeRow, retiring?: boolean): TargetSession;
18
+ /** Picker order: most recent activity first; undatable rows last, by ID. */
19
+ export declare function sortByActivity<T extends {
20
+ activityMs: number | null;
21
+ details: {
22
+ sessionId: string;
23
+ };
24
+ }>(rows: readonly T[]): T[];
@@ -0,0 +1,65 @@
1
+ /**
2
+ * P61 target matching (SESSION-RESUME-DESIGN.md §5.1, §6.2 step 1, §12.2.3).
3
+ * Pure: the caller has already read `listResumableSessions` to the end, the
4
+ * account's `listSessions`, and this computer's resume ledgers, and has
5
+ * decrypted each recorded directory it can.
6
+ */
7
+ /** §5.1 — the shortest session ID prefix `--resume <id>` accepts. */
8
+ export declare const RESUME_ID_PREFIX_MIN_CHARS = 8;
9
+ export type ResumeEntryPoint = {
10
+ kind: 'continue';
11
+ cwd: string;
12
+ } | {
13
+ kind: 'resume-id';
14
+ id: string;
15
+ } | {
16
+ kind: 'resume-pick';
17
+ }
18
+ /** The `/resume` child: its exact P, nothing else. */
19
+ | {
20
+ kind: 'child';
21
+ predecessorSessionId: string;
22
+ };
23
+ export interface TargetSession {
24
+ sessionId: string;
25
+ /** Decrypted recorded directory; null when this computer cannot unlock the row. */
26
+ directory: string | null;
27
+ /** `max(lastHeartbeatAt, createdAt, updatedAt)` in ms; null when none parses. */
28
+ activityMs: number | null;
29
+ /** True for an ACTIVE row this launch retires (`willRetire`), resumable once retired. */
30
+ retiring?: true;
31
+ }
32
+ export interface TargetLedger {
33
+ predecessorSessionId: string;
34
+ /** P's recorded directory, sealed in the ledger at resume time. */
35
+ predecessorDirectory: string;
36
+ sealedAtMs: number;
37
+ }
38
+ export type ResumeTarget =
39
+ /** Finish this interrupted resume as the targeted one (its S's shell starts). */
40
+ {
41
+ kind: 'ledger';
42
+ predecessorSessionId: string;
43
+ }
44
+ /** Resume this session (retire it first when `retiring`). */
45
+ | {
46
+ kind: 'session';
47
+ sessionId: string;
48
+ retiring: boolean;
49
+ }
50
+ /** Ask the user; `sessionIds` limits the picker (all resumable sessions when absent). */
51
+ | {
52
+ kind: 'pick';
53
+ sessionIds?: string[];
54
+ }
55
+ /** `--continue` found nothing in this directory. */
56
+ | {
57
+ kind: 'nothing-to-continue';
58
+ }
59
+ /** No resumable session or interrupted resume matches; the caller asks `getResumeDetails` for the exact §10 refusal of a full ID. */
60
+ | {
61
+ kind: 'no-match';
62
+ id: string;
63
+ };
64
+ export declare function isFullSessionId(id: string): boolean;
65
+ export declare function resolveResumeTarget(entry: ResumeEntryPoint, sessions: readonly TargetSession[], ledgers: readonly TargetLedger[]): ResumeTarget;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * P61 `/resume` inside the shell — production bindings (design §5.3). The
3
+ * picker and the §8.4 choice open as in-shell modals; prints become shell
4
+ * advisories. The rules live in `shell-resume.ts`.
5
+ */
6
+ import type { AppSyncClient } from '../appsync/appsync-client';
7
+ import type { Logger } from '../logger';
8
+ import type { OrchestrationAction, OrchestrationState, Tier } from '../orchestration-shell/types';
9
+ import { type ShellResumeOutcome } from './shell-resume';
10
+ /** Running work as `/status` shows it: a running task, or a team that has not resolved or halted. */
11
+ export declare function shellHasRunningWork(state: Pick<OrchestrationState, 'runningTasks' | 'team'>): boolean;
12
+ export declare function runShellResumeInShell(opts: {
13
+ client: AppSyncClient;
14
+ store: {
15
+ dispatch(action: OrchestrationAction): void;
16
+ getState(): OrchestrationState;
17
+ };
18
+ ownSessionId: string;
19
+ tier: Tier;
20
+ isTTY: boolean;
21
+ origin: 'keyboard' | 'mobile';
22
+ id: string | null;
23
+ cwd: string;
24
+ logger: Logger;
25
+ }): Promise<ShellResumeOutcome>;
@@ -0,0 +1,34 @@
1
+ import type { RelaunchRequest } from './relaunch';
2
+ import { type ResumeEntryDeps } from './resume-entry';
3
+ export declare const RESUME_DESKTOP_ONLY = "/resume works on the desktop only.";
4
+ export declare const RESUME_RUNNING_WORK = "Finish or cancel the running work first \u2014 /status shows it.";
5
+ export declare const RESUME_NEEDS_PAID_TIER = "Resuming a session needs CodeVibe Pro or Max.";
6
+ export declare const RESUME_CANCELLED = "Resume cancelled. This session continues.";
7
+ export type ShellResumeOutcome =
8
+ /** Print and keep this session running (an empty text prints nothing). */
9
+ {
10
+ kind: 'message';
11
+ text: string;
12
+ }
13
+ /** End this session through `/exit`, then relaunch as the resume of P. */
14
+ | {
15
+ kind: 'switch';
16
+ request: RelaunchRequest;
17
+ };
18
+ /** Step 1's first checks, before anything is read (a phone, Free, running work). */
19
+ export declare function shellResumePreCheck(args: {
20
+ origin: 'keyboard' | 'mobile';
21
+ paidTier: boolean;
22
+ hasRunningWork(): boolean;
23
+ }): string | null;
24
+ export declare function runShellResume(args: {
25
+ /** `/resume <id>`; null opens the picker (or lists without a terminal). */
26
+ id: string | null;
27
+ origin: 'keyboard' | 'mobile';
28
+ /** Pro or Max. */
29
+ paidTier: boolean;
30
+ hasRunningWork(): boolean;
31
+ ownSessionId: string;
32
+ /** Entry-point deps built for this shell (`ownSessionId` set; in-shell picker and choice). */
33
+ deps: ResumeEntryDeps;
34
+ }): Promise<ShellResumeOutcome>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * P61 §8.1 — which copy of P's context a resume reads, and whether this
3
+ * process may write P's directory. Pure over what the caller has already
4
+ * established: the resume lock, P's owner record here, a read-only read of
5
+ * P's local log, and whether P ran on this computer.
6
+ */
7
+ import type { ContextItem, ReadOnlyContextLogResult } from '../orchestration-shell/context-items';
8
+ import type { OwnerRecordState } from './local-records';
9
+ /** Why a resume on the computer where P ran uses the hosted copy (the §8.1 fallback-reason table). */
10
+ export type HostedFallbackReason = 'keychain-unavailable' | 'local-log-absent' | 'unreadable-local-log';
11
+ export type ResumeSourceChoice = {
12
+ source: 'local';
13
+ items: ContextItem[];
14
+ tail: {
15
+ seq: string;
16
+ itemSha256: string;
17
+ } | null;
18
+ /** P's owner is finished or dead: this resumer is P's sole post-mortem writer and drains P's outbox (best effort). */
19
+ postMortemWriter: boolean;
20
+ } | {
21
+ source: 'hosted';
22
+ /** Null when P ran on another computer. */
23
+ fallbackReason: HostedFallbackReason | null;
24
+ }
25
+ /** Every local line unsealed but the chain did not verify: refused, no fallback. */
26
+ | {
27
+ source: 'refuse-integrity';
28
+ }
29
+ /** P's own process is still closing here (§8.1 copy). */
30
+ | {
31
+ source: 'still-closing';
32
+ };
33
+ export declare function chooseResumeSource(args: {
34
+ ownerState: OwnerRecordState;
35
+ ranHere: boolean;
36
+ local: ReadOnlyContextLogResult;
37
+ }): ResumeSourceChoice;