@gajae-code/coding-agent 0.12.10 → 0.12.12

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 (120) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/dist/types/cli/notify-cli.d.ts +42 -1
  3. package/dist/types/cli/update-cli.d.ts +9 -3
  4. package/dist/types/commands/notify.d.ts +6 -0
  5. package/dist/types/config/file-lock.d.ts +3 -0
  6. package/dist/types/coordinator/contract.d.ts +1 -1
  7. package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
  8. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  9. package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
  10. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
  11. package/dist/types/extensibility/extensions/types.d.ts +2 -0
  12. package/dist/types/main.d.ts +6 -2
  13. package/dist/types/modes/components/session-selector.d.ts +1 -1
  14. package/dist/types/modes/utils/host-status.d.ts +23 -0
  15. package/dist/types/runtime-mcp/types.d.ts +2 -0
  16. package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
  17. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
  18. package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
  19. package/dist/types/sdk/bus/chat-daemon-control.d.ts +13 -1
  20. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
  21. package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
  22. package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
  23. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
  24. package/dist/types/sdk/bus/notification-service.d.ts +10 -0
  25. package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
  26. package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
  27. package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
  28. package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
  29. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +25 -7
  31. package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
  32. package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
  33. package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
  34. package/dist/types/sdk/host/host.d.ts +52 -0
  35. package/dist/types/sdk/lifecycle-session.d.ts +6 -0
  36. package/dist/types/sdk/session-activation.d.ts +41 -0
  37. package/dist/types/sdk/session.d.ts +18 -0
  38. package/dist/types/sdk/startup-capability.d.ts +8 -1
  39. package/dist/types/session/agent-session.d.ts +7 -0
  40. package/dist/types/session/artifacts.d.ts +2 -2
  41. package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
  42. package/dist/types/session/session-manager.d.ts +40 -5
  43. package/dist/types/session/session-storage.d.ts +1 -0
  44. package/dist/types/tools/computer.d.ts +35 -2
  45. package/dist/types/tools/index.d.ts +8 -0
  46. package/dist/types/tools/todo-write.d.ts +2 -0
  47. package/dist/types/utils/npm-registry.d.ts +71 -0
  48. package/examples/extensions/README.md +38 -4
  49. package/examples/extensions/ooo-bridge.ts +15 -0
  50. package/package.json +8 -8
  51. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  52. package/src/async/job-manager.ts +9 -2
  53. package/src/cli/notify-cli.ts +226 -2
  54. package/src/cli/update-cli.ts +90 -23
  55. package/src/commands/notify.ts +27 -3
  56. package/src/commands/sdk.ts +1 -0
  57. package/src/config/file-lock-gc.ts +18 -0
  58. package/src/config/file-lock.ts +61 -16
  59. package/src/config/model-profiles.ts +31 -15
  60. package/src/coordinator/contract.ts +1 -0
  61. package/src/coordinator-mcp/question-state.ts +2 -0
  62. package/src/coordinator-mcp/server.ts +358 -7
  63. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
  64. package/src/extensibility/extensions/index.ts +1 -0
  65. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
  66. package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
  67. package/src/extensibility/extensions/runner.ts +15 -2
  68. package/src/extensibility/extensions/types.ts +2 -0
  69. package/src/gjc-runtime/launch-tmux.ts +53 -1
  70. package/src/internal-urls/docs-index.generated.ts +13 -12
  71. package/src/main.ts +203 -67
  72. package/src/modes/DESIGN.md +8 -5
  73. package/src/modes/components/footer.ts +10 -18
  74. package/src/modes/components/session-selector.ts +26 -3
  75. package/src/modes/components/status-line/gh.ts +23 -2
  76. package/src/modes/controllers/event-controller.ts +3 -0
  77. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  78. package/src/modes/controllers/input-controller.ts +11 -0
  79. package/src/modes/controllers/selector-controller.ts +8 -2
  80. package/src/modes/interactive-mode.ts +7 -1
  81. package/src/modes/utils/host-status.ts +38 -0
  82. package/src/prompts/tools/computer.md +6 -1
  83. package/src/runtime-mcp/types.ts +2 -0
  84. package/src/sdk/broker/lifecycle.ts +60 -3
  85. package/src/sdk/bus/chat-daemon-cli.ts +42 -0
  86. package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
  87. package/src/sdk/bus/chat-daemon-control.ts +18 -3
  88. package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
  89. package/src/sdk/bus/control-plane-frames.ts +51 -0
  90. package/src/sdk/bus/conversation-store.ts +29 -1
  91. package/src/sdk/bus/existing-thread-readiness.ts +155 -0
  92. package/src/sdk/bus/index.ts +70 -0
  93. package/src/sdk/bus/notification-service.ts +28 -1
  94. package/src/sdk/bus/rich-render.ts +3 -2
  95. package/src/sdk/bus/slack-daemon.ts +99 -4
  96. package/src/sdk/bus/slack-live-provider.ts +32 -2
  97. package/src/sdk/bus/slack-provider.ts +11 -0
  98. package/src/sdk/bus/slack-thread-binding.ts +641 -0
  99. package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
  100. package/src/sdk/bus/telegram-daemon-contract.ts +25 -7
  101. package/src/sdk/bus/telegram-daemon.ts +1961 -497
  102. package/src/sdk/bus/telegram-reference.ts +42 -6
  103. package/src/sdk/bus/topic-registry.ts +674 -181
  104. package/src/sdk/host/host.ts +180 -1
  105. package/src/sdk/lifecycle-session.ts +8 -2
  106. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  107. package/src/sdk/session-activation.ts +127 -0
  108. package/src/sdk/session.ts +139 -28
  109. package/src/sdk/startup-capability.ts +10 -1
  110. package/src/session/agent-session.ts +331 -30
  111. package/src/session/artifacts.ts +56 -13
  112. package/src/session/internal/managed-session-storage.ts +440 -243
  113. package/src/session/session-manager.ts +288 -44
  114. package/src/session/session-storage.ts +3 -0
  115. package/src/setup/provider-presets.json +1 -1
  116. package/src/task/index.ts +92 -44
  117. package/src/tools/computer.ts +322 -45
  118. package/src/tools/index.ts +8 -0
  119. package/src/tools/todo-write.ts +29 -11
  120. package/src/utils/npm-registry.ts +660 -0
@@ -333,6 +333,7 @@ export interface ResumeSessionIdentity {
333
333
  size: number;
334
334
  mtimeMs: number;
335
335
  mtimeNs: bigint;
336
+ ctimeNs?: bigint;
336
337
  sha256: string;
337
338
  }
338
339
  export interface ResumeTailResumable {
@@ -348,6 +349,13 @@ export interface ResumeTailError {
348
349
  reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled";
349
350
  }
350
351
  export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy;
352
+ export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure";
353
+ export declare class SessionAppendPersistenceError extends Error {
354
+ readonly phase: SessionAppendPersistenceFailurePhase;
355
+ readonly entryId: string;
356
+ readonly persistenceError: Error;
357
+ constructor(phase: SessionAppendPersistenceFailurePhase, entryId: string, persistenceError: Error);
358
+ }
351
359
  export declare class SessionManagedStorageError extends Error {
352
360
  readonly code = "managed_storage_unsupported";
353
361
  constructor();
@@ -607,6 +615,7 @@ interface SessionManagerStateSnapshot {
607
615
  needsFullRewriteOnNextPersist: boolean;
608
616
  fileEntries: FileEntry[];
609
617
  materializedFileEntries: FileEntry[];
618
+ adoptedArtifactManager: ArtifactManager | null;
610
619
  }
611
620
  /** Benchmark-derived cap for strong materialized session snapshots. */
612
621
  export declare const MATERIALIZED_CACHE_MAX_BYTES: number;
@@ -615,6 +624,7 @@ export declare const SessionManagerTestHooks: {
615
624
  materializedCacheMaxBytesOverride?: number;
616
625
  beforeResidentTransitionIndexBuild?: () => void;
617
626
  afterForkSnapshot?: () => void | Promise<void>;
627
+ beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
618
628
  };
619
629
  export declare class SessionManager {
620
630
  #private;
@@ -635,7 +645,9 @@ export declare class SessionManager {
635
645
  captureState(): SessionManagerStateSnapshot;
636
646
  restoreState(snapshot: SessionManagerStateSnapshot): void;
637
647
  /** Switch to a different session file (used for resume and branching). */
638
- setSessionFile(sessionFile: string): Promise<void>;
648
+ setSessionFile(sessionFile: string, options?: {
649
+ deferEphemeralArtifactRetirement?: boolean;
650
+ }): Promise<void>;
639
651
  /** Start a new session. Closes any existing writer first. */
640
652
  newSession(options?: NewSessionOptions): Promise<string | undefined>;
641
653
  /**
@@ -743,13 +755,23 @@ export declare class SessionManager {
743
755
  * the parent session's artifact directory and ID counter.
744
756
  */
745
757
  adoptArtifactManager(manager: ArtifactManager): void;
758
+ /** Release only the matching externally adopted manager. */
759
+ releaseArtifactManager(manager: ArtifactManager): void;
760
+ /** Temporarily release adopted authority while an outer transition validates its successor. */
761
+ stageAdoptedArtifactManagerForTransition(): void;
762
+ /** Prove manager authority by exact object identity, never by pathname shape. */
763
+ isArtifactManagerAuthorized(manager: ArtifactManager): boolean;
746
764
  /**
747
765
  * Returns the ArtifactManager this session writes through. Lazily creates
748
766
  * one bound to the current session file unless an external manager was
749
- * adopted via `adoptArtifactManager`. Returns null only for non-persistent
750
- * sessions with no adopted manager.
767
+ * adopted via `adoptArtifactManager`. Falls back to the lazily created
768
+ * ephemeral filesystem store once a non-persistent session has saved an
769
+ * artifact, so `artifact://` stays resolvable. Returns null only when no
770
+ * store has been established yet.
751
771
  */
752
772
  getArtifactManager(): ArtifactManager | null;
773
+ /** Linearizably establish this session's persistent or ephemeral artifact manager. */
774
+ ensureArtifactManager(): Promise<ArtifactManager | null>;
753
775
  /**
754
776
  * Allocate a new artifact path and ID for the current session.
755
777
  * Returns an empty object when the session is not persisted.
@@ -760,14 +782,18 @@ export declare class SessionManager {
760
782
  }>;
761
783
  /**
762
784
  * Save artifact content under the current session and return artifact ID.
763
- * Returns an artifact ID for all sessions (file-backed for persistent, in-memory fallback otherwise).
785
+ * Persistent sessions write into the session artifact directory; non-persistent
786
+ * sessions write into a lazily created temporary directory so the content is
787
+ * read back from the filesystem instead of being retained in memory.
764
788
  */
765
789
  saveArtifact(content: string, toolType: string): Promise<string | undefined>;
766
790
  /**
767
791
  * Resolve an artifact ID to an on-disk path for the current session.
768
- * Returns null when missing or when the session is not persisted.
792
+ * Returns null when the artifact is missing.
769
793
  */
770
794
  getArtifactPath(id: string): Promise<string | null>;
795
+ /** Retire predecessor ephemeral artifacts after an outer logical transition commits. */
796
+ retireEphemeralArtifactsAfterTransition(): void;
771
797
  /**
772
798
  * Persist (or clear) the current editor draft so the next resume of this
773
799
  * session can restore it. Empty text deletes any stale draft. No-op when the
@@ -838,6 +864,15 @@ export declare class SessionManager {
838
864
  applyEntryMessageUpdates(entries: readonly SessionMessageEntry[]): void;
839
865
  /** Write mutated custom-message entries back into the canonical entry store by id. */
840
866
  applyCustomMessageEntryUpdates(entries: readonly CustomMessageEntry[]): void;
867
+ /**
868
+ * Rehydrate the canonical transcript after a synchronous persistence failure.
869
+ *
870
+ * The failed append may have committed before reporting an uncertain outcome,
871
+ * so callers must not clear the sticky error or retry against the resident
872
+ * branch. Reloading the exact session file is the only supported recovery
873
+ * boundary for both managed and explicit persistent destinations.
874
+ */
875
+ recoverPersistenceFailure(): Promise<void>;
841
876
  /**
842
877
  * Rewrite the session file after in-place entry updates.
843
878
  * Use sparingly (e.g., pruning old tool outputs).
@@ -7,6 +7,7 @@ export interface SessionStorageStat {
7
7
  size: number;
8
8
  mtimeMs: number;
9
9
  mtimeNs: bigint;
10
+ ctimeNs: bigint;
10
11
  mtime: Date;
11
12
  isFile: boolean;
12
13
  }
@@ -239,7 +239,7 @@ export interface ComputerScreenshotDetails {
239
239
  originX?: number;
240
240
  originY?: number;
241
241
  displayEpoch?: number;
242
- captureId?: string;
242
+ captureId?: number;
243
243
  pngBytes?: number;
244
244
  path?: string;
245
245
  }
@@ -258,11 +258,44 @@ export interface ComputerToolDetails {
258
258
  keys?: string[];
259
259
  ms?: number;
260
260
  screenshot?: ComputerScreenshotDetails;
261
+ primaryError?: {
262
+ code: string;
263
+ message: string;
264
+ };
261
265
  supervisor?: string;
262
266
  steps?: ComputerToolDetails[];
263
267
  meta?: OutputMeta;
264
268
  }
269
+ type NativeBatchAction = {
270
+ action: "screenshot" | "click" | "double_click" | "move" | "drag" | "scroll" | "type" | "keypress" | "wait";
271
+ x?: number;
272
+ y?: number;
273
+ toX?: number;
274
+ toY?: number;
275
+ scrollX?: number;
276
+ scrollY?: number;
277
+ button?: string;
278
+ text?: string;
279
+ keys?: string[];
280
+ ms?: number;
281
+ timeoutMs?: number;
282
+ timeoutGroup?: number;
283
+ };
284
+ type NativeBatchStepResult = {
285
+ index: number;
286
+ action: string;
287
+ screenshot?: NativeScreenshot;
288
+ };
289
+ type NativeBatchResult = {
290
+ results: NativeBatchStepResult[];
291
+ failureCode?: string;
292
+ failureIndex?: number;
293
+ failureMessage?: string;
294
+ primaryFailureCode?: string;
295
+ primaryFailureMessage?: string;
296
+ };
265
297
  type NativeController = {
298
+ executeBatch?: (expectedEpoch: number | undefined, actions: NativeBatchAction[], timeoutMs?: number, signal?: AbortSignal) => Promise<NativeBatchResult> | NativeBatchResult;
266
299
  screenshot?: () => Promise<NativeScreenshot> | NativeScreenshot;
267
300
  click?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
268
301
  doubleClick?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
@@ -282,7 +315,7 @@ type NativeScreenshot = {
282
315
  originX?: number;
283
316
  originY?: number;
284
317
  displayEpoch?: number;
285
- captureId?: string;
318
+ captureId?: number;
286
319
  };
287
320
  export type ComputerControllerFactory = () => NativeController;
288
321
  export declare const COMPUTER_DISABLED_CODE = "COMPUTER_DISABLED";
@@ -219,6 +219,14 @@ export interface ToolSession {
219
219
  getAuthorizedArtifactsDirs?: () => readonly string[];
220
220
  /** Get the ArtifactManager backing this session (shared across parent + subagents). */
221
221
  getArtifactManager?: () => ArtifactManager | null;
222
+ /** Prove that an ArtifactManager belongs to this concrete session or its explicitly adopted parent tree. */
223
+ isArtifactManagerAuthorized?: (manager: ArtifactManager) => boolean;
224
+ /** Adopt a task-created fallback manager into the concrete session owner. */
225
+ adoptArtifactManager?: (manager: ArtifactManager) => void;
226
+ /** Linearizably establish the concrete session owner's canonical artifact manager. */
227
+ ensureArtifactManager?: () => Promise<ArtifactManager | null>;
228
+ /** Release a task-created fallback manager when its logical session ends. */
229
+ releaseArtifactManager?: (manager: ArtifactManager) => void;
222
230
  /** Register teardown work owned by the current logical session. */
223
231
  registerSessionCleanup?: (cleanup: () => Promise<void> | void) => () => void;
224
232
  /** Allocate a new artifact path and ID for session-scoped truncated output. */
@@ -25,6 +25,7 @@ export interface TodoPhase {
25
25
  export interface TodoWriteToolDetails {
26
26
  phases: TodoPhase[];
27
27
  storage: "session" | "memory";
28
+ failureKind?: "payload_rejected" | "persistence";
28
29
  }
29
30
  declare function validateRawTodoArguments(arguments_: Record<string, unknown>): RawArgumentValidationResult;
30
31
  declare const todoWriteSchema: z.ZodObject<{
@@ -117,6 +118,7 @@ export declare const todoWriteToolRenderer: {
117
118
  text?: string;
118
119
  }>;
119
120
  details?: TodoWriteToolDetails;
121
+ isError?: boolean;
120
122
  }, options: RenderResultOptions, uiTheme: Theme, _args?: TodoWriteRenderArgs): Component;
121
123
  mergeCallAndResult: boolean;
122
124
  };
@@ -0,0 +1,71 @@
1
+ export declare const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
2
+ export type NpmConfigOrigin = "env" | "user" | "global";
3
+ /** Which package manager will run the install this check is gating. */
4
+ export type Installer = "bun" | "npm";
5
+ /** Injection points so tests never read the real environment. */
6
+ export interface NpmRegistryEnvironment {
7
+ /** Manager that will perform the install; decides Bun-vs-npm config priority. */
8
+ installer?: Installer;
9
+ /** Resolves an executable on PATH; used to locate npm's effective prefix. */
10
+ whichExecutable?: (name: string) => string | undefined;
11
+ /** Defaults to `$credentialEnv`, which excludes the caller's `cwd/.env`. */
12
+ lookupEnv?: (name: string) => string | undefined;
13
+ homeDir?: string;
14
+ platform?: NodeJS.Platform;
15
+ readFile?: (filePath: string) => Promise<string | undefined>;
16
+ }
17
+ export interface ResolvedNpmRegistry {
18
+ registry: string;
19
+ headers: Record<string, string>;
20
+ origin: NpmConfigOrigin | "default";
21
+ /** Where the value came from, e.g. `$npm_config_registry` or `~/.npmrc`. */
22
+ source: string;
23
+ /** Config files that exist but could not be read. */
24
+ warnings: string[];
25
+ }
26
+ export interface LatestPackageVersion {
27
+ version: string;
28
+ registry: string;
29
+ /** Config problems that did not stop the lookup but changed its outcome. */
30
+ warnings: string[];
31
+ }
32
+ export interface FetchResponseLike {
33
+ ok: boolean;
34
+ status: number;
35
+ statusText: string;
36
+ json: () => Promise<unknown>;
37
+ }
38
+ export type FetchLike = (input: string, init?: {
39
+ headers?: Record<string, string>;
40
+ signal?: AbortSignal;
41
+ }) => Promise<FetchResponseLike>;
42
+ export interface NpmRegistryLookupOptions extends NpmRegistryEnvironment {
43
+ fetchImpl?: FetchLike;
44
+ timeoutMs?: number;
45
+ }
46
+ /** Thrown when a registry is configured but unusable, so it is never silently ignored. */
47
+ export declare class NpmRegistryConfigError extends Error {
48
+ }
49
+ /**
50
+ * Parse an `.npmrc` into raw key/value pairs. Later duplicates win, as in npm.
51
+ *
52
+ * @internal Exported for tests; production code goes through `resolveNpmRegistry`.
53
+ */
54
+ export declare function parseNpmrc(contents: string, lookupEnv?: (name: string) => string | undefined): Map<string, string>;
55
+ /**
56
+ * Build the credential lookup keys npm calls "nerf darts": `//host/path/`,
57
+ * walking up one path segment at a time until the bare host.
58
+ *
59
+ * @internal Exported for tests; production code goes through `resolveNpmRegistry`.
60
+ */
61
+ export declare function credentialPrefixes(registry: string): string[];
62
+ /** Resolve the registry npm/bun would use for `packageName`, with credentials attached. */
63
+ export declare function resolveNpmRegistry(packageName: string, environment?: NpmRegistryEnvironment): Promise<ResolvedNpmRegistry>;
64
+ /**
65
+ * Registries can live under a path prefix, so join by string rather than `new URL`.
66
+ *
67
+ * @internal Exported for tests; production code goes through `fetchLatestPackageVersion`.
68
+ */
69
+ export declare function buildRegistryPackageUrl(registry: string, packageName: string, spec?: string): string;
70
+ /** Fetch the latest published version of `packageName` from the configured registry. */
71
+ export declare function fetchLatestPackageVersion(packageName: string, options?: NpmRegistryLookupOptions): Promise<LatestPackageVersion>;
@@ -14,6 +14,39 @@ mkdir -p .gjc/extensions
14
14
  cp packages/coding-agent/examples/extensions/pirate.ts .gjc/extensions/
15
15
  ```
16
16
 
17
+ ### Enable the Ouroboros `ooo` bridge
18
+
19
+ Install the version-pinned Ouroboros `v0.50.7` MCP profile, then configure its GJC runtime:
20
+
21
+ ```bash
22
+ uv tool install 'ouroboros-ai[mcp]==0.50.7'
23
+ ouroboros setup --runtime gjc
24
+ ```
25
+
26
+ `pipx install 'ouroboros-ai[mcp]==0.50.7'` is the equivalent pipx installation. Do not pipe a mutable branch installer into a shell. Pin source audits to commit `cb658aa819bfabafecbbe91bc36327f10691171b`. The [v0.50.7 release](https://github.com/Q00/ouroboros/releases/tag/v0.50.7) publishes `ouroboros_ai-0.50.7-py3-none-any.whl` with SHA-256 `df42f4ef10e032f2edc3249534bf91e8612dee789dfc3517895a9eb2df7f82c4`; verify downloaded release assets before installing them.
27
+
28
+ Ouroboros setup installs its own managed bridge. Replace that file with this standalone GJC bridge, which preserves the interview session across serialized follow-up answers, disposes it on GJC session switches, and drops queued predecessor-session starts. Download the example from immutable GJC commit `4311fefd49e9c6781c4d1111b8dd3f758e7d8974` and verify it before installation:
29
+
30
+ ```bash
31
+ curl -fL https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/4311fefd49e9c6781c4d1111b8dd3f758e7d8974/packages/coding-agent/examples/extensions/ooo-bridge.ts -o /tmp/gjc-ooo-bridge.ts
32
+ shasum -a 256 /tmp/gjc-ooo-bridge.ts
33
+ mkdir -p "${HOME}/${GJC_CONFIG_DIR:-.gjc}/agent/extensions/ouroboros-ooo-bridge" && cp /tmp/gjc-ooo-bridge.ts "${HOME}/${GJC_CONFIG_DIR:-.gjc}/agent/extensions/ouroboros-ooo-bridge/index.ts"
34
+ ```
35
+
36
+ The `shasum` output must match `2b0e1e25ac145331f112da629076875542db6f6e63c3c17adcd6770a4dcaf7bd` before the copy. The file has no runtime package imports and uses the host API injected by GJC, so compiled binaries do not require a peer `node_modules` directory beside the installation.
37
+
38
+ For a project-only installation, copy the same verified file to `.gjc/extensions/ouroboros-ooo-bridge/index.ts`. Start a new GJC session after installation, then enter:
39
+
40
+ ```text
41
+ ooo interview "I want to build a task management CLI"
42
+ ```
43
+
44
+ The first question is rendered in GJC. While that interview remains active, ordinary interactive input is sent as the answer with the same Ouroboros session ID; completion clears the correlation and returns subsequent ordinary prompts to GJC. Other `ooo ...` commands continue through `ouroboros dispatch --runtime gjc`, including exit-code `78` pass-through.
45
+
46
+ Set `OUROBOROS_CLI=/absolute/path/to/ouroboros` when the executable is outside `PATH`. Missing executable, MCP startup, and dispatch failures produce an error notification for the claimed input without preventing GJC startup or ordinary prompts.
47
+
48
+ This external path is separate from GJC's native `/skill:deep-interview`: the native skill runs GJC's bundled interview workflow, while `ooo interview` delegates to the installed Ouroboros MCP interview tool.
49
+
17
50
  ## Examples
18
51
 
19
52
  ### Custom Tools & API
@@ -39,10 +72,11 @@ cp packages/coding-agent/examples/extensions/pirate.ts .gjc/extensions/
39
72
 
40
73
  ### External Dependencies
41
74
 
42
- | Extension | Description |
43
- | ----------------- | ------------------------------------------------------------------------- |
44
- | `chalk-logger.ts` | Uses chalk from parent node_modules (demonstrates jiti module resolution) |
45
- | `with-deps/` | Extension with its own package.json and dependencies |
75
+ | Extension | Description |
76
+ | ----------------- | ---------------------------------------------------------------------------- |
77
+ | `chalk-logger.ts` | Uses chalk from parent node_modules (demonstrates jiti module resolution) |
78
+ | `ooo-bridge.ts` | Opt-in `ooo ...` input bridge to the installed Ouroboros CLI and MCP runtime |
79
+ | `with-deps/` | Extension with its own package.json and dependencies |
46
80
 
47
81
  ## Writing Extensions
48
82
 
@@ -0,0 +1,15 @@
1
+ interface OooBridgeExtensionAPI {
2
+ pi: unknown;
3
+ on(event: "input" | "session_switch", handler: (event: unknown, context: unknown) => unknown): void;
4
+ }
5
+
6
+ interface OooBridgeHost {
7
+ createOuroborosOooBridge(): ((event: unknown, context: unknown) => unknown) & { reset(): Promise<void> };
8
+ }
9
+
10
+ export default function (pi: OooBridgeExtensionAPI) {
11
+ const host = pi.pi as OooBridgeHost;
12
+ const bridge = host.createOuroborosOooBridge();
13
+ pi.on("input", bridge);
14
+ pi.on("session_switch", () => bridge.reset());
15
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.12.10",
4
+ "version": "0.12.12",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -53,13 +53,13 @@
53
53
  "@agentclientprotocol/sdk": "1.3.0",
54
54
  "@babel/parser": "^7.29.3",
55
55
  "@mozilla/readability": "^0.6.0",
56
- "@gajae-code/stats": "0.12.10",
57
- "@gajae-code/agent-core": "0.12.10",
58
- "@gajae-code/ai": "0.12.10",
59
- "@gajae-code/bridge-client": "0.12.10",
60
- "@gajae-code/natives": "0.12.10",
61
- "@gajae-code/tui": "0.12.10",
62
- "@gajae-code/utils": "0.12.10",
56
+ "@gajae-code/stats": "0.12.12",
57
+ "@gajae-code/agent-core": "0.12.12",
58
+ "@gajae-code/ai": "0.12.12",
59
+ "@gajae-code/bridge-client": "0.12.12",
60
+ "@gajae-code/natives": "0.12.12",
61
+ "@gajae-code/tui": "0.12.12",
62
+ "@gajae-code/utils": "0.12.12",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -37,6 +37,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
37
37
  "agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
38
38
  "agent_session:registerToolSessionCleanup":
39
39
  "internal tool lifecycle cleanup registration, not a user-facing SDK control seam",
40
+ "agent_session:registerToolSessionTransitionCleanup":
41
+ "internal tool transition cleanup registration for shared artifact-manager ownership, not a user-facing SDK control seam",
40
42
  "agent_session:nextToolChoice": "internal accessor/plumbing, not a user-facing control seam",
41
43
  "agent_session:setForcedToolChoice": "internal accessor/plumbing, not a user-facing control seam",
42
44
  "agent_session:getActiveSkillState": "internal accessor/plumbing, not a user-facing control seam",
@@ -66,6 +68,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
66
68
  "agent_session:closeWriterStrict": "internal ACP lifecycle teardown plumbing, not a user-facing control seam",
67
69
  "agent_session:disposeChildSubprocesses": "internal accessor/plumbing, not a user-facing control seam",
68
70
  "agent_session:waitForIdle": "internal accessor/plumbing, not a user-facing control seam",
71
+ "agent_session:extendStartupTurnBarrier":
72
+ "internal CLI startup-readiness fence composition, not a user-facing SDK control seam",
69
73
  "agent_session:awaitPendingContextTransformations":
70
74
  "internal context-transformation lifecycle barrier, not a user-facing SDK control seam",
71
75
  "agent_session:drainAsyncJobDeliveriesForAcp": "internal accessor/plumbing, not a user-facing control seam",
@@ -463,6 +463,7 @@ export class AsyncJobManager {
463
463
  readonly #terminalWaits = new Map<string, TerminalWaitState>();
464
464
  #waitSeq = 0;
465
465
  readonly #publishedTerminalGenerations = new Set<string>();
466
+ readonly #settledJobIds = new Set<string>();
466
467
  #jobGenerationSeq = 0;
467
468
  readonly #liveHandles = new Map<string, SubagentLiveHandle>();
468
469
  readonly #subagentProgress = new Map<string, AgentProgress>();
@@ -820,6 +821,7 @@ export class AsyncJobManager {
820
821
 
821
822
  this.#expireMonitorTombstones();
822
823
  const id = this.#resolveJobId(options?.id);
824
+ this.#settledJobIds.delete(id);
823
825
  const abortController = new AbortController();
824
826
  const startTime = Date.now();
825
827
 
@@ -919,7 +921,9 @@ export class AsyncJobManager {
919
921
  this.#scheduleEviction(id);
920
922
  this.#drainResumeQueue();
921
923
  }
922
- })();
924
+ })().finally(() => {
925
+ this.#settledJobIds.add(id);
926
+ });
923
927
 
924
928
  this.#jobs.set(id, job);
925
929
  this.#notifyChange();
@@ -1218,6 +1222,8 @@ export class AsyncJobManager {
1218
1222
  ) {
1219
1223
  continue;
1220
1224
  }
1225
+ if (job.status === "cancelled" && this.#settledJobIds.has(job.id) && !this.#subagentRecords.has(subagentId))
1226
+ continue;
1221
1227
  if (!targets.has(subagentId)) {
1222
1228
  targets.set(subagentId, { subagentId, jobId: job.id, source: "metadata_job" });
1223
1229
  }
@@ -1298,7 +1304,7 @@ export class AsyncJobManager {
1298
1304
  lease.targets.map(target => target.subagentId),
1299
1305
  );
1300
1306
  }
1301
- if (state.phase === "proved" && state.proof) return state.proof;
1307
+ if (state.phase === "proved" && state.proof?.confirmed) return state.proof;
1302
1308
  state.phase = "proving";
1303
1309
  const settled = new Set<string>();
1304
1310
  const promises: Promise<void>[] = [];
@@ -2064,6 +2070,7 @@ export class AsyncJobManager {
2064
2070
  this.#purgeTerminalSubagentStateForJob(jobId);
2065
2071
  const job = this.#jobs.get(jobId);
2066
2072
  this.#jobs.delete(jobId);
2073
+ this.#settledJobIds.delete(jobId);
2067
2074
  this.#lifecycles.delete(jobId);
2068
2075
  this.#lifecyclePhases.delete(jobId);
2069
2076
  this.#deadLetteredDeliveries.delete(jobId);