@sayknow-cli/coding-agent 0.5.1 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +185 -14
  2. package/dist/types/cli/update-cli.d.ts +8 -1
  3. package/dist/types/commands/session.d.ts +7 -0
  4. package/dist/types/config/atomic-yaml-patch.d.ts +20 -1
  5. package/dist/types/config/keybindings.d.ts +10 -0
  6. package/dist/types/config/model-registry.d.ts +1 -1
  7. package/dist/types/config/models-config-schema.d.ts +4 -0
  8. package/dist/types/config/settings-schema.d.ts +9 -0
  9. package/dist/types/config/telegram-autostart.d.ts +9 -1
  10. package/dist/types/dap/client.d.ts +1 -0
  11. package/dist/types/lsp/client.d.ts +5 -0
  12. package/dist/types/modes/components/pet-capability.d.ts +8 -7
  13. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  14. package/dist/types/modes/components/sayknow-pet-widget.d.ts +1 -1
  15. package/dist/types/modes/components/welcome.d.ts +1 -0
  16. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  17. package/dist/types/modes/interactive-mode.d.ts +13 -1
  18. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +7 -0
  19. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -0
  20. package/dist/types/modes/types.d.ts +7 -1
  21. package/dist/types/runtime-mcp/transports/stdio.d.ts +2 -0
  22. package/dist/types/session/agent-session.d.ts +9 -0
  23. package/dist/types/session/internal/managed-session-scope.d.ts +3 -1
  24. package/dist/types/session/internal/managed-session-storage.d.ts +2 -0
  25. package/dist/types/session/session-manager.d.ts +9 -0
  26. package/dist/types/session/session-storage.d.ts +17 -0
  27. package/dist/types/session-import/claude.d.ts +26 -0
  28. package/dist/types/session-import/codex.d.ts +22 -0
  29. package/dist/types/session-import/command.d.ts +20 -0
  30. package/dist/types/session-import/detect.d.ts +22 -0
  31. package/dist/types/session-import/index.d.ts +7 -0
  32. package/dist/types/session-import/redact.d.ts +23 -0
  33. package/dist/types/session-import/service.d.ts +28 -0
  34. package/dist/types/session-import/types.d.ts +125 -0
  35. package/dist/types/skc-runtime/boot-generation.d.ts +59 -0
  36. package/dist/types/skc-runtime/launch-tmux.d.ts +10 -2
  37. package/dist/types/skc-runtime/launch-worktree.d.ts +17 -1
  38. package/dist/types/skc-runtime/session-restore-runtime.d.ts +41 -0
  39. package/dist/types/skc-runtime/session-restore.d.ts +99 -0
  40. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +160 -0
  41. package/dist/types/skc-runtime/tmux-sessions.d.ts +26 -1
  42. package/dist/types/slash-commands/builtin-registry.d.ts +4 -0
  43. package/dist/types/slash-commands/types.d.ts +5 -0
  44. package/dist/types/tools/ask.d.ts +169 -4
  45. package/package.json +16 -7
  46. package/scripts/generate-sdk-operation-inventory.ts +5 -6
  47. package/src/cli/config-cli.ts +23 -8
  48. package/src/cli/update-cli.ts +265 -21
  49. package/src/commands/session.ts +88 -2
  50. package/src/config/atomic-yaml-patch.ts +167 -30
  51. package/src/config/keybindings.ts +10 -0
  52. package/src/config/model-profiles.ts +69 -1
  53. package/src/config/model-registry.ts +72 -38
  54. package/src/config/models-config-schema.ts +1 -1
  55. package/src/config/settings-schema.ts +9 -0
  56. package/src/config/settings.ts +13 -2
  57. package/src/config/telegram-autostart.ts +11 -4
  58. package/src/dap/client.ts +78 -30
  59. package/src/defaults/skc/skills/deep-interview/SKILL.md +29 -3
  60. package/src/internal-urls/docs-index.generated.ts +5 -4
  61. package/src/lsp/client.ts +17 -29
  62. package/src/main.ts +1 -1
  63. package/src/modes/action-registry.ts +2 -0
  64. package/src/modes/components/pet-capability.ts +22 -13
  65. package/src/modes/components/pet-selector.ts +1 -1
  66. package/src/modes/components/sayknow-pet-widget.ts +41 -7
  67. package/src/modes/components/welcome.ts +5 -0
  68. package/src/modes/controllers/event-controller.ts +1 -1
  69. package/src/modes/controllers/goal-mode-controller.ts +7 -2
  70. package/src/modes/controllers/input-controller.ts +71 -6
  71. package/src/modes/controllers/plan-mode-controller.ts +8 -1
  72. package/src/modes/controllers/runtime-mcp-command-controller.ts +14 -0
  73. package/src/modes/controllers/selector-controller.ts +47 -19
  74. package/src/modes/interactive-mode.ts +54 -4
  75. package/src/modes/shared/agent-wire/unattended-session.ts +40 -9
  76. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -0
  77. package/src/modes/types.ts +5 -1
  78. package/src/notifications/lifecycle-control-runtime.ts +258 -179
  79. package/src/prompts/system/eager-todo.md +2 -0
  80. package/src/prompts/system/plan-mode-approved.md +1 -1
  81. package/src/prompts/system/system-prompt.md +4 -2
  82. package/src/runtime-mcp/transports/stdio.ts +96 -33
  83. package/src/sdk/broker/lifecycle.ts +6 -5
  84. package/src/sdk/bus/lifecycle-control-runtime.ts +189 -110
  85. package/src/sdk/protocol/operation-inventory.generated.json +33 -0
  86. package/src/sdk/session.ts +25 -18
  87. package/src/session/agent-session.ts +120 -25
  88. package/src/session/internal/managed-session-scope.ts +41 -6
  89. package/src/session/internal/managed-session-storage.ts +14 -0
  90. package/src/session/session-manager.ts +113 -11
  91. package/src/session/session-storage.ts +70 -0
  92. package/src/session-import/claude.ts +382 -0
  93. package/src/session-import/codex.ts +458 -0
  94. package/src/session-import/command.ts +61 -0
  95. package/src/session-import/detect.ts +133 -0
  96. package/src/session-import/index.ts +27 -0
  97. package/src/session-import/redact.ts +125 -0
  98. package/src/session-import/service.ts +749 -0
  99. package/src/session-import/types.ts +163 -0
  100. package/src/skc-runtime/boot-generation.ts +172 -0
  101. package/src/skc-runtime/launch-tmux.ts +219 -41
  102. package/src/skc-runtime/launch-worktree.ts +277 -4
  103. package/src/skc-runtime/session-restore-runtime.ts +120 -0
  104. package/src/skc-runtime/session-restore.ts +296 -0
  105. package/src/skc-runtime/session-state-sidecar.ts +41 -0
  106. package/src/skc-runtime/tmux-owner-isolation.ts +665 -0
  107. package/src/skc-runtime/tmux-sessions.ts +284 -108
  108. package/src/slash-commands/acp-builtins.ts +5 -1
  109. package/src/slash-commands/builtin-registry.ts +109 -5
  110. package/src/slash-commands/types.ts +5 -0
  111. package/src/tools/ask.ts +188 -10
  112. package/src/tools/eval.ts +2 -2
  113. package/src/export/html/template.css +0 -1060
  114. package/src/export/html/template.html +0 -47
  115. package/src/export/html/template.js +0 -2348
  116. package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
  117. package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
  118. package/vendor/insane-search/engine/tests/test_u1.py +0 -200
  119. package/vendor/insane-search/engine/tests/test_u4.py +0 -131
  120. package/vendor/insane-search/engine/tests/test_u5.py +0 -163
  121. package/vendor/insane-search/engine/tests/test_u7.py +0 -124
  122. package/vendor/insane-search/engine/tests/test_u8.py +0 -216
@@ -0,0 +1,99 @@
1
+ import { type BootGeneration, type RecordedBootGeneration } from "./boot-generation";
2
+ export declare const RESTORE_POINTER_SCHEMA_VERSION = 1;
3
+ export interface RestorePointer {
4
+ schema_version: number;
5
+ /** Coordinator identity: what the create fence and the sidecar are keyed by. */
6
+ coordinator_session_id: string;
7
+ state_file: string;
8
+ /** SKC session id whose transcript `skc --resume` would reopen. */
9
+ skc_session_id: string;
10
+ session_file: string;
11
+ cwd: string;
12
+ branch: string | null;
13
+ boot: RecordedBootGeneration;
14
+ updated_at: string;
15
+ }
16
+ /** Pointers live under SKC's own root so they are discoverable without scanning projects. */
17
+ export declare function restorePointerDirectory(): string;
18
+ export declare function restorePointerFile(coordinatorSessionId: string, stateFile: string): string;
19
+ export declare function isRestorePointer(value: unknown): value is RestorePointer;
20
+ /**
21
+ * The session id `skc --resume` resolves is the transcript header id, which is
22
+ * NOT the coordinator id: a normal `skc --tmux` child inherits only the
23
+ * coordinator identity and then mints its own session id. Reading it from the
24
+ * transcript is the only way a pointer can name the conversation that will
25
+ * actually be reopened.
26
+ */
27
+ export declare function readTranscriptSessionId(sessionFile: string): string | null;
28
+ export interface PublishRestorePointerInput {
29
+ coordinatorSessionId: string;
30
+ stateFile: string;
31
+ skcSessionId: string;
32
+ sessionFile: string;
33
+ cwd: string;
34
+ branch?: string | null;
35
+ bootGeneration?: BootGeneration;
36
+ now?: () => Date;
37
+ }
38
+ /**
39
+ * Publishes (or refreshes) the pointer for a live session.
40
+ *
41
+ * Returns false without writing when the platform cannot produce boot evidence:
42
+ * a pointer whose boot value is unusable could never be judged `changed`, so
43
+ * writing one would only add noise. Never throws — losing a pointer must not
44
+ * break the session that was trying to publish it.
45
+ */
46
+ export declare function publishRestorePointer(input: PublishRestorePointerInput): boolean;
47
+ /** Lists candidate pointers. Unreadable or malformed entries are skipped, never guessed at. */
48
+ export declare function listRestorePointers(): RestorePointer[];
49
+ /**
50
+ * Exact reference to one candidate, for `skc session restore --reference`.
51
+ *
52
+ * base64url of the identity pair, so a reference cannot be confused with a
53
+ * session id, a path, or a prefix. It selects a candidate; it never overrides
54
+ * any eligibility check.
55
+ */
56
+ export declare function encodeRestoreReference(coordinatorSessionId: string, stateFile: string): string;
57
+ export declare function decodeRestoreReference(reference: string): {
58
+ coordinatorSessionId: string;
59
+ stateFile: string;
60
+ } | null;
61
+ export type RestoreIneligibleReason = "same_boot" | "boot_unknown" | "sidecar_missing" | "sidecar_identity_mismatch" | "sidecar_terminal" | "transcript_missing" | "cwd_missing" | "live_identity_collision" | "transcript_identity_mismatch" | "unsupported_owner_proof";
62
+ export type RestoreCandidateVerdict = {
63
+ eligible: true;
64
+ pointer: RestorePointer;
65
+ } | {
66
+ eligible: false;
67
+ pointer: RestorePointer;
68
+ reason: RestoreIneligibleReason;
69
+ detail?: string;
70
+ };
71
+ /** The sidecar fields restore is allowed to trust, read fresh at decision time. */
72
+ export interface RestoreSidecarFacts {
73
+ sessionId: string;
74
+ stateFile: string;
75
+ sessionFile: string | null;
76
+ cwd: string | null;
77
+ terminal: boolean;
78
+ }
79
+ export interface RestoreCandidateDeps {
80
+ currentBoot: BootGeneration;
81
+ /** Strict re-read of the referenced sidecar. Null when absent or unparseable. */
82
+ readSidecar: (pointer: RestorePointer) => RestoreSidecarFacts | null;
83
+ pathExists: (target: string) => boolean;
84
+ /** True when a live tmux session already owns this identity. */
85
+ hasLiveIdentity: (pointer: RestorePointer) => boolean;
86
+ /** Header id of the transcript the pointer names, re-read at decision time. */
87
+ readTranscriptSessionId: (pointer: RestorePointer) => string | null;
88
+ /** False when this host cannot produce the owner proof restore requires (psmux). */
89
+ ownerProofAvailable: () => boolean;
90
+ }
91
+ /**
92
+ * Decides whether one candidate may be restored.
93
+ *
94
+ * Order matters: the reboot proof comes first because it is the cheapest and the
95
+ * most restrictive gate, and the pointer's own contents are never trusted beyond
96
+ * naming what to re-read.
97
+ */
98
+ export declare function evaluateRestoreCandidate(pointer: RestorePointer, deps: RestoreCandidateDeps): RestoreCandidateVerdict;
99
+ export declare function evaluateRestoreCandidates(pointers: readonly RestorePointer[], deps: RestoreCandidateDeps): RestoreCandidateVerdict[];
@@ -302,6 +302,166 @@ export declare function replaceOwnerGenerationSync(stateDir: string, sessionId:
302
302
  /** Publishes a raw-owner generation through the canonical SQLite-serialized CAS path. */
303
303
  export declare function publishOwnerGenerationSync(request: PublishGenerationRequest): PublishGenerationResult;
304
304
  export declare function createOwnerIntent(stateDir: string, input: Omit<OwnerIntent, "schema_version" | "intent_id" | "state">): Promise<OwnerIntent>;
305
+ /**
306
+ * Shared identity create fence (frozen contract F1'-F9''', architect-approved).
307
+ *
308
+ * Concurrent creators of the SAME canonical `(stateDir, sessionId)` identity must
309
+ * not both spawn a child: `SessionManager` opens transcripts with append flags and
310
+ * has no inter-process single-writer lock, so two owners on one transcript is a
311
+ * data-integrity failure rather than a recoverable skip.
312
+ *
313
+ * The fence deliberately does NOT hold the SQLite writer transaction across the
314
+ * spawn. `bootstrapTmuxOwnerIsolation()` runs in a separate process and acquires
315
+ * this very database twice itself, and the synchronous creator blocks on that
316
+ * helper through `Bun.spawnSync`, so a held transaction would deadlock the helper
317
+ * against its own parent. A long hold would also starve the 250 ms verdict path
318
+ * that shares this database. Instead each transition takes a short (target 50 ms)
319
+ * transaction that only reads and writes a durable reservation row; mutual
320
+ * exclusion outlives the transaction as row state, not as a held lock.
321
+ */
322
+ export type IdentityCreatePhase = "reserved" | "helper_invoked" | "spawned" | "tagged" | "published";
323
+ /** Phases at or beyond which an untagged child may already exist on the server. */
324
+ export declare function identityCreatePhaseMayHaveChild(phase: IdentityCreatePhase): boolean;
325
+ export interface IdentityCreateKey {
326
+ stateDir: string;
327
+ sessionId: string;
328
+ stateFile: string;
329
+ }
330
+ export interface IdentityCreateReservation {
331
+ reservationId: string;
332
+ stateDir: string;
333
+ sessionId: string;
334
+ stateFile: string;
335
+ ownerPid: number;
336
+ ownerIncarnation: string;
337
+ phase: IdentityCreatePhase;
338
+ attemptSessionName: string | null;
339
+ nativeSessionId: string | null;
340
+ serverPid: number | null;
341
+ serverStartTime: string | null;
342
+ claimedAt: string;
343
+ updatedAt: string;
344
+ leaseDeadline: string;
345
+ }
346
+ export type IdentityCreateReservationResult = {
347
+ ok: true;
348
+ reservation: IdentityCreateReservation;
349
+ recovered: IdentityCreateReservation | null;
350
+ } | {
351
+ ok: false;
352
+ code: "identity_reserved_live" | "identity_reserved_unknown" | "identity_fence_contended" | "identity_incarnation_unavailable" | "identity_orphan_unresolved" | "identity_existing_owner";
353
+ existing: IdentityCreateReservation | null;
354
+ diagnostic: string;
355
+ };
356
+ /** Owner liveness is tri-state: only a proven-dead owner may be displaced. */
357
+ export type OwnerLiveness = "alive" | "dead" | "unknown";
358
+ /**
359
+ * `processIncarnation()` returns `undefined` both for a vanished process and for a
360
+ * failed probe, so existence is resolved first. Signal 0 distinguishes them:
361
+ * `ESRCH` proves absence, `EPERM` proves a live process owned by another user, and
362
+ * anything else is unknown. Only then does the incarnation discriminate PID reuse.
363
+ */
364
+ export declare function probeOwnerLiveness(pid: number, recordedIncarnation: string, deps?: {
365
+ readIncarnation?: (pid: number) => string | undefined;
366
+ signal?: (pid: number) => void;
367
+ }): OwnerLiveness;
368
+ /** @internal Test seam for the incarnation reader. */
369
+ export declare function __setOwnerIncarnationReaderForTests(reader: ((pid: number) => string | undefined) | null): void;
370
+ export declare function canonicalIdentityCreateKey(key: IdentityCreateKey): IdentityCreateKey;
371
+ /**
372
+ * Ends the attempt while KEEPING the durable row.
373
+ *
374
+ * Used when cleanup after a spawn was uncertain: a child may survive that we
375
+ * could not remove, so the evidence must outlive the attempt. Unlike
376
+ * `releaseIdentityCreate` this leaves the row for authority-first recovery.
377
+ */
378
+ export declare function abandonIdentityCreate(reservation: IdentityCreateReservation): void;
379
+ export interface ReserveIdentityCreateOptions {
380
+ ttlMs?: number;
381
+ ownerPid?: number;
382
+ ownerIncarnation?: string;
383
+ now?: () => Date;
384
+ probeLiveness?: (pid: number, incarnation: string) => OwnerLiveness;
385
+ }
386
+ /**
387
+ * Claims the identity when it is free, and otherwise reports why not.
388
+ *
389
+ * A dead previous owner is deliberately NOT displaced here: authority-first
390
+ * recovery has to census live tmux, which shells out and must never run inside
391
+ * the fence window. The caller performs that census and then calls
392
+ * {@link reclaimIdentityCreate}.
393
+ *
394
+ * The lease deadline is diagnostic only. Expiry never authorizes takeover; only a
395
+ * proven-dead owner does, because real creator paths block in unbounded
396
+ * `Bun.spawnSync` and cannot heartbeat while blocked.
397
+ */
398
+ export declare function reserveIdentityCreate(rawKey: IdentityCreateKey, options?: ReserveIdentityCreateOptions): IdentityCreateReservationResult;
399
+ /**
400
+ * Completes authority-first recovery after the caller proved, outside the fence
401
+ * window, that the abandoned reservation left no authoritative child behind.
402
+ *
403
+ * `expectedReservationId` pins the exact abandoned row: if another process already
404
+ * recovered it, the row no longer matches and this fails closed rather than
405
+ * producing a second child.
406
+ */
407
+ export declare function reclaimIdentityCreate(rawKey: IdentityCreateKey, expectedReservationId: string, options?: ReserveIdentityCreateOptions): IdentityCreateReservationResult;
408
+ /**
409
+ * What a live-tmux census concluded about the child an abandoned reservation may
410
+ * have left behind. `unknown` is not a soft failure: it is the fail-closed case.
411
+ */
412
+ export type AbandonedIdentityVerdict = {
413
+ kind: "authoritative";
414
+ nativeSessionId: string;
415
+ } | {
416
+ kind: "orphan";
417
+ nativeSessionId: string;
418
+ } | {
419
+ kind: "absent";
420
+ } | {
421
+ kind: "unknown";
422
+ reason: string;
423
+ };
424
+ /**
425
+ * Injected so this module never imports the tmux session helpers (which already
426
+ * depend on it). The census shells out and therefore runs OUTSIDE the fence
427
+ * window, by construction.
428
+ */
429
+ export interface AbandonedIdentityCensus {
430
+ inspect(evidence: {
431
+ stateDir: string;
432
+ sessionId: string;
433
+ attemptSessionName: string | null;
434
+ nativeSessionId: string | null;
435
+ }): AbandonedIdentityVerdict;
436
+ cleanupOrphan(nativeSessionId: string, attemptSessionName: string | null): void;
437
+ }
438
+ /**
439
+ * Authority-first recovery for a reservation whose owner is proven dead and that
440
+ * reached at least `helper_invoked`, so an untagged child may exist.
441
+ *
442
+ * The reservation's own `phase` is a hint, never the authority: a creator can
443
+ * publish its generation and die before recording `published`. The census reads
444
+ * the live canonical tags and the current published generation instead, so a
445
+ * valid child is preserved rather than killed — which is what keeps a
446
+ * tag-before-report crash from producing a successor.
447
+ */
448
+ export declare function recoverAbandonedIdentityCreate(key: IdentityCreateKey, existing: IdentityCreateReservation, census: AbandonedIdentityCensus, options?: ReserveIdentityCreateOptions): IdentityCreateReservationResult;
449
+ export interface IdentityCreatePhasePatch {
450
+ attemptSessionName?: string | null;
451
+ nativeSessionId?: string | null;
452
+ serverPid?: number | null;
453
+ serverStartTime?: string | null;
454
+ }
455
+ /**
456
+ * Records a create-progress transition and renews the lease.
457
+ *
458
+ * Returns `null` when the reservation is no longer ours. The caller MUST treat
459
+ * that as a lost fence and perform no tmux mutation: another process has already
460
+ * recovered this identity.
461
+ */
462
+ export declare function advanceIdentityCreatePhase(reservation: IdentityCreateReservation, phase: IdentityCreatePhase, patch?: IdentityCreatePhasePatch, options?: Pick<ReserveIdentityCreateOptions, "ttlMs" | "now">): IdentityCreateReservation | null;
463
+ /** Releases the reservation. Idempotent, and never deletes a successor's row. */
464
+ export declare function releaseIdentityCreate(reservation: IdentityCreateReservation): void;
305
465
  /** Publish exactly one terminal verdict. Existing valid verdicts always win. */
306
466
  export declare function observeOwnerTerminal(request: ObserveTerminalRequest): Promise<OwnerVerdict>;
307
467
  /** Strictly validate the persisted authorization, optionally against its terminal observation. */
@@ -1,4 +1,4 @@
1
- import { type OwnerIsolationProbeSync, type PlanResponse, type TmuxOwnerIsolationExecutionDependencies, type TmuxOwnerIsolationExecutionResult } from "./tmux-owner-isolation";
1
+ import { type AbandonedIdentityCensus, type IdentityCreateKey, type IdentityCreateReservationResult, type OwnerIsolationProbeSync, type PlanResponse, type TmuxOwnerIsolationExecutionDependencies, type TmuxOwnerIsolationExecutionResult } from "./tmux-owner-isolation";
2
2
  export interface SkcTmuxSessionStatus {
3
3
  name: string;
4
4
  attached: boolean;
@@ -67,6 +67,17 @@ export interface ForceCloseOwnerDependencies {
67
67
  }
68
68
  export interface CreateSkcTmuxSessionOptions {
69
69
  platform?: NodeJS.Platform;
70
+ /**
71
+ * Restore only: run the child in the session's recorded directory instead of
72
+ * this process's cwd.
73
+ */
74
+ cwd?: string;
75
+ /**
76
+ * Restore only: argv appended to the `skc` child, e.g. `["--resume", "<id>"]`.
77
+ * Restore reuses this creator verbatim so it inherits the same identity fence
78
+ * and owner-isolation proof as an ordinary create.
79
+ */
80
+ childArgs?: string[];
70
81
  }
71
82
  export type CreateOwnerIsolationTestDependencies = {
72
83
  probe?: Partial<OwnerIsolationProbeSync>;
@@ -83,6 +94,20 @@ export declare function findSkcTmuxSessionByBranch(branch: string, env?: NodeJS.
83
94
  export declare function findSkcTmuxSessionByName(sessionName: string, env?: NodeJS.ProcessEnv): SkcTmuxSessionStatus | undefined;
84
95
  export declare function findSkcTmuxSessionByScope(project: string, branch: string | null | undefined, env?: NodeJS.ProcessEnv): SkcTmuxSessionStatus | undefined;
85
96
  export declare function statusSkcTmuxSession(sessionName: string, env?: NodeJS.ProcessEnv): SkcTmuxSessionStatus;
97
+ /**
98
+ * Live-tmux census for authority-first recovery of an abandoned reservation.
99
+ *
100
+ * Authority is the CURRENT published generation on disk compared against the
101
+ * live session's canonical tags — never the reservation's own `phase`, because a
102
+ * creator can publish and then die before recording that it published.
103
+ */
104
+ /**
105
+ * Resolves a reservation refused because its previous owner died after reaching
106
+ * the helper. Shared by every producer so none of them can brick an identity by
107
+ * treating an abandoned row as a permanent collision.
108
+ */
109
+ export declare function settleAbandonedIdentity(reserved: IdentityCreateReservationResult, identityKey: IdentityCreateKey, census: AbandonedIdentityCensus): IdentityCreateReservationResult;
110
+ export declare function abandonedIdentityCensus(tmuxCommand: string, env: NodeJS.ProcessEnv, stateFile: string): AbandonedIdentityCensus;
86
111
  export declare function createSkcTmuxSession(env?: NodeJS.ProcessEnv, options?: CreateSkcTmuxSessionOptions): SkcTmuxSessionStatus;
87
112
  /** Proves a managed reusable name still resolves to one immutable session on one server. */
88
113
  export declare function proveSkcTmuxSessionMutationTarget(sessionName: string, env?: NodeJS.ProcessEnv): ProvenTmuxSessionIdentity;
@@ -1,10 +1,14 @@
1
1
  import { type ComposerSubmissionOptions } from "../modes/types";
2
+ import { runSessionImportCommand } from "../session-import";
2
3
  import type { BuiltinSlashCommand, ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime, SlashCommandSpec, TuiSlashCommandRuntime } from "./types";
3
4
  export type { BuiltinSlashCommand, SubcommandDef } from "./types";
4
5
  /** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
5
6
  export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime & {
6
7
  composer?: ComposerSubmissionOptions;
7
8
  };
9
+ type SessionImportRunner = typeof runSessionImportCommand;
10
+ export declare function handleImportSessionCommand(command: ParsedSlashCommand, runtime: SlashCommandRuntime, importer?: SessionImportRunner): Promise<SlashCommandResult>;
11
+ export declare function handleImportSessionTuiCommand(command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime, importer?: SessionImportRunner): Promise<SlashCommandResult>;
8
12
  export declare function formatUnknownBuiltinSlashCommandDiagnostic(commandName: string): string | undefined;
9
13
  /** Builtin command metadata used for slash-command autocomplete and help text. */
10
14
  export declare const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand>;
@@ -81,6 +81,11 @@ export interface SlashCommandSpec extends BuiltinSlashCommand {
81
81
  aliases?: string[];
82
82
  /** When false, the dispatcher refuses to handle invocations that include arguments. */
83
83
  allowArgs?: boolean;
84
+ /**
85
+ * Whether ACP may advertise and execute this command. Commands that read
86
+ * caller-selected local paths must set this to false.
87
+ */
88
+ acp?: boolean;
84
89
  /**
85
90
  * ACP-specific override for `description`. Used by `ACP_BUILTIN_SLASH_COMMANDS`
86
91
  * when building `available_commands_update` payloads so the client receives
@@ -29,13 +29,92 @@ export declare const askSchema: z.ZodObject<{
29
29
  }, z.core.$strip>>;
30
30
  multi: z.ZodOptional<z.ZodBoolean>;
31
31
  recommended: z.ZodOptional<z.ZodNumber>;
32
- deepInterview: z.ZodOptional<z.ZodObject<{
32
+ workflowGate: z.ZodOptional<z.ZodObject<{
33
+ stage: z.ZodEnum<{
34
+ "deep-interview": "deep-interview";
35
+ ralplan: "ralplan";
36
+ ultragoal: "ultragoal";
37
+ }>;
38
+ kind: z.ZodEnum<{
39
+ question: "question";
40
+ approval: "approval";
41
+ execution: "execution";
42
+ }>;
43
+ }, z.core.$strip>>;
44
+ deepInterview: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
45
+ round: z.ZodLiteral<0>;
46
+ component: z.ZodLiteral<"review-topology">;
47
+ intent_contract: z.ZodObject<{
48
+ items: z.ZodArray<z.ZodObject<{
49
+ id: z.ZodString;
50
+ category: z.ZodEnum<{
51
+ surface: "surface";
52
+ artifact: "artifact";
53
+ integration: "integration";
54
+ constraint: "constraint";
55
+ }>;
56
+ statement: z.ZodString;
57
+ }, z.core.$strict>>;
58
+ confirmation_options: z.ZodArray<z.ZodString>;
59
+ }, z.core.$strict>;
33
60
  round_id: z.ZodOptional<z.ZodString>;
61
+ dimension: z.ZodString;
62
+ ambiguity: z.ZodNumber;
63
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
64
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
65
+ reference_id: z.ZodString;
66
+ label: z.ZodString;
67
+ origin: z.ZodString;
68
+ url: z.ZodOptional<z.ZodString>;
69
+ excerpt: z.ZodOptional<z.ZodString>;
70
+ }, z.core.$strict>>>;
71
+ }, z.core.$strict>, z.ZodObject<{
34
72
  round: z.ZodNumber;
73
+ intent_review: z.ZodObject<{
74
+ observed_items: z.ZodArray<z.ZodObject<{
75
+ id: z.ZodString;
76
+ category: z.ZodEnum<{
77
+ surface: "surface";
78
+ artifact: "artifact";
79
+ integration: "integration";
80
+ constraint: "constraint";
81
+ }>;
82
+ statement: z.ZodString;
83
+ }, z.core.$strict>>;
84
+ supporting_substitutions: z.ZodArray<z.ZodObject<{
85
+ removed_id: z.ZodString;
86
+ replacement_ids: z.ZodArray<z.ZodString>;
87
+ rationale: z.ZodString;
88
+ }, z.core.$strict>>;
89
+ approval_options: z.ZodArray<z.ZodString>;
90
+ }, z.core.$strict>;
91
+ round_id: z.ZodOptional<z.ZodString>;
35
92
  component: z.ZodString;
36
93
  dimension: z.ZodString;
37
94
  ambiguity: z.ZodNumber;
38
- }, z.core.$strip>>;
95
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
96
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
97
+ reference_id: z.ZodString;
98
+ label: z.ZodString;
99
+ origin: z.ZodString;
100
+ url: z.ZodOptional<z.ZodString>;
101
+ excerpt: z.ZodOptional<z.ZodString>;
102
+ }, z.core.$strict>>>;
103
+ }, z.core.$strict>, z.ZodObject<{
104
+ round_id: z.ZodOptional<z.ZodString>;
105
+ round: z.ZodNumber;
106
+ component: z.ZodString;
107
+ dimension: z.ZodString;
108
+ ambiguity: z.ZodNumber;
109
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
110
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
111
+ reference_id: z.ZodString;
112
+ label: z.ZodString;
113
+ origin: z.ZodString;
114
+ url: z.ZodOptional<z.ZodString>;
115
+ excerpt: z.ZodOptional<z.ZodString>;
116
+ }, z.core.$strict>>>;
117
+ }, z.core.$strict>]>>;
39
118
  }, z.core.$strip>>;
40
119
  }, z.core.$strip>;
41
120
  export type AskToolInput = z.infer<typeof askSchema>;
@@ -54,6 +133,8 @@ export interface AskToolDetails {
54
133
  multi?: boolean;
55
134
  selectedOptions?: string[];
56
135
  customInput?: string;
136
+ /** Set when the user answered with a clarification question instead of a choice. */
137
+ clarificationQuestion?: string;
57
138
  /** Multi-part question mode */
58
139
  results?: QuestionResult[];
59
140
  }
@@ -80,16 +161,100 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
80
161
  }, z.core.$strip>>;
81
162
  multi: z.ZodOptional<z.ZodBoolean>;
82
163
  recommended: z.ZodOptional<z.ZodNumber>;
83
- deepInterview: z.ZodOptional<z.ZodObject<{
164
+ workflowGate: z.ZodOptional<z.ZodObject<{
165
+ stage: z.ZodEnum<{
166
+ "deep-interview": "deep-interview";
167
+ ralplan: "ralplan";
168
+ ultragoal: "ultragoal";
169
+ }>;
170
+ kind: z.ZodEnum<{
171
+ question: "question";
172
+ approval: "approval";
173
+ execution: "execution";
174
+ }>;
175
+ }, z.core.$strip>>;
176
+ deepInterview: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
177
+ round: z.ZodLiteral<0>;
178
+ component: z.ZodLiteral<"review-topology">;
179
+ intent_contract: z.ZodObject<{
180
+ items: z.ZodArray<z.ZodObject<{
181
+ id: z.ZodString;
182
+ category: z.ZodEnum<{
183
+ surface: "surface";
184
+ artifact: "artifact";
185
+ integration: "integration";
186
+ constraint: "constraint";
187
+ }>;
188
+ statement: z.ZodString;
189
+ }, z.core.$strict>>;
190
+ confirmation_options: z.ZodArray<z.ZodString>;
191
+ }, z.core.$strict>;
84
192
  round_id: z.ZodOptional<z.ZodString>;
193
+ dimension: z.ZodString;
194
+ ambiguity: z.ZodNumber;
195
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
196
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
197
+ reference_id: z.ZodString;
198
+ label: z.ZodString;
199
+ origin: z.ZodString;
200
+ url: z.ZodOptional<z.ZodString>;
201
+ excerpt: z.ZodOptional<z.ZodString>;
202
+ }, z.core.$strict>>>;
203
+ }, z.core.$strict>, z.ZodObject<{
85
204
  round: z.ZodNumber;
205
+ intent_review: z.ZodObject<{
206
+ observed_items: z.ZodArray<z.ZodObject<{
207
+ id: z.ZodString;
208
+ category: z.ZodEnum<{
209
+ surface: "surface";
210
+ artifact: "artifact";
211
+ integration: "integration";
212
+ constraint: "constraint";
213
+ }>;
214
+ statement: z.ZodString;
215
+ }, z.core.$strict>>;
216
+ supporting_substitutions: z.ZodArray<z.ZodObject<{
217
+ removed_id: z.ZodString;
218
+ replacement_ids: z.ZodArray<z.ZodString>;
219
+ rationale: z.ZodString;
220
+ }, z.core.$strict>>;
221
+ approval_options: z.ZodArray<z.ZodString>;
222
+ }, z.core.$strict>;
223
+ round_id: z.ZodOptional<z.ZodString>;
86
224
  component: z.ZodString;
87
225
  dimension: z.ZodString;
88
226
  ambiguity: z.ZodNumber;
89
- }, z.core.$strip>>;
227
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
228
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
229
+ reference_id: z.ZodString;
230
+ label: z.ZodString;
231
+ origin: z.ZodString;
232
+ url: z.ZodOptional<z.ZodString>;
233
+ excerpt: z.ZodOptional<z.ZodString>;
234
+ }, z.core.$strict>>>;
235
+ }, z.core.$strict>, z.ZodObject<{
236
+ round_id: z.ZodOptional<z.ZodString>;
237
+ round: z.ZodNumber;
238
+ component: z.ZodString;
239
+ dimension: z.ZodString;
240
+ ambiguity: z.ZodNumber;
241
+ confused_terms: z.ZodOptional<z.ZodArray<z.ZodString>>;
242
+ references: z.ZodOptional<z.ZodArray<z.ZodObject<{
243
+ reference_id: z.ZodString;
244
+ label: z.ZodString;
245
+ origin: z.ZodString;
246
+ url: z.ZodOptional<z.ZodString>;
247
+ excerpt: z.ZodOptional<z.ZodString>;
248
+ }, z.core.$strict>>>;
249
+ }, z.core.$strict>]>>;
90
250
  }, z.core.$strip>>;
91
251
  }, z.core.$strip>;
92
252
  readonly strict = true;
253
+ /**
254
+ * These fields are rendered to the user and carry no executable or durable
255
+ * meaning. IDs and workflow/deep-interview metadata remain fail-closed.
256
+ */
257
+ readonly displaySafeEscapedArgFields: readonly ["questions.question", "questions.options.label"];
93
258
  readonly loadMode = "discoverable";
94
259
  constructor(session: ToolSession);
95
260
  static createIf(session: ToolSession): AskTool | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/coding-agent",
4
- "version": "0.5.1",
4
+ "version": "0.5.6",
5
5
  "description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://sayknow-cli.com",
7
7
  "author": "jaybeyond",
@@ -33,6 +33,7 @@
33
33
  "scripts": {
34
34
  "build": "bun scripts/build-binary.ts",
35
35
  "check": "biome check . && bun run check:types",
36
+ "check:runtime": "bun run generate-hotkeys-docs --check",
36
37
  "check:types": "tsgo -p tsconfig.json --noEmit",
37
38
  "lint": "biome lint .",
38
39
  "test": "bun test --isolate",
@@ -43,6 +44,7 @@
43
44
  "fmt": "biome format --write . && bun run format-prompts",
44
45
  "format-prompts": "bun scripts/format-prompts.ts",
45
46
  "generate-docs-index": "bun scripts/generate-docs-index.ts",
47
+ "generate-hotkeys-docs": "bun scripts/generate-hotkeys-docs.ts",
46
48
  "prepack": "bun scripts/generate-docs-index.ts",
47
49
  "generate-template": "bun scripts/generate-template.ts",
48
50
  "install:defaults": "bun src/cli.ts setup defaults",
@@ -52,12 +54,12 @@
52
54
  "@agentclientprotocol/sdk": "1.3.0",
53
55
  "@babel/parser": "^7.29.3",
54
56
  "@mozilla/readability": "^0.6.0",
55
- "@sayknow-cli/stats": "0.5.1",
56
- "@sayknow-cli/agent-core": "0.5.1",
57
- "@sayknow-cli/ai": "0.5.1",
58
- "@sayknow-cli/natives": "0.5.1",
59
- "@sayknow-cli/tui": "0.5.1",
60
- "@sayknow-cli/utils": "0.5.1",
57
+ "@sayknow-cli/stats": "0.5.6",
58
+ "@sayknow-cli/agent-core": "0.5.6",
59
+ "@sayknow-cli/ai": "0.5.6",
60
+ "@sayknow-cli/natives": "0.5.6",
61
+ "@sayknow-cli/tui": "0.5.6",
62
+ "@sayknow-cli/utils": "0.5.6",
61
63
  "@puppeteer/browsers": "^2.13.0",
62
64
  "@types/turndown": "5.0.6",
63
65
  "@xterm/headless": "^6.0.0",
@@ -88,6 +90,10 @@
88
90
  "README.md",
89
91
  "CHANGELOG.md",
90
92
  "vendor",
93
+ "!vendor/insane-search/engine/tests/**",
94
+ "!src/export/html/template.html",
95
+ "!src/export/html/template.css",
96
+ "!src/export/html/template.js",
91
97
  "dist/types"
92
98
  ],
93
99
  "exports": {
@@ -124,6 +130,9 @@
124
130
  "./commands/skc-runtime-bridge": null,
125
131
  "./capability/mcp": null,
126
132
  "./config/mcp-schema": null,
133
+ "./config/model-auth": null,
134
+ "./config/model-bindings-applier": null,
135
+ "./config/model-discovery-manager": null,
127
136
  "./discovery/mcp-json": null,
128
137
  "./exa": null,
129
138
  "./exa/*": null,
@@ -34,6 +34,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
34
34
  "slash_command:pet": "visual/local-only command, not a user-facing SDK control seam",
35
35
  "slash_command:transcript": "visual/local-only transcript viewer, not a user-facing SDK control seam",
36
36
  "slash_command:sessions": "visual/local-only sessions dashboard, not a user-facing SDK control seam",
37
+ "slash_command:import-session":
38
+ "local transcript-file import into a new session; ACP is explicitly disabled and no SDK operation reads caller-selected host paths",
37
39
  "agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
38
40
  "agent_session:nextToolChoice": "internal accessor/plumbing, not a user-facing control seam",
39
41
  "agent_session:setForcedToolChoice": "internal accessor/plumbing, not a user-facing control seam",
@@ -51,6 +53,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
51
53
  "agent_session:clearPlanCompactAbortPending": "internal accessor/plumbing, not a user-facing control seam",
52
54
  "agent_session:enqueueCustomMessageDisplay": "internal accessor/plumbing, not a user-facing control seam",
53
55
  "agent_session:runMidRunMaintenanceForTests": "test-only maintenance seam, not a user-facing SDK control seam",
56
+ "agent_session:runPrePromptContextCheckForTests": "test-only context check seam, not a user-facing SDK control seam",
54
57
  "agent_session:estimateMidRunContextTokensForTests": "test-only estimator seam, not a user-facing SDK control seam",
55
58
  "agent_session:activeMidRunBarrierCountForTests": "read-only test seam, not a user-facing SDK control seam",
56
59
  "agent_session:activeMidRunMaintenanceCountForTests": "read-only test seam, not a user-facing SDK control seam",
@@ -58,6 +61,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
58
61
  "agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
59
62
  "test-only cancellation seam, not a user-facing SDK control seam",
60
63
  "agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
64
+ "agent_session:hasExplicitlyDisabledTools":
65
+ "internal automatic-tool-selection predicate, not a user-facing SDK control seam",
61
66
  "agent_session:emitNotice": "internal accessor/plumbing, not a user-facing control seam",
62
67
  "agent_session:subscribe": "internal accessor/plumbing, not a user-facing control seam",
63
68
  "agent_session:dispose": "internal accessor/plumbing, not a user-facing control seam",
@@ -275,9 +280,6 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
275
280
  "slash_command:move": "session.cwd.move",
276
281
  };
277
282
 
278
- /** Genuine action seams awaiting a reviewed registry mapping or exclusion. */
279
- const PENDING_REVIEW: Readonly<Record<string, string>> = {};
280
-
281
283
  function lockedExclusion(sourceId: string): string | undefined {
282
284
  return LOCKED_EXCLUSIONS[sourceId];
283
285
  }
@@ -812,7 +814,6 @@ function generatedRecords(seams: Awaited<ReturnType<typeof scanSeams>>): Invento
812
814
  for (const seam of seams) {
813
815
  const sdkId = SEAM_TO_SDK[seam.sourceId];
814
816
  const rationale = lockedExclusion(seam.sourceId);
815
- if (!sdkId && !rationale) continue;
816
817
  if (!sdkId) {
817
818
  if (!rationale) continue;
818
819
  records.push({
@@ -877,8 +878,6 @@ export function pendingReviewErrors(seams: readonly Pick<SourceSeam, "sourceId">
877
878
 
878
879
  async function check(records: InventoryRecord[], seams: Awaited<ReturnType<typeof scanSeams>>): Promise<void> {
879
880
  const errors = [...validateRegistry(records), ...pendingReviewErrors(seams)];
880
- for (const sourceId of Object.keys(PENDING_REVIEW))
881
- errors.push(`Pending review source seam: ${sourceId}. ${PENDING_REVIEW[sourceId]}`);
882
881
  let checkedIn: InventoryRecord[];
883
882
  try {
884
883
  checkedIn = await Bun.file(inventoryPath).json();