@gajae-code/coding-agent 0.4.2 → 0.4.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 (115) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/types/async/job-manager.d.ts +44 -1
  3. package/dist/types/cli/setup-cli.d.ts +14 -1
  4. package/dist/types/commands/coordinator.d.ts +19 -0
  5. package/dist/types/commands/mcp-serve.d.ts +24 -0
  6. package/dist/types/commands/setup.d.ts +41 -0
  7. package/dist/types/commit/model-selection.d.ts +1 -1
  8. package/dist/types/config/model-registry.d.ts +3 -1
  9. package/dist/types/config/model-resolver.d.ts +1 -19
  10. package/dist/types/config/models-config-schema.d.ts +12 -0
  11. package/dist/types/config/settings-schema.d.ts +15 -1
  12. package/dist/types/coordinator/contract.d.ts +4 -0
  13. package/dist/types/coordinator-mcp/policy.d.ts +24 -0
  14. package/dist/types/coordinator-mcp/safety.d.ts +26 -0
  15. package/dist/types/coordinator-mcp/server.d.ts +52 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +13 -0
  17. package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
  18. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  19. package/dist/types/harness-control-plane/types.d.ts +7 -2
  20. package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  22. package/dist/types/modes/components/hook-selector.d.ts +11 -0
  23. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
  24. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
  25. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
  26. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
  27. package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
  28. package/dist/types/session/agent-session.d.ts +12 -1
  29. package/dist/types/session/session-manager.d.ts +1 -1
  30. package/dist/types/setup/hermes-setup.d.ts +71 -0
  31. package/dist/types/task/render.d.ts +7 -1
  32. package/dist/types/tools/bash.d.ts +2 -0
  33. package/dist/types/tools/browser/actions.d.ts +54 -0
  34. package/dist/types/tools/browser.d.ts +80 -0
  35. package/dist/types/tools/image-gen.d.ts +1 -0
  36. package/dist/types/tools/index.d.ts +3 -1
  37. package/dist/types/tools/job.d.ts +1 -1
  38. package/dist/types/tools/subagent-render.d.ts +25 -0
  39. package/dist/types/tools/subagent.d.ts +5 -1
  40. package/package.json +7 -7
  41. package/src/async/job-manager.ts +163 -2
  42. package/src/cli/setup-cli.ts +86 -2
  43. package/src/cli.ts +2 -0
  44. package/src/commands/coordinator.ts +70 -0
  45. package/src/commands/mcp-serve.ts +62 -0
  46. package/src/commands/setup.ts +30 -1
  47. package/src/commands/ultragoal.ts +7 -1
  48. package/src/commit/agentic/index.ts +2 -2
  49. package/src/commit/model-selection.ts +7 -22
  50. package/src/commit/pipeline.ts +2 -2
  51. package/src/config/model-registry.ts +17 -9
  52. package/src/config/model-resolver.ts +14 -84
  53. package/src/config/models-config-schema.ts +2 -0
  54. package/src/config/settings-schema.ts +14 -1
  55. package/src/coordinator/contract.ts +20 -0
  56. package/src/coordinator-mcp/policy.ts +160 -0
  57. package/src/coordinator-mcp/safety.ts +80 -0
  58. package/src/coordinator-mcp/server.ts +1316 -0
  59. package/src/extensibility/extensions/types.ts +13 -0
  60. package/src/gjc-runtime/goal-mode-request.ts +21 -1
  61. package/src/gjc-runtime/session-state-sidecar.ts +79 -0
  62. package/src/harness-control-plane/owner.ts +3 -3
  63. package/src/harness-control-plane/rpc-adapter.ts +7 -1
  64. package/src/harness-control-plane/types.ts +8 -11
  65. package/src/internal-urls/docs-index.generated.ts +6 -5
  66. package/src/memories/index.ts +1 -1
  67. package/src/modes/acp/acp-agent.ts +17 -9
  68. package/src/modes/acp/acp-event-mapper.ts +33 -1
  69. package/src/modes/components/custom-editor.ts +19 -3
  70. package/src/modes/components/hook-selector.ts +109 -5
  71. package/src/modes/controllers/extension-ui-controller.ts +16 -1
  72. package/src/modes/controllers/input-controller.ts +27 -7
  73. package/src/modes/controllers/selector-controller.ts +7 -1
  74. package/src/modes/interactive-mode.ts +3 -1
  75. package/src/modes/rpc/rpc-client.ts +16 -3
  76. package/src/modes/rpc/rpc-mode.ts +5 -2
  77. package/src/modes/shared/agent-wire/command-contract.ts +18 -0
  78. package/src/modes/shared/agent-wire/event-contract.ts +147 -0
  79. package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
  80. package/src/modes/shared/agent-wire/event-observation.ts +397 -0
  81. package/src/modes/shared/agent-wire/protocol.ts +24 -81
  82. package/src/modes/utils/context-usage.ts +2 -2
  83. package/src/prompts/agents/architect.md +6 -0
  84. package/src/prompts/agents/critic.md +6 -0
  85. package/src/prompts/agents/explore.md +1 -1
  86. package/src/prompts/agents/plan.md +1 -1
  87. package/src/prompts/agents/planner.md +8 -1
  88. package/src/prompts/agents/reviewer.md +1 -1
  89. package/src/prompts/tools/browser.md +3 -2
  90. package/src/runtime-mcp/manager.ts +15 -2
  91. package/src/sdk.ts +3 -1
  92. package/src/session/agent-session.ts +66 -4
  93. package/src/session/session-manager.ts +1 -1
  94. package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
  95. package/src/setup/hermes-setup.ts +429 -0
  96. package/src/task/agents.ts +1 -1
  97. package/src/task/index.ts +2 -0
  98. package/src/task/render.ts +14 -0
  99. package/src/tools/ask.ts +30 -10
  100. package/src/tools/bash.ts +6 -1
  101. package/src/tools/browser/actions.ts +189 -0
  102. package/src/tools/browser.ts +91 -1
  103. package/src/tools/image-gen.ts +42 -15
  104. package/src/tools/index.ts +7 -1
  105. package/src/tools/inspect-image.ts +10 -8
  106. package/src/tools/job.ts +12 -2
  107. package/src/tools/monitor.ts +98 -17
  108. package/src/tools/renderers.ts +2 -0
  109. package/src/tools/subagent-render.ts +160 -0
  110. package/src/tools/subagent.ts +49 -7
  111. package/src/utils/commit-message-generator.ts +6 -13
  112. package/src/utils/title-generator.ts +1 -1
  113. package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
  114. package/src/harness-control-plane/frame-mapper.ts +0 -286
  115. package/src/priority.json +0 -37
@@ -122,6 +122,19 @@ export interface ExtensionUIDialogOptions {
122
122
  * select-only rendering hint; non-TUI bridges drop it and do not serialize it.
123
123
  */
124
124
  scrollTitleRows?: number;
125
+ /**
126
+ * For interactive TUI select dialogs, handle the option with `optionLabel`
127
+ * inline: selecting it keeps the title and option list on screen and opens
128
+ * a free-text input below the list. Submitting calls `onSubmit` with the
129
+ * typed text and resolves the select with `optionLabel`; Escape returns to
130
+ * option selection. Non-TUI bridges (RPC, ACP) drop it; callers must keep
131
+ * a fallback path for selects that resolve `optionLabel` without invoking
132
+ * `onSubmit`.
133
+ */
134
+ customInput?: {
135
+ optionLabel: string;
136
+ onSubmit: (text: string) => void;
137
+ };
125
138
  }
126
139
 
127
140
  /** Raw terminal input listener for extensions. */
@@ -25,6 +25,12 @@ export interface PendingGoalModeRequest {
25
25
  objective: string;
26
26
  createdAt: string;
27
27
  goalsPath?: string;
28
+ /**
29
+ * Session id that produced this request (from GJC_SESSION_ID). When present,
30
+ * only the originating session may consume it, so concurrent sessions sharing
31
+ * the same `.gjc` project state never auto-run each other's ultragoal.
32
+ */
33
+ sessionId?: string;
28
34
  }
29
35
 
30
36
  export type CurrentSessionGoalModeWriteResult =
@@ -77,9 +83,11 @@ export async function writePendingGoalModeRequest(input: {
77
83
  cwd: string;
78
84
  objective: string;
79
85
  goalsPath?: string;
86
+ sessionId?: string | null;
80
87
  }): Promise<PendingGoalModeRequest> {
81
88
  const objective = input.objective.trim();
82
89
  if (!objective) throw new Error("goal objective is required");
90
+ const sessionId = input.sessionId?.trim();
83
91
  const request: PendingGoalModeRequest = {
84
92
  version: REQUEST_VERSION,
85
93
  kind: "goal_mode_request",
@@ -87,6 +95,7 @@ export async function writePendingGoalModeRequest(input: {
87
95
  objective,
88
96
  createdAt: new Date().toISOString(),
89
97
  goalsPath: input.goalsPath,
98
+ ...(sessionId ? { sessionId } : {}),
90
99
  };
91
100
  const filePath = requestPath(input.cwd);
92
101
  await writeJsonAtomic(filePath, request, {
@@ -162,7 +171,10 @@ export async function writeCurrentSessionGoalModeState(input: {
162
171
  return { status: "updated", goal: state.goal, sessionFile };
163
172
  }
164
173
 
165
- export async function consumePendingGoalModeRequest(cwd: string): Promise<PendingGoalModeRequest | null> {
174
+ export async function consumePendingGoalModeRequest(
175
+ cwd: string,
176
+ currentSessionId?: string | null,
177
+ ): Promise<PendingGoalModeRequest | null> {
166
178
  const filePath = requestPath(cwd);
167
179
  let raw: unknown;
168
180
  try {
@@ -181,6 +193,14 @@ export async function consumePendingGoalModeRequest(cwd: string): Promise<Pendin
181
193
  ) {
182
194
  return null;
183
195
  }
196
+ // Session isolation: a request stamped with an owning session id may only be
197
+ // consumed by that same session. Leave another session's request untouched
198
+ // (do not delete it) so its rightful owner can still pick it up. Legacy/unscoped
199
+ // requests (no sessionId) remain consumable by any session in this cwd.
200
+ const ownerSessionId = typeof candidate.sessionId === "string" ? candidate.sessionId.trim() : "";
201
+ if (ownerSessionId && ownerSessionId !== (currentSessionId?.trim() ?? "")) {
202
+ return null;
203
+ }
184
204
  await removeFileAudited(filePath, {
185
205
  cwd,
186
206
  audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
@@ -0,0 +1,79 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import type { AssistantMessage } from "@gajae-code/ai";
4
+ import { logger } from "@gajae-code/utils";
5
+
6
+ export const GJC_COORDINATOR_SESSION_STATE_FILE_ENV = "GJC_COORDINATOR_SESSION_STATE_FILE";
7
+ export const GJC_COORDINATOR_SESSION_ID_ENV = "GJC_COORDINATOR_SESSION_ID";
8
+
9
+ type RuntimeState = "ready_for_input" | "running" | "needs_user_input" | "completed" | "errored";
10
+
11
+ interface RuntimeStateEvent {
12
+ type: string;
13
+ messages?: unknown[];
14
+ }
15
+
16
+ interface RuntimeStateContext {
17
+ sessionId: string;
18
+ cwd: string;
19
+ sessionFile?: string | null;
20
+ }
21
+
22
+ function lastAssistant(messages: unknown[] | undefined): AssistantMessage | undefined {
23
+ if (!messages) return undefined;
24
+ for (let index = messages.length - 1; index >= 0; index--) {
25
+ const message = messages[index];
26
+ if (message && typeof message === "object" && (message as { role?: unknown }).role === "assistant") {
27
+ return message as AssistantMessage;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ function stateForEvent(event: RuntimeStateEvent): RuntimeState | null {
34
+ if (event.type === "agent_start" || event.type === "turn_start") return "running";
35
+ if (event.type === "agent_end") {
36
+ const assistant = lastAssistant(event.messages);
37
+ return assistant?.stopReason === "error" ? "errored" : "completed";
38
+ }
39
+ if (event.type === "notice") return null;
40
+ return null;
41
+ }
42
+
43
+ export async function persistCoordinatorRuntimeStateFromEvent(
44
+ event: RuntimeStateEvent,
45
+ context: RuntimeStateContext,
46
+ ): Promise<void> {
47
+ const stateFile = process.env[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim();
48
+ if (!stateFile) return;
49
+ const state = stateForEvent(event);
50
+ if (!state) return;
51
+ const now = new Date().toISOString();
52
+ let previous: Record<string, unknown> = {};
53
+ try {
54
+ previous = JSON.parse(await Bun.file(stateFile).text()) as Record<string, unknown>;
55
+ } catch {
56
+ previous = {};
57
+ }
58
+ const payload = {
59
+ schema_version: 1,
60
+ session_id: process.env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || context.sessionId,
61
+ state,
62
+ ready_for_input: state === "completed" || state === "ready_for_input",
63
+ updated_at: now,
64
+ current_turn_id: typeof previous.current_turn_id === "string" ? previous.current_turn_id : null,
65
+ last_turn_id: typeof previous.last_turn_id === "string" ? previous.last_turn_id : null,
66
+ live: typeof previous.live === "boolean" ? previous.live : null,
67
+ reason: null,
68
+ source: "agent_session_event",
69
+ event: event.type,
70
+ cwd: context.cwd,
71
+ session_file: context.sessionFile ?? null,
72
+ };
73
+ try {
74
+ await fs.mkdir(path.dirname(stateFile), { recursive: true });
75
+ await Bun.write(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
76
+ } catch (error) {
77
+ logger.warn("Failed to persist coordinator runtime state", { error: String(error), stateFile });
78
+ }
79
+ }
@@ -14,10 +14,10 @@
14
14
  import { execFileSync } from "node:child_process";
15
15
  import { randomBytes, randomUUID } from "node:crypto";
16
16
  import { existsSync } from "node:fs";
17
+ import { observeRpcOutboundFrame } from "../modes/shared/agent-wire/event-observation";
17
18
  import { classifyRecovery } from "./classifier";
18
19
  import { ControlServer, type EndpointRequest } from "./control-endpoint";
19
20
  import { defaultFinalizeChecks, type FinalizeChecks, runFinalize, type ValidationCommandSpec } from "./finalize";
20
- import { mapRpcFrame } from "./frame-mapper";
21
21
  import { type OperateResult, operate } from "./operate";
22
22
  import { preserveDirtyWorktree } from "./preserve";
23
23
  import {
@@ -174,7 +174,7 @@ export class RuntimeOwner {
174
174
 
175
175
  /** Map an RPC frame and route it: semantic/signal-bearing -> serial emit; high-frequency progress -> coalesce. */
176
176
  #handleFrame(frame: Record<string, unknown>): void {
177
- const mapped = mapRpcFrame(frame);
177
+ const mapped = observeRpcOutboundFrame(frame);
178
178
  if (!mapped) return;
179
179
  if (mapped.semantic || (mapped.signal && !mapped.coalesceKey)) {
180
180
  this.#framePump = this.#framePump
@@ -199,7 +199,7 @@ export class RuntimeOwner {
199
199
  await this.#emit("info", "rpc_activity", { coalescedFrames });
200
200
  }
201
201
 
202
- async #emitMapped(mapped: NonNullable<ReturnType<typeof mapRpcFrame>>): Promise<void> {
202
+ async #emitMapped(mapped: NonNullable<ReturnType<typeof observeRpcOutboundFrame>>): Promise<void> {
203
203
  await this.#emit(
204
204
  mapped.severity,
205
205
  mapped.kind,
@@ -174,7 +174,13 @@ export class GajaeCodeRpc implements HarnessRpc {
174
174
  // Any other frame is a session/agent event: advance the cursor.
175
175
  this.#cursor += 1;
176
176
  this.#lastFrameAt = new Date().toISOString();
177
- if (type === "agent_start") {
177
+ // Session events arrive as canonical `event` frames: the agent event type
178
+ // lives in `payload.event_type`. Non-event frames keep their flat `type`.
179
+ const effectiveType =
180
+ type === "event" && frame.payload && typeof frame.payload === "object"
181
+ ? (frame.payload as { event_type?: unknown }).event_type
182
+ : type;
183
+ if (effectiveType === "agent_start") {
178
184
  const cursor = this.#cursor;
179
185
  this.#agentStartCursors.push(cursor);
180
186
  this.#waiters = this.#waiters.filter(w => {
@@ -8,6 +8,8 @@
8
8
  * v1 implements the gajae-code adapter only. omx/codex/remote/auth are deferred seams.
9
9
  */
10
10
 
11
+ import type { AgentWireObservedSignal } from "../modes/shared/agent-wire/event-contract";
12
+
11
13
  /** Harnesses the control plane can operate. v1 implements `gajae-code` only. */
12
14
  export type Harness = "gajae-code" | "codex" | "omx";
13
15
 
@@ -147,17 +149,12 @@ export interface SessionState {
147
149
  updatedAt: string;
148
150
  }
149
151
 
150
- /** Bounded observed-signal vocabulary surfaced by `observe` (the owner only ever emits these). */
151
- export type ObservedSignal =
152
- | "SessionStart"
153
- | "prompt-accepted"
154
- | "tool-call"
155
- | "test-running"
156
- | "commit-created"
157
- | "completed"
158
- | "error"
159
- | "streaming"
160
- | "idle";
152
+ /**
153
+ * Bounded observed-signal vocabulary surfaced by `observe` (the owner only ever
154
+ * emits these). Aliased to the canonical agent-wire signal vocabulary so there
155
+ * is a single source of truth shared with the observation core.
156
+ */
157
+ export type ObservedSignal = AgentWireObservedSignal;
161
158
 
162
159
  export const OBSERVED_SIGNALS: readonly ObservedSignal[] = [
163
160
  "SessionStart",