@gajae-code/coding-agent 0.12.11 → 0.12.13

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 (140) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/types/cli/marketplace-hint.d.ts +28 -0
  3. package/dist/types/cli/notify-cli.d.ts +42 -1
  4. package/dist/types/cli/update-cli.d.ts +9 -3
  5. package/dist/types/cli.d.ts +10 -0
  6. package/dist/types/commands/notify.d.ts +6 -0
  7. package/dist/types/config/file-lock-gc.d.ts +13 -1
  8. package/dist/types/config/file-lock.d.ts +3 -0
  9. package/dist/types/config/model-registry.d.ts +6 -0
  10. package/dist/types/config/models-config-schema.d.ts +99 -0
  11. package/dist/types/coordinator/contract.d.ts +1 -1
  12. package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
  13. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  14. package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
  15. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
  16. package/dist/types/extensibility/extensions/types.d.ts +2 -0
  17. package/dist/types/gjc-runtime/deep-interview-state.d.ts +14 -0
  18. package/dist/types/gjc-runtime/gc-runtime.d.ts +11 -0
  19. package/dist/types/main.d.ts +6 -2
  20. package/dist/types/modes/acp/acp-agent.d.ts +24 -1
  21. package/dist/types/modes/components/session-selector.d.ts +1 -1
  22. package/dist/types/modes/utils/host-status.d.ts +23 -0
  23. package/dist/types/runtime-mcp/types.d.ts +2 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
  25. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
  26. package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
  27. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
  28. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
  29. package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
  30. package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
  31. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
  32. package/dist/types/sdk/bus/html-format.d.ts +10 -0
  33. package/dist/types/sdk/bus/notification-service.d.ts +10 -0
  34. package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
  35. package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
  36. package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
  37. package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
  38. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +28 -7
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -17
  41. package/dist/types/sdk/bus/telegram-reference.d.ts +20 -1
  42. package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
  43. package/dist/types/sdk/host/host.d.ts +52 -0
  44. package/dist/types/sdk/lifecycle-session.d.ts +6 -0
  45. package/dist/types/sdk/session-activation.d.ts +41 -0
  46. package/dist/types/sdk/session.d.ts +18 -0
  47. package/dist/types/sdk/startup-capability.d.ts +8 -1
  48. package/dist/types/session/agent-session.d.ts +7 -2
  49. package/dist/types/session/artifacts.d.ts +2 -2
  50. package/dist/types/session/session-manager.d.ts +40 -6
  51. package/dist/types/tools/index.d.ts +15 -0
  52. package/dist/types/utils/npm-registry.d.ts +71 -0
  53. package/examples/extensions/README.md +38 -4
  54. package/examples/extensions/ooo-bridge.ts +15 -0
  55. package/package.json +8 -8
  56. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  57. package/src/async/job-manager.ts +9 -2
  58. package/src/cli/fast-help.ts +1 -0
  59. package/src/cli/marketplace-hint.ts +39 -0
  60. package/src/cli/notify-cli.ts +226 -2
  61. package/src/cli/plugin-cli.ts +10 -0
  62. package/src/cli/update-cli.ts +90 -23
  63. package/src/cli.ts +23 -0
  64. package/src/commands/notify.ts +27 -3
  65. package/src/commands/sdk.ts +1 -0
  66. package/src/config/file-lock-gc.ts +65 -26
  67. package/src/config/file-lock.ts +61 -16
  68. package/src/config/model-profiles.ts +46 -30
  69. package/src/config/model-registry.ts +17 -5
  70. package/src/config/models-config-schema.ts +9 -4
  71. package/src/coordinator/contract.ts +1 -0
  72. package/src/coordinator-mcp/question-state.ts +2 -0
  73. package/src/coordinator-mcp/server.ts +358 -7
  74. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  75. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
  76. package/src/extensibility/extensions/index.ts +1 -0
  77. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
  78. package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
  79. package/src/extensibility/extensions/runner.ts +15 -2
  80. package/src/extensibility/extensions/types.ts +2 -0
  81. package/src/gjc-runtime/deep-interview-recorder.ts +9 -5
  82. package/src/gjc-runtime/deep-interview-state.ts +27 -3
  83. package/src/gjc-runtime/gc-render.ts +8 -1
  84. package/src/gjc-runtime/gc-runtime.ts +15 -1
  85. package/src/gjc-runtime/launch-tmux.ts +53 -1
  86. package/src/internal-urls/docs-index.generated.ts +14 -13
  87. package/src/main.ts +214 -70
  88. package/src/modes/DESIGN.md +8 -5
  89. package/src/modes/acp/acp-agent.ts +123 -7
  90. package/src/modes/components/footer.ts +10 -18
  91. package/src/modes/components/model-selector.ts +52 -5
  92. package/src/modes/components/session-selector.ts +26 -3
  93. package/src/modes/components/status-line/gh.ts +23 -2
  94. package/src/modes/controllers/event-controller.ts +3 -0
  95. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  96. package/src/modes/controllers/input-controller.ts +11 -0
  97. package/src/modes/controllers/selector-controller.ts +8 -2
  98. package/src/modes/interactive-mode.ts +7 -1
  99. package/src/modes/print-mode.ts +10 -0
  100. package/src/modes/utils/host-status.ts +38 -0
  101. package/src/prompts/system/project-prompt.md +19 -0
  102. package/src/runtime-mcp/types.ts +2 -0
  103. package/src/sdk/broker/lifecycle.ts +60 -3
  104. package/src/sdk/bus/chat-daemon-cli.ts +42 -0
  105. package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
  106. package/src/sdk/bus/chat-daemon-control.ts +16 -3
  107. package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
  108. package/src/sdk/bus/control-plane-frames.ts +51 -0
  109. package/src/sdk/bus/conversation-store.ts +29 -1
  110. package/src/sdk/bus/existing-thread-readiness.ts +155 -0
  111. package/src/sdk/bus/html-format.ts +19 -3
  112. package/src/sdk/bus/index.ts +91 -12
  113. package/src/sdk/bus/notification-service.ts +28 -1
  114. package/src/sdk/bus/rich-render.ts +3 -2
  115. package/src/sdk/bus/slack-daemon.ts +99 -4
  116. package/src/sdk/bus/slack-live-provider.ts +32 -2
  117. package/src/sdk/bus/slack-provider.ts +11 -0
  118. package/src/sdk/bus/slack-thread-binding.ts +641 -0
  119. package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
  120. package/src/sdk/bus/telegram-daemon-contract.ts +28 -7
  121. package/src/sdk/bus/telegram-daemon.ts +2007 -509
  122. package/src/sdk/bus/telegram-reference.ts +114 -15
  123. package/src/sdk/bus/topic-registry.ts +674 -181
  124. package/src/sdk/host/host.ts +180 -1
  125. package/src/sdk/lifecycle-session.ts +8 -2
  126. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  127. package/src/sdk/session-activation.ts +127 -0
  128. package/src/sdk/session.ts +144 -29
  129. package/src/sdk/startup-capability.ts +10 -1
  130. package/src/session/agent-session.ts +220 -13
  131. package/src/session/artifacts.ts +56 -13
  132. package/src/session/internal/managed-session-storage.ts +228 -7
  133. package/src/session/session-manager.ts +225 -60
  134. package/src/setup/provider-presets.json +3 -3
  135. package/src/task/index.ts +92 -44
  136. package/src/thinking-metadata.ts +2 -1
  137. package/src/tools/ask.ts +10 -0
  138. package/src/tools/index.ts +15 -0
  139. package/src/tools/todo-write.ts +4 -4
  140. package/src/utils/npm-registry.ts +660 -0
@@ -346,10 +346,20 @@ export interface ResumeTailTerminal {
346
346
  }
347
347
  export interface ResumeTailError {
348
348
  kind: "error";
349
- reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled";
349
+ reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled" | "oversized";
350
+ size?: number;
350
351
  }
351
352
  export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy;
352
353
  export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure";
354
+ /**
355
+ * Safety bound for resuming a single transcript file. Matches the managed-storage
356
+ * per-file artifact limit (`MANAGED_ARTIFACT_MAX_FILE_BYTES`): transcripts at or above
357
+ * this size are not loaded into memory wholesale because the decode + per-line JSON
358
+ * parse + identity hash would OOM or stall the process (#3851). The bound is a
359
+ * load-time guard only; it does not raise the on-disk managed-storage limit.
360
+ */
361
+ export declare const RESUME_TRANSCRIPT_MAX_BYTES: number;
362
+ export declare const SESSION_OVERSIZED_RECOVERY_MESSAGE = "The selected session transcript is too large to resume safely. Use `gjc export <session-file>` to export its content into a new session, or remove/archive it after confirming its content is no longer needed.";
353
363
  export declare class SessionAppendPersistenceError extends Error {
354
364
  readonly phase: SessionAppendPersistenceFailurePhase;
355
365
  readonly entryId: string;
@@ -368,6 +378,11 @@ export declare class SessionArtifactCapacityError extends Error {
368
378
  readonly code = "artifact_capacity_exceeded";
369
379
  constructor(message: string);
370
380
  }
381
+ export declare class SessionTranscriptOversizedError extends Error {
382
+ readonly code = "oversized";
383
+ readonly size: number;
384
+ constructor(size: number);
385
+ }
371
386
  export type ResumeTailInspection = ResumeTailResumable | ResumeTailTerminal | ResumeTailError;
372
387
  export interface StrictSessionOpenSuccess {
373
388
  kind: "opened";
@@ -377,6 +392,7 @@ export interface StrictSessionOpenFailure {
377
392
  kind: "error";
378
393
  reason: ResumeTailError["reason"] | "identity-mismatch" | "migration-required" | "artifact_capacity_exceeded" | "migration_busy";
379
394
  message?: string;
395
+ size?: number;
380
396
  }
381
397
  /** Exact transcript bytes and authority captured from one descriptor-bound read. */
382
398
  export interface CapturedSessionTranscriptSnapshot {
@@ -615,6 +631,7 @@ interface SessionManagerStateSnapshot {
615
631
  needsFullRewriteOnNextPersist: boolean;
616
632
  fileEntries: FileEntry[];
617
633
  materializedFileEntries: FileEntry[];
634
+ adoptedArtifactManager: ArtifactManager | null;
618
635
  }
619
636
  /** Benchmark-derived cap for strong materialized session snapshots. */
620
637
  export declare const MATERIALIZED_CACHE_MAX_BYTES: number;
@@ -623,6 +640,7 @@ export declare const SessionManagerTestHooks: {
623
640
  materializedCacheMaxBytesOverride?: number;
624
641
  beforeResidentTransitionIndexBuild?: () => void;
625
642
  afterForkSnapshot?: () => void | Promise<void>;
643
+ beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
626
644
  };
627
645
  export declare class SessionManager {
628
646
  #private;
@@ -643,7 +661,9 @@ export declare class SessionManager {
643
661
  captureState(): SessionManagerStateSnapshot;
644
662
  restoreState(snapshot: SessionManagerStateSnapshot): void;
645
663
  /** Switch to a different session file (used for resume and branching). */
646
- setSessionFile(sessionFile: string): Promise<void>;
664
+ setSessionFile(sessionFile: string, options?: {
665
+ deferEphemeralArtifactRetirement?: boolean;
666
+ }): Promise<void>;
647
667
  /** Start a new session. Closes any existing writer first. */
648
668
  newSession(options?: NewSessionOptions): Promise<string | undefined>;
649
669
  /**
@@ -751,13 +771,23 @@ export declare class SessionManager {
751
771
  * the parent session's artifact directory and ID counter.
752
772
  */
753
773
  adoptArtifactManager(manager: ArtifactManager): void;
774
+ /** Release only the matching externally adopted manager. */
775
+ releaseArtifactManager(manager: ArtifactManager): void;
776
+ /** Temporarily release adopted authority while an outer transition validates its successor. */
777
+ stageAdoptedArtifactManagerForTransition(): void;
778
+ /** Prove manager authority by exact object identity, never by pathname shape. */
779
+ isArtifactManagerAuthorized(manager: ArtifactManager): boolean;
754
780
  /**
755
781
  * Returns the ArtifactManager this session writes through. Lazily creates
756
782
  * one bound to the current session file unless an external manager was
757
- * adopted via `adoptArtifactManager`. Returns null only for non-persistent
758
- * sessions with no adopted manager.
783
+ * adopted via `adoptArtifactManager`. Falls back to the lazily created
784
+ * ephemeral filesystem store once a non-persistent session has saved an
785
+ * artifact, so `artifact://` stays resolvable. Returns null only when no
786
+ * store has been established yet.
759
787
  */
760
788
  getArtifactManager(): ArtifactManager | null;
789
+ /** Linearizably establish this session's persistent or ephemeral artifact manager. */
790
+ ensureArtifactManager(): Promise<ArtifactManager | null>;
761
791
  /**
762
792
  * Allocate a new artifact path and ID for the current session.
763
793
  * Returns an empty object when the session is not persisted.
@@ -768,14 +798,18 @@ export declare class SessionManager {
768
798
  }>;
769
799
  /**
770
800
  * Save artifact content under the current session and return artifact ID.
771
- * Returns an artifact ID for all sessions (file-backed for persistent, in-memory fallback otherwise).
801
+ * Persistent sessions write into the session artifact directory; non-persistent
802
+ * sessions write into a lazily created temporary directory so the content is
803
+ * read back from the filesystem instead of being retained in memory.
772
804
  */
773
805
  saveArtifact(content: string, toolType: string): Promise<string | undefined>;
774
806
  /**
775
807
  * Resolve an artifact ID to an on-disk path for the current session.
776
- * Returns null when missing or when the session is not persisted.
808
+ * Returns null when the artifact is missing.
777
809
  */
778
810
  getArtifactPath(id: string): Promise<string | null>;
811
+ /** Retire predecessor ephemeral artifacts after an outer logical transition commits. */
812
+ retireEphemeralArtifactsAfterTransition(): void;
779
813
  /**
780
814
  * Persist (or clear) the current editor draft so the next resume of this
781
815
  * session can restore it. Empty text deletes any stale draft. No-op when the
@@ -84,6 +84,13 @@ export interface AskAnswerRequest {
84
84
  controls: readonly AskRemoteControl[];
85
85
  /** Optional zero-based recommendation into the authoritative raw options. */
86
86
  recommendedIndex?: number;
87
+ /** True while the question accepts several options before the navigation control commits. */
88
+ multi?: boolean;
89
+ /**
90
+ * Options currently selected in the multi-select loop, as authoritative option
91
+ * labels. Remote transports render the selection state so a toggle is visible.
92
+ */
93
+ selectedOptions?: readonly string[];
87
94
  }
88
95
  export type AskRemoteInteraction = {
89
96
  kind: "value";
@@ -219,6 +226,14 @@ export interface ToolSession {
219
226
  getAuthorizedArtifactsDirs?: () => readonly string[];
220
227
  /** Get the ArtifactManager backing this session (shared across parent + subagents). */
221
228
  getArtifactManager?: () => ArtifactManager | null;
229
+ /** Prove that an ArtifactManager belongs to this concrete session or its explicitly adopted parent tree. */
230
+ isArtifactManagerAuthorized?: (manager: ArtifactManager) => boolean;
231
+ /** Adopt a task-created fallback manager into the concrete session owner. */
232
+ adoptArtifactManager?: (manager: ArtifactManager) => void;
233
+ /** Linearizably establish the concrete session owner's canonical artifact manager. */
234
+ ensureArtifactManager?: () => Promise<ArtifactManager | null>;
235
+ /** Release a task-created fallback manager when its logical session ends. */
236
+ releaseArtifactManager?: (manager: ArtifactManager) => void;
222
237
  /** Register teardown work owned by the current logical session. */
223
238
  registerSessionCleanup?: (cleanup: () => Promise<void> | void) => () => void;
224
239
  /** Allocate a new artifact path and ID for session-scoped truncated output. */
@@ -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.11",
4
+ "version": "0.12.13",
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.11",
57
- "@gajae-code/agent-core": "0.12.11",
58
- "@gajae-code/ai": "0.12.11",
59
- "@gajae-code/bridge-client": "0.12.11",
60
- "@gajae-code/natives": "0.12.11",
61
- "@gajae-code/tui": "0.12.11",
62
- "@gajae-code/utils": "0.12.11",
56
+ "@gajae-code/stats": "0.12.13",
57
+ "@gajae-code/agent-core": "0.12.13",
58
+ "@gajae-code/ai": "0.12.13",
59
+ "@gajae-code/bridge-client": "0.12.13",
60
+ "@gajae-code/natives": "0.12.13",
61
+ "@gajae-code/tui": "0.12.13",
62
+ "@gajae-code/utils": "0.12.13",
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);
@@ -106,5 +106,6 @@ Available Tools (default-enabled unless noted):
106
106
 
107
107
  Useful Commands:
108
108
  ${APP_NAME} --list-models - List configured provider models
109
+ ${APP_NAME} models - Alias for --list-models (never starts an agent)
109
110
  ${APP_NAME} --help - Show this help`;
110
111
  }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Install-failure hints for marketplace-shaped plugin names.
3
+ *
4
+ * A bare spec (no `@marketplace`) is always classified as npm, so a user who
5
+ * copies a plugin name out of `plugin discover` gets an npm resolution failure
6
+ * with no hint that the same name is offered by a registered marketplace.
7
+ *
8
+ * Kept dependency-free — the marketplace barrel loads native addons — so this
9
+ * takes the narrow catalog surface it actually reads instead of the manager.
10
+ */
11
+
12
+ /** The catalog lookups this module needs; `MarketplaceManager` satisfies it. */
13
+ export interface MarketplaceCatalogLookup {
14
+ listMarketplaces(): Promise<ReadonlyArray<{ name: string }>>;
15
+ getPluginInfo(name: string, marketplace: string): Promise<unknown | null>;
16
+ }
17
+
18
+ /**
19
+ * A bare name carries no `@scope`, version specifier, or path separator, so it
20
+ * is the only install spec shape that is safe to echo back in an error.
21
+ */
22
+ export function isBareInstallName(spec: string): boolean {
23
+ return spec.length > 0 && !spec.includes("@") && !spec.includes("/") && !spec.includes("\\");
24
+ }
25
+
26
+ /**
27
+ * Names of registered marketplaces whose catalog offers a plugin under exactly
28
+ * `name`. Only marketplace names are returned: they are user-chosen registry
29
+ * labels, never the plugin source, so they carry no credentials or home paths.
30
+ */
31
+ export async function findMarketplacesOffering(catalogs: MarketplaceCatalogLookup, name: string): Promise<string[]> {
32
+ const marketplaces = await catalogs.listMarketplaces();
33
+ const offering: string[] = [];
34
+ for (const marketplace of marketplaces) {
35
+ const info = await catalogs.getPluginInfo(name, marketplace.name).catch(() => null);
36
+ if (info) offering.push(marketplace.name);
37
+ }
38
+ return offering;
39
+ }