@gajae-code/coding-agent 0.6.3 → 0.6.5
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 +50 -0
- package/README.md +73 -1
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +27 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +36 -7
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +2 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +7 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/package.json +7 -7
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/setup-cli.ts +14 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/launch.ts +1 -1
- package/src/commands/migrate.ts +46 -0
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-registry.ts +9 -2
- package/src/config/model-resolver.ts +13 -2
- package/src/config/settings-schema.ts +17 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +17 -13
- package/src/exec/bash-executor.ts +3 -1
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +43 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +68 -15
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +230 -121
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +43 -2
- package/src/gjc-runtime/ultragoal-guard.ts +45 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +121 -41
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.ts +1 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +9 -6
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/welcome.ts +42 -9
- package/src/modes/controllers/input-controller.ts +21 -3
- package/src/modes/interactive-mode.ts +22 -1
- package/src/modes/prompt-action-autocomplete.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +7 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/session/agent-session.ts +15 -21
- package/src/session/session-manager.ts +19 -2
- package/src/setup/hermes/templates/operator-instructions.v1.md +8 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +8 -4
- package/src/system-prompt.ts +11 -9
- package/src/task/agents.ts +1 -22
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +34 -12
- package/src/tools/computer.ts +58 -4
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -4,6 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { AgentTool } from "@gajae-code/agent-core";
|
|
5
5
|
import { logger } from "@gajae-code/utils";
|
|
6
6
|
import { expandApplyPatchToEntries } from "../edit/modes/apply-patch";
|
|
7
|
+
import { GJC_SESSION_PREFIX, modeStatePath as sessionModeStatePath } from "../gjc-runtime/session-layout";
|
|
8
|
+
import { resolveGjcSessionForRead } from "../gjc-runtime/session-resolution";
|
|
7
9
|
import { ModeStateSchema } from "../gjc-runtime/state-schema";
|
|
8
10
|
import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol";
|
|
9
11
|
import { resolveToCwd } from "../tools/path-utils";
|
|
@@ -31,27 +33,10 @@ function planningPhaseBlockMessage(skill: CanonicalGjcWorkflowSkill): string {
|
|
|
31
33
|
return DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"
|
|
36
|
+
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"]);
|
|
35
37
|
const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i;
|
|
36
38
|
const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
37
39
|
const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:<CR>|\r|\n|$)/i;
|
|
38
|
-
const BASH_TOKEN_RE = /'[^']*'|"(?:\\.|[^"\\])*"|\S+/g;
|
|
39
|
-
const BASH_REDIRECT_RE = /^(?:\d*)>>?$/;
|
|
40
|
-
const BASH_HEREDOC_RE = /^(?:\d*)<<-?$/;
|
|
41
|
-
// Shell command-list / redirection / substitution operators. Includes `\r` and
|
|
42
|
-
// `\n` because the shell treats a newline as a command separator and tool command
|
|
43
|
-
// strings can be multiline (e.g. heredocs).
|
|
44
|
-
const BASH_CONTROL_OPERATOR_RE = /[;&|<>`\r\n]|\$\(/;
|
|
45
|
-
// Best-effort, defense-in-depth bash mutation detection. The authoritative
|
|
46
|
-
// planning-phase guard is the dedicated `write`/`edit`/`ast_edit` tools (fully
|
|
47
|
-
// pathed); this catches the common shell mutators plus all redirect targets so a
|
|
48
|
-
// cooperative agent cannot trivially side-step those tools. It is deliberately
|
|
49
|
-
// NOT exhaustive: arbitrary interpreters (`python -c`, `node -e`) and the
|
|
50
|
-
// `key=value` operand forms of utilities like `dd of=` are not parsed, and path
|
|
51
|
-
// classification is lexical (no realpath), matching the rest of this guard and
|
|
52
|
-
// the broader `.gjc` path handling. Hardening any of these would require a real
|
|
53
|
-
// shell parser / symlink resolution and is out of scope for the planning rails.
|
|
54
|
-
const BASH_MUTATION_COMMANDS = new Set(["rm", "mv", "cp", "touch", "mkdir", "ln", "tee"]);
|
|
55
40
|
|
|
56
41
|
type ToolWithEditMode = AgentTool & {
|
|
57
42
|
mode?: unknown;
|
|
@@ -93,15 +78,18 @@ function safeString(value: unknown): string {
|
|
|
93
78
|
return typeof value === "string" ? value : "";
|
|
94
79
|
}
|
|
95
80
|
|
|
96
|
-
function
|
|
97
|
-
|
|
81
|
+
async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | null> {
|
|
82
|
+
const normalizedSessionId = sessionId?.trim();
|
|
83
|
+
if (normalizedSessionId) return normalizedSessionId;
|
|
84
|
+
try {
|
|
85
|
+
return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
98
89
|
}
|
|
99
90
|
|
|
100
|
-
function modeStatePath(cwd: string, skill: string, sessionId
|
|
101
|
-
|
|
102
|
-
const fileName = `${skill}-state.json`;
|
|
103
|
-
if (sessionId) return path.join(stateDir, "sessions", encodePathSegment(sessionId), fileName);
|
|
104
|
-
return path.join(stateDir, fileName);
|
|
91
|
+
function modeStatePath(cwd: string, skill: string, sessionId: string): string {
|
|
92
|
+
return sessionModeStatePath(cwd, sessionId, skill);
|
|
105
93
|
}
|
|
106
94
|
|
|
107
95
|
function warnInvalidModeState(filePath: string, error: string): void {
|
|
@@ -129,12 +117,8 @@ async function readValidatedModeState(filePath: string): Promise<ModeState | nul
|
|
|
129
117
|
}
|
|
130
118
|
return state;
|
|
131
119
|
}
|
|
132
|
-
async function readVisibleModeState(cwd: string, skill: string, sessionId
|
|
133
|
-
|
|
134
|
-
const sessionState = await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
|
|
135
|
-
if (sessionState) return sessionState;
|
|
136
|
-
}
|
|
137
|
-
return await readValidatedModeState(modeStatePath(cwd, skill));
|
|
120
|
+
async function readVisibleModeState(cwd: string, skill: string, sessionId: string): Promise<ModeState | null> {
|
|
121
|
+
return await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
|
|
138
122
|
}
|
|
139
123
|
|
|
140
124
|
/**
|
|
@@ -228,16 +212,20 @@ async function getActivePlanningSkill(
|
|
|
228
212
|
sessionId?: string,
|
|
229
213
|
threadId?: string,
|
|
230
214
|
): Promise<ActivePlanningSkill | null> {
|
|
231
|
-
const
|
|
215
|
+
const resolvedSessionId = await resolveBoundarySessionId(cwd, sessionId);
|
|
216
|
+
if (!resolvedSessionId) return null;
|
|
217
|
+
const skillState = await readVisibleSkillActiveState(cwd, resolvedSessionId);
|
|
232
218
|
if (!skillState) return null;
|
|
233
|
-
const activeEntries = listActiveSkills(skillState).filter(entry =>
|
|
219
|
+
const activeEntries = listActiveSkills(skillState).filter(entry =>
|
|
220
|
+
entryMatchesContext(entry, resolvedSessionId, threadId),
|
|
221
|
+
);
|
|
234
222
|
if (activeEntries.length === 0) return null;
|
|
235
223
|
const current = resolveCurrentWorkflowEntry(activeEntries, safeString(skillState.skill).trim());
|
|
236
224
|
if (!isPlanningSkill(current.skill)) return null;
|
|
237
|
-
const modeState = await readVisibleModeState(cwd, current.skill,
|
|
225
|
+
const modeState = await readVisibleModeState(cwd, current.skill, resolvedSessionId);
|
|
238
226
|
if (!modeState) return null;
|
|
239
227
|
if (modeState.active !== true) return null;
|
|
240
|
-
if (!modeStateMatchesContext(modeState,
|
|
228
|
+
if (!modeStateMatchesContext(modeState, resolvedSessionId, threadId)) return null;
|
|
241
229
|
const phase = String(modeState.current_phase ?? current.phase ?? "").trim();
|
|
242
230
|
if (!isBlockingPlanningPhase(current.skill, phase)) return null;
|
|
243
231
|
return { skill: current.skill, phase };
|
|
@@ -342,81 +330,10 @@ function extractEditTargets(args: unknown, tool: ToolWithEditMode): ExtractedTar
|
|
|
342
330
|
return targets;
|
|
343
331
|
}
|
|
344
332
|
|
|
345
|
-
function extractBashTargets(args: unknown): ExtractedTargets {
|
|
346
|
-
const record = getRecord(args);
|
|
347
|
-
const command = safeString(record?.command).trim();
|
|
348
|
-
const targets: ExtractedTargets = { paths: [], unknown: false };
|
|
349
|
-
if (!command) {
|
|
350
|
-
targets.unknown = true;
|
|
351
|
-
return targets;
|
|
352
|
-
}
|
|
353
|
-
// Fast path for a sanctioned `gjc …` invocation, but ONLY when it is a single
|
|
354
|
-
// command with no shell control operators or redirects. Otherwise a compound
|
|
355
|
-
// like `gjc … ; tee src/x` or `gjc … > .gjc/state/foo` would skip scanning and
|
|
356
|
-
// bypass both the planning block and the always-on `.gjc/**` block, so fall
|
|
357
|
-
// through to full token scanning (which leaves the `gjc` segment's own args
|
|
358
|
-
// unextracted but still catches the trailing mutation/redirect).
|
|
359
|
-
if (/^gjc(?:\s|$)/.test(command) && !BASH_CONTROL_OPERATOR_RE.test(command)) return targets;
|
|
360
|
-
|
|
361
|
-
const tokens = command.match(BASH_TOKEN_RE)?.map(unquoteBashToken) ?? [];
|
|
362
|
-
for (let index = 0; index < tokens.length; index++) {
|
|
363
|
-
const token = tokens[index] ?? "";
|
|
364
|
-
if (BASH_REDIRECT_RE.test(token)) {
|
|
365
|
-
addPath(targets, tokens[index + 1]);
|
|
366
|
-
index++;
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
const redirectMatch = token.match(/^(?:\d*)>>?(.+)$/);
|
|
370
|
-
if (redirectMatch?.[1]) {
|
|
371
|
-
addPath(targets, redirectMatch[1]);
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
// A heredoc delimiter (`<<EOF`) is a here-document word, NOT a filesystem
|
|
375
|
-
// target. Consume it without recording a target so a legitimate
|
|
376
|
-
// `cat <<EOF > /tmp/scratch.md` is judged solely by its redirect target.
|
|
377
|
-
if (BASH_HEREDOC_RE.test(token)) {
|
|
378
|
-
index++;
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
if (/^(?:\d*)<<-?.+$/.test(token)) {
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (isMutationBashCommand(tokens, index)) {
|
|
385
|
-
for (let targetIndex = index + 1; targetIndex < tokens.length; targetIndex++) {
|
|
386
|
-
const target = tokens[targetIndex] ?? "";
|
|
387
|
-
if (isBashCommandBoundary(target)) break;
|
|
388
|
-
if (target.startsWith("-")) continue;
|
|
389
|
-
addPath(targets, target);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return targets;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function unquoteBashToken(token: string): string {
|
|
397
|
-
if (token.length < 2) return token;
|
|
398
|
-
const quote = token[0];
|
|
399
|
-
if ((quote === "'" || quote === '"') && token.at(-1) === quote) return token.slice(1, -1);
|
|
400
|
-
return token;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function isBashCommandBoundary(token: string): boolean {
|
|
404
|
-
return [";", "&&", "||", "|"].includes(token);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function isMutationBashCommand(tokens: string[], index: number): boolean {
|
|
408
|
-
const token = path.basename(tokens[index] ?? "");
|
|
409
|
-
if (BASH_MUTATION_COMMANDS.has(token)) return true;
|
|
410
|
-
if (token !== "sed") return false;
|
|
411
|
-
const next = tokens[index + 1] ?? "";
|
|
412
|
-
return next === "-i" || next.startsWith("-i") || next.includes("i");
|
|
413
|
-
}
|
|
414
|
-
|
|
415
333
|
function extractTargets(tool: ToolWithEditMode, args: unknown): ExtractedTargets {
|
|
416
334
|
if (tool.name === "write") return extractWriteTargets(args);
|
|
417
335
|
if (tool.name === "ast_edit") return extractAstEditTargets(args);
|
|
418
336
|
if (tool.name === "edit") return extractEditTargets(args, tool);
|
|
419
|
-
if (tool.name === "bash") return extractBashTargets(args);
|
|
420
337
|
return { paths: [], unknown: true };
|
|
421
338
|
}
|
|
422
339
|
|
|
@@ -460,8 +377,9 @@ function relativeGjcSegments(cwd: string, rawPath: string): string[] | null {
|
|
|
460
377
|
function blockedWorkflowStateSkill(cwd: string, rawPath: string): CanonicalGjcWorkflowSkill | null {
|
|
461
378
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
462
379
|
if (segments?.[0] !== ".gjc") return null;
|
|
463
|
-
|
|
464
|
-
if (
|
|
380
|
+
const generatedRoot = segments[1]?.startsWith(GJC_SESSION_PREFIX) ? segments[2] : segments[1];
|
|
381
|
+
if (generatedRoot === "specs" || generatedRoot === "plans") return null;
|
|
382
|
+
if (generatedRoot !== "state") return null;
|
|
465
383
|
const fileName = segments.at(-1) ?? "";
|
|
466
384
|
for (const skillName of ["deep-interview", "ralplan", "ultragoal", "team"] as const) {
|
|
467
385
|
if (fileName === workflowModeStateFileName(skillName)) return skillName;
|
|
@@ -481,7 +399,8 @@ function firstBlockedWorkflowStateSkill(cwd: string, targets: ExtractedTargets):
|
|
|
481
399
|
function isAllowlistedPath(cwd: string, rawPath: string): boolean {
|
|
482
400
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
483
401
|
if (segments?.[0] !== ".gjc") return false;
|
|
484
|
-
|
|
402
|
+
const generatedRoot = segments[1]?.startsWith(GJC_SESSION_PREFIX) ? segments[2] : segments[1];
|
|
403
|
+
return generatedRoot === "specs" || generatedRoot === "plans";
|
|
485
404
|
}
|
|
486
405
|
function isBlockedGjcPath(cwd: string, rawPath: string): boolean {
|
|
487
406
|
const segments = relativeGjcSegments(cwd, rawPath);
|
|
@@ -40,7 +40,7 @@ interface UltragoalHudState extends WorkflowGateHudState {
|
|
|
40
40
|
currentGoal?: UltragoalLikeGoal;
|
|
41
41
|
counts: Record<string, number>;
|
|
42
42
|
goals: UltragoalLikeGoal[];
|
|
43
|
-
latestLedgerEvent?: { event?: string; goalId?: string; timestamp?: string };
|
|
43
|
+
latestLedgerEvent?: { event?: string; goalId?: string; timestamp?: string; kind?: string; evidence?: string };
|
|
44
44
|
updatedAt?: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -237,7 +237,9 @@ export function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudS
|
|
|
237
237
|
chip(
|
|
238
238
|
"ledger",
|
|
239
239
|
state.latestLedgerEvent?.event
|
|
240
|
-
? [state.latestLedgerEvent.event, state.latestLedgerEvent.goalId]
|
|
240
|
+
? [state.latestLedgerEvent.event, state.latestLedgerEvent.kind, state.latestLedgerEvent.goalId]
|
|
241
|
+
.filter(Boolean)
|
|
242
|
+
.join(":")
|
|
241
243
|
: undefined,
|
|
242
244
|
35,
|
|
243
245
|
),
|
|
@@ -141,10 +141,10 @@ export function sanctionedWorkflowStateCommand(skill: CanonicalGjcWorkflowSkill)
|
|
|
141
141
|
export function describeWorkflowStateContract(skill: CanonicalGjcWorkflowSkill): string[] {
|
|
142
142
|
return [
|
|
143
143
|
`Sanctioned mutation path: gjc state ${skill} read|write --input '<json>'`,
|
|
144
|
-
`Canonical active HUD state: .gjc/
|
|
145
|
-
`Skill mode state: .gjc/
|
|
144
|
+
`Canonical active HUD state: .gjc/_session-{sessionid}/state/${SKILL_ACTIVE_STATE_FILE}`,
|
|
145
|
+
`Skill mode state: .gjc/_session-{sessionid}/state/${workflowModeStateFileName(skill)}`,
|
|
146
146
|
"Receipts include version, skill, owner, command, state_path, storage_path, mutated_at, fresh_until, status, and mutation_id.",
|
|
147
147
|
"Receipts are fresh for 30 minutes; older receipts are stale and render as HUD warnings.",
|
|
148
|
-
"Planning artifacts under .gjc/specs/** and .gjc/plans/** remain writable outside the state command.",
|
|
148
|
+
"Planning artifacts under .gjc/_session-{sessionid}/specs/** and .gjc/_session-{sessionid}/plans/** remain writable outside the state command.",
|
|
149
149
|
];
|
|
150
150
|
}
|
|
@@ -250,11 +250,15 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
250
250
|
inlineHint: "[objective]",
|
|
251
251
|
allowArgs: true,
|
|
252
252
|
handleTui: async (command, runtime) => {
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
|
|
253
|
+
// The goal command always consumes the typed input: it either submits
|
|
254
|
+
// the bare objective (never the literal `/goal …` text the user typed)
|
|
255
|
+
// or shows a warning, so the normal submission path never records it in
|
|
256
|
+
// input history. Preserve the typed command whenever args were supplied
|
|
257
|
+
// — including the first-time `/goal set <objective>` case where goal
|
|
258
|
+
// mode was not yet active. A previous `wasGoalModeEnabled` guard dropped
|
|
259
|
+
// that first-time case from history (up/down-arrow recall).
|
|
256
260
|
await runtime.ctx.handleGoalModeCommand(command.args || undefined);
|
|
257
|
-
if (
|
|
261
|
+
if (command.args) {
|
|
258
262
|
runtime.ctx.editor.addToHistory(command.text);
|
|
259
263
|
}
|
|
260
264
|
runtime.ctx.editor.setText("");
|
package/src/system-prompt.ts
CHANGED
|
@@ -253,15 +253,17 @@ export async function loadProjectContextFiles(
|
|
|
253
253
|
|
|
254
254
|
const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
|
|
255
255
|
|
|
256
|
-
// Convert ContextFile items and preserve depth info
|
|
257
|
-
const files = result.items
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
256
|
+
// Convert project-level ContextFile items and preserve depth info
|
|
257
|
+
const files = result.items
|
|
258
|
+
.filter(item => (item as ContextFile).level === "project")
|
|
259
|
+
.map(item => {
|
|
260
|
+
const contextFile = item as ContextFile;
|
|
261
|
+
return {
|
|
262
|
+
path: contextFile.path,
|
|
263
|
+
content: contextFile.content,
|
|
264
|
+
depth: contextFile.depth,
|
|
265
|
+
};
|
|
266
|
+
});
|
|
265
267
|
|
|
266
268
|
// Sort by depth (descending): higher depth (farther from cwd) comes first,
|
|
267
269
|
// so files closer to cwd appear later and are more prominent
|
package/src/task/agents.ts
CHANGED
|
@@ -3,20 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
|
5
5
|
*/
|
|
6
|
-
import { Effort } from "@gajae-code/ai";
|
|
7
6
|
import { parseFrontmatter, prompt } from "@gajae-code/utils";
|
|
8
7
|
import { parseAgentFields } from "../discovery/helpers";
|
|
8
|
+
// Embed agent markdown files at build time
|
|
9
9
|
import architectMd from "../prompts/agents/architect.md" with { type: "text" };
|
|
10
10
|
import criticMd from "../prompts/agents/critic.md" with { type: "text" };
|
|
11
11
|
import executorMd from "../prompts/agents/executor.md" with { type: "text" };
|
|
12
|
-
import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
|
|
13
|
-
// Embed agent markdown files at build time
|
|
14
12
|
import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
|
|
15
|
-
|
|
16
|
-
import planMd from "../prompts/agents/plan.md" with { type: "text" };
|
|
17
13
|
import plannerMd from "../prompts/agents/planner.md" with { type: "text" };
|
|
18
|
-
import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
|
|
19
|
-
import taskMd from "../prompts/agents/task.md" with { type: "text" };
|
|
20
14
|
|
|
21
15
|
import type { AgentDefinition, AgentSource } from "./types";
|
|
22
16
|
|
|
@@ -50,21 +44,6 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
|
|
50
44
|
{ fileName: "architect.md", template: architectMd },
|
|
51
45
|
{ fileName: "planner.md", template: plannerMd },
|
|
52
46
|
{ fileName: "critic.md", template: criticMd },
|
|
53
|
-
{ fileName: "explore.md", template: exploreMd },
|
|
54
|
-
{ fileName: "plan.md", template: planMd },
|
|
55
|
-
{ fileName: "reviewer.md", template: reviewerMd },
|
|
56
|
-
{
|
|
57
|
-
fileName: "task.md",
|
|
58
|
-
frontmatter: {
|
|
59
|
-
name: "task",
|
|
60
|
-
description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
|
|
61
|
-
spawns: "*",
|
|
62
|
-
model: "pi/default",
|
|
63
|
-
thinkingLevel: Effort.Medium,
|
|
64
|
-
hide: true,
|
|
65
|
-
},
|
|
66
|
-
template: taskMd,
|
|
67
|
-
},
|
|
68
47
|
];
|
|
69
48
|
|
|
70
49
|
// Computed lazily on first loadBundledAgents() call to avoid eager prompt.render at module load.
|
package/src/task/index.ts
CHANGED
|
@@ -55,7 +55,7 @@ import { assertNoRawTaskFields, buildTaskReceipt, buildTaskRoiSummary } from "./
|
|
|
55
55
|
import { renderResult, renderCall as renderTaskCall } from "./render";
|
|
56
56
|
import { reconcileSpawnRoi } from "./roi-reconciliation";
|
|
57
57
|
import { getTaskSimpleModeCapabilities, type TaskSimpleMode } from "./simple-mode";
|
|
58
|
-
import { DEFAULT_SPAWN_THRESHOLD,
|
|
58
|
+
import { DEFAULT_SPAWN_THRESHOLD, evaluateSpawnGate } from "./spawn-gate";
|
|
59
59
|
import {
|
|
60
60
|
applyNestedPatches,
|
|
61
61
|
captureBaseline,
|
|
@@ -359,7 +359,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
359
359
|
readonly renderResult = renderResult;
|
|
360
360
|
readonly #discoveredAgents: AgentDefinition[];
|
|
361
361
|
readonly #blockedAgent: string | undefined;
|
|
362
|
-
readonly #spawningAgentType: string | undefined;
|
|
363
362
|
|
|
364
363
|
get parameters(): TaskToolSchemaInstance {
|
|
365
364
|
const isolationEnabled = this.session.settings.get("task.isolation.mode") !== "none";
|
|
@@ -391,7 +390,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
391
390
|
discoveredAgents: AgentDefinition[],
|
|
392
391
|
) {
|
|
393
392
|
this.#blockedAgent = $env.PI_BLOCKED_AGENT;
|
|
394
|
-
this.#spawningAgentType = session.currentAgentType;
|
|
395
393
|
this.#discoveredAgents = discoveredAgents;
|
|
396
394
|
}
|
|
397
395
|
|
|
@@ -478,23 +476,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
478
476
|
};
|
|
479
477
|
}
|
|
480
478
|
|
|
481
|
-
const reviewerExploreDecision = evaluateReviewerExploreGate({
|
|
482
|
-
spawningAgentType: this.#spawningAgentType,
|
|
483
|
-
targetAgent: params.agent,
|
|
484
|
-
plan: params.spawnPlan,
|
|
485
|
-
});
|
|
486
|
-
if (reviewerExploreDecision.outcome === "rejected") {
|
|
487
|
-
return {
|
|
488
|
-
content: [
|
|
489
|
-
{
|
|
490
|
-
type: "text",
|
|
491
|
-
text: `Task spawn gate rejected reviewer->explore: ${reviewerExploreDecision.reason}. Provide spawnPlan fields: ${reviewerExploreDecision.missingFields.join(", ")}.`,
|
|
492
|
-
},
|
|
493
|
-
],
|
|
494
|
-
details: { projectAgentsDir: null, results: [], totalDurationMs: 0 },
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
479
|
const manager = AsyncJobManager.instance();
|
|
499
480
|
if (!manager) {
|
|
500
481
|
return {
|
|
@@ -1083,27 +1064,6 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1083
1064
|
};
|
|
1084
1065
|
}
|
|
1085
1066
|
|
|
1086
|
-
const reviewerExploreDecision = evaluateReviewerExploreGate({
|
|
1087
|
-
spawningAgentType: this.#spawningAgentType,
|
|
1088
|
-
targetAgent: agentName,
|
|
1089
|
-
plan: params.spawnPlan,
|
|
1090
|
-
});
|
|
1091
|
-
if (reviewerExploreDecision.outcome === "rejected") {
|
|
1092
|
-
return {
|
|
1093
|
-
content: [
|
|
1094
|
-
{
|
|
1095
|
-
type: "text",
|
|
1096
|
-
text: `Task spawn gate rejected reviewer->explore: ${reviewerExploreDecision.reason}. Provide spawnPlan fields: ${reviewerExploreDecision.missingFields.join(", ")}.`,
|
|
1097
|
-
},
|
|
1098
|
-
],
|
|
1099
|
-
details: {
|
|
1100
|
-
projectAgentsDir,
|
|
1101
|
-
results: [],
|
|
1102
|
-
totalDurationMs: Date.now() - startTime,
|
|
1103
|
-
},
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
1067
|
let repoRoot: string | null = null;
|
|
1108
1068
|
let baseline: WorktreeBaseline | null = null;
|
|
1109
1069
|
if (isIsolated) {
|
package/src/task/spawn-gate.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The hard, locked batch threshold enforced by the runtime gate. */
|
|
2
2
|
export const DEFAULT_SPAWN_THRESHOLD = 4;
|
|
3
3
|
|
|
4
|
-
/** The justification a large batch
|
|
4
|
+
/** The justification a large batch must supply to pass the hard gate. */
|
|
5
5
|
export interface SpawnPlanReceipt {
|
|
6
6
|
whyParallel: string;
|
|
7
7
|
whyNotLocal: string;
|
|
@@ -17,15 +17,6 @@ export interface SpawnGateRequest {
|
|
|
17
17
|
plan?: SpawnPlanReceipt;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export interface ReviewerExploreGateRequest {
|
|
21
|
-
/** Agent type/name doing the spawning, when known. */
|
|
22
|
-
spawningAgentType?: string | null;
|
|
23
|
-
/** Target agent type/name requested by the task call. */
|
|
24
|
-
targetAgent: string;
|
|
25
|
-
/** The spawn-plan receipt, when provided. */
|
|
26
|
-
plan?: SpawnPlanReceipt;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
20
|
export type SpawnGateOutcome = "allowed" | "rejected";
|
|
30
21
|
|
|
31
22
|
export interface SpawnGateDecision {
|
|
@@ -102,31 +93,3 @@ export function decide(childCount: number, threshold: number, plan: SpawnPlanRec
|
|
|
102
93
|
export function evaluateSpawnGate(request: SpawnGateRequest): SpawnGateDecision {
|
|
103
94
|
return decide(request.childCount, DEFAULT_SPAWN_THRESHOLD, request.plan);
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
export function evaluateReviewerExploreGate(request: ReviewerExploreGateRequest): SpawnGateDecision {
|
|
107
|
-
if (request.spawningAgentType !== "reviewer" || request.targetAgent !== "explore") {
|
|
108
|
-
return {
|
|
109
|
-
outcome: "allowed",
|
|
110
|
-
reason: "reviewer->explore gate does not apply",
|
|
111
|
-
planRequired: false,
|
|
112
|
-
missingFields: [],
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const missingFields = findMissingPlanFields(request.plan);
|
|
117
|
-
if (missingFields.length > 0) {
|
|
118
|
-
return {
|
|
119
|
-
outcome: "rejected",
|
|
120
|
-
reason: `reviewer->explore spawn requires a complete spawn-plan receipt (${missingFields.join(", ")})`,
|
|
121
|
-
planRequired: true,
|
|
122
|
-
missingFields,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
outcome: "allowed",
|
|
128
|
-
reason: "reviewer->explore spawn has a complete spawn-plan receipt",
|
|
129
|
-
planRequired: true,
|
|
130
|
-
missingFields: [],
|
|
131
|
-
};
|
|
132
|
-
}
|
package/src/task/types.ts
CHANGED
|
@@ -72,7 +72,7 @@ const spawnPlanSchema = z
|
|
|
72
72
|
expectedReceiptShape: z.string(),
|
|
73
73
|
maxInlineTokens: z.number(),
|
|
74
74
|
})
|
|
75
|
-
.describe("justification required before spawning more than four tasks
|
|
75
|
+
.describe("justification required before spawning more than four tasks");
|
|
76
76
|
|
|
77
77
|
const createTaskItemSchema = (_contextEnabled: boolean) =>
|
|
78
78
|
z.object({
|
package/src/tools/ask.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
renderDeepInterviewAskQuestion,
|
|
35
35
|
} from "../deep-interview/render-middleware";
|
|
36
36
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
37
|
-
import { appendOrMergeDeepInterviewRound } from "../gjc-runtime/deep-interview-recorder";
|
|
37
|
+
import { appendOrMergeDeepInterviewRound, syncDeepInterviewRecorderHud } from "../gjc-runtime/deep-interview-recorder";
|
|
38
38
|
import { deepInterviewStatePath } from "../gjc-runtime/deep-interview-runtime";
|
|
39
39
|
import { gateAnswerToResult, questionToGate } from "../modes/shared/agent-wire/deep-interview-gate";
|
|
40
40
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
@@ -104,6 +104,30 @@ export interface AskToolDetails {
|
|
|
104
104
|
const OTHER_OPTION = "Other (type your own)";
|
|
105
105
|
const RECOMMENDED_SUFFIX = " (Recommended)";
|
|
106
106
|
const DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS = Number.MAX_SAFE_INTEGER;
|
|
107
|
+
const DEEP_INTERVIEW_RECORDER_AWAIT_TIMEOUT_MS = 250;
|
|
108
|
+
|
|
109
|
+
function errorMessage(error: unknown): string {
|
|
110
|
+
return error instanceof Error ? error.message : String(error);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function awaitDeepInterviewRecorderPersistence(persistence: Promise<void>): Promise<void> {
|
|
114
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
115
|
+
try {
|
|
116
|
+
await Promise.race([
|
|
117
|
+
persistence,
|
|
118
|
+
new Promise<never>((_resolve, reject) => {
|
|
119
|
+
timeout = setTimeout(
|
|
120
|
+
() => reject(new Error(`timed out after ${DEEP_INTERVIEW_RECORDER_AWAIT_TIMEOUT_MS}ms`)),
|
|
121
|
+
DEEP_INTERVIEW_RECORDER_AWAIT_TIMEOUT_MS,
|
|
122
|
+
);
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.warn(`ask: deep-interview round recording failed: ${errorMessage(error)}`);
|
|
127
|
+
} finally {
|
|
128
|
+
if (timeout) clearTimeout(timeout);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
107
131
|
|
|
108
132
|
function getDoneOptionLabel(): string {
|
|
109
133
|
return `${theme.status.success} Done selecting`;
|
|
@@ -481,11 +505,11 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
481
505
|
): Promise<void> {
|
|
482
506
|
const meta = q.deepInterview;
|
|
483
507
|
if (!meta) return;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
508
|
+
const cwd = this.session.cwd;
|
|
509
|
+
const sessionId = this.session.getSessionId?.() ?? undefined;
|
|
510
|
+
const statePath = deepInterviewStatePath(cwd, sessionId);
|
|
511
|
+
await awaitDeepInterviewRecorderPersistence(
|
|
512
|
+
appendOrMergeDeepInterviewRound(
|
|
489
513
|
cwd,
|
|
490
514
|
statePath,
|
|
491
515
|
{
|
|
@@ -500,12 +524,10 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
500
524
|
customInput,
|
|
501
525
|
},
|
|
502
526
|
{ sessionId },
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
);
|
|
508
|
-
}
|
|
527
|
+
).then(async () => {
|
|
528
|
+
await syncDeepInterviewRecorderHud(cwd, statePath, sessionId);
|
|
529
|
+
}),
|
|
530
|
+
);
|
|
509
531
|
}
|
|
510
532
|
|
|
511
533
|
async execute(
|