@gajae-code/coding-agent 0.16.3 → 0.16.4

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 (133) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/types/cli/args.d.ts +2 -2
  3. package/dist/types/cli/root-flags.d.ts +4 -2
  4. package/dist/types/cli.d.ts +1 -1
  5. package/dist/types/commands/launch.d.ts +1 -1
  6. package/dist/types/config/autorouting-tier-map.d.ts +3 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/model-resolver.d.ts +9 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/discovery/claude-plugins.d.ts +3 -1
  11. package/dist/types/discovery/helpers.d.ts +2 -0
  12. package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
  13. package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
  14. package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
  15. package/dist/types/extensibility/shared-events.d.ts +1 -1
  16. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  17. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  18. package/dist/types/modes/components/settings-selector.d.ts +6 -1
  19. package/dist/types/modes/components/status-line/command.d.ts +35 -0
  20. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  21. package/dist/types/modes/components/status-line/types.d.ts +5 -0
  22. package/dist/types/modes/components/tool-status-header.d.ts +14 -0
  23. package/dist/types/modes/controllers/event-controller.d.ts +6 -0
  24. package/dist/types/modes/interactive-mode.d.ts +1 -0
  25. package/dist/types/modes/types.d.ts +1 -0
  26. package/dist/types/runtime-mcp/client.d.ts +9 -1
  27. package/dist/types/runtime-mcp/manager.d.ts +7 -1
  28. package/dist/types/runtime-mcp/pool.d.ts +22 -3
  29. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  30. package/dist/types/runtime-mcp/types.d.ts +22 -0
  31. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
  32. package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
  33. package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
  34. package/dist/types/sdk/host/session-runtime.d.ts +23 -0
  35. package/dist/types/sdk/session-api-key.d.ts +26 -0
  36. package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
  37. package/dist/types/session/agent-session.d.ts +9 -0
  38. package/dist/types/setup/paseo/announce.d.ts +111 -0
  39. package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
  40. package/dist/types/system-prompt.d.ts +2 -0
  41. package/dist/types/task/executor.d.ts +2 -1
  42. package/dist/types/tools/fetch.d.ts +7 -0
  43. package/dist/types/tools/index.d.ts +8 -0
  44. package/dist/types/tools/path-utils.d.ts +2 -0
  45. package/dist/types/tools/search.d.ts +6 -0
  46. package/package.json +7 -7
  47. package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
  48. package/scripts/generate-sdk-operation-inventory.ts +9 -0
  49. package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
  50. package/src/cli/args.ts +20 -9
  51. package/src/cli/root-flags.ts +5 -3
  52. package/src/config/autorouting-tier-map.ts +1 -0
  53. package/src/config/file-lock.ts +181 -17
  54. package/src/config/model-preset-registry.ts +5 -3
  55. package/src/config/model-profiles.ts +40 -0
  56. package/src/config/model-registry.ts +57 -11
  57. package/src/config/model-resolver.ts +48 -0
  58. package/src/config/settings-schema.ts +33 -1
  59. package/src/config/settings.ts +1 -0
  60. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
  61. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
  62. package/src/discovery/builtin.ts +13 -4
  63. package/src/discovery/claude-plugins.ts +12 -4
  64. package/src/discovery/helpers.ts +10 -2
  65. package/src/extensibility/gjc-plugins/compiler.ts +208 -58
  66. package/src/extensibility/gjc-plugins/installer.ts +13 -12
  67. package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
  68. package/src/extensibility/gjc-plugins/paths.ts +13 -0
  69. package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
  70. package/src/extensibility/runtime-skill-discovery.ts +77 -151
  71. package/src/extensibility/shared-events.ts +1 -1
  72. package/src/extensibility/skills.ts +44 -16
  73. package/src/gjc-runtime/repository-binding.ts +1 -1
  74. package/src/gjc-runtime/session-state-lock.ts +92 -2
  75. package/src/internal-urls/docs-index.generated.ts +7 -7
  76. package/src/main.ts +72 -34
  77. package/src/modes/DESIGN.md +54 -0
  78. package/src/modes/components/assistant-message.ts +22 -9
  79. package/src/modes/components/model-selector.ts +166 -33
  80. package/src/modes/components/settings-selector.ts +1075 -235
  81. package/src/modes/components/status-line/command.ts +190 -0
  82. package/src/modes/components/status-line/index.ts +1 -0
  83. package/src/modes/components/status-line/segments.ts +32 -5
  84. package/src/modes/components/status-line/types.ts +5 -0
  85. package/src/modes/components/tool-status-header.ts +393 -21
  86. package/src/modes/controllers/command-controller.ts +2 -0
  87. package/src/modes/controllers/event-controller.ts +148 -5
  88. package/src/modes/controllers/selector-controller.ts +21 -0
  89. package/src/modes/interactive-mode.ts +47 -1
  90. package/src/modes/types.ts +1 -0
  91. package/src/modes/utils/ui-helpers.ts +1 -0
  92. package/src/prompts/system/system-prompt.md +8 -0
  93. package/src/prompts/tools/search.md +2 -0
  94. package/src/runtime-mcp/client.ts +60 -17
  95. package/src/runtime-mcp/manager.ts +171 -14
  96. package/src/runtime-mcp/pool.test.ts +209 -0
  97. package/src/runtime-mcp/pool.ts +224 -23
  98. package/src/runtime-mcp/transports/stdio.ts +158 -24
  99. package/src/runtime-mcp/types.ts +24 -0
  100. package/src/sdk/broker/broker.ts +5 -4
  101. package/src/sdk/bus/chat-daemon-control.ts +1 -1
  102. package/src/sdk/bus/chat-daemon-runtime.ts +37 -1
  103. package/src/sdk/bus/slack-daemon.ts +92 -2
  104. package/src/sdk/bus/slack-live-provider.ts +12 -0
  105. package/src/sdk/bus/slack-provider.ts +5 -0
  106. package/src/sdk/cli/session-cli.ts +9 -6
  107. package/src/sdk/host/session-runtime.test.ts +2 -2
  108. package/src/sdk/host/session-runtime.ts +275 -43
  109. package/src/sdk/protocol/operation-inventory.generated.json +66 -0
  110. package/src/sdk/session-api-key.ts +94 -0
  111. package/src/sdk/session.ts +73 -29
  112. package/src/sdk/transport/serve-cli.ts +3 -3
  113. package/src/session/agent-session.ts +123 -38
  114. package/src/session/contribution-prep.ts +214 -21
  115. package/src/setup/paseo/announce.ts +450 -0
  116. package/src/setup/paseo/setup-deps.ts +15 -3
  117. package/src/slash-commands/builtin-registry.ts +1 -0
  118. package/src/system-prompt.ts +4 -0
  119. package/src/task/executor.ts +2 -1
  120. package/src/task/index.ts +17 -4
  121. package/src/tools/bash.ts +35 -8
  122. package/src/tools/cron.ts +1 -1
  123. package/src/tools/fetch.ts +29 -1
  124. package/src/tools/index.ts +8 -0
  125. package/src/tools/job.ts +8 -2
  126. package/src/tools/monitor.ts +8 -2
  127. package/src/tools/path-utils.ts +7 -0
  128. package/src/tools/search.ts +116 -58
  129. package/src/tools/skill.ts +22 -2
  130. package/src/tools/subagent.ts +3 -1
  131. package/src/tools/tool-catalog.generated.ts +8 -1
  132. package/src/web/insane/bridge.ts +113 -23
  133. package/src/web/search/providers/perplexity.ts +102 -41
@@ -78,6 +78,20 @@ export interface MCPServerConfigBase {
78
78
  };
79
79
  }
80
80
  /** Stdio server configuration */
81
+ export interface MCPStdioSpawnLaunch {
82
+ command: string;
83
+ args: readonly string[];
84
+ cwd: string;
85
+ /** Transport-supplied registrar for retryable cleanup before preparation allocates resources. */
86
+ registerCleanup?: (cleanup: () => Promise<void>) => void;
87
+ }
88
+ export interface MCPStdioPreparedLaunch extends MCPStdioSpawnLaunch {
89
+ /**
90
+ * Per-attempt cleanup run after confirmed child exit or failed spawn. The same
91
+ * callback may already have been registered during preparation.
92
+ */
93
+ afterProcessExit?: () => Promise<void>;
94
+ }
81
95
  export interface MCPStdioServerConfig extends MCPServerConfigBase {
82
96
  type?: "stdio";
83
97
  command: string;
@@ -91,6 +105,14 @@ export interface MCPStdioServerConfig extends MCPServerConfigBase {
91
105
  */
92
106
  noInheritEnv?: boolean;
93
107
  cwd?: string;
108
+ /** Internal trusted-boundary preparation of immutable per-spawn authority. */
109
+ prepareSpawn?: (launch: MCPStdioSpawnLaunch) => Promise<MCPStdioPreparedLaunch>;
110
+ /** Internal trusted-boundary check run immediately before process creation. */
111
+ spawnGuard?: (launch: MCPStdioSpawnLaunch) => Promise<void>;
112
+ /** Internal cleanup run only after the owned stdio child is confirmed exited. */
113
+ afterProcessExit?: () => Promise<void>;
114
+ /** Test seam for deterministic mutation after the guard and before process creation. */
115
+ afterSpawnGuardForTest?: () => Promise<void>;
94
116
  }
95
117
  /** HTTP server configuration (Streamable HTTP transport) */
96
118
  export interface MCPHttpServerConfig extends MCPServerConfigBase {
@@ -36,6 +36,18 @@ export interface ChatDaemonRuntimeDeps {
36
36
  createSlackProvider?: (config: NonNullable<ChatDaemonRuntimeConfig["notifications"]["slack"]>) => SlackProviderClient;
37
37
  routerDeps?: SessionRouterDeps;
38
38
  }
39
+ /**
40
+ * Chat notifications in lean mode are an answer surface, not a mirror of the
41
+ * session protocol. Lifecycle, identity, and activity frames remain available
42
+ * to verbose consumers, while finalized answers and action-needed events are
43
+ * delivered in lean mode.
44
+ */
45
+ export declare function shouldPublishChatFrame(frame: Record<string, unknown>, verbosity: "lean" | "verbose" | undefined): boolean;
46
+ /**
47
+ * A final answer's message ref identifies its one rendered message even when
48
+ * the SDK transport delivers an unpositioned duplicate frame.
49
+ */
50
+ export declare function publicationIdForFinalChatAnswer(sessionId: string, frame: Record<string, unknown>): string | undefined;
39
51
  /**
40
52
  * Worker-owned session discovery and event fanout. It connects only through the
41
53
  * public SDK transport and retains SDK credentials solely in live client objects.
@@ -59,6 +59,12 @@ export declare class SlackLiveProvider implements SlackProviderClient, SlackDiag
59
59
  clientMsgId: string;
60
60
  signal?: AbortSignal;
61
61
  }): Promise<SlackPostedMessage>;
62
+ addReaction(input: {
63
+ channel: string;
64
+ timestamp: string;
65
+ name: string;
66
+ signal?: AbortSignal;
67
+ }): Promise<void>;
62
68
  findMessageByClientMsgId(input: {
63
69
  channel: string;
64
70
  clientMsgId: string;
@@ -52,6 +52,12 @@ export interface SlackProviderClient {
52
52
  threadTs?: string;
53
53
  signal?: AbortSignal;
54
54
  }): Promise<SlackMessageSearchResult | null>;
55
+ addReaction?(input: {
56
+ channel: string;
57
+ timestamp: string;
58
+ name: string;
59
+ signal?: AbortSignal;
60
+ }): Promise<void>;
55
61
  /**
56
62
  * Prove that an operator-supplied timestamp addresses a real message in the
57
63
  * requested channel. Adoption of an existing root is refused when a client
@@ -87,6 +93,12 @@ export declare class SlackProvider {
87
93
  threadTs?: string;
88
94
  signal?: AbortSignal;
89
95
  }): Promise<SlackMessageSearchResult | null>;
96
+ addReaction(input: {
97
+ channel: string;
98
+ timestamp: string;
99
+ name: string;
100
+ signal?: AbortSignal;
101
+ }): Promise<void>;
90
102
  /** Fail-closed root verification: a client without this capability can never confirm a root. */
91
103
  findMessageByTimestamp(input: {
92
104
  channel: string;
@@ -1,5 +1,6 @@
1
1
  import { type Settings } from "../../config/settings";
2
2
  import type { ExtensionAPI, ExtensionContext } from "../../extensibility/extensions";
3
+ import type { AgentEndEvent } from "../../extensibility/shared-events";
3
4
  import { type MasterRoleAttestationV2, SessionIndex, type SessionLocatorV2 } from "../broker/session-index";
4
5
  import type { SdkPromptTerminalOutcome } from "../prompt-status";
5
6
  import { type ReceiptState } from "../receipt-state";
@@ -24,6 +25,16 @@ export declare function verifyMasterCapabilityFrame(input: {
24
25
  nonce: string;
25
26
  attestationEpoch: string;
26
27
  };
28
+ /**
29
+ * Normalize an agent-loop stop reason into the published terminal outcome.
30
+ *
31
+ * Only reached for a boundary that actually terminalizes an owned invocation,
32
+ * so every result is a `stopped` disposition: a failed run publishes its own
33
+ * correlated `agent_failed` frame with the sanitized cause instead.
34
+ */
35
+ export declare function terminalStoppedOutcome(stopReason: AgentEndEvent["stopReason"], maintenanceOutcome: string | undefined): Extract<SdkPromptTerminalOutcome, {
36
+ kind: "stopped";
37
+ }>;
27
38
  /** Transport-neutral endpoint contract consumed by the SDK session runtime. */
28
39
  export interface SessionSdkTransport {
29
40
  readonly sessionId: string;
@@ -167,6 +178,18 @@ export declare class SessionSdkSessionRuntime {
167
178
  get generation(): number;
168
179
  getProviderDefinitions(capability: string): unknown | undefined;
169
180
  emitEvent(frame: SdkFrame): void;
181
+ /**
182
+ * Deliver a frame to specific connections without recording it.
183
+ *
184
+ * Deliberately not `emitEvent`: that assigns a ring position and broadcasts to
185
+ * every authenticated socket. Streamed turn content is high-frequency and
186
+ * unbounded in size, so a count-bounded ring would evict the lifecycle events
187
+ * clients depend on -- including the `agent_end` that settles their prompt --
188
+ * and broadcasting would hand every subscriber a stream only the submitter
189
+ * asked for. Delivery failure is ignored: a disconnected consumer must never
190
+ * disturb the turn producing the content.
191
+ */
192
+ sendFrameTo(connectionIds: Iterable<string>, frame: SdkFrame): void;
170
193
  publish(frame: SdkFrame): void;
171
194
  startHost(): Promise<"started" | "already">;
172
195
  startTransport(): Promise<{
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AgentLoop calls `getApiKey(provider)` before `streamFn`.
3
+ * Resolve/stream already use `getApiKey(model)`. If those two lookups diverge,
4
+ * the child dies locally (~8ms, 0 tokens, sanitized "Agent run failed.")
5
+ * and never reaches the working model-scoped path (#5081).
6
+ */
7
+ export type SessionApiKeyModel = {
8
+ provider: string;
9
+ baseUrl?: string;
10
+ };
11
+ export type SessionApiKeyRegistry = {
12
+ getApiKey(model: SessionApiKeyModel, sessionId?: string, options?: {
13
+ signal?: AbortSignal;
14
+ }): Promise<string | undefined>;
15
+ getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string, options?: {
16
+ signal?: AbortSignal;
17
+ }): Promise<string | undefined>;
18
+ };
19
+ export type SessionApiKeyResolution = {
20
+ apiKey: string;
21
+ credentialSessionId: string | undefined;
22
+ };
23
+ export type SessionApiKeyCredentialType = "api_key" | "oauth" | undefined;
24
+ export declare function isStableSessionApiKeyCredentialType(previous: SessionApiKeyCredentialType, replacement: SessionApiKeyCredentialType): boolean;
25
+ export declare function resolveLiveSessionApiKeyModel(live: SessionApiKeyModel | undefined, captured: SessionApiKeyModel | undefined, provider: string): SessionApiKeyModel | undefined;
26
+ export declare function lookupSessionApiKey(registry: SessionApiKeyRegistry, provider: string, sessionId: string | undefined, model: SessionApiKeyModel | undefined, allowUnscopedRetry?: boolean, signal?: AbortSignal): Promise<SessionApiKeyResolution>;
@@ -7,7 +7,7 @@ type BrokerSessionRow = {
7
7
  ambiguous: boolean;
8
8
  };
9
9
  /** Exhausts strict broker `session.list` pages into one full session snapshot. */
10
- export declare function listBrokerSessions(broker: SdkClient): Promise<BrokerSessionRow[]>;
10
+ export declare function listBrokerSessions(broker: SdkClient, explicitSessionId?: string): Promise<BrokerSessionRow[]>;
11
11
  /** Selects the session to serve through broker `session.list` truth (C10); exported for tests. */
12
12
  export declare function selectBrokerSession(sessions: BrokerSessionRow[], explicitSessionId: string | undefined): string;
13
13
  /**
@@ -241,6 +241,13 @@ export interface AgentSessionConfig {
241
241
  skills?: Skill[];
242
242
  /** Skill loading warnings (already captured by SDK) */
243
243
  skillWarnings?: SkillWarning[];
244
+ /** Re-enumerate filesystem and marketplace skills after plugin lifecycle changes. */
245
+ reloadSkills?: (cwd: string) => Promise<{
246
+ skills: Skill[];
247
+ warnings: SkillWarning[];
248
+ }>;
249
+ /** Publish a successfully committed skill catalog to SDK/process snapshots. */
250
+ onSkillsReloaded?: (skills: Skill[], warnings: SkillWarning[]) => void;
244
251
  /** Custom commands (TypeScript slash commands) */
245
252
  customCommands?: LoadedCustomCommand[];
246
253
  skillsSettings?: SkillsSettings;
@@ -1176,6 +1183,8 @@ export declare class AgentSession {
1176
1183
  * they must not survive a move out of it.
1177
1184
  */
1178
1185
  replaceSkills(skills: Skill[]): Promise<void>;
1186
+ /** Refresh the live registered-skill catalog after install/remove/reload. */
1187
+ reloadSkills(cwd?: string): Promise<void>;
1179
1188
  /**
1180
1189
  * Retire the cached workspace tree after a rescope so the next turn re-scans
1181
1190
  * at the new cwd instead of re-presenting the abandoned launcher root.
@@ -0,0 +1,111 @@
1
+ export { resolvePaseoHome } from "./setup-deps";
2
+ export type PaseoDaemonTarget = {
3
+ readonly kind: "ipc";
4
+ readonly socketPath: string;
5
+ } | {
6
+ readonly kind: "tcp";
7
+ readonly host: string;
8
+ readonly port: number;
9
+ };
10
+ /** Why an announcement did nothing. Each reason is a distinct, actionable state. */
11
+ export type PaseoAnnounceSkip = "no-paseo-config" | "no-provider" | "cli-missing" | "daemon-unreachable" | "unsupported-daemon-target" | "daemon-auth-required" | "session-not-live";
12
+ export type PaseoAnnounceOutcome = {
13
+ readonly kind: "imported";
14
+ readonly providerKey: string;
15
+ } | {
16
+ readonly kind: "already-imported";
17
+ readonly providerKey: string;
18
+ } | {
19
+ readonly kind: "skipped";
20
+ readonly reason: PaseoAnnounceSkip;
21
+ } | {
22
+ readonly kind: "failed";
23
+ readonly detail: string;
24
+ };
25
+ export interface PaseoAnnounceDependencies {
26
+ /** `~/.paseo/config.json` (or the `$PASEO_HOME` equivalent). */
27
+ readonly configJson: string;
28
+ /** `~/.paseo` (or `$PASEO_HOME`). */
29
+ readonly paseoHome: string;
30
+ readonly env: NodeJS.ProcessEnv;
31
+ /** Parsed JSON, or `undefined` when the file is absent or unreadable. */
32
+ readJson(file: string): Promise<unknown>;
33
+ /** Absolute path of the `paseo` CLI, or `undefined` when it is not installed. */
34
+ resolveCli(): string | undefined;
35
+ probeDaemon(target: PaseoDaemonTarget): Promise<boolean>;
36
+ /** True only when the broker index reports this session id as live in `cwd`. */
37
+ isSessionLive(sessionId: string, cwd: string): Promise<boolean>;
38
+ runImport(input: {
39
+ readonly cli: string;
40
+ readonly providerKey: string;
41
+ readonly cwd: string;
42
+ readonly sessionId: string;
43
+ readonly signal?: AbortSignal;
44
+ }): Promise<PaseoAnnounceOutcome>;
45
+ }
46
+ /**
47
+ * Pick the Paseo provider entry to import under.
48
+ *
49
+ * `gjc setup paseo` writes `gjc`, and `--mpreset <name>` writes `gjc-<name>`, so
50
+ * the base key is preferred and a preset provider is only used when it is the
51
+ * only one installed. A disabled entry is never selected: Paseo would refuse to
52
+ * launch it.
53
+ */
54
+ export declare function selectProviderKey(config: unknown): string | undefined;
55
+ /**
56
+ * Parse one Paseo listener spelling into a connectable target.
57
+ *
58
+ * Mirrors the spellings Paseo's own CLI accepts: `unix://`/`pipe://` prefixes, a
59
+ * bare absolute path, a bare port, `tcp://host:port`, and plain `host:port`.
60
+ */
61
+ export declare function parseDaemonListen(raw: string): PaseoDaemonTarget | undefined;
62
+ /**
63
+ * Resolve where the daemon listens, in the same precedence Paseo's CLI uses:
64
+ * environment override, then the running daemon's own pid file, then the
65
+ * configured listener, then Paseo's default.
66
+ */
67
+ export declare function resolveDaemonTarget(config: unknown, deps: Pick<PaseoAnnounceDependencies, "env" | "paseoHome" | "readJson">): Promise<PaseoDaemonTarget>;
68
+ /**
69
+ * Turn a non-zero `paseo import` into an outcome.
70
+ *
71
+ * Two of Paseo's refusals are not failures:
72
+ *
73
+ * - an already-known session is exactly the state we wanted to reach;
74
+ * - a password-protected daemon (`Cannot connect to daemon ...: Password
75
+ * required`) is a configuration the user chose. The socket probe cannot see
76
+ * it -- the TCP connect succeeds -- so it can only be recognized here, and
77
+ * reporting it would put an error on screen at every single launch for
78
+ * something GJC must not fix on its own. Exporting `PASEO_PASSWORD` makes it
79
+ * work, since the CLI inherits this process's environment.
80
+ */
81
+ export declare function classifyImportFailure(providerKey: string, detail: string): PaseoAnnounceOutcome;
82
+ /**
83
+ * Run the full announcement once.
84
+ *
85
+ * The order is load-bearing: every cheap filesystem check runs before the socket
86
+ * probe, and the socket probe runs before anything spawns the Paseo CLI.
87
+ */
88
+ export declare function announceSessionToPaseo(input: {
89
+ readonly sessionId: string;
90
+ readonly cwd: string;
91
+ }, deps: PaseoAnnounceDependencies, signal?: AbortSignal): Promise<PaseoAnnounceOutcome>;
92
+ /** Only a daemon that is not up yet, or a registration that has not landed yet, is worth retrying. */
93
+ export declare function isRetryableOutcome(outcome: PaseoAnnounceOutcome): boolean;
94
+ export interface PaseoAnnouncementHandle {
95
+ cancel(): void;
96
+ }
97
+ /**
98
+ * Announce the session, retrying only while Paseo is not ready to hear it.
99
+ *
100
+ * The user routinely opens Paseo after their terminal, so a single attempt at
101
+ * launch would silently never land. Retries are socket probes, not CLI spawns,
102
+ * and every timer is unref'd so the loop can never hold the process open.
103
+ */
104
+ export declare function startPaseoAnnouncement(input: {
105
+ readonly sessionId: string;
106
+ readonly cwd: string;
107
+ isEnabled?: () => boolean;
108
+ onOutcome?: (outcome: PaseoAnnounceOutcome) => void;
109
+ }, deps: PaseoAnnounceDependencies): PaseoAnnouncementHandle;
110
+ export declare function sessionListContainsLiveSessionForTests(sessions: readonly unknown[], sessionId: string, cwd: string): boolean;
111
+ export declare function createDefaultPaseoAnnounceDependencies(agentDir: string, env?: NodeJS.ProcessEnv): PaseoAnnounceDependencies;
@@ -61,7 +61,8 @@ export interface PaseoSetupDependencies {
61
61
  runProviderLs(timeoutMs: number): Promise<PaseoLsOutcome>;
62
62
  now(): Date;
63
63
  }
64
- export declare function createDefaultPaseoPaths(agentDir?: string, home?: string): PaseoPaths;
64
+ export declare function resolvePaseoHome(env?: NodeJS.ProcessEnv, home?: string): string;
65
+ export declare function createDefaultPaseoPaths(agentDir?: string, home?: string, env?: NodeJS.ProcessEnv): PaseoPaths;
65
66
  /**
66
67
  * Parse `paseo provider ls --json`.
67
68
  *
@@ -64,6 +64,8 @@ export interface BuildSystemPromptOptions {
64
64
  pluginAppendices?: string;
65
65
  /** Repeat full tool descriptions in system prompt. Default: false */
66
66
  repeatToolDescriptions?: boolean;
67
+ /** Language guidance for internal technical reasoning. Default: off. */
68
+ reasoningLanguage?: "off" | "english";
67
69
  /** Skills settings for discovery. */
68
70
  skillsSettings?: SkillsSettings;
69
71
  /** Working directory. Default: getProjectDir() */
@@ -114,7 +114,8 @@ export interface ExecutorOptions {
114
114
  managedPersistence?: ManagedTaskPersistence;
115
115
  /**
116
116
  * The parent session's ENDPOINT-owned AsyncJobManager (resolved by the
117
- * TaskTool via forEndpoint(sessionId) ?? instance()). Model metadata and
117
+ * TaskTool via its opaque async endpoint accessor, then the process-global
118
+ * fallback). Model metadata and
118
119
  * live-handle state for THIS subagent are recorded in the SAME manager the
119
120
  * task job runs in — with concurrent top-level sessions the process-global
120
121
  * instance belongs to a different session and would surface this subagent
@@ -58,6 +58,13 @@ interface ReadUrlCacheEntry {
58
58
  /** Offset of the preamble after `wrapUntrustedContent(output)` is persisted. */
59
59
  wrappedPreambleChars?: number;
60
60
  }
61
+ export declare const READ_URL_CACHE_MAX_KEYS = 64;
62
+ /** Test-only seam for deterministic module-global cache assertions. */
63
+ export declare const readUrlCacheTestHooks: {
64
+ readonly size: number;
65
+ readonly retainedOutputChars: number;
66
+ reset(): void;
67
+ };
61
68
  export declare function loadReadUrlCacheEntry(session: ToolSession, params: {
62
69
  path: string;
63
70
  raw?: boolean;
@@ -197,6 +197,14 @@ export interface ToolSession {
197
197
  waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
198
198
  /** Get session ID */
199
199
  getSessionId?: () => string | null;
200
+ /**
201
+ * Opaque async-job ownership/lookup endpoint key. This is not a workflow or
202
+ * path identity: workflow/session consumers must use getSessionId(). Only
203
+ * AsyncJobManager ownership and endpoint-first lookup may use this accessor
204
+ * (subagent-inherited managers are registered under the parent's endpoint;
205
+ * review thread P1).
206
+ */
207
+ getAsyncEndpointId?: () => string | null;
200
208
  /** Get credential-selection session identity. */
201
209
  getCredentialSessionId?: () => string | null;
202
210
  /** Scope-held MCP facade for mcp:// resolution. */
@@ -122,6 +122,8 @@ export interface ToolScopeOptions {
122
122
  surfaceExactFilePaths?: boolean;
123
123
  /** Extra hint appended to "Path not found" when stat fails and the user supplied multiple paths. */
124
124
  multipathStatHint?: string;
125
+ /** Cancels internal-URL resolution and prevents further scope work. */
126
+ signal?: AbortSignal;
125
127
  }
126
128
  export interface ToolScopeResolution {
127
129
  searchPath: string;
@@ -1,4 +1,5 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
+ import type { grep as grepFn } from "@gajae-code/natives";
2
3
  import type { Component } from "@gajae-code/tui";
3
4
  import * as z from "zod/v4";
4
5
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
@@ -12,6 +13,7 @@ declare const searchSchema: z.ZodObject<{
12
13
  i: z.ZodOptional<z.ZodBoolean>;
13
14
  gitignore: z.ZodOptional<z.ZodBoolean>;
14
15
  skip: z.ZodOptional<z.ZodNumber>;
16
+ timeout: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
15
17
  }, z.core.$strict>;
16
18
  export type SearchToolInput = z.infer<typeof searchSchema>;
17
19
  /** Maximum number of distinct files surfaced in a single response. The
@@ -56,6 +58,8 @@ export interface SearchToolOptions {
56
58
  context?: number;
57
59
  /** Per-call total match cap used by Cursor's native Pi grep frame. */
58
60
  totalMatchLimit?: number;
61
+ /** Test or remote override for the native grep implementation. */
62
+ grep?: typeof grepFn;
59
63
  }
60
64
  type SearchParams = z.infer<typeof searchSchema>;
61
65
  export declare class SearchTool implements AgentTool<typeof searchSchema, SearchToolDetails> {
@@ -72,6 +76,7 @@ export declare class SearchTool implements AgentTool<typeof searchSchema, Search
72
76
  i: z.ZodOptional<z.ZodBoolean>;
73
77
  gitignore: z.ZodOptional<z.ZodBoolean>;
74
78
  skip: z.ZodOptional<z.ZodNumber>;
79
+ timeout: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
75
80
  }, z.core.$strict>;
76
81
  readonly strict = true;
77
82
  constructor(session: ToolSession, options?: SearchToolOptions);
@@ -83,6 +88,7 @@ interface SearchRenderArgs {
83
88
  i?: boolean;
84
89
  gitignore?: boolean;
85
90
  skip?: number;
91
+ timeout?: number;
86
92
  }
87
93
  export declare const searchToolRenderer: {
88
94
  inline: boolean;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.16.3",
4
+ "version": "0.16.4",
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",
@@ -56,12 +56,12 @@
56
56
  "@agentclientprotocol/sdk": "1.3.0",
57
57
  "@babel/parser": "^7.29.3",
58
58
  "@mozilla/readability": "^0.6.0",
59
- "@gajae-code/stats": "0.16.3",
60
- "@gajae-code/agent-core": "0.16.3",
61
- "@gajae-code/ai": "0.16.3",
62
- "@gajae-code/natives": "0.16.3",
63
- "@gajae-code/tui": "0.16.3",
64
- "@gajae-code/utils": "0.16.3",
59
+ "@gajae-code/stats": "0.16.4",
60
+ "@gajae-code/agent-core": "0.16.4",
61
+ "@gajae-code/ai": "0.16.4",
62
+ "@gajae-code/natives": "0.16.4",
63
+ "@gajae-code/tui": "0.16.4",
64
+ "@gajae-code/utils": "0.16.4",
65
65
  "@puppeteer/browsers": "^2.13.0",
66
66
  "@types/turndown": "5.0.6",
67
67
  "@xterm/headless": "^6.0.0",
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import {
4
+ renderStatusLineCustomEditorShowcase,
5
+ STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES,
6
+ type StatusLineCustomEditorShowcaseEntry,
7
+ } from "../test/fixtures/tui/status-line-custom-editor-showcase";
8
+ import { ansiToHtml } from "./capture-sticky-viewport-showcase";
9
+
10
+ const CANONICAL_COMMAND =
11
+ "bun packages/coding-agent/scripts/capture-status-line-custom-editor-showcase.ts --out .gjc/qa/status-line-custom-editor-<run>";
12
+ const CAPTURE_TOOL_VERSION = "status-line-custom-editor-showcase-v1";
13
+ const SOURCE_PATHS = [
14
+ "packages/coding-agent/src/modes/components/settings-selector.ts",
15
+ "packages/coding-agent/src/modes/components/tool-status-header.ts",
16
+ "packages/coding-agent/src/modes/controllers/selector-controller.ts",
17
+ "packages/tui/src/components/settings-list.ts",
18
+ "packages/coding-agent/test/fixtures/tui/status-line-custom-editor-showcase.ts",
19
+ "packages/coding-agent/scripts/verify-status-line-custom-editor-showcase.ts",
20
+ ] as const;
21
+
22
+ interface ArtifactFile {
23
+ path: string;
24
+ sha256: string;
25
+ byte_length: number;
26
+ }
27
+
28
+ interface ManifestEntry {
29
+ key: string;
30
+ stateId: string;
31
+ viewport: { columns: number; rows: number };
32
+ renderMode: string;
33
+ simulatedStatusbarRows: number;
34
+ files: ArtifactFile[];
35
+ }
36
+
37
+ function usage(): never {
38
+ throw new Error(`Usage: ${CANONICAL_COMMAND}`);
39
+ }
40
+
41
+ function outputPath(args: string[]): string {
42
+ if (args.length !== 2 || args[0] !== "--out" || !args[1]) usage();
43
+ return args[1];
44
+ }
45
+
46
+ function json(value: unknown): string {
47
+ return `${JSON.stringify(value, null, 2)}\n`;
48
+ }
49
+
50
+ function sha256(value: string): string {
51
+ return new Bun.CryptoHasher("sha256").update(value).digest("hex");
52
+ }
53
+
54
+ function commandOutput(command: string[]): string | undefined {
55
+ const result = Bun.spawnSync({ cmd: command, stdout: "pipe", stderr: "pipe" });
56
+ if (result.exitCode !== 0) return undefined;
57
+ const output = result.stdout.toString().trim();
58
+ return output || undefined;
59
+ }
60
+
61
+ async function sourceHash(): Promise<string> {
62
+ const entries = await Promise.all(
63
+ SOURCE_PATHS.map(async sourcePath => `${sourcePath}:${sha256(await Bun.file(sourcePath).text())}`),
64
+ );
65
+ return `sha256:${sha256(entries.join("\n"))}`;
66
+ }
67
+
68
+ function entryKey(entry: StatusLineCustomEditorShowcaseEntry): string {
69
+ return `${entry.stateId}/${entry.columns}x${entry.rows}/${entry.renderMode}`;
70
+ }
71
+
72
+ function simulatedStatusbarRows(text: string): number {
73
+ const lines = Bun.stripANSI(text).split("\n");
74
+ const heading = lines.findIndex(line => line.includes("Simulated statusbar"));
75
+ if (heading < 0) return 0;
76
+ let end = heading + 1;
77
+ while (end < lines.length && lines[end]?.trim() && !lines[end]?.includes("Warning:")) end++;
78
+ return Math.max(0, end - heading - 1);
79
+ }
80
+
81
+ async function writeArtifact(root: string, relativePath: string, content: string): Promise<ArtifactFile> {
82
+ const fullPath = path.join(root, relativePath);
83
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
84
+ await Bun.write(fullPath, content);
85
+ return { path: relativePath, sha256: sha256(content), byte_length: Buffer.byteLength(content) };
86
+ }
87
+
88
+ async function main(): Promise<void> {
89
+ const root = outputPath(process.argv.slice(2));
90
+ const sourceHead = commandOutput(["git", "rev-parse", "HEAD"]);
91
+ if (!sourceHead) throw new Error("Could not resolve source HEAD");
92
+ const captureActor = commandOutput(["gh", "api", "user", "--jq", ".login"]) ?? "unauthenticated";
93
+ const digest = await sourceHash();
94
+ const independentReviewPath = path.join(root, "independent-review.json");
95
+ const existingIndependentReview = (await Bun.file(independentReviewPath).exists())
96
+ ? await Bun.file(independentReviewPath).text()
97
+ : undefined;
98
+ await fs.rm(root, { recursive: true, force: true });
99
+ await fs.mkdir(root, { recursive: true });
100
+ if (existingIndependentReview !== undefined) {
101
+ await Bun.write(independentReviewPath, existingIndependentReview);
102
+ }
103
+ const entries: ManifestEntry[] = [];
104
+ for (const entry of STATUS_LINE_CUSTOM_EDITOR_SHOWCASE_ENTRIES) {
105
+ const key = entryKey(entry);
106
+ const dir = key.replace(/[^a-zA-Z0-9._/-]/g, "_");
107
+ const terminalAnsi = await renderStatusLineCustomEditorShowcase(entry);
108
+ const renderedRows = terminalAnsi.split("\n").length;
109
+ if (renderedRows > entry.rows) {
110
+ throw new Error(`${key}: rendered ${renderedRows} rows for a ${entry.rows}-row viewport`);
111
+ }
112
+ const statusbarRows = simulatedStatusbarRows(terminalAnsi);
113
+ const terminalPlain = Bun.stripANSI(terminalAnsi);
114
+ const html = ansiToHtml(terminalAnsi);
115
+ const metadata = json({
116
+ tool: CAPTURE_TOOL_VERSION,
117
+ command: CANONICAL_COMMAND,
118
+ stateId: entry.stateId,
119
+ viewport: { columns: entry.columns, rows: entry.rows },
120
+ renderMode: entry.renderMode,
121
+ fixedClock: "2026-01-01T00:00:00.000Z",
122
+ simulatedStatusbarRows: statusbarRows,
123
+ wrappingPolicy: statusbarRows > 2 ? "two-row" : "single-row",
124
+ });
125
+ const files = [
126
+ await writeArtifact(root, `${dir}/terminal.txt`, terminalPlain),
127
+ await writeArtifact(root, `${dir}/terminal-ansi.txt`, terminalAnsi),
128
+ await writeArtifact(root, `${dir}/terminal.html`, html),
129
+ await writeArtifact(root, `${dir}/metadata.json`, metadata),
130
+ ];
131
+ entries.push({
132
+ key,
133
+ stateId: entry.stateId,
134
+ viewport: { columns: entry.columns, rows: entry.rows },
135
+ renderMode: entry.renderMode,
136
+ simulatedStatusbarRows: statusbarRows,
137
+ files,
138
+ });
139
+ }
140
+ await writeArtifact(
141
+ root,
142
+ "manifest.json",
143
+ json({ tool: CAPTURE_TOOL_VERSION, sourceHead, sourceHash: digest, captureActor, entries }),
144
+ );
145
+ process.stdout.write(`Captured ${entries.length} status-line custom editor showcase entries at ${root}\n`);
146
+ }
147
+
148
+ void main().catch(error => {
149
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
150
+ process.exit(1);
151
+ });
@@ -11,6 +11,8 @@ const inventoryPath = process.env.GJC_SDK_OPERATION_INVENTORY
11
11
 
12
12
  /** Reviewed seams deliberately excluded from the public SDK operation surface. */
13
13
  const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
14
+ "agent_session:reloadSkills":
15
+ "internal marketplace lifecycle refresh for the session skill catalog; not an independent SDK ingress operation",
14
16
  "agent_session:getTerminalTurnEpoch":
15
17
  "internal terminal-abort bus seam (epoch only), threaded via terminalAbortSeams; not a user-facing SDK control seam",
16
18
  "agent_session:cancelPendingPreflightForTerminalAbort":
@@ -119,6 +121,13 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
119
121
  "test-only parked terminal flush seam, not a user-facing SDK control seam",
120
122
  "agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
121
123
  "test-only cancellation seam, not a user-facing SDK control seam",
124
+ "agent_session:awaitDisposeCompletion": "test-only disposal completion seam, not a user-facing SDK control seam",
125
+ "agent_session:setDisposeTimeoutForTests": "test-only disposal timeout seam, not a user-facing SDK control seam",
126
+ "agent_session:trackPostPromptTaskForTests": "test-only post-prompt task seam, not a user-facing SDK control seam",
127
+ "agent_session:requestWorkerIntegrationForTests":
128
+ "test-only worker integration seam, not a user-facing SDK control seam",
129
+ "agent_session:runWithPromptAdmissionForTests":
130
+ "test-only prompt admission seam, not a user-facing SDK control seam",
122
131
  "agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
123
132
  "agent_session:emitNotice": "internal accessor/plumbing, not a user-facing control seam",
124
133
  "agent_session:subscribe": "internal accessor/plumbing, not a user-facing control seam",