@gajae-code/coding-agent 0.11.5 → 0.11.7

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 (138) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/commands/daemon.d.ts +2 -2
  5. package/dist/types/commands/sdk.d.ts +20 -2
  6. package/dist/types/config/keybindings.d.ts +1 -1
  7. package/dist/types/config/settings-schema.d.ts +67 -7
  8. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  9. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +5 -5
  10. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +19 -5
  12. package/dist/types/main.d.ts +24 -1
  13. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  14. package/dist/types/modes/components/model-selector.d.ts +2 -0
  15. package/dist/types/modes/components/tool-status-header.d.ts +1 -0
  16. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  17. package/dist/types/modes/interactive-mode.d.ts +2 -0
  18. package/dist/types/modes/types.d.ts +1 -0
  19. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -0
  21. package/dist/types/sdk/bus/index.d.ts +1 -1
  22. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  23. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +13 -3
  25. package/dist/types/sdk/bus/telegram-daemon.d.ts +61 -3
  26. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  27. package/dist/types/sdk/client/discovery.d.ts +2 -0
  28. package/dist/types/sdk/client/liveness.d.ts +7 -0
  29. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  30. package/dist/types/sdk/transport/index.d.ts +12 -0
  31. package/dist/types/sdk/transport/relay.d.ts +43 -0
  32. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  33. package/dist/types/sdk/transport/socket.d.ts +7 -0
  34. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  35. package/dist/types/session/agent-session.d.ts +28 -5
  36. package/dist/types/session/btw-contract.d.ts +16 -0
  37. package/dist/types/session/internal/managed-session-scope.d.ts +21 -7
  38. package/dist/types/session/internal/managed-session-storage.d.ts +10 -8
  39. package/dist/types/session/session-manager.d.ts +35 -4
  40. package/dist/types/session/yield-queue.d.ts +8 -0
  41. package/dist/types/setup/credential-import.d.ts +5 -1
  42. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  43. package/dist/types/ssh/utils.d.ts +1 -0
  44. package/dist/types/task/discovery.d.ts +3 -1
  45. package/dist/types/tools/fetch.d.ts +5 -8
  46. package/dist/types/tools/image-gen.d.ts +16 -0
  47. package/dist/types/tools/skill-discovery.d.ts +7 -0
  48. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  49. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  50. package/dist/types/web/insane/bridge.d.ts +1 -0
  51. package/dist/types/web/insane/url-guard.d.ts +17 -0
  52. package/package.json +8 -8
  53. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  54. package/src/capability/index.ts +50 -42
  55. package/src/capability/types.ts +3 -0
  56. package/src/cli/setup-cli.ts +10 -4
  57. package/src/cli.ts +34 -9
  58. package/src/commands/daemon.ts +22 -5
  59. package/src/commands/sdk.ts +42 -9
  60. package/src/config/keybindings.ts +1 -1
  61. package/src/config/model-profiles.ts +18 -0
  62. package/src/config/settings-schema.ts +71 -7
  63. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  64. package/src/discovery/ssh.ts +12 -1
  65. package/src/eval/py/kernel.ts +1 -21
  66. package/src/eval/py/runner-artifact.ts +84 -0
  67. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  68. package/src/gjc-runtime/ultragoal-guard.ts +88 -10
  69. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  70. package/src/gjc-runtime/ultragoal-runtime.ts +275 -12
  71. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  72. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  73. package/src/internal-urls/docs-index.generated.ts +9 -8
  74. package/src/internal-urls/local-protocol.ts +32 -6
  75. package/src/main.ts +87 -28
  76. package/src/modes/action-registry.ts +1 -1
  77. package/src/modes/components/btw-panel.ts +82 -16
  78. package/src/modes/components/model-selector.ts +31 -3
  79. package/src/modes/components/tool-status-header.ts +6 -1
  80. package/src/modes/controllers/btw-controller.ts +119 -36
  81. package/src/modes/controllers/command-controller.ts +15 -0
  82. package/src/modes/controllers/extension-ui-controller.ts +14 -2
  83. package/src/modes/controllers/input-controller.ts +31 -0
  84. package/src/modes/controllers/selector-controller.ts +106 -12
  85. package/src/modes/interactive-mode.ts +16 -7
  86. package/src/modes/runtime-init.ts +16 -2
  87. package/src/modes/types.ts +1 -0
  88. package/src/prompts/system/btw-user.md +3 -8
  89. package/src/prompts/tools/skill-discovery.md +1 -0
  90. package/src/runtime-mcp/client.ts +41 -55
  91. package/src/runtime-mcp/content-limits.ts +71 -0
  92. package/src/runtime-mcp/json-rpc.ts +23 -9
  93. package/src/runtime-mcp/transports/http.ts +50 -16
  94. package/src/sdk/bus/chat-daemon-control.ts +20 -7
  95. package/src/sdk/bus/index.ts +8 -7
  96. package/src/sdk/bus/lifecycle-commands.ts +16 -7
  97. package/src/sdk/bus/notification-service.ts +41 -18
  98. package/src/sdk/bus/telegram-daemon-contract.ts +13 -3
  99. package/src/sdk/bus/telegram-daemon-control.ts +26 -8
  100. package/src/sdk/bus/telegram-daemon.ts +1736 -338
  101. package/src/sdk/bus/topic-registry.ts +451 -12
  102. package/src/sdk/client/discovery.ts +19 -3
  103. package/src/sdk/client/liveness.ts +49 -0
  104. package/src/sdk/host/control/dispatch.ts +14 -5
  105. package/src/sdk/protocol/operation-inventory.generated.json +29 -0
  106. package/src/sdk/session.ts +29 -5
  107. package/src/sdk/transport/auth-preface.ts +82 -0
  108. package/src/sdk/transport/index.ts +18 -0
  109. package/src/sdk/transport/relay.ts +252 -0
  110. package/src/sdk/transport/serve-cli.ts +105 -0
  111. package/src/sdk/transport/socket.ts +197 -0
  112. package/src/sdk/transport/stdio.ts +17 -0
  113. package/src/session/agent-session.ts +1212 -768
  114. package/src/session/blob-store.ts +19 -6
  115. package/src/session/btw-contract.ts +59 -0
  116. package/src/session/internal/managed-session-scope.ts +313 -57
  117. package/src/session/internal/managed-session-storage.ts +146 -35
  118. package/src/session/session-manager.ts +385 -85
  119. package/src/session/yield-queue.ts +20 -0
  120. package/src/setup/credential-import.ts +28 -3
  121. package/src/setup/provider-onboarding.ts +30 -1
  122. package/src/setup/provider-presets.json +2 -1
  123. package/src/slash-commands/builtin-registry.ts +37 -1
  124. package/src/ssh/utils.ts +14 -0
  125. package/src/task/discovery.ts +8 -2
  126. package/src/tools/ask-answer-registry.ts +15 -1
  127. package/src/tools/ask.ts +1 -1
  128. package/src/tools/fetch.ts +13 -131
  129. package/src/tools/image-gen.ts +301 -20
  130. package/src/tools/read.ts +2 -0
  131. package/src/tools/skill-discovery.ts +39 -3
  132. package/src/tools/sqlite-reader.ts +63 -5
  133. package/src/utils/pasted-image-path.ts +15 -6
  134. package/src/utils/shell-snapshot.ts +173 -63
  135. package/src/web/insane/bridge.ts +5 -1
  136. package/src/web/insane/url-guard.ts +138 -27
  137. package/src/web/scrapers/types.ts +19 -49
  138. package/src/web/scrapers/utils.ts +12 -26
@@ -74,6 +74,40 @@ export interface NotificationStatusReport {
74
74
  export declare function buildNotificationStatusReport(settings: Settings): NotificationStatusReport;
75
75
  /** Render a status report as human-readable lines (no secrets). */
76
76
  export declare function formatNotificationStatusReport(report: NotificationStatusReport): string;
77
+ export interface NotificationEndpointView {
78
+ sessionId: string;
79
+ pid: number | undefined;
80
+ stale: boolean;
81
+ }
82
+ export type NotificationEndpointLiveness = "live" | "dead" | "unknown";
83
+ /**
84
+ * Classification used by recovery and startup takeover. A file is an endpoint
85
+ * only when it has endpoint authority fields; lifecycle/audit records are never
86
+ * candidates for endpoint cleanup.
87
+ */
88
+ export type NotificationEndpointClassification = {
89
+ kind: "endpoint";
90
+ view: NotificationEndpointView;
91
+ liveness: NotificationEndpointLiveness;
92
+ identity: NotificationEndpointFileIdentity;
93
+ } | {
94
+ kind: "non-endpoint";
95
+ } | {
96
+ kind: "unreadable";
97
+ };
98
+ /**
99
+ * Classify an endpoint using owner-proof semantics. An endpoint is only `dead`
100
+ * with positive proof: an explicit `stale` tombstone, or a recorded pid that is
101
+ * confirmed not alive. A PID-less endpoint is `unknown` (not provably dead) and
102
+ * must never be treated as dead — removing it could delete a live session's
103
+ * discovery file that simply omitted a pid.
104
+ */
105
+ export declare function notificationEndpointLiveness(view: NotificationEndpointView, pidAlive: (pid: number) => boolean): NotificationEndpointLiveness;
106
+ /**
107
+ * Read and classify one endpoint candidate. The returned identity belongs to
108
+ * exactly the bytes inspected and is required for any later deletion.
109
+ */
110
+ export declare function classifyNotificationEndpoint(fs: Pick<NotificationServiceFs, "readEndpointFile">, file: string, pidAlive: (pid: number) => boolean): Promise<NotificationEndpointClassification>;
77
111
  export type HealthLevel = "ok" | "warn" | "error";
78
112
  export interface HealthCheck {
79
113
  name: string;
@@ -14,7 +14,17 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
14
14
  * The current development baseline already includes #2299's generation 4,
15
15
  * incarnation fencing in generation 5, owner-lock authority in generation 6,
16
16
  * identity-atomic transition markers in generation 7, stable signaling plus
17
- * tri-state foreign-owner provenance in generation 8, and retained managed
18
- * filesystem authority changes in generation 9.
17
+ * tri-state foreign-owner provenance in generation 8, retained managed
18
+ * filesystem authority changes in generation 9, SDK-startup auto-reclaim of a
19
+ * confirmed-dead owner's lock in generation 10, legacy stopped-tombstone
20
+ * reclamation in generation 11, force-escalated SIGKILL of an unresponsive
21
+ * older-generation owner during automatic generation-upgrade reload in
22
+ * generation 12, restored macOS daemon signaling (kill(2) with a start-time
23
+ * incarnation recheck, replacing the darwin no-op) in generation 13, retained
24
+ * legacy stopped-lock reclamation in generation 14, Windows expected-identity
25
+ * ACL verification and repair in generation 15, identity-fenced stale endpoint
26
+ * startup recovery in generation 16, Telegram topic recovery authority fencing
27
+ * in generation 17, and fail-closed blank-token validation plus lifecycle-startup
28
+ * stop fencing in generation 18.
19
29
  */
20
- export declare const DAEMON_GENERATION = 9;
30
+ export declare const DAEMON_GENERATION = 18;
@@ -9,6 +9,7 @@ import { type ControlServerLike, type LifecycleControlServerFactory } from "./li
9
9
  import type { OrchestratorDeps } from "./lifecycle-orchestrator";
10
10
  import { NotificationOperatorRuntime, OperatorBackoffPolicy } from "./operator-runtime";
11
11
  import { type AliasTable, type CallbackRoute } from "./telegram-reference";
12
+ import { type TopicEndpointBinding } from "./topic-registry";
12
13
  export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
13
14
  /** Detailed result for orchestration that must distinguish a #2028 handoff from a fresh spawn. */
14
15
  export type EnsureTelegramDaemonDetailedResult = "spawned" | "reloaded" | "attached" | "disabled" | "blocked_identity";
@@ -389,6 +390,29 @@ export declare function buildTelegramDaemonSpawnArgs(input: {
389
390
  * already-persisted roots.
390
391
  */
391
392
  export declare function spawnTelegramDaemonOwner(input: TelegramSpawnOwnerInput, deps?: TelegramDaemonDeps): Promise<TelegramSpawnOwnerResult>;
393
+ /**
394
+ * Owner-bound reclamation of a confirmed-dead daemon owner, mirroring the
395
+ * daemon step of `gjc notify recovery`. It returns a structured, actionable
396
+ * result and removes only identity-verified dead-owner artifacts while holding
397
+ * the transition fence; live, successor, unknown, or unreadable evidence is
398
+ * retained.
399
+ */
400
+ export type DeadOwnerRecoveryResult = {
401
+ recovered: true;
402
+ reason: "cleared";
403
+ } | {
404
+ recovered: false;
405
+ reason: "not-confirmed-dead" | "unsafe-lock" | "transition-contended" | "owner-superseded" | "unsafe-endpoint" | "endpoint-changed" | "endpoint-directory-unreadable" | "lock-changed";
406
+ };
407
+ /** Preflight cleanup for a confirmed-dead owner, with identity-bound removal. */
408
+ export declare function reclaimDeadDaemonOwner(input: {
409
+ settings: Settings;
410
+ endpointDir?: string;
411
+ fs?: TelegramDaemonFs;
412
+ now?: () => number;
413
+ pidAlive?: (pid: number) => boolean;
414
+ pidIncarnation?: (pid: number) => string | undefined;
415
+ }): Promise<DeadOwnerRecoveryResult>;
392
416
  /**
393
417
  * Ensure a configured daemon owns this session root, preserving ownership safety
394
418
  * while exposing whether a #2028 generation handoff was required.
@@ -469,6 +493,11 @@ export declare class TelegramEventDispatchState {
469
493
  readonly busy: Set<string>;
470
494
  readonly inboundReactions: Map<number, {
471
495
  messageId: number;
496
+ socketLease?: {
497
+ session: SessionSocket;
498
+ token: number;
499
+ logicalSessionId: string;
500
+ };
472
501
  }>;
473
502
  readonly seenUpdateIds: Set<number>;
474
503
  }
@@ -551,11 +580,15 @@ interface SessionSocket {
551
580
  sessionId: string;
552
581
  /** Current logical session carried by valid threaded frames on this transport. */
553
582
  logicalSessionId: string;
583
+ /** Endpoint metadata proved this logical id belongs to this discovery record. */
584
+ logicalSessionIdTrusted: boolean;
554
585
  token: string;
555
586
  endpointKey: string;
556
587
  endpointDigest: string;
557
588
  hostGeneration: number;
558
589
  ws: WebSocket;
590
+ /** Timestamp (via opts.now) at which this socket began connecting. */
591
+ connectingSince: number;
559
592
  pending: Map<string, {
560
593
  sessionId: string;
561
594
  actionId: string;
@@ -574,6 +607,13 @@ interface SessionSocket {
574
607
  /** Queues live frames until startup replay is applied. */
575
608
  replayPending: boolean;
576
609
  replayQueue: Record<string, unknown>[];
610
+ /** Trusted recovery may not route until its durable endpoint lease is committed. */
611
+ recoveryLease?: {
612
+ state: "pending" | "authorized" | "rejected";
613
+ logicalSessionId: string;
614
+ binding: TopicEndpointBinding;
615
+ token: number;
616
+ };
577
617
  }
578
618
  export declare class TelegramNotificationDaemon {
579
619
  #private;
@@ -598,12 +638,20 @@ export declare class TelegramNotificationDaemon {
598
638
  private readonly pollConflictBackoff;
599
639
  private readonly loopBackoff;
600
640
  private running;
641
+ /** Once set, a concurrent startup await can never restore a running daemon. */
642
+ private stopRequested;
601
643
  private readonly fsImpl;
602
644
  private readonly botApi;
603
645
  private readonly effects;
604
646
  private readonly topics;
605
647
  /** Serializes registry snapshots so an older atomic write cannot overwrite newer rename state. */
648
+ /** Legacy sockets have no durable token, so explicit teardown is their revocation fence. */
649
+ private readonly droppedSessions;
606
650
  private topicsPersistQueue;
651
+ /** Serializes recovery compare/write/publish claims so competing endpoint migrations cannot durably diverge. */
652
+ private recoveryBindingClaimQueue;
653
+ /** Durable compensation fences retry under supervision until persistence succeeds. */
654
+ private readonly compensationFenceRetries;
607
655
  /** Daemon edit attempts that can race an accepted user service message. */
608
656
  private readonly daemonRenameAttempts;
609
657
  private readonly selectedAckPending;
@@ -616,10 +664,17 @@ export declare class TelegramNotificationDaemon {
616
664
  private readonly draftStream;
617
665
  /** Identity-bearing sessions by repo/branch surface, used to avoid transient duplicate topics. */
618
666
  private readonly topicOwnerByIdentity;
667
+ /** Ephemeral legacy topic owners retained across a same-socket config rekey. */
668
+ private readonly legacyTopicOwners;
669
+ /** Preserved initiator topics must not route through a rekeyed transport. */
670
+ private readonly preservedInitiatorTopics;
619
671
  /** Non-identity frames held until identity creates the correct thread. */
620
672
  private readonly pendingThreadedFrames;
621
- /** Endpoint generation tombstones for sessions that already sent session_closed. */
673
+ /** Durable endpoint leases for sessions that already sent an authorized session_closed. */
622
674
  private readonly closedEndpointKeys;
675
+ /** Exactly one authorized transport may route each recovered logical session. */
676
+ private readonly logicalSessionOwners;
677
+ private nextSocketLeaseToken;
623
678
  /** True once the daemon has nudged the user to enable Threaded Mode. */
624
679
  private threadedFallbackNoticeSent;
625
680
  /** Sessions whose identity header was already sent flat (Threaded Mode off). */
@@ -698,6 +753,7 @@ export declare class TelegramNotificationDaemon {
698
753
  private pruneSeenUpdateIds;
699
754
  private rememberSeenUpdateId;
700
755
  private reserveSeenUpdateId;
756
+ private releaseSeenUpdateId;
701
757
  scanRoots(): Promise<void>;
702
758
  connectSession(sessionId: string, url: string, token: string): void;
703
759
  /**
@@ -734,7 +790,6 @@ export declare class TelegramNotificationDaemon {
734
790
  private topicIdentityKey;
735
791
  private topicIdentityBase;
736
792
  private topicOwnerForIdentity;
737
- private sessionCanClaimIdentity;
738
793
  private toolActivityOwner;
739
794
  private toolActivityAuthorityIsCurrent;
740
795
  private submitThreadedFrame;
@@ -759,7 +814,9 @@ export declare class TelegramNotificationDaemon {
759
814
  private deleteTopic;
760
815
  private persistTopics;
761
816
  loadTopics(): Promise<void>;
762
- /** Download a Telegram file by its file_path (from getFile) into memory. */
817
+ /** Retry crash-interrupted topic deletes; only a definite Telegram result clears the durable fence. */
818
+ private reconcilePendingTopicDeletes;
819
+ /** Download one Telegram file with the Bot API's 20 MiB ceiling and one end-to-end deadline. */
763
820
  private downloadTelegramFile;
764
821
  /**
765
822
  * Per-session private temp directories (mode 0700) holding inbound non-image
@@ -781,6 +838,7 @@ export declare class TelegramNotificationDaemon {
781
838
  * to append to the injected text.
782
839
  */
783
840
  private resolveInboundAttachment;
841
+ private resolveInboundAttachmentSerial;
784
842
  /**
785
843
  * Serialize all pool flushes. Every caller (`submitThreadedFrame`, the flat
786
844
  * fallback, the drain timer's `void this.flushPool()`, topic teardown) goes
@@ -38,8 +38,22 @@ export interface TopicRecord {
38
38
  replaySeq?: number;
39
39
  /** Serialized authority epoch; a late create may commit only in its starting epoch. */
40
40
  authorityEpoch?: number;
41
+ /** Immutable authority epoch held when this remote topic create began. */
42
+ creationLeaseEpoch?: number;
41
43
  /** An uncertain delete fences future creation and inbound routing. */
42
44
  authorityState?: "active" | "delete_pending";
45
+ /** Telegram chat and endpoint authority last proven to use this topic. */
46
+ chatId?: string;
47
+ /** Canonical endpoint tuple (URL + token) that currently holds the lease. */
48
+ endpointKey?: string;
49
+ /** Authenticated endpoint authority digest, excluding transport presentation. */
50
+ endpointDigest?: string;
51
+ /** SDK event generation associated with the current endpoint lease. */
52
+ endpointGeneration?: number;
53
+ /** Monotonic authenticated endpoint handoffs; legacy bindings begin at zero. */
54
+ endpointIncarnation?: number;
55
+ /** True when persisted binding fields were present but malformed; recovery must fail closed. */
56
+ bindingMalformed?: true;
43
57
  }
44
58
  /** Serialisable shape persisted to disk. */
45
59
  export interface TopicRegistryState {
@@ -47,6 +61,34 @@ export interface TopicRegistryState {
47
61
  topics: Record<string, TopicRecord>;
48
62
  /** Durable deletion epochs retained after a definite delete. */
49
63
  fences?: Record<string, number>;
64
+ /** Closed transport endpoint leases; unchanged endpoint discovery remains fenced across restart. */
65
+ closedEndpoints?: Record<string, TopicEndpointBinding>;
66
+ }
67
+ /** Authenticated runtime binding for a durable topic lease. */
68
+ export interface TopicEndpointBinding {
69
+ chatId: string;
70
+ endpointKey: string;
71
+ endpointDigest: string;
72
+ endpointGeneration?: number;
73
+ }
74
+ /** Discriminated durable endpoint authority for identity-less replay admission. */
75
+ export type TopicEndpointAuthority = {
76
+ state: "none";
77
+ } | {
78
+ state: "unique";
79
+ sessionId: string;
80
+ } | {
81
+ state: "ambiguous";
82
+ };
83
+ /** Conditional rollback token for a delete fence publication. */
84
+ export interface TopicDeleteAuthoritySnapshot {
85
+ sessionId: string;
86
+ topicId?: string;
87
+ authorityEpoch?: number;
88
+ authorityState?: TopicRecord["authorityState"];
89
+ fenceEpoch?: number;
90
+ /** Exact fenced record, retained to restore an in-memory tombstone after a failed clear publication. */
91
+ record?: TopicRecord;
50
92
  }
51
93
  export declare function emptyTopicRegistryState(): TopicRegistryState;
52
94
  /**
@@ -61,9 +103,17 @@ export declare class TopicRegistry {
61
103
  private readonly byTopic;
62
104
  /** In-flight create promises, keyed by session, to dedupe concurrent creates. */
63
105
  private readonly inflight;
106
+ /** Newly-created records being durably published; never routable until committed. */
107
+ private readonly staged;
108
+ /** Socket-specific provenance for transient endpoint claims. */
109
+ private readonly transientClaimants;
110
+ /** Endpoint claims registered before a remote topic create can publish a record. */
111
+ private readonly creatingBindings;
64
112
  /** Monotonic authority epochs, including deletion fences for absent records. */
65
113
  private readonly epochs;
66
114
  constructor(state?: TopicRegistryState);
115
+ /** Replace all runtime state after a successfully persisted staged publication. */
116
+ replace(state: TopicRegistryState): void;
67
117
  /** Merge serialized state and normalize authority fields from older releases. */
68
118
  load(state: TopicRegistryState): void;
69
119
  private rebuildInboundRoutes;
@@ -71,13 +121,39 @@ export declare class TopicRegistry {
71
121
  sessionForTopic(topicId: string): string | undefined;
72
122
  /** All session ids with a persisted topic record. */
73
123
  sessionIds(): string[];
124
+ /** Persisted remote deletes that must be reconciled before recovery can proceed. */
125
+ deletePendingSessionIds(): string[];
74
126
  /** The existing topic record for a session, if any. */
75
127
  get(sessionId: string): TopicRecord | undefined;
128
+ /** Current immutable authority epoch for a creation lease. */
129
+ authorityEpoch(sessionId: string): number;
130
+ /** Whether this session has an active, unambiguous topic authority. */
131
+ isActiveUnambiguous(sessionId: string): boolean;
132
+ /**
133
+ * Resolve endpoint authority for identity-less replay. An endpoint may bootstrap
134
+ * only when no committed, staged, or pre-create claim can own it; malformed
135
+ * partial bindings and deletion fences deliberately fail closed.
136
+ */
137
+ endpointAuthority(binding: TopicEndpointBinding, excludedTransientClaimant?: object): TopicEndpointAuthority;
138
+ /** Resolve a uniquely bound logical owner for callers that only need the owner. */
139
+ uniqueSessionForEndpoint(binding: TopicEndpointBinding): string | undefined;
140
+ /** Whether this exact session owns the complete durable endpoint binding. */
141
+ matchesEndpoint(sessionId: string, binding: TopicEndpointBinding): boolean;
142
+ /**
143
+ * Rebind an existing topic to an authenticated successor endpoint. The exact
144
+ * logical session id is proved by replay before this method is called. A
145
+ * rotated credential may replace only an inactive incumbent; concurrent
146
+ * incumbents, cross-chat records, malformed evidence, collisions, and delete
147
+ * fences remain fail-closed.
148
+ */
149
+ bindEndpoint(sessionId: string, binding: TopicEndpointBinding, activeEndpointKeys?: ReadonlySet<string>, allowEndpointRotation?: boolean): "bound" | "unchanged" | "rejected";
150
+ /** Undo a failed durable endpoint migration without disturbing concurrent metadata writers. */
151
+ restoreEndpointBinding(sessionId: string, expected: TopicEndpointBinding, previous: Pick<TopicRecord, "chatId" | "endpointKey" | "endpointDigest" | "endpointGeneration" | "endpointIncarnation">): boolean;
76
152
  /**
77
153
  * Return the existing active topic for `sessionId`, or create one via
78
154
  * `create` (called only on first use).
79
155
  */
80
- getOrCreateTopic(sessionId: string, create: () => Promise<unknown>, now?: () => number, name?: string): Promise<TopicRecord>;
156
+ getOrCreateTopic(sessionId: string, create: () => Promise<unknown>, now?: () => number, name?: string, binding?: TopicEndpointBinding, commit?: () => Promise<void>, transientClaimant?: object): Promise<TopicRecord>;
81
157
  /** Mark the identity header as sent for a session. Idempotent. */
82
158
  markIdentitySent(sessionId: string): void;
83
159
  /** Whether the identity header still needs sending for this session. */
@@ -109,14 +185,24 @@ export declare class TopicRegistry {
109
185
  markUserNamePending(sessionId: string, name: string): boolean;
110
186
  /** Commit a successfully-applied daemon topic title. */
111
187
  markNameApplied(sessionId: string, name: string): void;
188
+ /** Capture only authority fields that a failed delete publication may restore. */
189
+ captureDeleteAuthority(sessionId: string): TopicDeleteAuthoritySnapshot;
190
+ /** Restore a failed delete fence only while its exact authority mutation remains current. */
191
+ restoreDeleteAuthority(snapshot: TopicDeleteAuthoritySnapshot): boolean;
192
+ /** Restore the exact delete fence after a failed compensation publication. */
193
+ restoreDeleteFence(snapshot: TopicDeleteAuthoritySnapshot): boolean;
112
194
  /** Fence new work before the remote delete starts, including an absent in-flight create. */
113
195
  beginDelete(sessionId: string): TopicRecord | undefined;
196
+ /** Retain an accepted create as deletion-fenced before remote compensation can begin. */
197
+ fenceAcceptedCreate(sessionId: string, topicId: string, now?: () => number, name?: string, binding?: TopicEndpointBinding): TopicRecord;
198
+ /** Fence an accepted create only when its exact creator lease still owns the record. */
199
+ fenceAcceptedCreateForLease(sessionId: string, topicId: string, creationLeaseEpoch: number, now?: () => number, name?: string, binding?: TopicEndpointBinding): TopicRecord | undefined;
114
200
  /** Wait for a revoked create to settle before admitting a later lifecycle epoch. */
115
201
  awaitInflight(sessionId: string): Promise<void>;
116
202
  /** Remove only after a definite remote deletion; ambiguity deliberately retains its fence. */
117
203
  settleDelete(sessionId: string, topicId: string): boolean;
118
204
  /** Remove a topic record immediately for local/test cleanup compatibility. */
119
205
  delete(sessionId: string): boolean;
120
- /** Serialise for atomic persistence beside the daemon state. */
206
+ /** Serialise active records plus unpublished staged creates for atomic commit. */
121
207
  serialize(): TopicRegistryState;
122
208
  }
@@ -3,6 +3,8 @@ export interface SdkSessionEndpoint {
3
3
  sessionId: string;
4
4
  url: string;
5
5
  token: string;
6
+ pid?: number;
7
+ stale?: boolean;
6
8
  path: string;
7
9
  }
8
10
  export interface SdkDiscoveryWarning {
@@ -0,0 +1,7 @@
1
+ import type { SdkSessionEndpoint } from "./discovery";
2
+ export type SdkEndpointLiveness = "live" | "stale" | "dead" | "unknown";
3
+ export interface SdkEndpointSelectionError {
4
+ code: "endpoint_stale" | "endpoint_dead" | "endpoint_unknown" | "no_live_endpoint" | "multiple_live_endpoints" | "not_found";
5
+ }
6
+ export declare function classifyEndpoint(endpoint: SdkSessionEndpoint): SdkEndpointLiveness;
7
+ export declare function selectLiveEndpoint(records: readonly SdkSessionEndpoint[], explicitSessionId?: string): SdkSessionEndpoint | SdkEndpointSelectionError;
@@ -0,0 +1,9 @@
1
+ import type * as net from "node:net";
2
+ type AuthFailureCode = "auth_missing" | "auth_malformed" | "auth_invalid" | "auth_oversize" | "auth_timeout" | "unsupported_version";
3
+ export declare class AuthPrefaceError extends Error {
4
+ readonly code: AuthFailureCode;
5
+ constructor(code: AuthFailureCode);
6
+ }
7
+ /** Consumes the one-shot socket authentication preface and leaves later bytes readable. */
8
+ export declare function authenticatePreface(socket: net.Socket, token: string): Promise<Buffer>;
9
+ export {};
@@ -0,0 +1,12 @@
1
+ export { DEFAULT_PENDING_CEILING_BYTES, MIN_PENDING_CEILING_BYTES, REQUEST_FRAME_BYTES, } from "./relay";
2
+ export { startSocketServe } from "./socket";
3
+ export { startStdioServe } from "./stdio";
4
+ export type ServeOptions = {
5
+ url: string;
6
+ token: string;
7
+ pendingCeilingBytes: number;
8
+ };
9
+ export type ServeHandle = {
10
+ close(): Promise<void>;
11
+ readonly done: Promise<void>;
12
+ };
@@ -0,0 +1,43 @@
1
+ import type { Readable, Writable } from "node:stream";
2
+ export declare const REQUEST_FRAME_BYTES: number;
3
+ export declare const DEFAULT_PENDING_CEILING_BYTES: number;
4
+ export declare const MIN_PENDING_CEILING_BYTES: number;
5
+ export type RelayDirection = "downstream->ws" | "ws->downstream";
6
+ export type TransportError = {
7
+ type: "transport_error";
8
+ code: "frame_oversize" | "pending_overflow" | "protocol_error";
9
+ direction?: RelayDirection;
10
+ };
11
+ export type RelayPair = {
12
+ close(): Promise<void>;
13
+ readonly done: Promise<void>;
14
+ };
15
+ export declare class RelayOpenAbortedError extends Error {
16
+ constructor();
17
+ }
18
+ export type RelayWebSocketEvent = {
19
+ data?: unknown;
20
+ };
21
+ export type RelayWebSocket = {
22
+ readonly bufferedAmount: number;
23
+ readonly readyState: number;
24
+ close(): void;
25
+ send(data: string): void;
26
+ addEventListener(type: string, listener: (event: RelayWebSocketEvent) => void, options?: {
27
+ once?: boolean;
28
+ }): void;
29
+ removeEventListener(type: string, listener: (event: RelayWebSocketEvent) => void): void;
30
+ };
31
+ export type RelayOptions = {
32
+ url: string;
33
+ token: string;
34
+ pendingCeilingBytes: number;
35
+ downstream: Readable;
36
+ downstreamSink: Writable;
37
+ initialDownstreamBytes?: Buffer;
38
+ onTransportError: (error: TransportError) => void;
39
+ webSocketFactory?: (url: string) => RelayWebSocket;
40
+ signal?: AbortSignal;
41
+ };
42
+ /** Starts a dedicated raw-WebSocket relay for exactly one downstream stream. */
43
+ export declare function startRelayPair(options: RelayOptions): Promise<RelayPair>;
@@ -0,0 +1,4 @@
1
+ /** Resolves the pending ceiling with flag > env > default precedence; exported for tests. */
2
+ export declare function resolveServePendingCeiling(flagValue: string | undefined, envValue: string | undefined): number;
3
+ /** Attaches a stdio or Unix-socket relay to one live SDK session endpoint. */
4
+ export declare function runSdkServe(argv: string[]): Promise<void>;
@@ -0,0 +1,7 @@
1
+ import type { ServeHandle, ServeOptions } from "./index";
2
+ import type { RelayOptions } from "./relay";
3
+ /** Serves authenticated SDK JSONL clients over a private Unix-domain socket. */
4
+ export declare function startSocketServe(options: ServeOptions & {
5
+ socketPath: string;
6
+ webSocketFactory?: RelayOptions["webSocketFactory"];
7
+ }): Promise<ServeHandle>;
@@ -0,0 +1,3 @@
1
+ import type { ServeHandle, ServeOptions } from "./index";
2
+ /** Serves one parent-owned JSONL connection over the process standard streams. */
3
+ export declare function startStdioServe(options: ServeOptions): Promise<ServeHandle>;
@@ -15,6 +15,7 @@
15
15
  import { type Agent, type AgentContext, type AgentEvent, type AgentLoopConfig, type AgentMessage, type AgentState, type AgentTool, type MidRunMaintenanceOutcome, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core";
16
16
  import { type CompactionResult, type EmergencyCompactionSample } from "@gajae-code/agent-core/compaction";
17
17
  import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolCall, ToolChoice, Usage, UsageReport } from "@gajae-code/ai";
18
+ import { type BtwTextExchange } from "./btw-contract";
18
19
  export interface ForkContextSeedMetadata {
19
20
  sourceSessionId: string;
20
21
  parentMessageCount: number;
@@ -392,8 +393,29 @@ interface EphemeralTurnBaseArgs {
392
393
  onTextDelta?: (delta: string) => void;
393
394
  signal?: AbortSignal;
394
395
  }
395
- export type EphemeralTurnArgs = (EphemeralTurnBaseArgs & {
396
+ export interface BtwRoleTextMessage {
397
+ role: "user" | "assistant";
398
+ text: string;
399
+ }
400
+ export interface BtwConversationScope {
401
+ model: Model;
402
+ systemPrompt: string[];
403
+ messages: BtwRoleTextMessage[];
404
+ thinkingLevel: ThinkingLevel;
405
+ hideThinkingSummary: boolean;
406
+ serviceTier: ServiceTier | undefined;
407
+ credentialSessionId: string;
408
+ providerAffinitySessionId: string;
409
+ sideSessionId: string;
410
+ }
411
+ export interface BtwTurnCapture {
412
+ question: string;
413
+ scope: BtwConversationScope | undefined;
414
+ }
415
+ export type EphemeralTurnArgs = (Omit<EphemeralTurnBaseArgs, "promptText"> & {
396
416
  purpose: "btw";
417
+ turn: BtwTurnCapture;
418
+ contextExchanges?: readonly BtwTextExchange[];
397
419
  }) | (EphemeralTurnBaseArgs & {
398
420
  purpose?: "background";
399
421
  /** Internal caller-supplied, non-persistent context such as the IRC roster. */
@@ -1271,11 +1293,12 @@ export declare class AgentSession {
1271
1293
  timestamp?: number;
1272
1294
  }): void;
1273
1295
  emitSubagentSteerRelayObservation(record: CustomMessage): void;
1296
+ createBtwConversationScope(instruction: string): BtwConversationScope;
1274
1297
  /**
1275
- * Run a single ephemeral side-channel turn against this session's current
1276
- * model + system prompt + history. No tools are used and session history is
1277
- * not modified. Background turns retain the IRC/session behavior used by
1278
- * autoreplies; `/btw` turns use an isolated committed-context policy.
1298
+ * Run a single ephemeral side-channel turn without modifying session history.
1299
+ * Background turns retain IRC/session behavior. `/btw` turns require a
1300
+ * pre-frozen, visible-text-only scope and bypass extension context transforms,
1301
+ * provider observability hooks, and session persistence surfaces.
1279
1302
  */
1280
1303
  runEphemeralTurn(args: EphemeralTurnArgs): Promise<EphemeralTurnResult>;
1281
1304
  /**
@@ -0,0 +1,16 @@
1
+ export declare const BTW_MAX_CONTEXT_TURNS = 12;
2
+ export declare const BTW_MAX_CONTEXT_UTF8_BYTES: number;
3
+ export declare const BTW_MAX_QUESTION_UTF8_BYTES: number;
4
+ export declare const BTW_MAX_ANSWER_UTF8_BYTES: number;
5
+ export declare const BTW_MAX_ERROR_UTF8_BYTES: number;
6
+ export declare const BTW_STREAM_IDLE_TIMEOUT_MS = 30000;
7
+ export declare const BTW_STREAM_TOTAL_TIMEOUT_MS = 120000;
8
+ export interface BtwTextExchange {
9
+ question: string;
10
+ answer: string;
11
+ }
12
+ export declare function utf8ByteLength(text: string): number;
13
+ export declare function truncateUtf8(text: string, maxBytes: number): string;
14
+ export declare function sanitizeBtwError(text: string): string;
15
+ export declare function exchangeUtf8Bytes(exchange: BtwTextExchange): number;
16
+ export declare function boundBtwExchanges(exchanges: readonly BtwTextExchange[]): BtwTextExchange[];
@@ -1,5 +1,6 @@
1
1
  import * as native from "@gajae-code/natives";
2
2
  import type { ResumeSessionIdentity } from "../session-manager";
3
+ import { type ManagedDirectoryRoot, type ManagedSessionSecurityPolicy } from "./managed-session-storage";
3
4
  export declare const MANAGED_SESSION_LAYOUT_VERSION: 2;
4
5
  export declare const MANAGED_SESSION_IDENTITY_VERSION: 1;
5
6
  export declare const MANAGED_SESSION_BINDING_FILE = ".gjc-managed-session-scope.v2.json";
@@ -15,12 +16,22 @@ export interface ManagedScope {
15
16
  directoryPath: string;
16
17
  platform: "posix" | "win32";
17
18
  }
19
+ /**
20
+ * Opaque managed writer authority captured by a trusted destination. The open
21
+ * transaction must use this authority rather than reacquiring its root from a
22
+ * pathname after resume inspection.
23
+ */
24
+ export interface ManagedCandidateWriteAuthority {
25
+ readonly rootAuthority: ManagedDirectoryRoot;
26
+ readonly retainedAuthority?: native.RecoveryFsRoot;
27
+ readonly retainedDirectory?: string;
28
+ }
18
29
  export declare function managedDirectoryAuthorityForScope(scope: ManagedScope): native.RecoveryFsRoot | undefined;
19
30
  export declare function managedDirectoryIdentityForScope(scope: ManagedScope): {
20
31
  dev: bigint;
21
32
  ino: bigint;
22
33
  };
23
- export declare function managedRootForScope(scope: ManagedScope): import("./managed-session-storage").ManagedDirectoryRoot;
34
+ export declare function managedRootForScope(scope: ManagedScope): ManagedDirectoryRoot;
24
35
  export type ManagedMigrationPolicy = "copy-retain" | "disabled";
25
36
  export type ManagedScopeErrorCode = "cwd_missing" | "cwd_not_directory" | "identity_unavailable" | "network_unsupported" | "sessions_root_unavailable" | "binding_conflict" | "binding_invalid";
26
37
  export type ManagedScopeResolution = {
@@ -86,24 +97,27 @@ export declare const computeManagedScopeDigest: typeof scopeDigest;
86
97
  * attacker-plantable component below the root.
87
98
  */
88
99
  export declare function canonicalizeTrustedPath(target: string): string;
89
- export declare function resolveManagedScope(input: {
100
+ interface ManagedScopeInput {
90
101
  cwd: string;
91
102
  agentDir: string;
92
103
  sessionsRoot: string;
93
- }): ManagedScopeResolution;
94
- export declare function ensureManagedScope(scope: ManagedScope): Promise<ManagedScopeResolution>;
104
+ }
105
+ export declare function resolveManagedScope(input: ManagedScopeInput): ManagedScopeResolution;
106
+ /** Resolve a scope for a synchronous write without mutating an existing ACL mismatch. */
107
+ export declare function resolveManagedScopeForWrite(input: ManagedScopeInput): ManagedScopeResolution;
108
+ export declare function ensureManagedScope(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy): Promise<ManagedScopeResolution>;
95
109
  /** Synchronously create and validate the v2 binding before a default session writer exists. */
96
- export declare function prepareManagedSessionScopeForWriteSync(scope: ManagedScope): ManagedScopeResolution;
110
+ export declare function prepareManagedSessionScopeForWriteSync(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority): ManagedScopeResolution;
97
111
  export declare function listManagedCandidates(scope: ManagedScope): ManagedCandidateListing;
98
112
  /** Resume tombstoned cleanup under its original operation lease without restoring retired candidates. */
99
113
  export declare function reconcileManagedTombstones(scope: ManagedScope): Promise<void>;
100
114
  /** Create the v2 binding and private write protocol directories before managed writes. */
101
- export declare function prepareManagedSessionScopeForWrite(scope: ManagedScope): Promise<ManagedScopeResolution>;
115
+ export declare function prepareManagedSessionScopeForWrite(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority, expectedCandidate?: ManagedCandidate, expectedIdentity?: ResumeSessionIdentity): Promise<ManagedScopeResolution>;
102
116
  /**
103
117
  * Open a validated candidate for mutation. Legacy transcripts are copied exactly once
104
118
  * into v2 and retained at their original location; no transcript data is merged.
105
119
  */
106
- export declare function openManagedCandidateForWrite(scope: ManagedScope, candidate: ManagedCandidate, migrationPolicy?: ManagedMigrationPolicy): Promise<ManagedOpenCandidateResult>;
120
+ export declare function openManagedCandidateForWrite(scope: ManagedScope, candidate: ManagedCandidate, expectedIdentityOrMigrationPolicy?: ResumeSessionIdentity | ManagedMigrationPolicy, migrationPolicy?: ManagedMigrationPolicy, authority?: ManagedCandidateWriteAuthority): Promise<ManagedOpenCandidateResult>;
107
121
  /** Tombstone a verified managed candidate before exact-identity deletion. */
108
122
  export declare function deleteManagedSessionCandidate(scope: ManagedScope, candidate: ManagedCandidate): Promise<ManagedDeleteCandidateResult>;
109
123
  export {};