@gajae-code/coding-agent 0.4.1 → 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 +22 -0
- package/dist/types/async/job-manager.d.ts +25 -0
- package/dist/types/commands/ultragoal.d.ts +1 -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 +26 -4
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/finalize.d.ts +8 -0
- package/dist/types/harness-control-plane/receipts.d.ts +16 -1
- package/dist/types/harness-control-plane/types.d.ts +16 -3
- 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/reminders/star-reminder.d.ts +115 -0
- package/dist/types/session/agent-session.d.ts +30 -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/examples/extensions/README.md +20 -41
- package/package.json +7 -7
- package/src/async/job-manager.ts +120 -1
- package/src/cli/grep-cli.ts +1 -1
- package/src/commands/harness.ts +42 -3
- package/src/commands/ultragoal.ts +8 -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 +27 -4
- package/src/defaults/gjc/skills/team/SKILL.md +10 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +3 -2
- package/src/gjc-runtime/goal-mode-request.ts +21 -1
- package/src/gjc-runtime/launch-tmux.ts +25 -2
- package/src/gjc-runtime/team-runtime.ts +78 -3
- package/src/gjc-runtime/ultragoal-guard.ts +18 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +240 -30
- package/src/harness-control-plane/finalize.ts +84 -0
- package/src/harness-control-plane/owner.ts +16 -3
- package/src/harness-control-plane/receipts.ts +39 -1
- package/src/harness-control-plane/rpc-adapter.ts +7 -1
- package/src/harness-control-plane/types.ts +33 -12
- 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 +29 -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/reminders/star-reminder.ts +422 -0
- package/src/runtime-mcp/manager.ts +15 -2
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +139 -17
- 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
|
@@ -228,18 +228,35 @@ function planSnapshotForReceipt(input: {
|
|
|
228
228
|
goal: UltragoalGoal;
|
|
229
229
|
beforeStatus: UltragoalGoalStatus;
|
|
230
230
|
targetGoalUpdatedAt: string;
|
|
231
|
+
receiptKind: UltragoalReceiptKind;
|
|
231
232
|
}): unknown {
|
|
233
|
+
const targetGoalSnapshot = {
|
|
234
|
+
...input.goal,
|
|
235
|
+
status: input.beforeStatus,
|
|
236
|
+
updatedAt: input.targetGoalUpdatedAt,
|
|
237
|
+
evidence: undefined,
|
|
238
|
+
completedAt: undefined,
|
|
239
|
+
completionVerification: undefined,
|
|
240
|
+
};
|
|
241
|
+
const goals =
|
|
242
|
+
input.receiptKind === "final-aggregate"
|
|
243
|
+
? input.plan.goals.map(goal => ({
|
|
244
|
+
...goal,
|
|
245
|
+
status: goal.id === input.goal.id ? input.beforeStatus : goal.status,
|
|
246
|
+
updatedAt: goal.id === input.goal.id ? input.targetGoalUpdatedAt : goal.updatedAt,
|
|
247
|
+
evidence: goal.id === input.goal.id ? undefined : goal.evidence,
|
|
248
|
+
completedAt: goal.id === input.goal.id ? undefined : goal.completedAt,
|
|
249
|
+
completionVerification: undefined,
|
|
250
|
+
}))
|
|
251
|
+
: [targetGoalSnapshot];
|
|
232
252
|
return {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
completedAt: goal.id === input.goal.id ? undefined : goal.completedAt,
|
|
241
|
-
completionVerification: undefined,
|
|
242
|
-
})),
|
|
253
|
+
version: input.plan.version,
|
|
254
|
+
brief: input.plan.brief,
|
|
255
|
+
gjcGoalMode: input.plan.gjcGoalMode,
|
|
256
|
+
gjcObjective: input.plan.gjcObjective,
|
|
257
|
+
gjcObjectiveAliases: input.plan.gjcObjectiveAliases,
|
|
258
|
+
createdAt: input.plan.createdAt,
|
|
259
|
+
goals,
|
|
243
260
|
};
|
|
244
261
|
}
|
|
245
262
|
|
|
@@ -264,6 +281,7 @@ export function computeUltragoalPlanGeneration(input: {
|
|
|
264
281
|
goal: input.goal,
|
|
265
282
|
beforeStatus: input.beforeStatus,
|
|
266
283
|
targetGoalUpdatedAt,
|
|
284
|
+
receiptKind: input.receiptKind,
|
|
267
285
|
}),
|
|
268
286
|
);
|
|
269
287
|
const requiredGoalSetHashBeforeCheckpoint = hashStructuredValue(
|
|
@@ -569,6 +587,31 @@ function chooseNextGoal(plan: UltragoalPlan, retryFailed: boolean): UltragoalGoa
|
|
|
569
587
|
(retryFailed ? plan.goals.find(goal => goal.status === "failed") : undefined)
|
|
570
588
|
);
|
|
571
589
|
}
|
|
590
|
+
export interface UltragoalRunCompletionState {
|
|
591
|
+
requiredGoals: UltragoalGoal[];
|
|
592
|
+
incompleteGoals: UltragoalGoal[];
|
|
593
|
+
nextGoal?: UltragoalGoal;
|
|
594
|
+
allComplete: boolean;
|
|
595
|
+
hasBlockers: boolean;
|
|
596
|
+
needsFinalAggregateReceipt: boolean;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function getUltragoalRunCompletionState(
|
|
600
|
+
plan: UltragoalPlan,
|
|
601
|
+
options: { retryFailed?: boolean } = {},
|
|
602
|
+
): UltragoalRunCompletionState {
|
|
603
|
+
const requiredGoals = requiredUltragoalGoals(plan);
|
|
604
|
+
const incompleteGoals = requiredGoals.filter(goal => !TERMINAL_OR_SKIPPED_STATUSES.has(goal.status));
|
|
605
|
+
const nextGoal = chooseNextGoal(plan, options.retryFailed === true);
|
|
606
|
+
return {
|
|
607
|
+
requiredGoals,
|
|
608
|
+
incompleteGoals,
|
|
609
|
+
nextGoal,
|
|
610
|
+
allComplete: requiredGoals.length > 0 && incompleteGoals.length === 0,
|
|
611
|
+
hasBlockers: incompleteGoals.some(goal => goal.status === "blocked" || goal.status === "review_blocked"),
|
|
612
|
+
needsFinalAggregateReceipt: plan.gjcGoalMode === "aggregate" && incompleteGoals.length === 0,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
572
615
|
|
|
573
616
|
export async function startNextUltragoalGoal(input: { cwd: string; retryFailed?: boolean }): Promise<{
|
|
574
617
|
plan: UltragoalPlan;
|
|
@@ -578,7 +621,7 @@ export async function startNextUltragoalGoal(input: { cwd: string; retryFailed?:
|
|
|
578
621
|
const plan = await readUltragoalPlan(input.cwd);
|
|
579
622
|
if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
|
|
580
623
|
const goal = chooseNextGoal(plan, input.retryFailed === true);
|
|
581
|
-
if (!goal) return { plan, allComplete: plan.
|
|
624
|
+
if (!goal) return { plan, allComplete: getUltragoalRunCompletionState(plan).allComplete };
|
|
582
625
|
if (goal.status !== "active") {
|
|
583
626
|
const now = new Date().toISOString();
|
|
584
627
|
goal.status = "active";
|
|
@@ -639,7 +682,11 @@ function requireObjectArray(value: unknown, fieldName: string): JsonObject[] {
|
|
|
639
682
|
function requiredStringField(row: JsonObject, key: string, fieldName: string): string {
|
|
640
683
|
const value = row[key];
|
|
641
684
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
642
|
-
|
|
685
|
+
const hint =
|
|
686
|
+
key === "obligation" && typeof row.description === "string" && row.description.trim().length > 0
|
|
687
|
+
? "; found description, but complete-checkpoint contractCoverage rows require obligation"
|
|
688
|
+
: "";
|
|
689
|
+
throw new Error(`qualityGate ${fieldName}.${key} must be a non-empty string${hint}`);
|
|
643
690
|
}
|
|
644
691
|
return value.trim();
|
|
645
692
|
}
|
|
@@ -1015,6 +1062,17 @@ async function readRequiredCompletionQualityGate(cwd: string, value: string | un
|
|
|
1015
1062
|
return gate;
|
|
1016
1063
|
}
|
|
1017
1064
|
|
|
1065
|
+
function snapshotUpdatedAtMilliseconds(value: unknown): number | null {
|
|
1066
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1067
|
+
if (typeof value !== "string" || value.trim().length === 0) return null;
|
|
1068
|
+
const trimmed = value.trim();
|
|
1069
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1070
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
1071
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1072
|
+
}
|
|
1073
|
+
const parsed = Date.parse(trimmed);
|
|
1074
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1075
|
+
}
|
|
1018
1076
|
async function readGjcGoalSnapshot(input: {
|
|
1019
1077
|
cwd: string;
|
|
1020
1078
|
value: string | undefined;
|
|
@@ -1026,16 +1084,23 @@ async function readGjcGoalSnapshot(input: {
|
|
|
1026
1084
|
}): Promise<unknown> {
|
|
1027
1085
|
if (!input.value?.trim()) {
|
|
1028
1086
|
if (!input.required) return undefined;
|
|
1029
|
-
throw new Error(
|
|
1087
|
+
throw new Error(
|
|
1088
|
+
`${input.errorPrefix} require --gjc-goal-json from a fresh active goal({"op":"get"}) snapshot; this is the GJC goal-mode receipt, not the .gjc/ultragoal/goals.json goal record`,
|
|
1089
|
+
);
|
|
1030
1090
|
}
|
|
1031
1091
|
const snapshot = await readStructuredValue(input.cwd, input.value);
|
|
1032
1092
|
const snapshotObject = qualityGateObject(snapshot);
|
|
1033
1093
|
const detailsObject = qualityGateObject(snapshotObject?.details);
|
|
1034
1094
|
const goalObject = qualityGateObject(snapshotObject?.goal) ?? qualityGateObject(detailsObject?.goal);
|
|
1035
|
-
if (!goalObject)
|
|
1036
|
-
|
|
1095
|
+
if (!goalObject)
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`${input.errorPrefix} require --gjc-goal-json with a goal object from goal({"op":"get"}); pass the active GJC goal-mode snapshot, not the .gjc/ultragoal/goals.json goal record`,
|
|
1098
|
+
);
|
|
1099
|
+
const updatedAt = snapshotUpdatedAtMilliseconds(goalObject.updatedAt);
|
|
1037
1100
|
if (!updatedAt)
|
|
1038
|
-
throw new Error(
|
|
1101
|
+
throw new Error(
|
|
1102
|
+
`${input.errorPrefix} require --gjc-goal-json goal.updatedAt as epoch milliseconds or an ISO timestamp from goal({"op":"get"}); pass the active GJC goal-mode snapshot, not the .gjc/ultragoal/goals.json goal record`,
|
|
1103
|
+
);
|
|
1039
1104
|
const nowMilliseconds = Date.now();
|
|
1040
1105
|
if (updatedAt < nowMilliseconds - GJC_GOAL_SNAPSHOT_MAX_AGE_MILLISECONDS) {
|
|
1041
1106
|
throw new Error(`${input.errorPrefix} require a fresh --gjc-goal-json snapshot`);
|
|
@@ -1052,7 +1117,9 @@ async function readGjcGoalSnapshot(input: {
|
|
|
1052
1117
|
return snapshot;
|
|
1053
1118
|
}
|
|
1054
1119
|
if (!expectedObjectives.has(objective)) {
|
|
1055
|
-
throw new Error(
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`${input.errorPrefix} require --gjc-goal-json objective to match the active GJC goal-mode objective from goal({"op":"get"}), not the .gjc/ultragoal/goals.json goal ${input.goal?.id ?? "record"}`,
|
|
1122
|
+
);
|
|
1056
1123
|
}
|
|
1057
1124
|
if (goalObject.status !== "active") {
|
|
1058
1125
|
throw new Error(`${input.errorPrefix} require --gjc-goal-json goal.status to be active`);
|
|
@@ -1147,6 +1214,54 @@ export async function checkpointUltragoalGoal(input: {
|
|
|
1147
1214
|
});
|
|
1148
1215
|
return plan;
|
|
1149
1216
|
}
|
|
1217
|
+
export interface UltragoalCheckpointContinuation {
|
|
1218
|
+
plan: UltragoalPlan;
|
|
1219
|
+
checkpointedGoal: UltragoalGoal;
|
|
1220
|
+
nextGoal?: UltragoalGoal;
|
|
1221
|
+
startedNext: boolean;
|
|
1222
|
+
allComplete: boolean;
|
|
1223
|
+
incompleteGoals: UltragoalGoal[];
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export async function checkpointAndContinueUltragoalGoal(input: {
|
|
1227
|
+
cwd: string;
|
|
1228
|
+
goalId: string;
|
|
1229
|
+
status: UltragoalGoalStatus;
|
|
1230
|
+
evidence: string;
|
|
1231
|
+
gjcGoalJson?: string;
|
|
1232
|
+
qualityGateJson?: string;
|
|
1233
|
+
advanceNext?: boolean;
|
|
1234
|
+
retryFailed?: boolean;
|
|
1235
|
+
}): Promise<UltragoalCheckpointContinuation> {
|
|
1236
|
+
let plan = await checkpointUltragoalGoal(input);
|
|
1237
|
+
const checkpointedGoal = plan.goals.find(goal => goal.id === input.goalId);
|
|
1238
|
+
if (!checkpointedGoal) throw new Error(`No ultragoal goal found for ${input.goalId}.`);
|
|
1239
|
+
if (input.status === "complete" && input.advanceNext === true) {
|
|
1240
|
+
const beforeAdvance = getUltragoalRunCompletionState(plan, { retryFailed: input.retryFailed });
|
|
1241
|
+
if (beforeAdvance.nextGoal && beforeAdvance.nextGoal.status !== "active") {
|
|
1242
|
+
const started = await startNextUltragoalGoal({ cwd: input.cwd, retryFailed: input.retryFailed });
|
|
1243
|
+
plan = started.plan;
|
|
1244
|
+
const afterAdvance = getUltragoalRunCompletionState(plan, { retryFailed: input.retryFailed });
|
|
1245
|
+
return {
|
|
1246
|
+
plan,
|
|
1247
|
+
checkpointedGoal,
|
|
1248
|
+
nextGoal: started.goal,
|
|
1249
|
+
startedNext: Boolean(started.goal),
|
|
1250
|
+
allComplete: afterAdvance.allComplete,
|
|
1251
|
+
incompleteGoals: afterAdvance.incompleteGoals,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const state = getUltragoalRunCompletionState(plan, { retryFailed: input.retryFailed });
|
|
1256
|
+
return {
|
|
1257
|
+
plan,
|
|
1258
|
+
checkpointedGoal,
|
|
1259
|
+
nextGoal: state.nextGoal,
|
|
1260
|
+
startedNext: false,
|
|
1261
|
+
allComplete: state.allComplete,
|
|
1262
|
+
incompleteGoals: state.incompleteGoals,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1150
1265
|
|
|
1151
1266
|
export async function addUltragoalSubgoal(input: {
|
|
1152
1267
|
cwd: string;
|
|
@@ -1235,6 +1350,8 @@ function hasFlag(args: readonly string[], flag: string): boolean {
|
|
|
1235
1350
|
return args.includes(flag);
|
|
1236
1351
|
}
|
|
1237
1352
|
|
|
1353
|
+
const HELP_FLAGS = new Set(["--help", "-h"]);
|
|
1354
|
+
|
|
1238
1355
|
const FLAGS_WITH_VALUES = new Set([
|
|
1239
1356
|
"--brief",
|
|
1240
1357
|
"--brief-file",
|
|
@@ -1250,6 +1367,10 @@ const FLAGS_WITH_VALUES = new Set([
|
|
|
1250
1367
|
"--rationale",
|
|
1251
1368
|
]);
|
|
1252
1369
|
|
|
1370
|
+
function isHelpArg(arg: string): boolean {
|
|
1371
|
+
return HELP_FLAGS.has(arg);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1253
1374
|
function commandName(args: readonly string[]): string {
|
|
1254
1375
|
let skipNext = false;
|
|
1255
1376
|
for (const arg of args) {
|
|
@@ -1261,11 +1382,62 @@ function commandName(args: readonly string[]): string {
|
|
|
1261
1382
|
skipNext = true;
|
|
1262
1383
|
continue;
|
|
1263
1384
|
}
|
|
1385
|
+
if (isHelpArg(arg)) continue;
|
|
1264
1386
|
if (!arg.startsWith("-")) return arg;
|
|
1265
1387
|
}
|
|
1266
1388
|
return "status";
|
|
1267
1389
|
}
|
|
1268
1390
|
|
|
1391
|
+
function renderUltragoalHelp(args: readonly string[]): string | null {
|
|
1392
|
+
if (!args.some(isHelpArg) && args[0] !== "help") return null;
|
|
1393
|
+
const subject =
|
|
1394
|
+
args[0] === "help" ? args.find((arg, index) => index > 0 && !arg.startsWith("-")) : commandName(args);
|
|
1395
|
+
if (subject === "checkpoint") {
|
|
1396
|
+
return [
|
|
1397
|
+
"Run native GJC Ultragoal workflow commands",
|
|
1398
|
+
"",
|
|
1399
|
+
"USAGE",
|
|
1400
|
+
" $ gjc ultragoal checkpoint --goal-id <id> --status <status> --evidence <text> [FLAGS]",
|
|
1401
|
+
"",
|
|
1402
|
+
"FLAGS",
|
|
1403
|
+
" --goal-id=<value> Durable .gjc/ultragoal goal id, e.g. G001",
|
|
1404
|
+
" --status=<value> pending|active|complete|failed|blocked|review_blocked|superseded",
|
|
1405
|
+
" --evidence=<value> Completion or checkpoint evidence text",
|
|
1406
|
+
" --quality-gate-json=<value> JSON string or path for complete checkpoints",
|
|
1407
|
+
' --gjc-goal-json=<value> JSON string or path containing the current goal({"op":"get"}) snapshot',
|
|
1408
|
+
" --json Output a machine-readable receipt",
|
|
1409
|
+
"",
|
|
1410
|
+
"COMPLETE CHECKPOINT RECEIPTS",
|
|
1411
|
+
" --quality-gate-json must be an object with architectReview, executorQa, and iteration.",
|
|
1412
|
+
" executorQa.contractCoverage[] rows require an obligation field; description is not a substitute.",
|
|
1413
|
+
' --gjc-goal-json must contain the active GJC goal-mode snapshot from goal({"op":"get"}), not the .gjc/ultragoal/goals.json goal record.',
|
|
1414
|
+
" goal.updatedAt may be epoch milliseconds or an ISO timestamp and must be fresh.",
|
|
1415
|
+
"",
|
|
1416
|
+
"EXAMPLES",
|
|
1417
|
+
' $ gjc ultragoal checkpoint --goal-id G001 --status blocked --evidence "waiting on review"',
|
|
1418
|
+
' $ gjc ultragoal checkpoint --goal-id G001 --status complete --evidence "tests passed" --gjc-goal-json ./goal.json --quality-gate-json ./quality-gate.json --json',
|
|
1419
|
+
"",
|
|
1420
|
+
].join("\n");
|
|
1421
|
+
}
|
|
1422
|
+
return [
|
|
1423
|
+
"Run native GJC Ultragoal workflow commands",
|
|
1424
|
+
"",
|
|
1425
|
+
"USAGE",
|
|
1426
|
+
" $ gjc ultragoal <command> [FLAGS]",
|
|
1427
|
+
"",
|
|
1428
|
+
"COMMANDS",
|
|
1429
|
+
" status",
|
|
1430
|
+
" create-goals",
|
|
1431
|
+
" complete-goals",
|
|
1432
|
+
" checkpoint",
|
|
1433
|
+
" steer",
|
|
1434
|
+
" record-review-blockers",
|
|
1435
|
+
"",
|
|
1436
|
+
"Run `gjc ultragoal checkpoint --help` for complete checkpoint receipt requirements.",
|
|
1437
|
+
"",
|
|
1438
|
+
].join("\n");
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1269
1441
|
async function readBrief(cwd: string, args: readonly string[]): Promise<string> {
|
|
1270
1442
|
const inline = flagValue(args, "--brief");
|
|
1271
1443
|
if (inline !== undefined) return inline;
|
|
@@ -1306,8 +1478,54 @@ function renderCompleteHandoff(
|
|
|
1306
1478
|
"",
|
|
1307
1479
|
].join("\n");
|
|
1308
1480
|
}
|
|
1481
|
+
function renderCheckpointContinuation(
|
|
1482
|
+
result: UltragoalCheckpointContinuation,
|
|
1483
|
+
status: UltragoalGoalStatus,
|
|
1484
|
+
json: boolean,
|
|
1485
|
+
cwd: string,
|
|
1486
|
+
): string {
|
|
1487
|
+
if (json)
|
|
1488
|
+
return renderCliWriteReceipt({
|
|
1489
|
+
ok: true,
|
|
1490
|
+
goal_id: result.checkpointedGoal.id,
|
|
1491
|
+
status,
|
|
1492
|
+
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1493
|
+
completion_receipt_kind: result.checkpointedGoal.completionVerification?.receiptKind,
|
|
1494
|
+
quality_gate_hash: result.checkpointedGoal.completionVerification?.qualityGateHash,
|
|
1495
|
+
all_complete: result.allComplete,
|
|
1496
|
+
next_goal_id: result.nextGoal?.id,
|
|
1497
|
+
next_goal_status: result.nextGoal?.status,
|
|
1498
|
+
started_next: result.startedNext,
|
|
1499
|
+
incomplete_goal_ids: result.incompleteGoals.map(goal => goal.id),
|
|
1500
|
+
});
|
|
1501
|
+
const lines = [`Checkpointed ${result.checkpointedGoal.id} as ${status}.`];
|
|
1502
|
+
if (status === "complete") {
|
|
1503
|
+
if (result.allComplete) {
|
|
1504
|
+
lines.push("All ultragoal goals are complete.");
|
|
1505
|
+
} else if (result.nextGoal) {
|
|
1506
|
+
lines.push(`Next ultragoal goal: ${result.nextGoal.id} — ${result.nextGoal.title}`);
|
|
1507
|
+
lines.push(`Objective: ${result.nextGoal.objective}`);
|
|
1508
|
+
lines.push(`GJC objective: ${result.plan.gjcObjective}`);
|
|
1509
|
+
lines.push(
|
|
1510
|
+
result.startedNext
|
|
1511
|
+
? "The next ultragoal goal is active; continue the current aggregate GJC goal and checkpoint this story when verified."
|
|
1512
|
+
: "Run `gjc ultragoal complete-goals` to activate the next ultragoal story.",
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
} else if (status === "failed") {
|
|
1516
|
+
lines.push("Resume failed goals with `gjc ultragoal complete-goals --retry-failed` after the blocker is fixed.");
|
|
1517
|
+
} else if (status === "blocked" || status === "review_blocked") {
|
|
1518
|
+
lines.push(
|
|
1519
|
+
"Blocked ultragoal work must be resolved with explicit blocker work or steering before final completion.",
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
lines.push("");
|
|
1523
|
+
return lines.join("\n");
|
|
1524
|
+
}
|
|
1309
1525
|
|
|
1310
1526
|
async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
|
|
1527
|
+
const help = renderUltragoalHelp(args);
|
|
1528
|
+
if (help) return { status: 0, stdout: help };
|
|
1311
1529
|
try {
|
|
1312
1530
|
const command = commandName(args);
|
|
1313
1531
|
const json = hasFlag(args, "--json");
|
|
@@ -1344,27 +1562,18 @@ async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<Ul
|
|
|
1344
1562
|
const goalId = flagValue(args, "--goal-id") ?? "";
|
|
1345
1563
|
const status = parseGoalStatus(flagValue(args, "--status"));
|
|
1346
1564
|
const evidence = flagValue(args, "--evidence") ?? "";
|
|
1347
|
-
const
|
|
1565
|
+
const result = await checkpointAndContinueUltragoalGoal({
|
|
1348
1566
|
cwd,
|
|
1349
1567
|
goalId,
|
|
1350
1568
|
status,
|
|
1351
1569
|
evidence,
|
|
1352
1570
|
gjcGoalJson: flagValue(args, "--gjc-goal-json"),
|
|
1353
1571
|
qualityGateJson: flagValue(args, "--quality-gate-json"),
|
|
1572
|
+
advanceNext: status === "complete",
|
|
1354
1573
|
});
|
|
1355
|
-
const goal = plan.goals.find(item => item.id === goalId);
|
|
1356
1574
|
return {
|
|
1357
1575
|
status: 0,
|
|
1358
|
-
stdout: json
|
|
1359
|
-
? renderCliWriteReceipt({
|
|
1360
|
-
ok: true,
|
|
1361
|
-
goal_id: goalId,
|
|
1362
|
-
status,
|
|
1363
|
-
goals_path: getUltragoalPaths(cwd).goalsPath,
|
|
1364
|
-
completion_receipt_kind: goal?.completionVerification?.receiptKind,
|
|
1365
|
-
quality_gate_hash: goal?.completionVerification?.qualityGateHash,
|
|
1366
|
-
})
|
|
1367
|
-
: `ultragoal checkpoint goal-id=${goalId} status=${status}\n`,
|
|
1576
|
+
stdout: renderCheckpointContinuation(result, status, json, cwd),
|
|
1368
1577
|
};
|
|
1369
1578
|
}
|
|
1370
1579
|
case "steer": {
|
|
@@ -1469,7 +1678,8 @@ async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
|
1469
1678
|
export async function runNativeUltragoalCommand(args: string[], cwd = process.cwd()): Promise<UltragoalCommandResult> {
|
|
1470
1679
|
const command = commandName(args);
|
|
1471
1680
|
const result = await dispatchUltragoalCommand(args, cwd);
|
|
1472
|
-
|
|
1681
|
+
const isHelp = args.some(isHelpArg) || args[0] === "help";
|
|
1682
|
+
if (!isHelp && result.status === 0 && RECONCILE_COMMANDS.has(command)) {
|
|
1473
1683
|
await reconcileUltragoalState(cwd);
|
|
1474
1684
|
}
|
|
1475
1685
|
return result;
|
|
@@ -17,10 +17,13 @@ import {
|
|
|
17
17
|
type CompletionEvidence,
|
|
18
18
|
type ReceiptEnvelope,
|
|
19
19
|
type ReceiptSubject,
|
|
20
|
+
type ReviewFailureEvidence,
|
|
21
|
+
type ReviewVerdictEvidence,
|
|
20
22
|
type ValidationEvidence,
|
|
21
23
|
validateReceipt,
|
|
22
24
|
} from "./receipts";
|
|
23
25
|
import { readReceiptIndex, writeReceiptImmutable } from "./storage";
|
|
26
|
+
import { isReviewVerdict, type ReviewVerdict } from "./types";
|
|
24
27
|
|
|
25
28
|
export interface ValidationCommandSpec {
|
|
26
29
|
name: string;
|
|
@@ -49,6 +52,12 @@ export interface FinalizeOptions {
|
|
|
49
52
|
requireTests?: boolean;
|
|
50
53
|
requireCommit?: boolean;
|
|
51
54
|
requirePr?: boolean;
|
|
55
|
+
/** Review-only sessions produce a terminal verdict instead of implementation validation. */
|
|
56
|
+
reviewOnly?: boolean;
|
|
57
|
+
/** Operator/loop-supplied terminal review verdict (closed vocabulary). */
|
|
58
|
+
verdict?: string | null;
|
|
59
|
+
/** Bounded PR/issue reference for the review target (e.g. "PR-414"). Never resolved from the live repo. */
|
|
60
|
+
prTarget?: string | null;
|
|
52
61
|
validationCommands?: ValidationCommandSpec[];
|
|
53
62
|
checks: FinalizeChecks;
|
|
54
63
|
clock?: () => number;
|
|
@@ -60,6 +69,7 @@ export interface FinalizeResult {
|
|
|
60
69
|
validation: { name: string; valid: boolean; exitStatus: number }[];
|
|
61
70
|
commitHash: string | null;
|
|
62
71
|
prUrl: string | null;
|
|
72
|
+
verdict?: ReviewVerdict | null;
|
|
63
73
|
issueArtifact: string | null;
|
|
64
74
|
blockers: string[];
|
|
65
75
|
}
|
|
@@ -69,6 +79,8 @@ function receiptId(prefix: string): string {
|
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
export async function runFinalize(opts: FinalizeOptions): Promise<FinalizeResult> {
|
|
82
|
+
if (opts.reviewOnly) return runReviewFinalize(opts);
|
|
83
|
+
|
|
72
84
|
const now = () => new Date(opts.clock ? opts.clock() : Date.now()).toISOString();
|
|
73
85
|
const blockers: string[] = [];
|
|
74
86
|
const validation: FinalizeResult["validation"] = [];
|
|
@@ -178,6 +190,78 @@ export async function runFinalize(opts: FinalizeOptions): Promise<FinalizeResult
|
|
|
178
190
|
};
|
|
179
191
|
}
|
|
180
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Review-only finalizer: produces a terminal verdict receipt (no implementation validation,
|
|
195
|
+
* no commit/PR resolution) when a valid, autonomous verdict is supplied; otherwise writes a
|
|
196
|
+
* durable, bounded `review-failure` receipt suitable for fallback routing.
|
|
197
|
+
*
|
|
198
|
+
* It never *resolves* PR/commit metadata from the live repo; the only PR reference attached is
|
|
199
|
+
* the session's own declared review target (`prTarget`), so a review session cannot report an
|
|
200
|
+
* unrelated PR resolved from the current checkout.
|
|
201
|
+
*
|
|
202
|
+
* `OWNER_CONFIRMATION_REQUIRED` is a valid verdict but is NOT an autonomous success: it is
|
|
203
|
+
* recorded durably yet returns `completed: false` with an `owner-confirmation-required` blocker
|
|
204
|
+
* so downstream routing escalates to a human instead of treating it as merge-ready.
|
|
205
|
+
*/
|
|
206
|
+
async function runReviewFinalize(opts: FinalizeOptions): Promise<FinalizeResult> {
|
|
207
|
+
const now = () => new Date(opts.clock ? opts.clock() : Date.now()).toISOString();
|
|
208
|
+
const prTarget = opts.prTarget ?? null;
|
|
209
|
+
const subject: ReceiptSubject = { workspace: opts.workspace, branch: opts.branch, head: null, commit: null };
|
|
210
|
+
const baseResult: Omit<FinalizeResult, "completed" | "receiptPath" | "verdict" | "blockers"> = {
|
|
211
|
+
validation: [],
|
|
212
|
+
commitHash: null,
|
|
213
|
+
prUrl: null,
|
|
214
|
+
issueArtifact: null,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (!isReviewVerdict(opts.verdict)) {
|
|
218
|
+
const reason = opts.verdict == null ? "review-verdict-missing" : "review-verdict-invalid";
|
|
219
|
+
const failure: ReviewFailureEvidence = { reason, prTarget, failedAt: now(), fallback: "operator-or-omx-review" };
|
|
220
|
+
const receipt = buildReceipt<ReviewFailureEvidence>({
|
|
221
|
+
receiptId: receiptId("revfail"),
|
|
222
|
+
sessionId: opts.sessionId,
|
|
223
|
+
family: "review-failure",
|
|
224
|
+
source: "finalizer",
|
|
225
|
+
subject,
|
|
226
|
+
evidence: failure,
|
|
227
|
+
createdAt: now(),
|
|
228
|
+
});
|
|
229
|
+
const outcome = validateReceipt(receipt);
|
|
230
|
+
const entry = await writeReceiptImmutable(
|
|
231
|
+
opts.root,
|
|
232
|
+
opts.sessionId,
|
|
233
|
+
"review-failure",
|
|
234
|
+
receipt.receiptId,
|
|
235
|
+
receipt,
|
|
236
|
+
);
|
|
237
|
+
const blockers = outcome.valid ? [reason] : [reason, ...outcome.reasons];
|
|
238
|
+
return { ...baseResult, completed: false, receiptPath: entry.path, verdict: null, blockers };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const verdict = opts.verdict as ReviewVerdict;
|
|
242
|
+
const evidence: ReviewVerdictEvidence = {
|
|
243
|
+
verdict,
|
|
244
|
+
prTarget,
|
|
245
|
+
finalizedAt: now(),
|
|
246
|
+
summaryRef: typeof opts.prTarget === "string" ? `verdict:${verdict}@${opts.prTarget}` : `verdict:${verdict}`,
|
|
247
|
+
};
|
|
248
|
+
const receipt = buildReceipt<ReviewVerdictEvidence>({
|
|
249
|
+
receiptId: receiptId("verdict"),
|
|
250
|
+
sessionId: opts.sessionId,
|
|
251
|
+
family: "review-verdict",
|
|
252
|
+
source: "finalizer",
|
|
253
|
+
subject,
|
|
254
|
+
evidence,
|
|
255
|
+
createdAt: now(),
|
|
256
|
+
});
|
|
257
|
+
const outcome = validateReceipt(receipt);
|
|
258
|
+
const entry = await writeReceiptImmutable(opts.root, opts.sessionId, "review-verdict", receipt.receiptId, receipt);
|
|
259
|
+
// A confirmation-required verdict is recorded but never an autonomous success.
|
|
260
|
+
const humanActionRequired = verdict === "OWNER_CONFIRMATION_REQUIRED";
|
|
261
|
+
const completed = outcome.valid && !humanActionRequired;
|
|
262
|
+
const blockers = !outcome.valid ? outcome.reasons : humanActionRequired ? ["owner-confirmation-required"] : [];
|
|
263
|
+
return { ...baseResult, completed, receiptPath: entry.path, verdict, blockers };
|
|
264
|
+
}
|
|
181
265
|
function git(workspace: string, args: string[]): string | null {
|
|
182
266
|
try {
|
|
183
267
|
return execFileSync("git", args, {
|
|
@@ -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,
|
|
@@ -359,6 +359,14 @@ export class RuntimeOwner {
|
|
|
359
359
|
|
|
360
360
|
async #validate(): Promise<PrimitiveResponse> {
|
|
361
361
|
const state = await this.#loadState();
|
|
362
|
+
if (state.handle.mode === "review") {
|
|
363
|
+
// Review-only sessions do not run implementation validation and never attach PR metadata.
|
|
364
|
+
state.lifecycle = "validating";
|
|
365
|
+
state.updatedAt = new Date(this.#opts.clock ? this.#opts.clock() : Date.now()).toISOString();
|
|
366
|
+
await writeSessionState(this.#opts.root, state);
|
|
367
|
+
await this.#emit("info", "validated", { count: 0, reviewOnly: true });
|
|
368
|
+
return this.#response(state, { validation: [], reviewOnly: true });
|
|
369
|
+
}
|
|
362
370
|
const checks = this.#finalizeChecks ?? defaultFinalizeChecks(state.handle.workspace);
|
|
363
371
|
const commit = await checks.resolveCommit();
|
|
364
372
|
const subject: ReceiptSubject = {
|
|
@@ -495,11 +503,15 @@ export class RuntimeOwner {
|
|
|
495
503
|
const state = await this.#loadState();
|
|
496
504
|
const workspace = state.handle.workspace;
|
|
497
505
|
const checks = this.#finalizeChecks ?? defaultFinalizeChecks(workspace);
|
|
506
|
+
const reviewOnly = state.handle.mode === "review";
|
|
498
507
|
const fin = await runFinalize({
|
|
499
508
|
root: this.#opts.root,
|
|
500
509
|
sessionId: this.#opts.sessionId,
|
|
501
510
|
workspace,
|
|
502
511
|
branch: state.handle.branch ?? "",
|
|
512
|
+
reviewOnly,
|
|
513
|
+
verdict: reviewOnly ? (typeof input.verdict === "string" ? input.verdict : null) : undefined,
|
|
514
|
+
prTarget: reviewOnly ? state.handle.issueOrPr : undefined,
|
|
503
515
|
requireTests: input.requireTests !== false,
|
|
504
516
|
requireCommit: input.requireCommit !== false,
|
|
505
517
|
requirePr: input.requirePr !== false,
|
|
@@ -514,6 +526,7 @@ export class RuntimeOwner {
|
|
|
514
526
|
await this.#emit(fin.completed ? "info" : "critical", "finalized", {
|
|
515
527
|
completed: fin.completed,
|
|
516
528
|
blockers: fin.blockers,
|
|
529
|
+
...(reviewOnly ? { verdict: fin.verdict ?? null, reviewOnly: true } : {}),
|
|
517
530
|
});
|
|
518
531
|
return this.#response(state, { finalize: fin }, fin.completed);
|
|
519
532
|
}
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
* - completion the finalize gate: receipt-valid + commit + PR/issue + validations
|
|
14
14
|
*/
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
type GitDelta,
|
|
18
|
+
isReviewVerdict,
|
|
19
|
+
type ReceiptFamily,
|
|
20
|
+
type RecoveryClassification,
|
|
21
|
+
type ReviewVerdict,
|
|
22
|
+
} from "./types";
|
|
17
23
|
|
|
18
24
|
export interface ReceiptSubject {
|
|
19
25
|
workspace: string;
|
|
@@ -144,6 +150,23 @@ export interface CompletionEvidence {
|
|
|
144
150
|
blockers: string[];
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
export interface ReviewVerdictEvidence {
|
|
154
|
+
verdict: ReviewVerdict;
|
|
155
|
+
prTarget: string | null;
|
|
156
|
+
finalizedAt: string;
|
|
157
|
+
/** Bounded summary code/reference for the verdict; never raw assistant text. */
|
|
158
|
+
summaryRef: string | null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ReviewFailureEvidence {
|
|
162
|
+
/** Machine-actionable reason the review produced no terminal verdict. */
|
|
163
|
+
reason: string;
|
|
164
|
+
prTarget: string | null;
|
|
165
|
+
failedAt: string;
|
|
166
|
+
/** Routing hint for the operator/fallback path. */
|
|
167
|
+
fallback: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
147
170
|
function validateFamily(receipt: ReceiptEnvelope<unknown>): string[] {
|
|
148
171
|
switch (receipt.family) {
|
|
149
172
|
case "vanish":
|
|
@@ -154,6 +177,10 @@ function validateFamily(receipt: ReceiptEnvelope<unknown>): string[] {
|
|
|
154
177
|
return validateValidation(receipt.evidence as ValidationEvidence);
|
|
155
178
|
case "completion":
|
|
156
179
|
return validateCompletion(receipt.evidence as CompletionEvidence);
|
|
180
|
+
case "review-verdict":
|
|
181
|
+
return validateReviewVerdict(receipt.evidence as ReviewVerdictEvidence);
|
|
182
|
+
case "review-failure":
|
|
183
|
+
return validateReviewFailure(receipt.evidence as ReviewFailureEvidence);
|
|
157
184
|
default:
|
|
158
185
|
return [`unknown-family:${receipt.family}`];
|
|
159
186
|
}
|
|
@@ -206,6 +233,17 @@ function validateCompletion(e: CompletionEvidence): string[] {
|
|
|
206
233
|
return reasons;
|
|
207
234
|
}
|
|
208
235
|
|
|
236
|
+
function validateReviewVerdict(e: ReviewVerdictEvidence): string[] {
|
|
237
|
+
if (!e) return ["review-verdict-missing-evidence"];
|
|
238
|
+
if (!isReviewVerdict(e.verdict)) return ["review-verdict-not-in-vocabulary"];
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function validateReviewFailure(e: ReviewFailureEvidence): string[] {
|
|
243
|
+
if (!e || typeof e.reason !== "string" || e.reason.length === 0) return ["review-failure-missing-reason"];
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
|
|
209
247
|
/** Classifications that MUST have a valid `vanish` receipt before the action proceeds. */
|
|
210
248
|
export function requiresVanishBeforeAction(classification: RecoveryClassification): boolean {
|
|
211
249
|
return (
|
|
@@ -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 => {
|