@gajae-code/coding-agent 0.2.5 → 0.3.1
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 +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +23 -29
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +198 -14
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +372 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +600 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +98 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +197 -64
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +423 -79
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
Snowflake,
|
|
28
28
|
toError,
|
|
29
29
|
} from "@gajae-code/utils";
|
|
30
|
+
import { writeTextAtomic } from "../gjc-runtime/state-writer";
|
|
30
31
|
import { ArtifactManager } from "./artifacts";
|
|
31
32
|
import {
|
|
32
33
|
type BlobPutResult,
|
|
@@ -56,6 +57,10 @@ import type { SessionStorage, SessionStorageWriter } from "./session-storage";
|
|
|
56
57
|
import { FileSessionStorage, MemorySessionStorage } from "./session-storage";
|
|
57
58
|
|
|
58
59
|
export const CURRENT_SESSION_VERSION = 3;
|
|
60
|
+
function isUnderProjectGjc(cwd: string, targetPath: string): boolean {
|
|
61
|
+
const relative = path.relative(path.join(path.resolve(cwd), ".gjc"), path.resolve(targetPath));
|
|
62
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
export interface SessionHeader {
|
|
61
66
|
type: "session";
|
|
@@ -384,6 +389,7 @@ const migratedSessionRoots = new Set<string>();
|
|
|
384
389
|
* Best effort: callers decide whether migration failures should surface.
|
|
385
390
|
*/
|
|
386
391
|
function migrateSessionDirPath(oldPath: string, newPath: string): void {
|
|
392
|
+
// Session-dir lifecycle migration: moves/removes whole directories, not file content writes.
|
|
387
393
|
const existing = fs.statSync(newPath, { throwIfNoEntry: false });
|
|
388
394
|
if (existing?.isDirectory()) {
|
|
389
395
|
for (const file of fs.readdirSync(oldPath)) {
|
|
@@ -752,7 +758,13 @@ function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void {
|
|
|
752
758
|
const breadcrumbFile = path.join(breadcrumbDir, terminalId);
|
|
753
759
|
const content = `${cwd}\n${sessionFile}\n`;
|
|
754
760
|
// Best-effort — don't break session creation if breadcrumb fails
|
|
755
|
-
|
|
761
|
+
const write = isUnderProjectGjc(cwd, breadcrumbFile)
|
|
762
|
+
? writeTextAtomic(breadcrumbFile, content, {
|
|
763
|
+
cwd,
|
|
764
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
|
|
765
|
+
})
|
|
766
|
+
: Bun.write(breadcrumbFile, content);
|
|
767
|
+
write.catch(() => {});
|
|
756
768
|
}
|
|
757
769
|
|
|
758
770
|
/**
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises";
|
|
2
1
|
import * as path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
type ActiveSessionScope,
|
|
4
|
+
rebuildActiveSnapshot,
|
|
5
|
+
removeActiveEntry,
|
|
6
|
+
writeActiveEntry,
|
|
7
|
+
} from "../gjc-runtime/state-writer";
|
|
3
8
|
import type { WorkflowStateReceipt } from "./workflow-state-contract";
|
|
4
9
|
|
|
5
10
|
export const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
|
|
@@ -56,6 +61,8 @@ export interface SkillActiveState {
|
|
|
56
61
|
session_id?: string;
|
|
57
62
|
thread_id?: string;
|
|
58
63
|
turn_id?: string;
|
|
64
|
+
initialized_mode?: CanonicalGjcWorkflowSkill;
|
|
65
|
+
initialized_state_path?: string;
|
|
59
66
|
active_skills?: SkillActiveEntry[];
|
|
60
67
|
[key: string]: unknown;
|
|
61
68
|
}
|
|
@@ -286,14 +293,6 @@ export function getSkillActiveStatePaths(cwd: string, sessionId?: string): Skill
|
|
|
286
293
|
};
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
async function readStateFile(filePath: string): Promise<SkillActiveState | null> {
|
|
290
|
-
try {
|
|
291
|
-
return normalizeSkillActiveState(JSON.parse(await Bun.file(filePath).text()));
|
|
292
|
-
} catch {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
296
|
/**
|
|
298
297
|
* Raw read for handoff mutations. Returns the *unnormalized* parsed object so
|
|
299
298
|
* inactive entries remain visible to `rawActiveEntries` — `normalizeSkillActiveState`
|
|
@@ -516,15 +515,41 @@ export async function readVisibleSkillActiveState(cwd: string, sessionId?: strin
|
|
|
516
515
|
};
|
|
517
516
|
}
|
|
518
517
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
518
|
+
function activeStateWriterAudit(verb: string) {
|
|
519
|
+
return { category: "state" as const, verb, owner: "gjc-runtime" as const };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function persistActiveEntry(
|
|
523
|
+
cwd: string,
|
|
524
|
+
sessionScope: ActiveSessionScope | undefined,
|
|
525
|
+
entry: SkillActiveEntry,
|
|
526
|
+
): Promise<void> {
|
|
527
|
+
if (entry.active === false) {
|
|
528
|
+
await removeActiveEntry(cwd, sessionScope, entry.skill, {
|
|
529
|
+
cwd,
|
|
530
|
+
audit: activeStateWriterAudit("remove-active-entry"),
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
|
|
534
|
+
cwd,
|
|
535
|
+
audit: activeStateWriterAudit("write-active-entry"),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
522
538
|
}
|
|
523
539
|
|
|
524
|
-
function
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
540
|
+
async function writeHandoffEntry(
|
|
541
|
+
cwd: string,
|
|
542
|
+
sessionScope: ActiveSessionScope | undefined,
|
|
543
|
+
entry: SkillActiveEntry,
|
|
544
|
+
): Promise<void> {
|
|
545
|
+
await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
|
|
546
|
+
cwd,
|
|
547
|
+
audit: activeStateWriterAudit("write-active-entry"),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function rebuildActiveState(cwd: string, sessionScope?: ActiveSessionScope): Promise<void> {
|
|
552
|
+
await rebuildActiveSnapshot(cwd, sessionScope, { cwd, audit: activeStateWriterAudit("rebuild-active-snapshot") });
|
|
528
553
|
}
|
|
529
554
|
|
|
530
555
|
export async function syncSkillActiveState(options: SyncSkillActiveStateOptions): Promise<void> {
|
|
@@ -545,36 +570,13 @@ export async function syncSkillActiveState(options: SyncSkillActiveStateOptions)
|
|
|
545
570
|
...(hud ? { hud } : {}),
|
|
546
571
|
...(options.receipt ? { receipt: options.receipt } : {}),
|
|
547
572
|
};
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const rootEntries = upsertEntry(listActiveSkills(rootState), entry, options.active);
|
|
551
|
-
const nextRoot: SkillActiveState = {
|
|
552
|
-
...rootState,
|
|
553
|
-
version: 1,
|
|
554
|
-
active: rootEntries.length > 0,
|
|
555
|
-
skill: rootEntries[0]?.skill ?? "",
|
|
556
|
-
phase: rootEntries[0]?.phase ?? "",
|
|
557
|
-
updated_at: nowIso,
|
|
558
|
-
source: options.source,
|
|
559
|
-
active_skills: rootEntries,
|
|
560
|
-
};
|
|
561
|
-
await writeStateFile(rootPath, nextRoot);
|
|
573
|
+
await persistActiveEntry(options.cwd, undefined, entry);
|
|
574
|
+
await rebuildActiveState(options.cwd);
|
|
562
575
|
|
|
563
|
-
if (!
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
...sessionState,
|
|
568
|
-
version: 1,
|
|
569
|
-
active: sessionEntries.length > 0,
|
|
570
|
-
skill: sessionEntries[0]?.skill ?? "",
|
|
571
|
-
phase: sessionEntries[0]?.phase ?? "",
|
|
572
|
-
session_id: options.sessionId,
|
|
573
|
-
updated_at: nowIso,
|
|
574
|
-
source: options.source,
|
|
575
|
-
active_skills: sessionEntries,
|
|
576
|
-
};
|
|
577
|
-
await writeStateFile(sessionPath, nextSession);
|
|
576
|
+
if (!options.sessionId) return;
|
|
577
|
+
const sessionScope = { sessionId: options.sessionId };
|
|
578
|
+
await persistActiveEntry(options.cwd, sessionScope, entry);
|
|
579
|
+
await rebuildActiveState(options.cwd, sessionScope);
|
|
578
580
|
}
|
|
579
581
|
|
|
580
582
|
export interface ApplyHandoffOptions {
|
|
@@ -604,6 +606,7 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
|
|
|
604
606
|
const sessionId = options.callee.sessionId ?? options.caller.sessionId;
|
|
605
607
|
const { rootPath, sessionPath } = getSkillActiveStatePaths(options.cwd, sessionId);
|
|
606
608
|
const readState = (filePath: string) => readRawActiveStateForHandoff(filePath, options.strict === true);
|
|
609
|
+
await Promise.all([readState(rootPath), ...(sessionPath ? [readState(sessionPath)] : [])]);
|
|
607
610
|
|
|
608
611
|
// A skill can hold more than one visible row in this session's scope — e.g.
|
|
609
612
|
// it was seeded without a session id (rendered globally) and is now handed
|
|
@@ -637,33 +640,23 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
|
|
|
637
640
|
: callerEntry;
|
|
638
641
|
return [...kept, mergedCaller, calleeEntry];
|
|
639
642
|
};
|
|
640
|
-
const
|
|
643
|
+
const writeEntries = async (
|
|
644
|
+
sessionScope: ActiveSessionScope | undefined,
|
|
641
645
|
prior: SkillActiveState | null,
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
version: 1,
|
|
649
|
-
active: visible.length > 0,
|
|
650
|
-
skill: visible[0]?.skill ?? "",
|
|
651
|
-
phase: visible[0]?.phase ?? "",
|
|
652
|
-
...(scope === "session" ? { session_id: sessionId } : {}),
|
|
653
|
-
updated_at: nowIso,
|
|
654
|
-
source: options.callee.source ?? options.caller.source,
|
|
655
|
-
active_skills: entries,
|
|
656
|
-
};
|
|
646
|
+
): Promise<void> => {
|
|
647
|
+
const nextEntries = applyEntries(rawActiveEntries(prior));
|
|
648
|
+
for (const entry of nextEntries) {
|
|
649
|
+
await writeHandoffEntry(options.cwd, sessionScope, entry);
|
|
650
|
+
}
|
|
651
|
+
await rebuildActiveState(options.cwd, sessionScope);
|
|
657
652
|
};
|
|
658
653
|
|
|
659
654
|
if (sessionPath) {
|
|
660
655
|
const prior = await readState(sessionPath);
|
|
661
|
-
|
|
662
|
-
await writeStateFile(sessionPath, next);
|
|
656
|
+
await writeEntries({ sessionId }, prior);
|
|
663
657
|
}
|
|
664
658
|
const priorRoot = await readState(rootPath);
|
|
665
|
-
|
|
666
|
-
await writeStateFile(rootPath, nextRoot);
|
|
659
|
+
await writeEntries(undefined, priorRoot);
|
|
667
660
|
}
|
|
668
661
|
|
|
669
662
|
function buildSyncEntry(options: SyncSkillActiveStateOptions, nowIso: string): SkillActiveEntry {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { AgentTool } from "@gajae-code/agent-core";
|
|
3
3
|
import { expandApplyPatchToEntries } from "../edit/modes/apply-patch";
|
|
4
|
+
import { ModeStateSchema } from "../gjc-runtime/state-schema";
|
|
4
5
|
import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol";
|
|
5
6
|
import { resolveToCwd } from "../tools/path-utils";
|
|
6
7
|
import { ToolError } from "../tools/tool-errors";
|
|
@@ -14,12 +15,16 @@ import {
|
|
|
14
15
|
export const DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE =
|
|
15
16
|
"Deep-interview phase boundary: continue gathering context/questions/risks and emit a handoff/spec before code edits. Mutation tools and patch execution are blocked while deep-interview is active; finalize specs through `gjc deep-interview --write --stage final` or hand off to an execution phase.";
|
|
16
17
|
export const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE =
|
|
17
|
-
"
|
|
18
|
+
".gjc workflow state and artifacts are runtime-owned. Agent mutation tools cannot edit `.gjc/**`; use the sanctioned `gjc` CLI instead.";
|
|
18
19
|
|
|
19
|
-
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"]);
|
|
20
|
+
const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit", "bash"]);
|
|
20
21
|
const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i;
|
|
21
22
|
const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
22
23
|
const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:<CR>|\r|\n|$)/i;
|
|
24
|
+
const BASH_TOKEN_RE = /'[^']*'|"(?:\\.|[^"\\])*"|\S+/g;
|
|
25
|
+
const BASH_REDIRECT_RE = /^(?:\d*)>>?$/;
|
|
26
|
+
const BASH_HEREDOC_RE = /^(?:\d*)<<-?$/;
|
|
27
|
+
const BASH_MUTATION_COMMANDS = new Set(["rm", "mv", "cp", "touch", "mkdir", "ln", "tee"]);
|
|
23
28
|
|
|
24
29
|
type ToolWithEditMode = AgentTool & {
|
|
25
30
|
mode?: unknown;
|
|
@@ -72,19 +77,37 @@ function modeStatePath(cwd: string, skill: string, sessionId?: string): string {
|
|
|
72
77
|
return path.join(stateDir, fileName);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
function warnInvalidModeState(filePath: string, error: string): void {
|
|
81
|
+
console.warn(`gjc skill-state: invalid mode-state at ${filePath}: ${error}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readValidatedModeState(filePath: string): Promise<ModeState | null> {
|
|
85
|
+
let raw: string;
|
|
76
86
|
try {
|
|
77
|
-
|
|
87
|
+
raw = await Bun.file(filePath).text();
|
|
78
88
|
} catch {
|
|
79
89
|
return null;
|
|
80
90
|
}
|
|
91
|
+
let state: ModeState;
|
|
92
|
+
try {
|
|
93
|
+
state = JSON.parse(raw) as ModeState;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
warnInvalidModeState(filePath, `invalid JSON: ${(error as Error).message}`);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const parsed = ModeStateSchema.safeParse(state);
|
|
99
|
+
if (!parsed.success) {
|
|
100
|
+
warnInvalidModeState(filePath, parsed.error.message);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return state;
|
|
81
104
|
}
|
|
82
105
|
async function readVisibleModeState(cwd: string, skill: string, sessionId?: string): Promise<ModeState | null> {
|
|
83
106
|
if (sessionId) {
|
|
84
|
-
const sessionState = await
|
|
107
|
+
const sessionState = await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
|
|
85
108
|
if (sessionState) return sessionState;
|
|
86
109
|
}
|
|
87
|
-
return await
|
|
110
|
+
return await readValidatedModeState(modeStatePath(cwd, skill));
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
function isTerminalModeState(state: ModeState | null): boolean {
|
|
@@ -219,10 +242,75 @@ function extractEditTargets(args: unknown, tool: ToolWithEditMode): ExtractedTar
|
|
|
219
242
|
return targets;
|
|
220
243
|
}
|
|
221
244
|
|
|
245
|
+
function extractBashTargets(args: unknown): ExtractedTargets {
|
|
246
|
+
const record = getRecord(args);
|
|
247
|
+
const command = safeString(record?.command).trim();
|
|
248
|
+
const targets: ExtractedTargets = { paths: [], unknown: false };
|
|
249
|
+
if (!command) {
|
|
250
|
+
targets.unknown = true;
|
|
251
|
+
return targets;
|
|
252
|
+
}
|
|
253
|
+
if (/^gjc(?:\s|$)/.test(command)) return targets;
|
|
254
|
+
|
|
255
|
+
const tokens = command.match(BASH_TOKEN_RE)?.map(unquoteBashToken) ?? [];
|
|
256
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
257
|
+
const token = tokens[index] ?? "";
|
|
258
|
+
if (BASH_REDIRECT_RE.test(token)) {
|
|
259
|
+
addPath(targets, tokens[index + 1]);
|
|
260
|
+
index++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const redirectMatch = token.match(/^(?:\d*)>>?(.+)$/);
|
|
264
|
+
if (redirectMatch?.[1]) {
|
|
265
|
+
addPath(targets, redirectMatch[1]);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (BASH_HEREDOC_RE.test(token)) {
|
|
269
|
+
addPath(targets, tokens[index + 1]);
|
|
270
|
+
index++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const heredocMatch = token.match(/^(?:\d*)<<-?(.+)$/);
|
|
274
|
+
if (heredocMatch?.[1]) {
|
|
275
|
+
addPath(targets, heredocMatch[1]);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (isMutationBashCommand(tokens, index)) {
|
|
279
|
+
for (let targetIndex = index + 1; targetIndex < tokens.length; targetIndex++) {
|
|
280
|
+
const target = tokens[targetIndex] ?? "";
|
|
281
|
+
if (isBashCommandBoundary(target)) break;
|
|
282
|
+
if (target.startsWith("-")) continue;
|
|
283
|
+
addPath(targets, target);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return targets;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function unquoteBashToken(token: string): string {
|
|
291
|
+
if (token.length < 2) return token;
|
|
292
|
+
const quote = token[0];
|
|
293
|
+
if ((quote === "'" || quote === '"') && token.at(-1) === quote) return token.slice(1, -1);
|
|
294
|
+
return token;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isBashCommandBoundary(token: string): boolean {
|
|
298
|
+
return [";", "&&", "||", "|"].includes(token);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function isMutationBashCommand(tokens: string[], index: number): boolean {
|
|
302
|
+
const token = path.basename(tokens[index] ?? "");
|
|
303
|
+
if (BASH_MUTATION_COMMANDS.has(token)) return true;
|
|
304
|
+
if (token !== "sed") return false;
|
|
305
|
+
const next = tokens[index + 1] ?? "";
|
|
306
|
+
return next === "-i" || next.startsWith("-i") || next.includes("i");
|
|
307
|
+
}
|
|
308
|
+
|
|
222
309
|
function extractTargets(tool: ToolWithEditMode, args: unknown): ExtractedTargets {
|
|
223
310
|
if (tool.name === "write") return extractWriteTargets(args);
|
|
224
311
|
if (tool.name === "ast_edit") return extractAstEditTargets(args);
|
|
225
312
|
if (tool.name === "edit") return extractEditTargets(args, tool);
|
|
313
|
+
if (tool.name === "bash") return extractBashTargets(args);
|
|
226
314
|
return { paths: [], unknown: true };
|
|
227
315
|
}
|
|
228
316
|
|
|
@@ -289,6 +377,14 @@ function isAllowlistedPath(cwd: string, rawPath: string): boolean {
|
|
|
289
377
|
if (segments?.[0] !== ".gjc") return false;
|
|
290
378
|
return segments[1] === "specs" || segments[1] === "plans";
|
|
291
379
|
}
|
|
380
|
+
function isBlockedGjcPath(cwd: string, rawPath: string): boolean {
|
|
381
|
+
const segments = relativeGjcSegments(cwd, rawPath);
|
|
382
|
+
return segments?.[0] === ".gjc";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function hasBlockedGjcTarget(cwd: string, targets: ExtractedTargets): boolean {
|
|
386
|
+
return targets.paths.some(rawPath => isBlockedGjcPath(cwd, rawPath));
|
|
387
|
+
}
|
|
292
388
|
|
|
293
389
|
function allTargetsAllowlisted(cwd: string, targets: ExtractedTargets): boolean {
|
|
294
390
|
return (
|
|
@@ -315,18 +411,16 @@ export async function getDeepInterviewMutationDecision(
|
|
|
315
411
|
): Promise<DeepInterviewMutationDecision> {
|
|
316
412
|
if (!BLOCKED_TOOL_NAMES.has(input.tool.name)) return { blocked: false, targets: [] };
|
|
317
413
|
const targets = extractTargets(input.tool, input.args);
|
|
318
|
-
if (input.enforceWorkflowState !== false) {
|
|
414
|
+
if (input.enforceWorkflowState !== false && hasBlockedGjcTarget(input.cwd, targets)) {
|
|
319
415
|
const stateSkill = firstBlockedWorkflowStateSkill(input.cwd, targets);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
};
|
|
329
|
-
}
|
|
416
|
+
const command = stateSkill ? sanctionedWorkflowStateCommand(stateSkill) : "gjc <workflow-command>";
|
|
417
|
+
return {
|
|
418
|
+
blocked: true,
|
|
419
|
+
message: `${WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE}\nUse: ${command}`,
|
|
420
|
+
targets: targets.paths,
|
|
421
|
+
reason: stateSkill ? "workflow-state-target" : "gjc-target",
|
|
422
|
+
command,
|
|
423
|
+
};
|
|
330
424
|
}
|
|
331
425
|
if (!(await isActiveDeepInterview(input.cwd, input.sessionId, input.threadId))) {
|
|
332
426
|
return { blocked: false, targets: [] };
|
|
@@ -340,6 +434,9 @@ export async function getDeepInterviewMutationDecision(
|
|
|
340
434
|
reason: "unknown-target",
|
|
341
435
|
};
|
|
342
436
|
}
|
|
437
|
+
if (input.tool.name === "bash") {
|
|
438
|
+
return { blocked: false, targets: targets.paths };
|
|
439
|
+
}
|
|
343
440
|
return {
|
|
344
441
|
blocked: true,
|
|
345
442
|
message: DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE,
|
|
@@ -13,5 +13,7 @@ import type { CanonicalGjcWorkflowSkill } from "./active-state";
|
|
|
13
13
|
export function initialPhaseForSkill(skill: CanonicalGjcWorkflowSkill | string): string {
|
|
14
14
|
if (skill === "deep-interview") return "interviewing";
|
|
15
15
|
if (skill === "ultragoal") return "goal-planning";
|
|
16
|
+
if (skill === "ralplan") return "planner";
|
|
17
|
+
if (skill === "team") return "starting";
|
|
16
18
|
return "planning";
|
|
17
19
|
}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill, SKILL_ACTIVE_STATE_FILE } from "./active-state";
|
|
3
|
+
import { WORKFLOW_STATE_RECEIPT_FRESH_MS, WORKFLOW_STATE_RECEIPT_VERSION } from "./workflow-state-version";
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export {
|
|
6
|
+
WORKFLOW_STATE_RECEIPT_FRESH_MS,
|
|
7
|
+
WORKFLOW_STATE_RECEIPT_VERSION,
|
|
8
|
+
WORKFLOW_STATE_VERSION,
|
|
9
|
+
} from "./workflow-state-version";
|
|
8
10
|
|
|
11
|
+
export type { CanonicalGjcWorkflowSkill };
|
|
9
12
|
export type WorkflowStateMutationOwner = "gjc-state-cli" | "gjc-runtime" | "gjc-hook";
|
|
10
13
|
export type WorkflowStateReceiptStatus = "fresh" | "stale";
|
|
11
14
|
|
|
15
|
+
export interface WorkflowStateContentChecksum {
|
|
16
|
+
algorithm: "sha256";
|
|
17
|
+
value: string;
|
|
18
|
+
covered_path: string;
|
|
19
|
+
computed_at: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
export interface WorkflowStateReceipt {
|
|
13
23
|
version: 1;
|
|
14
24
|
skill: CanonicalGjcWorkflowSkill;
|
|
@@ -20,6 +30,25 @@ export interface WorkflowStateReceipt {
|
|
|
20
30
|
fresh_until: string;
|
|
21
31
|
status: WorkflowStateReceiptStatus;
|
|
22
32
|
mutation_id: string;
|
|
33
|
+
verb?: string;
|
|
34
|
+
from_phase?: string;
|
|
35
|
+
to_phase?: string;
|
|
36
|
+
forced?: boolean;
|
|
37
|
+
paths?: string[];
|
|
38
|
+
content_sha256?: WorkflowStateContentChecksum;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AuditEntry {
|
|
42
|
+
ts: string;
|
|
43
|
+
skill?: string;
|
|
44
|
+
category: string;
|
|
45
|
+
verb: string;
|
|
46
|
+
owner: WorkflowStateMutationOwner;
|
|
47
|
+
mutation_id: string;
|
|
48
|
+
from_phase?: string;
|
|
49
|
+
to_phase?: string;
|
|
50
|
+
forced: boolean;
|
|
51
|
+
paths: string[];
|
|
23
52
|
}
|
|
24
53
|
|
|
25
54
|
function safeString(value: unknown): string {
|
|
@@ -586,6 +586,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
586
586
|
runtime.ctx.editor.setText("");
|
|
587
587
|
},
|
|
588
588
|
},
|
|
589
|
+
{
|
|
590
|
+
name: "monitors",
|
|
591
|
+
description: "Open the monitor/cron jobs overlay",
|
|
592
|
+
handleTui: (_command, runtime) => {
|
|
593
|
+
runtime.ctx.showJobsOverlay();
|
|
594
|
+
runtime.ctx.editor.setText("");
|
|
595
|
+
},
|
|
596
|
+
},
|
|
589
597
|
{
|
|
590
598
|
name: "tree",
|
|
591
599
|
description: "Navigate session tree (switch branches)",
|