@gajae-code/coding-agent 0.3.2 → 0.4.0
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 +32 -0
- package/dist/types/config/model-registry.d.ts +17 -10
- package/dist/types/config/models-config-schema.d.ts +37 -0
- package/dist/types/config/settings-schema.d.ts +5 -0
- package/dist/types/edit/diff.d.ts +16 -0
- package/dist/types/edit/modes/replace.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
- package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
- package/dist/types/extensibility/skills.d.ts +9 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -2
- package/dist/types/harness-control-plane/storage.d.ts +7 -0
- package/dist/types/lsp/client.d.ts +1 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/sdk.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/blob-store.d.ts +17 -0
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/skill-state/active-state.d.ts +13 -0
- package/dist/types/thinking.d.ts +3 -2
- package/dist/types/tools/index.d.ts +3 -0
- package/package.json +9 -7
- package/src/cli.ts +14 -0
- package/src/commands/harness.ts +192 -7
- package/src/commands/ultragoal.ts +1 -21
- package/src/config/model-equivalence.ts +1 -1
- package/src/config/model-registry.ts +32 -5
- package/src/config/models-config-schema.ts +7 -2
- package/src/config/settings-schema.ts +4 -1
- package/src/discovery/claude-plugins.ts +25 -5
- package/src/edit/diff.ts +64 -1
- package/src/edit/modes/replace.ts +60 -2
- package/src/extensibility/gjc-plugins/activation.ts +87 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +114 -0
- package/src/extensibility/gjc-plugins/loader.ts +131 -0
- package/src/extensibility/gjc-plugins/paths.ts +66 -0
- package/src/extensibility/gjc-plugins/schema.ts +79 -0
- package/src/extensibility/gjc-plugins/state.ts +29 -0
- package/src/extensibility/gjc-plugins/tools.ts +47 -0
- package/src/extensibility/gjc-plugins/types.ts +97 -0
- package/src/extensibility/gjc-plugins/validation.ts +76 -0
- package/src/extensibility/skills.ts +39 -7
- package/src/gjc-runtime/state-runtime.ts +93 -2
- package/src/gjc-runtime/state-writer.ts +17 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
- package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
- package/src/gjc-runtime/workflow-manifest.ts +2 -2
- package/src/harness-control-plane/storage.ts +144 -2
- package/src/hashline/hash.ts +23 -0
- package/src/hooks/skill-state.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +7 -0
- package/src/modes/acp/acp-agent.ts +25 -2
- package/src/modes/bridge/bridge-mode.ts +124 -2
- package/src/modes/controllers/input-controller.ts +14 -2
- package/src/modes/prompt-action-autocomplete.ts +49 -10
- package/src/modes/rpc/rpc-client.ts +57 -3
- package/src/modes/rpc/rpc-mode.ts +67 -0
- package/src/modes/rpc/rpc-types.ts +224 -2
- package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
- package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
- package/src/modes/shared/agent-wire/command-validation.ts +25 -1
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
- package/src/modes/shared/agent-wire/handshake.ts +43 -3
- package/src/modes/shared/agent-wire/protocol.ts +7 -0
- package/src/modes/shared/agent-wire/responses.ts +2 -2
- package/src/modes/shared/agent-wire/scopes.ts +2 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/runtime-mcp/client.ts +7 -4
- package/src/runtime-mcp/manager.ts +45 -13
- package/src/runtime-mcp/transports/http.ts +40 -14
- package/src/runtime-mcp/transports/stdio.ts +11 -10
- package/src/sdk.ts +47 -0
- package/src/session/agent-session.ts +211 -2
- package/src/session/blob-store.ts +84 -0
- package/src/session/messages.ts +3 -0
- package/src/session/session-manager.ts +390 -33
- package/src/session/session-storage.ts +26 -0
- package/src/setup/provider-onboarding.ts +2 -2
- package/src/skill-state/active-state.ts +89 -1
- package/src/task/discovery.ts +7 -1
- package/src/task/executor.ts +16 -2
- package/src/thinking.ts +8 -2
- package/src/tools/ask.ts +39 -9
- package/src/tools/index.ts +3 -0
- package/src/tools/skill.ts +15 -3
- package/src/utils/edit-mode.ts +1 -1
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
4
4
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
5
|
-
import { WORKFLOW_STATE_VERSION, workflowStateStoragePath } from "../skill-state/workflow-state-contract";
|
|
6
5
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
7
6
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
8
7
|
import { renderUltragoalStatusMarkdown } from "./state-renderer";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
readExistingStateForMutation,
|
|
12
|
-
writeArtifact,
|
|
13
|
-
writeJsonAtomic,
|
|
14
|
-
writeWorkflowEnvelopeAtomic,
|
|
15
|
-
} from "./state-writer";
|
|
8
|
+
import { reconcileWorkflowSkillState } from "./state-runtime";
|
|
9
|
+
import { appendJsonl, writeArtifact, writeJsonAtomic } from "./state-writer";
|
|
16
10
|
|
|
17
11
|
export type UltragoalGjcGoalMode = "aggregate" | "per-story";
|
|
18
12
|
export type UltragoalGoalStatus =
|
|
@@ -481,93 +475,6 @@ export function buildUltragoalHudSummary(
|
|
|
481
475
|
updatedAt: new Date().toISOString(),
|
|
482
476
|
});
|
|
483
477
|
}
|
|
484
|
-
function currentSessionId(): string | undefined {
|
|
485
|
-
const sessionId = process.env.GJC_SESSION_ID?.trim();
|
|
486
|
-
return sessionId || undefined;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function ultragoalModeStateFromSummary(
|
|
490
|
-
summary: UltragoalStatusSummary,
|
|
491
|
-
latestLedger: UltragoalLedgerEvent | undefined,
|
|
492
|
-
existing: Record<string, unknown> | undefined,
|
|
493
|
-
sessionId: string | undefined,
|
|
494
|
-
): Record<string, unknown> {
|
|
495
|
-
const updatedAt = new Date().toISOString();
|
|
496
|
-
return {
|
|
497
|
-
...(existing ?? {}),
|
|
498
|
-
skill: "ultragoal",
|
|
499
|
-
version: WORKFLOW_STATE_VERSION,
|
|
500
|
-
active: summary.exists && summary.status !== "complete",
|
|
501
|
-
current_phase: summary.status,
|
|
502
|
-
status: summary.status,
|
|
503
|
-
active_goal_id: summary.currentGoal?.id,
|
|
504
|
-
counts: summary.counts,
|
|
505
|
-
brief_path: summary.paths.briefPath,
|
|
506
|
-
ledger_path: summary.paths.ledgerPath,
|
|
507
|
-
goals_path: summary.paths.goalsPath,
|
|
508
|
-
latest_ledger_event: latestLedger?.event,
|
|
509
|
-
latest_ledger_event_id: latestLedger?.eventId,
|
|
510
|
-
updated_at: updatedAt,
|
|
511
|
-
...(sessionId ? { session_id: sessionId } : {}),
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
export async function syncUltragoalWorkflowState(cwd: string): Promise<void> {
|
|
516
|
-
const summary = await getUltragoalStatus(cwd);
|
|
517
|
-
const ledger = await readUltragoalLedger(cwd);
|
|
518
|
-
const latestLedger = ledger.at(-1);
|
|
519
|
-
const sessionId = currentSessionId();
|
|
520
|
-
const modeStateActive = summary.exists && summary.status !== "complete";
|
|
521
|
-
const syncModeState = async (targetSessionId: string | undefined): Promise<void> => {
|
|
522
|
-
const statePath = workflowStateStoragePath(cwd, "ultragoal", targetSessionId);
|
|
523
|
-
const existing = await readExistingStateForMutation(statePath);
|
|
524
|
-
if (existing.kind === "corrupt" && modeStateActive)
|
|
525
|
-
throw new Error(`Cannot sync corrupt ultragoal mode-state: ${existing.error}`);
|
|
526
|
-
const existingValue = existing.kind === "valid" ? existing.value : undefined;
|
|
527
|
-
await writeWorkflowEnvelopeAtomic(
|
|
528
|
-
statePath,
|
|
529
|
-
ultragoalModeStateFromSummary(summary, latestLedger, existingValue, targetSessionId),
|
|
530
|
-
{
|
|
531
|
-
cwd,
|
|
532
|
-
receipt: {
|
|
533
|
-
cwd,
|
|
534
|
-
skill: "ultragoal",
|
|
535
|
-
owner: "gjc-runtime",
|
|
536
|
-
command: "gjc ultragoal sync",
|
|
537
|
-
sessionId: targetSessionId,
|
|
538
|
-
},
|
|
539
|
-
audit: {
|
|
540
|
-
category: "state",
|
|
541
|
-
verb: "sync",
|
|
542
|
-
owner: "gjc-runtime",
|
|
543
|
-
skill: "ultragoal",
|
|
544
|
-
fromPhase: typeof existingValue?.current_phase === "string" ? existingValue.current_phase : undefined,
|
|
545
|
-
toPhase: summary.status,
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
);
|
|
549
|
-
};
|
|
550
|
-
await syncModeState(undefined);
|
|
551
|
-
if (sessionId) await syncModeState(sessionId);
|
|
552
|
-
await syncSkillActiveState({
|
|
553
|
-
cwd,
|
|
554
|
-
skill: "ultragoal",
|
|
555
|
-
active: summary.exists && summary.status !== "complete",
|
|
556
|
-
phase: summary.status,
|
|
557
|
-
sessionId: currentSessionId(),
|
|
558
|
-
hud: buildUltragoalHudSummary(summary, latestLedger),
|
|
559
|
-
source: "gjc-ultragoal",
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async function syncUltragoalWorkflowStateBestEffort(cwd: string): Promise<void> {
|
|
564
|
-
try {
|
|
565
|
-
await syncUltragoalWorkflowState(cwd);
|
|
566
|
-
} catch {
|
|
567
|
-
// HUD and mode-state sync are best-effort and must not change command semantics.
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
478
|
function clampTitle(title: string): string {
|
|
572
479
|
return title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
573
480
|
}
|
|
@@ -1400,22 +1307,18 @@ function renderCompleteHandoff(
|
|
|
1400
1307
|
].join("\n");
|
|
1401
1308
|
}
|
|
1402
1309
|
|
|
1403
|
-
|
|
1310
|
+
async function dispatchUltragoalCommand(args: string[], cwd: string): Promise<UltragoalCommandResult> {
|
|
1404
1311
|
try {
|
|
1405
1312
|
const command = commandName(args);
|
|
1406
1313
|
const json = hasFlag(args, "--json");
|
|
1407
|
-
let result: UltragoalCommandResult;
|
|
1408
1314
|
switch (command) {
|
|
1409
1315
|
case "status":
|
|
1410
|
-
await
|
|
1411
|
-
result = { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd), json) };
|
|
1412
|
-
break;
|
|
1316
|
+
return { status: 0, stdout: renderStatus(await getUltragoalStatus(cwd), json) };
|
|
1413
1317
|
case "create":
|
|
1414
1318
|
case "create-goals": {
|
|
1415
1319
|
const mode = flagValue(args, "--gjc-goal-mode") === "per-story" ? "per-story" : "aggregate";
|
|
1416
1320
|
const plan = await createUltragoalPlan({ cwd, brief: await readBrief(cwd, args), gjcGoalMode: mode });
|
|
1417
|
-
|
|
1418
|
-
result = {
|
|
1321
|
+
return {
|
|
1419
1322
|
status: 0,
|
|
1420
1323
|
createdPlan: true,
|
|
1421
1324
|
stdout: json
|
|
@@ -1427,17 +1330,16 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1427
1330
|
})
|
|
1428
1331
|
: `Created ultragoal plan with ${plan.goals.length} goal${plan.goals.length === 1 ? "" : "s"} at ${getUltragoalPaths(cwd).goalsPath}.\n`,
|
|
1429
1332
|
};
|
|
1430
|
-
break;
|
|
1431
1333
|
}
|
|
1432
|
-
case "complete-goals":
|
|
1433
|
-
|
|
1434
|
-
await syncUltragoalWorkflowStateBestEffort(cwd);
|
|
1435
|
-
result = {
|
|
1334
|
+
case "complete-goals":
|
|
1335
|
+
return {
|
|
1436
1336
|
status: 0,
|
|
1437
|
-
stdout: renderCompleteHandoff(
|
|
1337
|
+
stdout: renderCompleteHandoff(
|
|
1338
|
+
await startNextUltragoalGoal({ cwd, retryFailed: hasFlag(args, "--retry-failed") }),
|
|
1339
|
+
json,
|
|
1340
|
+
cwd,
|
|
1341
|
+
),
|
|
1438
1342
|
};
|
|
1439
|
-
break;
|
|
1440
|
-
}
|
|
1441
1343
|
case "checkpoint": {
|
|
1442
1344
|
const goalId = flagValue(args, "--goal-id") ?? "";
|
|
1443
1345
|
const status = parseGoalStatus(flagValue(args, "--status"));
|
|
@@ -1450,9 +1352,8 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1450
1352
|
gjcGoalJson: flagValue(args, "--gjc-goal-json"),
|
|
1451
1353
|
qualityGateJson: flagValue(args, "--quality-gate-json"),
|
|
1452
1354
|
});
|
|
1453
|
-
await syncUltragoalWorkflowStateBestEffort(cwd);
|
|
1454
1355
|
const goal = plan.goals.find(item => item.id === goalId);
|
|
1455
|
-
|
|
1356
|
+
return {
|
|
1456
1357
|
status: 0,
|
|
1457
1358
|
stdout: json
|
|
1458
1359
|
? renderCliWriteReceipt({
|
|
@@ -1465,7 +1366,6 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1465
1366
|
})
|
|
1466
1367
|
: `ultragoal checkpoint goal-id=${goalId} status=${status}\n`,
|
|
1467
1368
|
};
|
|
1468
|
-
break;
|
|
1469
1369
|
}
|
|
1470
1370
|
case "steer": {
|
|
1471
1371
|
const kind = flagValue(args, "--kind");
|
|
@@ -1477,9 +1377,8 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1477
1377
|
evidence: flagValue(args, "--evidence") ?? "",
|
|
1478
1378
|
rationale: flagValue(args, "--rationale") ?? "",
|
|
1479
1379
|
});
|
|
1480
|
-
await syncUltragoalWorkflowStateBestEffort(cwd);
|
|
1481
1380
|
const goal = plan.goals.at(-1);
|
|
1482
|
-
|
|
1381
|
+
return {
|
|
1483
1382
|
status: 0,
|
|
1484
1383
|
stdout: json
|
|
1485
1384
|
? renderCliWriteReceipt({
|
|
@@ -1490,7 +1389,6 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1490
1389
|
})
|
|
1491
1390
|
: "Accepted add_subgoal steering.\n",
|
|
1492
1391
|
};
|
|
1493
|
-
break;
|
|
1494
1392
|
}
|
|
1495
1393
|
case "record-review-blockers": {
|
|
1496
1394
|
const plan = await recordUltragoalReviewBlockers({
|
|
@@ -1501,21 +1399,78 @@ export async function runNativeUltragoalCommand(args: string[], cwd = process.cw
|
|
|
1501
1399
|
evidence: flagValue(args, "--evidence") ?? "",
|
|
1502
1400
|
gjcGoalJson: flagValue(args, "--gjc-goal-json"),
|
|
1503
1401
|
});
|
|
1504
|
-
await syncUltragoalWorkflowStateBestEffort(cwd);
|
|
1505
1402
|
const goal = plan.goals.at(-1);
|
|
1506
|
-
|
|
1403
|
+
return {
|
|
1507
1404
|
status: 0,
|
|
1508
1405
|
stdout: json
|
|
1509
1406
|
? renderCliWriteReceipt({ ok: true, goal_id: goal?.id, goals_path: getUltragoalPaths(cwd).goalsPath })
|
|
1510
1407
|
: "Recorded review blockers.\n",
|
|
1511
1408
|
};
|
|
1512
|
-
break;
|
|
1513
1409
|
}
|
|
1514
1410
|
default:
|
|
1515
1411
|
return { status: 1, stderr: `Unknown gjc ultragoal command: ${command}\n` };
|
|
1516
1412
|
}
|
|
1517
|
-
return result;
|
|
1518
1413
|
} catch (error) {
|
|
1519
1414
|
return { status: 1, stderr: `${error instanceof Error ? error.message : String(error)}\n` };
|
|
1520
1415
|
}
|
|
1521
1416
|
}
|
|
1417
|
+
|
|
1418
|
+
const RECONCILE_COMMANDS = new Set([
|
|
1419
|
+
"status",
|
|
1420
|
+
"create",
|
|
1421
|
+
"create-goals",
|
|
1422
|
+
"complete-goals",
|
|
1423
|
+
"checkpoint",
|
|
1424
|
+
"steer",
|
|
1425
|
+
"record-review-blockers",
|
|
1426
|
+
]);
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Derive a workflow-state payload from the ultragoal plan/ledger and reconcile the
|
|
1430
|
+
* ultragoal mode-state + active-state/HUD so `gjc state ultragoal read`, the
|
|
1431
|
+
* skill-tool chain guard, and the HUD chip mirror the plan/ledger. Session scope
|
|
1432
|
+
* follows `gjc state` (`GJC_SESSION_ID`). This is a derived repair: it never changes
|
|
1433
|
+
* the triggering command's status/stdout, but a failure is surfaced (stderr + a
|
|
1434
|
+
* `reconcile_failed` ledger audit event) rather than silently swallowed. `status` is
|
|
1435
|
+
* therefore a read PLUS a derived repair; it never mutates goals.json/ledger.jsonl
|
|
1436
|
+
* beyond that reconcile-failure audit event.
|
|
1437
|
+
*/
|
|
1438
|
+
async function reconcileUltragoalState(cwd: string): Promise<void> {
|
|
1439
|
+
const sessionId = process.env.GJC_SESSION_ID?.trim() || undefined;
|
|
1440
|
+
try {
|
|
1441
|
+
const summary = await getUltragoalStatus(cwd);
|
|
1442
|
+
const status = summary.status;
|
|
1443
|
+
const active = summary.exists && status !== "complete";
|
|
1444
|
+
const payload: Record<string, unknown> = {
|
|
1445
|
+
skill: "ultragoal",
|
|
1446
|
+
status,
|
|
1447
|
+
current_phase: status,
|
|
1448
|
+
active,
|
|
1449
|
+
goals: summary.goals.map(goal => ({ id: goal.id, title: goal.title, status: goal.status })),
|
|
1450
|
+
counts: summary.counts,
|
|
1451
|
+
active_goal_id: summary.currentGoal?.id ?? null,
|
|
1452
|
+
ledger_path: summary.paths.ledgerPath,
|
|
1453
|
+
brief_path: summary.paths.briefPath,
|
|
1454
|
+
goals_path: summary.paths.goalsPath,
|
|
1455
|
+
};
|
|
1456
|
+
if (summary.gjcObjective) payload.gjc_objective = summary.gjcObjective;
|
|
1457
|
+
await reconcileWorkflowSkillState({ cwd, mode: "ultragoal", sessionId, active, phase: status, payload });
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1460
|
+
process.stderr.write(`ultragoal state reconciliation failed: ${message}\n`);
|
|
1461
|
+
try {
|
|
1462
|
+
await appendLedger(cwd, { type: "reconcile_failed", error: message });
|
|
1463
|
+
} catch {
|
|
1464
|
+
// Best-effort audit; never let a secondary failure change command semantics.
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
export async function runNativeUltragoalCommand(args: string[], cwd = process.cwd()): Promise<UltragoalCommandResult> {
|
|
1470
|
+
const command = commandName(args);
|
|
1471
|
+
const result = await dispatchUltragoalCommand(args, cwd);
|
|
1472
|
+
if (result.status === 0 && RECONCILE_COMMANDS.has(command)) {
|
|
1473
|
+
await reconcileUltragoalState(cwd);
|
|
1474
|
+
}
|
|
1475
|
+
return result;
|
|
1476
|
+
}
|
|
@@ -1171,6 +1171,10 @@
|
|
|
1171
1171
|
],
|
|
1172
1172
|
"skill": "ultragoal",
|
|
1173
1173
|
"states": [
|
|
1174
|
+
{
|
|
1175
|
+
"id": "missing",
|
|
1176
|
+
"terminal": true
|
|
1177
|
+
},
|
|
1174
1178
|
{
|
|
1175
1179
|
"id": "goal-planning",
|
|
1176
1180
|
"initial": true
|
|
@@ -1198,6 +1202,7 @@
|
|
|
1198
1202
|
}
|
|
1199
1203
|
],
|
|
1200
1204
|
"terminalStates": [
|
|
1205
|
+
"missing",
|
|
1201
1206
|
"failed",
|
|
1202
1207
|
"complete",
|
|
1203
1208
|
"handoff"
|
|
@@ -212,8 +212,8 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
|
|
|
212
212
|
}),
|
|
213
213
|
ultragoal: manifest({
|
|
214
214
|
skill: "ultragoal",
|
|
215
|
-
states: ["goal-planning", "pending", "active", "blocked", "failed", "complete", "handoff"],
|
|
216
|
-
terminalStates: ["failed", "complete", "handoff"],
|
|
215
|
+
states: ["missing", "goal-planning", "pending", "active", "blocked", "failed", "complete", "handoff"],
|
|
216
|
+
terminalStates: ["missing", "failed", "complete", "handoff"],
|
|
217
217
|
transitions: [
|
|
218
218
|
{ from: "goal-planning", to: "pending", verb: "create-goals" },
|
|
219
219
|
{ from: "pending", to: "active", verb: "complete-goals" },
|
|
@@ -20,6 +20,85 @@ import * as os from "node:os";
|
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import type { EventEnvelope, ReceiptFamily, SessionState } from "./types";
|
|
22
22
|
|
|
23
|
+
interface HarnessRootRegistryEntry {
|
|
24
|
+
root: string;
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HarnessRootRegistry {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
roots: HarnessRootRegistryEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ResolveHarnessSessionRootOptions {
|
|
34
|
+
expectedWorkspace?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function canonicalWorkspacePath(workspace: string): string {
|
|
38
|
+
return path.resolve(workspace);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function samePath(left: string, right: string): boolean {
|
|
42
|
+
return canonicalWorkspacePath(left) === canonicalWorkspacePath(right);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function ensurePrivateDir(dir: string): Promise<void> {
|
|
46
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
47
|
+
await fs.chmod(dir, 0o700);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ensurePrivateDirSync(dir: string): void {
|
|
51
|
+
fsSync.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
52
|
+
fsSync.chmodSync(dir, 0o700);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sessionMatchesWorkspace(state: SessionState, expectedWorkspace: string): boolean {
|
|
56
|
+
return samePath(state.handle.workspace, expectedWorkspace);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function harnessRootRegistryDir(env: NodeJS.ProcessEnv = process.env): string {
|
|
60
|
+
const override = env.GJC_HARNESS_ROOT_REGISTRY_DIR?.trim();
|
|
61
|
+
if (override) return path.resolve(override);
|
|
62
|
+
return path.join(os.tmpdir(), `gjch${process.getuid?.() ?? "u"}`, "harness-roots");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function harnessRootRegistryPath(sessionId: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
66
|
+
assertSafeSessionId(sessionId);
|
|
67
|
+
return path.join(harnessRootRegistryDir(env), `${sessionId}.json`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function readHarnessRootRegistry(
|
|
71
|
+
sessionId: string,
|
|
72
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
73
|
+
): Promise<HarnessRootRegistry> {
|
|
74
|
+
const file = harnessRootRegistryPath(sessionId, env);
|
|
75
|
+
try {
|
|
76
|
+
const raw = await fs.readFile(file, "utf8");
|
|
77
|
+
const parsed = JSON.parse(raw) as HarnessRootRegistry;
|
|
78
|
+
if (parsed.sessionId === sessionId && Array.isArray(parsed.roots)) return parsed;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
81
|
+
}
|
|
82
|
+
return { sessionId, roots: [] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function writeJsonAtomicPrivate(file: string, value: unknown): Promise<void> {
|
|
86
|
+
await ensurePrivateDir(path.dirname(file));
|
|
87
|
+
const tmp = `${file}.tmp-${randomBytes(4).toString("hex")}`;
|
|
88
|
+
await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
89
|
+
await fs.rename(tmp, file);
|
|
90
|
+
await fs.chmod(file, 0o600);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function writeHarnessRootRegistry(
|
|
94
|
+
registry: HarnessRootRegistry,
|
|
95
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
const dir = harnessRootRegistryDir(env);
|
|
98
|
+
await ensurePrivateDir(dir);
|
|
99
|
+
const file = harnessRootRegistryPath(registry.sessionId, env);
|
|
100
|
+
await writeJsonAtomicPrivate(file, registry);
|
|
101
|
+
}
|
|
23
102
|
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
24
103
|
export const MAX_UNIX_SOCKET_PATH_BYTES = 100;
|
|
25
104
|
|
|
@@ -36,7 +115,7 @@ function socketBase(env: NodeJS.ProcessEnv, allowOverride: boolean): { base: str
|
|
|
36
115
|
|
|
37
116
|
function socketPathForBase(root: string, sessionId: string, base: string): string {
|
|
38
117
|
const digest = createHash("sha256").update(`${root}\0${sessionId}`).digest("hex");
|
|
39
|
-
|
|
118
|
+
ensurePrivateDirSync(base);
|
|
40
119
|
for (const len of [16, 24, 32, 48, 64]) {
|
|
41
120
|
const stem = `c-${digest.slice(0, len)}`;
|
|
42
121
|
const metadataPath = path.join(base, `${stem}.json`);
|
|
@@ -46,7 +125,10 @@ function socketPathForBase(root: string, sessionId: string, base: string): strin
|
|
|
46
125
|
if (existing.root === root && existing.sessionId === sessionId) return path.join(base, `${stem}.sock`);
|
|
47
126
|
} catch (error) {
|
|
48
127
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
49
|
-
fsSync.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`,
|
|
128
|
+
fsSync.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, {
|
|
129
|
+
encoding: "utf8",
|
|
130
|
+
mode: 0o600,
|
|
131
|
+
});
|
|
50
132
|
return path.join(base, `${stem}.sock`);
|
|
51
133
|
}
|
|
52
134
|
}
|
|
@@ -149,6 +231,66 @@ async function readJson<T>(file: string): Promise<T | null> {
|
|
|
149
231
|
export async function readSessionState(root: string, sessionId: string): Promise<SessionState | null> {
|
|
150
232
|
return readJson<SessionState>(sessionPaths(root, sessionId).state);
|
|
151
233
|
}
|
|
234
|
+
export async function rememberHarnessSessionRoot(
|
|
235
|
+
root: string,
|
|
236
|
+
sessionId: string,
|
|
237
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
238
|
+
): Promise<void> {
|
|
239
|
+
assertSafeSessionId(sessionId);
|
|
240
|
+
const resolvedRoot = path.resolve(root);
|
|
241
|
+
const registry = await readHarnessRootRegistry(sessionId, env);
|
|
242
|
+
const now = new Date().toISOString();
|
|
243
|
+
registry.roots = [
|
|
244
|
+
{ root: resolvedRoot, updatedAt: now },
|
|
245
|
+
...registry.roots.filter(entry => path.resolve(entry.root) !== resolvedRoot),
|
|
246
|
+
].slice(0, 8);
|
|
247
|
+
await writeHarnessRootRegistry(registry, env);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function resolveHarnessSessionRoot(
|
|
251
|
+
root: string,
|
|
252
|
+
sessionId: string,
|
|
253
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
254
|
+
options: ResolveHarnessSessionRootOptions = {},
|
|
255
|
+
): Promise<string> {
|
|
256
|
+
assertSafeSessionId(sessionId);
|
|
257
|
+
const resolvedRoot = path.resolve(root);
|
|
258
|
+
const candidates: { root: string; state: SessionState }[] = [];
|
|
259
|
+
const seenRoots = new Set<string>();
|
|
260
|
+
const addCandidate = async (candidateRoot: string): Promise<void> => {
|
|
261
|
+
const candidate = path.resolve(candidateRoot);
|
|
262
|
+
if (seenRoots.has(candidate)) return;
|
|
263
|
+
seenRoots.add(candidate);
|
|
264
|
+
const state = await readSessionState(candidate, sessionId);
|
|
265
|
+
if (state !== null) candidates.push({ root: candidate, state });
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
await addCandidate(resolvedRoot);
|
|
269
|
+
const registry = await readHarnessRootRegistry(sessionId, env);
|
|
270
|
+
for (const entry of registry.roots) await addCandidate(entry.root);
|
|
271
|
+
|
|
272
|
+
if (!options.expectedWorkspace) {
|
|
273
|
+
if (candidates.some(candidate => candidate.root === resolvedRoot)) return resolvedRoot;
|
|
274
|
+
if (candidates.length === 1) return candidates[0].root;
|
|
275
|
+
if (candidates.length > 1) {
|
|
276
|
+
throw new StorageError(`ambiguous_harness_session_root:${sessionId}`, "ambiguous_harness_session_root");
|
|
277
|
+
}
|
|
278
|
+
return resolvedRoot;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const expectedWorkspace = canonicalWorkspacePath(options.expectedWorkspace);
|
|
282
|
+
const matchingCandidates = candidates.filter(candidate =>
|
|
283
|
+
sessionMatchesWorkspace(candidate.state, expectedWorkspace),
|
|
284
|
+
);
|
|
285
|
+
if (matchingCandidates.length === 1) return matchingCandidates[0].root;
|
|
286
|
+
if (matchingCandidates.length > 1) {
|
|
287
|
+
throw new StorageError(`ambiguous_harness_session_root:${sessionId}`, "ambiguous_harness_session_root");
|
|
288
|
+
}
|
|
289
|
+
if (candidates.length > 0) {
|
|
290
|
+
throw new StorageError(`session_workspace_mismatch:${sessionId}`, "session_workspace_mismatch");
|
|
291
|
+
}
|
|
292
|
+
return resolvedRoot;
|
|
293
|
+
}
|
|
152
294
|
|
|
153
295
|
export async function writeSessionState(root: string, state: SessionState): Promise<void> {
|
|
154
296
|
const paths = sessionPaths(root, state.sessionId);
|
package/src/hashline/hash.ts
CHANGED
|
@@ -5,6 +5,20 @@
|
|
|
5
5
|
|
|
6
6
|
import bigrams from "./bigrams.json" with { type: "json" };
|
|
7
7
|
|
|
8
|
+
// Optional native acceleration for formatHashLines. Loaded WITHOUT throwing at
|
|
9
|
+
// module evaluation so this core module (and its re-exported helpers) stays
|
|
10
|
+
// usable and falls back to the TS loop if the native addon is unavailable.
|
|
11
|
+
let formatHashLinesNative: ((text: string, startLine?: number) => string) | undefined;
|
|
12
|
+
void import("../../../natives/native/index.js")
|
|
13
|
+
.then(mod => {
|
|
14
|
+
if (typeof mod.h06FormatHashLines === "function") {
|
|
15
|
+
formatHashLinesNative = mod.h06FormatHashLines;
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
.catch(() => {
|
|
19
|
+
// Native unavailable; formatHashLines uses the TS loop.
|
|
20
|
+
});
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* 647 single-token BPE bigrams for hashline anchors. Every entry tokenizes as
|
|
10
24
|
* exactly one token in modern BPE vocabularies (cl100k / o200k / Anthropic model family),
|
|
@@ -168,6 +182,15 @@ export function formatHashLine(lineNumber: number, line: string): string {
|
|
|
168
182
|
* ```
|
|
169
183
|
*/
|
|
170
184
|
export function formatHashLines(text: string, startLine = 1): string {
|
|
185
|
+
// Native path only for the supported startLine domain (non-negative integer);
|
|
186
|
+
// other values fall through to JS numeric semantics in the TS loop.
|
|
187
|
+
if (formatHashLinesNative && Number.isInteger(startLine) && startLine >= 0) {
|
|
188
|
+
try {
|
|
189
|
+
return formatHashLinesNative(text, startLine);
|
|
190
|
+
} catch {
|
|
191
|
+
// Native hashline formatting is an optimization only; preserve the TS contract.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
171
194
|
const lines = text.split("\n");
|
|
172
195
|
return lines.map((line, i) => formatHashLine(startLine + i, line)).join("\n");
|
|
173
196
|
}
|
package/src/hooks/skill-state.ts
CHANGED
|
@@ -382,6 +382,8 @@ async function seedSkillActivationState(
|
|
|
382
382
|
return state;
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
// Fallback for native-hook prompts when SkillPromptDetails.subskillActivation is absent;
|
|
386
|
+
// real /skill dispatch paths resolve sub-skill activation before prompt construction.
|
|
385
387
|
export async function recordSkillActivation(input: RecordSkillActivationInput): Promise<SkillActiveState | null> {
|
|
386
388
|
const match = detectPrimarySkillKeyword(input.text);
|
|
387
389
|
if (!match) return null;
|