@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.
- package/CHANGELOG.md +11 -0
- package/dist/types/async/job-manager.d.ts +25 -0
- package/dist/types/commit/model-selection.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/model-resolver.d.ts +1 -19
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +15 -1
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
- package/dist/types/harness-control-plane/types.d.ts +7 -2
- package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
- package/dist/types/session/agent-session.d.ts +12 -1
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser.d.ts +80 -0
- package/dist/types/tools/image-gen.d.ts +1 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +120 -1
- package/src/commands/ultragoal.ts +7 -1
- package/src/commit/agentic/index.ts +2 -2
- package/src/commit/model-selection.ts +7 -22
- package/src/commit/pipeline.ts +2 -2
- package/src/config/model-registry.ts +17 -9
- package/src/config/model-resolver.ts +14 -84
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +14 -1
- package/src/gjc-runtime/goal-mode-request.ts +21 -1
- package/src/harness-control-plane/owner.ts +3 -3
- package/src/harness-control-plane/rpc-adapter.ts +7 -1
- package/src/harness-control-plane/types.ts +8 -11
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +17 -9
- package/src/modes/acp/acp-event-mapper.ts +33 -1
- package/src/modes/components/custom-editor.ts +19 -3
- package/src/modes/controllers/input-controller.ts +27 -7
- package/src/modes/controllers/selector-controller.ts +7 -1
- package/src/modes/interactive-mode.ts +3 -1
- package/src/modes/rpc/rpc-client.ts +16 -3
- package/src/modes/rpc/rpc-mode.ts +5 -2
- package/src/modes/shared/agent-wire/command-contract.ts +18 -0
- package/src/modes/shared/agent-wire/event-contract.ts +147 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
- package/src/modes/shared/agent-wire/event-observation.ts +397 -0
- package/src/modes/shared/agent-wire/protocol.ts +24 -81
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/tools/browser.md +3 -2
- package/src/runtime-mcp/manager.ts +15 -2
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +60 -4
- package/src/session/session-manager.ts +1 -1
- package/src/task/agents.ts +1 -1
- package/src/tools/bash.ts +6 -1
- package/src/tools/browser/actions.ts +189 -0
- package/src/tools/browser.ts +91 -1
- package/src/tools/image-gen.ts +42 -15
- package/src/tools/index.ts +7 -1
- package/src/tools/inspect-image.ts +10 -8
- package/src/tools/job.ts +12 -2
- package/src/tools/monitor.ts +98 -17
- package/src/utils/commit-message-generator.ts +6 -13
- package/src/utils/title-generator.ts +1 -1
- package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
- package/src/harness-control-plane/frame-mapper.ts +0 -286
- 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"
|
|
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"
|
|
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}
|
|
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
|
|
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/
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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",
|