@gajae-code/coding-agent 0.4.2 → 0.4.3

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 (76) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/types/async/job-manager.d.ts +25 -0
  3. package/dist/types/commit/model-selection.d.ts +1 -1
  4. package/dist/types/config/model-registry.d.ts +3 -1
  5. package/dist/types/config/model-resolver.d.ts +1 -19
  6. package/dist/types/config/models-config-schema.d.ts +12 -0
  7. package/dist/types/config/settings-schema.d.ts +15 -1
  8. package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
  9. package/dist/types/harness-control-plane/types.d.ts +7 -2
  10. package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
  11. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  12. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
  13. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
  14. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
  15. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
  16. package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
  17. package/dist/types/session/agent-session.d.ts +12 -1
  18. package/dist/types/session/session-manager.d.ts +1 -1
  19. package/dist/types/tools/bash.d.ts +2 -0
  20. package/dist/types/tools/browser/actions.d.ts +54 -0
  21. package/dist/types/tools/browser.d.ts +80 -0
  22. package/dist/types/tools/image-gen.d.ts +1 -0
  23. package/dist/types/tools/index.d.ts +3 -1
  24. package/dist/types/tools/job.d.ts +1 -1
  25. package/package.json +7 -7
  26. package/src/async/job-manager.ts +120 -1
  27. package/src/commands/ultragoal.ts +7 -1
  28. package/src/commit/agentic/index.ts +2 -2
  29. package/src/commit/model-selection.ts +7 -22
  30. package/src/commit/pipeline.ts +2 -2
  31. package/src/config/model-registry.ts +17 -9
  32. package/src/config/model-resolver.ts +14 -84
  33. package/src/config/models-config-schema.ts +2 -0
  34. package/src/config/settings-schema.ts +14 -1
  35. package/src/gjc-runtime/goal-mode-request.ts +21 -1
  36. package/src/harness-control-plane/owner.ts +3 -3
  37. package/src/harness-control-plane/rpc-adapter.ts +7 -1
  38. package/src/harness-control-plane/types.ts +8 -11
  39. package/src/internal-urls/docs-index.generated.ts +3 -3
  40. package/src/memories/index.ts +1 -1
  41. package/src/modes/acp/acp-agent.ts +17 -9
  42. package/src/modes/acp/acp-event-mapper.ts +33 -1
  43. package/src/modes/components/custom-editor.ts +19 -3
  44. package/src/modes/controllers/input-controller.ts +27 -7
  45. package/src/modes/controllers/selector-controller.ts +7 -1
  46. package/src/modes/interactive-mode.ts +3 -1
  47. package/src/modes/rpc/rpc-client.ts +16 -3
  48. package/src/modes/rpc/rpc-mode.ts +5 -2
  49. package/src/modes/shared/agent-wire/command-contract.ts +18 -0
  50. package/src/modes/shared/agent-wire/event-contract.ts +147 -0
  51. package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
  52. package/src/modes/shared/agent-wire/event-observation.ts +397 -0
  53. package/src/modes/shared/agent-wire/protocol.ts +24 -81
  54. package/src/modes/utils/context-usage.ts +2 -2
  55. package/src/prompts/agents/explore.md +1 -1
  56. package/src/prompts/agents/plan.md +1 -1
  57. package/src/prompts/agents/reviewer.md +1 -1
  58. package/src/prompts/tools/browser.md +3 -2
  59. package/src/runtime-mcp/manager.ts +15 -2
  60. package/src/sdk.ts +3 -1
  61. package/src/session/agent-session.ts +60 -4
  62. package/src/session/session-manager.ts +1 -1
  63. package/src/task/agents.ts +1 -1
  64. package/src/tools/bash.ts +6 -1
  65. package/src/tools/browser/actions.ts +189 -0
  66. package/src/tools/browser.ts +91 -1
  67. package/src/tools/image-gen.ts +42 -15
  68. package/src/tools/index.ts +7 -1
  69. package/src/tools/inspect-image.ts +10 -8
  70. package/src/tools/job.ts +12 -2
  71. package/src/tools/monitor.ts +98 -17
  72. package/src/utils/commit-message-generator.ts +6 -13
  73. package/src/utils/title-generator.ts +1 -1
  74. package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
  75. package/src/harness-control-plane/frame-mapper.ts +0 -286
  76. package/src/priority.json +0 -37
@@ -62,7 +62,7 @@ export function isAuthenticated(apiKey: string | undefined | null): apiKey is st
62
62
  return Boolean(apiKey) && apiKey !== kNoAuth;
63
63
  }
64
64
 
65
- export type ModelRole = "default" | "smol" | "slow" | "vision" | "plan" | "designer" | "commit" | "task";
65
+ export type ModelRole = "default";
66
66
 
67
67
  export interface ModelRoleInfo {
68
68
  tag?: string;
@@ -72,16 +72,9 @@ export interface ModelRoleInfo {
72
72
 
73
73
  export const MODEL_ROLES: Record<ModelRole, ModelRoleInfo> = {
74
74
  default: { tag: "DEFAULT", name: "Default", color: "success" },
75
- smol: { tag: "SMOL", name: "Fast", color: "warning" },
76
- slow: { tag: "SLOW", name: "Thinking", color: "accent" },
77
- vision: { tag: "VISION", name: "Vision", color: "error" },
78
- plan: { tag: "PLAN", name: "Architect", color: "muted" },
79
- designer: { tag: "DESIGNER", name: "Designer", color: "muted" },
80
- commit: { tag: "COMMIT", name: "Commit", color: "dim" },
81
- task: { tag: "TASK", name: "Subtask", color: "muted" },
82
75
  };
83
76
 
84
- export const MODEL_ROLE_IDS: ModelRole[] = ["default", "smol", "slow", "vision", "plan", "designer", "commit", "task"];
77
+ export const MODEL_ROLE_IDS: ModelRole[] = ["default"];
85
78
 
86
79
  export type GjcModelAssignmentTargetId = "default" | "executor" | "architect" | "planner" | "critic";
87
80
 
@@ -688,6 +681,7 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
688
681
  if (override.reasoning !== undefined) result.reasoning = override.reasoning;
689
682
  if (override.thinking !== undefined) result.thinking = override.thinking as ThinkingConfig;
690
683
  if (override.input !== undefined) result.input = override.input as ("text" | "image")[];
684
+ if (override.output !== undefined) result.output = override.output as ("text" | "image")[];
691
685
  if (override.cacheRetention !== undefined) result.cacheRetention = override.cacheRetention;
692
686
  if (override.contextWindow !== undefined) result.contextWindow = override.contextWindow;
693
687
  if (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;
@@ -718,6 +712,7 @@ interface CustomModelDefinitionLike {
718
712
  reasoning?: boolean;
719
713
  thinking?: ThinkingConfig;
720
714
  input?: ("text" | "image")[];
715
+ output?: ("text" | "image")[];
721
716
  cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
722
717
  contextWindow?: number;
723
718
  maxTokens?: number;
@@ -743,6 +738,7 @@ type CustomModelOverlay = {
743
738
  reasoning?: boolean;
744
739
  thinking?: ThinkingConfig;
745
740
  input?: ("text" | "image")[];
741
+ output?: ("text" | "image")[];
746
742
  cost?: { input: number; output: number; cacheRead: number; cacheWrite: number };
747
743
  contextWindow?: number;
748
744
  maxTokens?: number;
@@ -818,6 +814,7 @@ function buildCustomModelOverlay(
818
814
  reasoning: modelDef.reasoning,
819
815
  thinking: modelDef.thinking as ThinkingConfig | undefined,
820
816
  input: modelDef.input as ("text" | "image")[] | undefined,
817
+ output: modelDef.output as ("text" | "image")[] | undefined,
821
818
  cost: modelDef.cost,
822
819
  contextWindow: modelDef.contextWindow,
823
820
  maxTokens: modelDef.maxTokens,
@@ -910,6 +907,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
910
907
  reference?.cost ??
911
908
  (options.useDefaults ? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } : undefined);
912
909
  const input = resolvedModel.input ?? reference?.input ?? (options.useDefaults ? ["text"] : undefined);
910
+ const output = resolvedModel.output ?? reference?.output;
913
911
  return enrichModelThinking({
914
912
  id: resolvedModel.id,
915
913
  name: resolvedModel.name ?? (options.useDefaults ? resolvedModel.id : undefined),
@@ -919,6 +917,7 @@ function finalizeCustomModel(model: CustomModelOverlay, options: CustomModelBuil
919
917
  reasoning: resolvedModel.reasoning ?? reference?.reasoning ?? (options.useDefaults ? false : undefined),
920
918
  thinking: resolvedModel.thinking ?? reference?.thinking,
921
919
  input: input as ("text" | "image")[],
920
+ output: output as ("text" | "image")[] | undefined,
922
921
  cost,
923
922
  contextWindow:
924
923
  resolvedModel.contextWindow ?? reference?.contextWindow ?? (options.useDefaults ? 128000 : undefined),
@@ -1213,6 +1212,7 @@ export class ModelRegistry {
1213
1212
  reasoning: customModel.reasoning ?? existingModel.reasoning,
1214
1213
  thinking: customModel.thinking ?? existingModel.thinking,
1215
1214
  input: customModel.input ?? existingModel.input,
1215
+ output: customModel.output ?? existingModel.output,
1216
1216
  cost: customModel.cost ?? existingModel.cost,
1217
1217
  contextWindow: customModel.contextWindow ?? existingModel.contextWindow,
1218
1218
  maxTokens: customModel.maxTokens ?? existingModel.maxTokens,
@@ -2325,6 +2325,14 @@ export class ModelRegistry {
2325
2325
  fallback: 3,
2326
2326
  };
2327
2327
  return [...variants].sort((left, right) => {
2328
+ // Prefer vision-capable variants over configured provider order so an
2329
+ // ambiguous canonical id never resolves to a text-only namesake when a
2330
+ // vision-capable variant of the same id is available.
2331
+ const leftVision = left.model.input.includes("image") ? 0 : 1;
2332
+ const rightVision = right.model.input.includes("image") ? 0 : 1;
2333
+ if (leftVision !== rightVision) {
2334
+ return leftVision - rightVision;
2335
+ }
2328
2336
  const leftProviderRank = providerRank.get(left.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2329
2337
  const rightProviderRank = providerRank.get(right.model.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER;
2330
2338
  if (leftProviderRank !== rightProviderRank) {
@@ -15,7 +15,6 @@ import {
15
15
  import { fuzzyMatch } from "@gajae-code/tui";
16
16
  import { logger } from "@gajae-code/utils";
17
17
  import chalk from "chalk";
18
- import MODEL_PRIO from "../priority.json" with { type: "json" };
19
18
  import { parseThinkingLevel, resolveThinkingLevelForModel } from "../thinking";
20
19
  import { isAuthenticated, kNoAuth, MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "./model-registry";
21
20
  import type { Settings } from "./settings";
@@ -246,6 +245,15 @@ function pickPreferredModel(candidates: Model<Api>[], context: ModelPreferenceCo
246
245
  return (aProviderUsage ?? Number.POSITIVE_INFINITY) - (bProviderUsage ?? Number.POSITIVE_INFINITY);
247
246
  }
248
247
 
248
+ // Prefer vision-capable variants over configured provider/registration order
249
+ // so an ambiguous id never resolves to a text-only namesake when a
250
+ // vision-capable variant of the same id is available.
251
+ const aVision = a.input.includes("image") ? 0 : 1;
252
+ const bVision = b.input.includes("image") ? 0 : 1;
253
+ if (aVision !== bVision) {
254
+ return aVision - bVision;
255
+ }
256
+
249
257
  const aDeprioritized = context.deprioritizedProviders.has(a.provider);
250
258
  const bDeprioritized = context.deprioritizedProviders.has(b.provider);
251
259
  if (aDeprioritized !== bDeprioritized) {
@@ -520,7 +528,7 @@ function normalizeModelPatternList(value: string | string[] | undefined): string
520
528
  }
521
529
 
522
530
  function isSessionInheritedAgentPattern(value: string): boolean {
523
- return value === DEFAULT_MODEL_ROLE || value === `${PREFIX_MODEL_ROLE}${DEFAULT_MODEL_ROLE}` || value === "pi/task";
531
+ return value === DEFAULT_MODEL_ROLE || value === `${PREFIX_MODEL_ROLE}${DEFAULT_MODEL_ROLE}`;
524
532
  }
525
533
 
526
534
  function resolveConfiguredRolePattern(value: string, settings?: Settings): string[] | undefined {
@@ -535,8 +543,7 @@ function resolveConfiguredRolePattern(value: string, settings?: Settings): strin
535
543
  if (!role) return [normalized];
536
544
 
537
545
  const configured = settings?.getModelRole(role)?.trim();
538
- const roleDefaults = normalizeModelPatternList(MODEL_PRIO[role as keyof typeof MODEL_PRIO]);
539
- const resolved = configured ? normalizeModelPatternList(configured) : roleDefaults;
546
+ const resolved = configured ? normalizeModelPatternList(configured) : undefined;
540
547
  if (!resolved || resolved.length === 0) {
541
548
  return undefined;
542
549
  }
@@ -545,7 +552,7 @@ function resolveConfiguredRolePattern(value: string, settings?: Settings): strin
545
552
  }
546
553
 
547
554
  /**
548
- * Expand a role alias like "pi/smol" to the configured model string.
555
+ * Expand a role alias like "pi/default" to the configured model string.
549
556
  */
550
557
  export function expandRoleAlias(value: string, settings?: Settings): string {
551
558
  const normalized = value.trim();
@@ -582,9 +589,8 @@ export function resolveAgentModelPatterns(options: AgentModelPatternResolutionOp
582
589
  const configuredAgentPatterns = resolveConfiguredModelPatterns(agentModel, settings);
583
590
  const singleAgentPattern = normalizedAgentPatterns.length === 1 ? normalizedAgentPatterns[0] : undefined;
584
591
  const agentInheritsSessionModel = singleAgentPattern ? isSessionInheritedAgentPattern(singleAgentPattern) : false;
585
- if (configuredAgentPatterns.length > 0) {
586
- if (!agentInheritsSessionModel) return configuredAgentPatterns;
587
- if (singleAgentPattern === "pi/task") return configuredAgentPatterns;
592
+ if (configuredAgentPatterns.length > 0 && !agentInheritsSessionModel) {
593
+ return configuredAgentPatterns;
588
594
  }
589
595
 
590
596
  const fallback =
@@ -1325,79 +1331,3 @@ export async function restoreModelFromSession(
1325
1331
  // No models available
1326
1332
  return { model: undefined, fallbackMessage: undefined };
1327
1333
  }
1328
-
1329
- /**
1330
- * Find a smol/fast model using the priority chain.
1331
- * Tries exact matches first, then fuzzy matches.
1332
- *
1333
- * @param modelRegistry The model registry to search
1334
- * @param savedModel Optional saved model string from settings (provider/modelId)
1335
- * @returns The best available smol model, or undefined if none found
1336
- */
1337
- export async function findSmolModel(
1338
- modelRegistry: ModelLookupRegistry,
1339
- savedModel?: string,
1340
- ): Promise<Model<Api> | undefined> {
1341
- const availableModels = modelRegistry.getAvailable();
1342
- if (availableModels.length === 0) return undefined;
1343
-
1344
- // 1. Try saved model from settings
1345
- if (savedModel) {
1346
- const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
1347
- if (match) return match;
1348
- }
1349
-
1350
- // 2. Try priority chain
1351
- for (const pattern of MODEL_PRIO.smol) {
1352
- // Try exact match with provider prefix
1353
- const providerMatch = availableModels.find(m => `${m.provider}/${m.id}`.toLowerCase() === pattern);
1354
- if (providerMatch) return providerMatch;
1355
-
1356
- // Try exact match first
1357
- const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
1358
- if (exactMatch) return exactMatch;
1359
-
1360
- // Try fuzzy match (substring)
1361
- const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern));
1362
- if (fuzzyMatch) return fuzzyMatch;
1363
- }
1364
-
1365
- // 3. Fallback to first available (same as default)
1366
- return availableModels[0];
1367
- }
1368
-
1369
- /**
1370
- * Find a slow/comprehensive model using the priority chain.
1371
- * Prioritizes reasoning and OpenAI code backend models for thorough analysis.
1372
- *
1373
- * @param modelRegistry The model registry to search
1374
- * @param savedModel Optional saved model string from settings (provider/modelId)
1375
- * @returns The best available slow model, or undefined if none found
1376
- */
1377
- export async function findSlowModel(
1378
- modelRegistry: ModelLookupRegistry,
1379
- savedModel?: string,
1380
- ): Promise<Model<Api> | undefined> {
1381
- const availableModels = modelRegistry.getAvailable();
1382
- if (availableModels.length === 0) return undefined;
1383
-
1384
- // 1. Try saved model from settings
1385
- if (savedModel) {
1386
- const match = resolveModelFromString(savedModel, availableModels, undefined, modelRegistry);
1387
- if (match) return match;
1388
- }
1389
-
1390
- // 2. Try priority chain
1391
- for (const pattern of MODEL_PRIO.slow) {
1392
- // Try exact match first
1393
- const exactMatch = parseModelPattern(pattern, availableModels, undefined, { modelRegistry }).model;
1394
- if (exactMatch) return exactMatch;
1395
-
1396
- // Try fuzzy match (substring)
1397
- const fuzzyMatch = availableModels.find(m => m.id.toLowerCase().includes(pattern.toLowerCase()));
1398
- if (fuzzyMatch) return fuzzyMatch;
1399
- }
1400
-
1401
- // 3. Fallback to first available (same as default)
1402
- return availableModels[0];
1403
- }
@@ -135,6 +135,7 @@ const ModelDefinitionSchema = z
135
135
  reasoning: z.boolean().optional(),
136
136
  thinking: ModelThinkingSchema.optional(),
137
137
  input: z.array(z.enum(["text", "image"])).optional(),
138
+ output: z.array(z.enum(["text", "image"])).optional(),
138
139
  cost: z
139
140
  .object({
140
141
  input: z.number(),
@@ -161,6 +162,7 @@ export const ModelOverrideSchema = z
161
162
  reasoning: z.boolean().optional(),
162
163
  thinking: ModelThinkingSchema.optional(),
163
164
  input: z.array(z.enum(["text", "image"])).optional(),
165
+ output: z.array(z.enum(["text", "image"])).optional(),
164
166
  cost: z
165
167
  .object({
166
168
  input: z.number().optional(),
@@ -938,6 +938,18 @@ export const SETTINGS_SCHEMA = {
938
938
  },
939
939
  },
940
940
 
941
+ busyPromptMode: {
942
+ type: "enum",
943
+ values: ["steer", "queue"] as const,
944
+ default: "steer",
945
+ ui: {
946
+ tab: "interaction",
947
+ label: "Busy Prompt Mode",
948
+ description:
949
+ "What a submitted prompt does while the agent is busy: steer (interrupt the active turn) or queue (run after the active turn completes)",
950
+ },
951
+ },
952
+
941
953
  // Input and startup
942
954
  doubleEscapeAction: {
943
955
  type: "enum",
@@ -2627,7 +2639,7 @@ export const SETTINGS_SCHEMA = {
2627
2639
  },
2628
2640
  "providers.image": {
2629
2641
  type: "enum",
2630
- values: ["auto", "openai", "gemini", "openrouter"] as const,
2642
+ values: ["auto", "openai", "gemini", "openrouter", "antigravity"] as const,
2631
2643
  default: "auto",
2632
2644
  ui: {
2633
2645
  tab: "providers",
@@ -2642,6 +2654,7 @@ export const SETTINGS_SCHEMA = {
2642
2654
  { value: "openai", label: "OpenAI", description: "Uses the active GPT Responses/Codex model" },
2643
2655
  { value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
2644
2656
  { value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
2657
+ { value: "antigravity", label: "Antigravity", description: "Requires login with google-antigravity" },
2645
2658
  ],
2646
2659
  },
2647
2660
  },
@@ -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" },
@@ -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",