@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,4 +1,4 @@
|
|
|
1
|
-
import { THINKING_EFFORTS } from "@gajae-code/ai";
|
|
1
|
+
import { THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
|
|
2
2
|
import { TASK_SIMPLE_MODES } from "../task/simple-mode";
|
|
3
3
|
import { getThinkingLevelMetadata } from "../thinking";
|
|
4
4
|
import { EDIT_MODES } from "../utils/edit-mode";
|
|
@@ -2775,6 +2775,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
2775
2775
|
"thinkingBudgets.high": { type: "number", default: 16384 },
|
|
2776
2776
|
|
|
2777
2777
|
"thinkingBudgets.xhigh": { type: "number", default: 32768 },
|
|
2778
|
+
|
|
2779
|
+
"thinkingBudgets.max": { type: "number", default: 65536 },
|
|
2778
2780
|
} as const;
|
|
2779
2781
|
|
|
2780
2782
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -2955,6 +2957,7 @@ export interface ThinkingBudgetsSettings {
|
|
|
2955
2957
|
medium: number;
|
|
2956
2958
|
high: number;
|
|
2957
2959
|
xhigh: number;
|
|
2960
|
+
max: number;
|
|
2958
2961
|
}
|
|
2959
2962
|
|
|
2960
2963
|
export interface SttSettings {
|
|
@@ -13,6 +13,7 @@ import { type Skill, skillCapability } from "../capability/skill";
|
|
|
13
13
|
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
14
14
|
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
15
15
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
16
|
+
import { rootContainsGjcManifest } from "../extensibility/gjc-plugins";
|
|
16
17
|
import {
|
|
17
18
|
type ClaudePluginRoot,
|
|
18
19
|
createSourceMeta,
|
|
@@ -91,6 +92,25 @@ async function resolvePluginDir(
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
async function listNonGjcPluginRoots(
|
|
96
|
+
home: string,
|
|
97
|
+
cwd: string,
|
|
98
|
+
): Promise<{ roots: ClaudePluginRoot[]; warnings: string[] }> {
|
|
99
|
+
const { roots, warnings } = await listClaudePluginRoots(home, cwd);
|
|
100
|
+
const filteredRoots: ClaudePluginRoot[] = [];
|
|
101
|
+
const filteredWarnings = [...warnings];
|
|
102
|
+
|
|
103
|
+
for (const root of roots) {
|
|
104
|
+
if (await rootContainsGjcManifest(root.path)) {
|
|
105
|
+
filteredWarnings.push(`[claude-plugins] Skipping gajae-code plugin root (binding-only): ${root.path}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
filteredRoots.push(root);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { roots: filteredRoots, warnings: filteredWarnings };
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
// =============================================================================
|
|
95
115
|
// Skills
|
|
96
116
|
// =============================================================================
|
|
@@ -99,7 +119,7 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
|
|
|
99
119
|
const items: Skill[] = [];
|
|
100
120
|
const warnings: string[] = [];
|
|
101
121
|
|
|
102
|
-
const { roots, warnings: rootWarnings } = await
|
|
122
|
+
const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
|
|
103
123
|
warnings.push(...rootWarnings);
|
|
104
124
|
|
|
105
125
|
const results = await Promise.all(
|
|
@@ -134,7 +154,7 @@ async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashComm
|
|
|
134
154
|
const items: SlashCommand[] = [];
|
|
135
155
|
const warnings: string[] = [];
|
|
136
156
|
|
|
137
|
-
const { roots, warnings: rootWarnings } = await
|
|
157
|
+
const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
|
|
138
158
|
warnings.push(...rootWarnings);
|
|
139
159
|
|
|
140
160
|
const results = await Promise.all(
|
|
@@ -174,7 +194,7 @@ async function loadHooks(ctx: LoadContext): Promise<LoadResult<Hook>> {
|
|
|
174
194
|
const items: Hook[] = [];
|
|
175
195
|
const warnings: string[] = [];
|
|
176
196
|
|
|
177
|
-
const { roots, warnings: rootWarnings } = await
|
|
197
|
+
const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
|
|
178
198
|
warnings.push(...rootWarnings);
|
|
179
199
|
|
|
180
200
|
const hookTypes = ["pre", "post"] as const;
|
|
@@ -221,7 +241,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
221
241
|
const items: CustomTool[] = [];
|
|
222
242
|
const warnings: string[] = [];
|
|
223
243
|
|
|
224
|
-
const { roots, warnings: rootWarnings } = await
|
|
244
|
+
const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
|
|
225
245
|
warnings.push(...rootWarnings);
|
|
226
246
|
|
|
227
247
|
const results = await Promise.all(
|
|
@@ -258,7 +278,7 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
258
278
|
const items: MCPServer[] = [];
|
|
259
279
|
const warnings: string[] = [];
|
|
260
280
|
|
|
261
|
-
const { roots, warnings: rootWarnings } = await
|
|
281
|
+
const { roots, warnings: rootWarnings } = await listNonGjcPluginRoots(ctx.home, ctx.cwd);
|
|
262
282
|
warnings.push(...rootWarnings);
|
|
263
283
|
|
|
264
284
|
for (const root of roots) {
|
package/src/edit/diff.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Provides diff string generation and the replace-mode edit logic
|
|
5
5
|
* used when not in patch mode.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from "node:module";
|
|
7
9
|
import * as Diff from "diff";
|
|
8
10
|
import { resolveToCwd } from "../tools/path-utils";
|
|
9
11
|
import { DEFAULT_FUZZY_THRESHOLD, EditMatchError, findMatch } from "./modes/replace";
|
|
@@ -54,12 +56,73 @@ function formatNumberedDiffLine(prefix: "+" | "-" | " ", lineNum: number, conten
|
|
|
54
56
|
return `${prefix}${lineNum}|${content}`;
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
type DiffLinePart = {
|
|
60
|
+
added?: boolean;
|
|
61
|
+
removed?: boolean;
|
|
62
|
+
value: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type DiffLinesFn = (oldStr: string, newStr: string) => DiffLinePart[];
|
|
66
|
+
|
|
67
|
+
const require = createRequire(import.meta.url);
|
|
68
|
+
const DIFF_LINES_TEST_OVERRIDE_UNSET = Symbol("DIFF_LINES_TEST_OVERRIDE_UNSET");
|
|
69
|
+
|
|
70
|
+
let cachedNativeDiffLines: DiffLinesFn | null | undefined;
|
|
71
|
+
let diffLinesTestOverride: DiffLinesFn | null | typeof DIFF_LINES_TEST_OVERRIDE_UNSET = DIFF_LINES_TEST_OVERRIDE_UNSET;
|
|
72
|
+
|
|
73
|
+
function resolveNativeDiffLines(): DiffLinesFn | undefined {
|
|
74
|
+
if (diffLinesTestOverride !== DIFF_LINES_TEST_OVERRIDE_UNSET) {
|
|
75
|
+
return diffLinesTestOverride ?? undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (cachedNativeDiffLines !== undefined) {
|
|
79
|
+
return cachedNativeDiffLines ?? undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const natives = require("@gajae-code/natives") as { diffLines?: unknown };
|
|
84
|
+
cachedNativeDiffLines = typeof natives.diffLines === "function" ? (natives.diffLines as DiffLinesFn) : null;
|
|
85
|
+
} catch {
|
|
86
|
+
cachedNativeDiffLines = null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return cachedNativeDiffLines ?? undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function diffLinesWithFallback(oldContent: string, newContent: string): DiffLinePart[] {
|
|
93
|
+
const nativeDiffLines = resolveNativeDiffLines();
|
|
94
|
+
if (nativeDiffLines) {
|
|
95
|
+
try {
|
|
96
|
+
return nativeDiffLines(oldContent, newContent);
|
|
97
|
+
} catch {
|
|
98
|
+
// Fall through to the JS implementation if the native export fails at runtime.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Diff.diffLines(oldContent, newContent);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function __setDiffLinesForTest(diffLines: DiffLinesFn | null): void {
|
|
106
|
+
diffLinesTestOverride = diffLines;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function __clearDiffLinesForTest(): void {
|
|
110
|
+
diffLinesTestOverride = DIFF_LINES_TEST_OVERRIDE_UNSET;
|
|
111
|
+
cachedNativeDiffLines = undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function __getNativeDiffLinesForTest(): DiffLinesFn | undefined {
|
|
115
|
+
return resolveNativeDiffLines();
|
|
116
|
+
}
|
|
117
|
+
|
|
57
118
|
/**
|
|
58
119
|
* Generate a unified diff string with line numbers and context.
|
|
59
120
|
* Returns both the diff string and the first changed line number (in the new file).
|
|
60
121
|
*/
|
|
61
122
|
export function generateDiffString(oldContent: string, newContent: string, contextLines = 4): DiffResult {
|
|
62
|
-
|
|
123
|
+
// Native line diff (Rust port of jsdiff `Diff.diffLines`, byte-identical
|
|
124
|
+
// output) — avoids the pure-JS Myers blowup (>1s on ~1MB files).
|
|
125
|
+
const parts = diffLinesWithFallback(oldContent, newContent);
|
|
63
126
|
const output: string[] = [];
|
|
64
127
|
|
|
65
128
|
let oldLineNum = 1;
|
|
@@ -21,6 +21,40 @@ import {
|
|
|
21
21
|
restoreLineEndings,
|
|
22
22
|
stripBom,
|
|
23
23
|
} from "../normalize";
|
|
24
|
+
|
|
25
|
+
type NativeBestFuzzyMatchResult = {
|
|
26
|
+
best?: FuzzyMatch;
|
|
27
|
+
aboveThresholdCount: number;
|
|
28
|
+
secondBestScore: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type NativeSequenceFuzzyResult = {
|
|
32
|
+
index?: number;
|
|
33
|
+
confidence: number;
|
|
34
|
+
matchCount: number;
|
|
35
|
+
matchIndices: number[];
|
|
36
|
+
secondBestScore: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let scoreSequenceFuzzyNative:
|
|
40
|
+
| ((lines: string[], pattern: string[], start: number, eof: boolean) => NativeSequenceFuzzyResult)
|
|
41
|
+
| undefined;
|
|
42
|
+
let findBestFuzzyMatchNative:
|
|
43
|
+
| ((content: string, target: string, threshold: number) => NativeBestFuzzyMatchResult)
|
|
44
|
+
| undefined;
|
|
45
|
+
void import("../../../../natives/native/index.js")
|
|
46
|
+
.then(mod => {
|
|
47
|
+
if (typeof mod.h02ScoreSequenceFuzzy === "function") {
|
|
48
|
+
scoreSequenceFuzzyNative = mod.h02ScoreSequenceFuzzy;
|
|
49
|
+
}
|
|
50
|
+
if (typeof mod.h01FindBestFuzzyMatch === "function") {
|
|
51
|
+
findBestFuzzyMatchNative = mod.h01FindBestFuzzyMatch;
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.catch(() => {
|
|
55
|
+
// Native unavailable; fuzzy matching uses the TS fallback.
|
|
56
|
+
});
|
|
57
|
+
|
|
24
58
|
import { readEditFileText, serializeEditFileText } from "../read-file";
|
|
25
59
|
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
|
26
60
|
|
|
@@ -444,7 +478,7 @@ function findBestFuzzyMatchCore(
|
|
|
444
478
|
return { best, aboveThresholdCount, secondBestScore };
|
|
445
479
|
}
|
|
446
480
|
|
|
447
|
-
function findBestFuzzyMatch(content: string, target: string, threshold: number): BestFuzzyMatchResult {
|
|
481
|
+
export function findBestFuzzyMatch(content: string, target: string, threshold: number): BestFuzzyMatchResult {
|
|
448
482
|
const contentLines = content.split("\n");
|
|
449
483
|
const targetLines = target.split("\n");
|
|
450
484
|
|
|
@@ -489,7 +523,8 @@ export function findMatch(
|
|
|
489
523
|
|
|
490
524
|
// Try fuzzy match
|
|
491
525
|
const threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
492
|
-
const { best, aboveThresholdCount, secondBestScore } =
|
|
526
|
+
const { best, aboveThresholdCount, secondBestScore } =
|
|
527
|
+
findBestFuzzyMatchNative?.(content, target, threshold) ?? findBestFuzzyMatch(content, target, threshold);
|
|
493
528
|
|
|
494
529
|
if (!best) {
|
|
495
530
|
return {};
|
|
@@ -682,6 +717,29 @@ export function seekSequence(
|
|
|
682
717
|
return { index: undefined, confidence: 0 };
|
|
683
718
|
}
|
|
684
719
|
|
|
720
|
+
const nativeFuzzyResult = scoreSequenceFuzzyNative?.(lines, pattern, start, eof);
|
|
721
|
+
if (nativeFuzzyResult?.index !== undefined && nativeFuzzyResult.confidence >= SEQUENCE_FUZZY_THRESHOLD) {
|
|
722
|
+
if (
|
|
723
|
+
nativeFuzzyResult.matchCount > 1 &&
|
|
724
|
+
nativeFuzzyResult.confidence >= DOMINANT_FUZZY_MIN_CONFIDENCE &&
|
|
725
|
+
nativeFuzzyResult.confidence - nativeFuzzyResult.secondBestScore >= DOMINANT_FUZZY_DELTA
|
|
726
|
+
) {
|
|
727
|
+
return {
|
|
728
|
+
index: nativeFuzzyResult.index,
|
|
729
|
+
confidence: nativeFuzzyResult.confidence,
|
|
730
|
+
matchCount: 1,
|
|
731
|
+
matchIndices: nativeFuzzyResult.matchIndices,
|
|
732
|
+
strategy: "fuzzy-dominant",
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
index: nativeFuzzyResult.index,
|
|
737
|
+
confidence: nativeFuzzyResult.confidence,
|
|
738
|
+
matchCount: nativeFuzzyResult.matchCount,
|
|
739
|
+
matchIndices: nativeFuzzyResult.matchIndices,
|
|
740
|
+
strategy: "fuzzy",
|
|
741
|
+
};
|
|
742
|
+
}
|
|
685
743
|
// Pass 7: Fuzzy matching - find best match above threshold
|
|
686
744
|
let bestScore = 0;
|
|
687
745
|
let secondBestScore = 0;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { logger } from "@gajae-code/utils";
|
|
2
|
+
import { loadGjcPlugins } from "./loader";
|
|
3
|
+
import { discoverGjcPluginRoots } from "./paths";
|
|
4
|
+
import { GjcPluginLoadError, type LoadedGjcPlugin, type LoadedSubskillActivation } from "./types";
|
|
5
|
+
|
|
6
|
+
export interface SubskillActivationResult {
|
|
7
|
+
cleanedArgs: string;
|
|
8
|
+
activation?: LoadedSubskillActivation;
|
|
9
|
+
activeSubskillsToPersist: LoadedSubskillActivation[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function resolveSubskillActivationForSkillInvocation(input: {
|
|
13
|
+
cwd: string;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
threadId?: string;
|
|
16
|
+
turnId?: string;
|
|
17
|
+
skillName: string;
|
|
18
|
+
args: string;
|
|
19
|
+
}): Promise<SubskillActivationResult> {
|
|
20
|
+
const roots = await discoverGjcPluginRoots({ cwd: input.cwd });
|
|
21
|
+
let plugins: LoadedGjcPlugin[];
|
|
22
|
+
try {
|
|
23
|
+
plugins = await loadGjcPlugins(roots);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof GjcPluginLoadError) throw error;
|
|
26
|
+
logger.warn("Skipping GJC plugin activation set after load error", {
|
|
27
|
+
error: error instanceof Error ? error.message : String(error),
|
|
28
|
+
});
|
|
29
|
+
plugins = [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const bindings = plugins.flatMap(plugin => plugin.bindings);
|
|
33
|
+
const activationsByArg = new Map<string, LoadedSubskillActivation>();
|
|
34
|
+
for (const binding of bindings) {
|
|
35
|
+
if (binding.parent !== input.skillName) continue;
|
|
36
|
+
activationsByArg.set(binding.activationArg, {
|
|
37
|
+
activationArg: binding.activationArg,
|
|
38
|
+
plugin: binding.plugin,
|
|
39
|
+
subskillName: binding.subskillName,
|
|
40
|
+
parent: binding.parent,
|
|
41
|
+
bindsTo: binding.bindsTo,
|
|
42
|
+
phase: binding.phase,
|
|
43
|
+
filePath: binding.filePath,
|
|
44
|
+
toolPaths: binding.toolPaths,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tokens = input.args
|
|
49
|
+
.trim()
|
|
50
|
+
.split(/\s+/)
|
|
51
|
+
.filter(token => token.length > 0);
|
|
52
|
+
let activation: LoadedSubskillActivation | undefined;
|
|
53
|
+
const cleanedTokens: string[] = [];
|
|
54
|
+
let consumed = false;
|
|
55
|
+
for (const token of tokens) {
|
|
56
|
+
if (!consumed && token.startsWith("--") && !token.includes("=")) {
|
|
57
|
+
const candidate = activationsByArg.get(token.slice(2));
|
|
58
|
+
if (candidate) {
|
|
59
|
+
activation = candidate;
|
|
60
|
+
consumed = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
cleanedTokens.push(token);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
cleanedArgs: consumed ? cleanedTokens.join(" ") : input.args,
|
|
69
|
+
activation,
|
|
70
|
+
activeSubskillsToPersist: activation
|
|
71
|
+
? bindings
|
|
72
|
+
.filter(
|
|
73
|
+
binding => binding.plugin === activation.plugin && binding.activationArg === activation.activationArg,
|
|
74
|
+
)
|
|
75
|
+
.map(binding => ({
|
|
76
|
+
activationArg: binding.activationArg,
|
|
77
|
+
plugin: binding.plugin,
|
|
78
|
+
subskillName: binding.subskillName,
|
|
79
|
+
parent: binding.parent,
|
|
80
|
+
bindsTo: binding.bindsTo,
|
|
81
|
+
phase: binding.phase,
|
|
82
|
+
filePath: binding.filePath,
|
|
83
|
+
toolPaths: binding.toolPaths,
|
|
84
|
+
}))
|
|
85
|
+
: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { readVisibleSkillActiveState } from "../../skill-state/active-state";
|
|
2
|
+
import { initialPhaseForSkill } from "../../skill-state/initial-phase";
|
|
3
|
+
import { readActiveSubskillsForParent } from "./state";
|
|
4
|
+
import { GJC_SUBSKILL_PARENT_AGENTS, type LoadedSubskillActivation } from "./types";
|
|
5
|
+
|
|
6
|
+
export async function readSubskillBody(filePath: string): Promise<string> {
|
|
7
|
+
const content = await Bun.file(filePath).text();
|
|
8
|
+
return content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapeAttribute(value: string): string {
|
|
12
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function wrapSubskillBlock(
|
|
16
|
+
activation: {
|
|
17
|
+
plugin: string;
|
|
18
|
+
subskillName: string;
|
|
19
|
+
parent: string;
|
|
20
|
+
phase: string;
|
|
21
|
+
activationArg: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
},
|
|
24
|
+
body: string,
|
|
25
|
+
): string {
|
|
26
|
+
return `\n\n---\n\n<gjc-subskill plugin="${escapeAttribute(activation.plugin)}" name="${escapeAttribute(activation.subskillName)}" parent="${escapeAttribute(activation.parent)}" phase="${escapeAttribute(activation.phase)}" arg="${escapeAttribute(activation.activationArg)}">\n${body}\n</gjc-subskill>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function resolveCurrentPhaseForParent(input: {
|
|
30
|
+
cwd: string;
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
parent: string;
|
|
33
|
+
explicitPhase?: string;
|
|
34
|
+
}): Promise<string> {
|
|
35
|
+
const explicitPhase = input.explicitPhase?.trim();
|
|
36
|
+
if (explicitPhase) return explicitPhase;
|
|
37
|
+
|
|
38
|
+
const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
|
|
39
|
+
const persistedPhase = state?.active_skills?.find(entry => entry.skill === input.parent)?.phase?.trim();
|
|
40
|
+
if (persistedPhase) return persistedPhase;
|
|
41
|
+
|
|
42
|
+
if (state?.skill === input.parent) {
|
|
43
|
+
const statePhase = state.phase?.trim();
|
|
44
|
+
if (statePhase) return statePhase;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return initialPhaseForSkill(input.parent);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function buildSubskillInjection(input: {
|
|
51
|
+
cwd: string;
|
|
52
|
+
sessionId?: string;
|
|
53
|
+
skillName: string;
|
|
54
|
+
activation?: LoadedSubskillActivation;
|
|
55
|
+
currentPhase?: string;
|
|
56
|
+
}): Promise<{ block: string; details?: LoadedSubskillActivation } | null> {
|
|
57
|
+
const resolvedPhase = await resolveCurrentPhaseForParent({
|
|
58
|
+
cwd: input.cwd,
|
|
59
|
+
sessionId: input.sessionId,
|
|
60
|
+
parent: input.skillName,
|
|
61
|
+
explicitPhase: input.currentPhase,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const directActivation = input.activation;
|
|
65
|
+
if (directActivation?.parent === input.skillName && directActivation.phase === resolvedPhase) {
|
|
66
|
+
const body = await readSubskillBody(directActivation.filePath);
|
|
67
|
+
return { block: wrapSubskillBlock(directActivation, body), details: directActivation };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [entry] = await readActiveSubskillsForParent({
|
|
71
|
+
cwd: input.cwd,
|
|
72
|
+
sessionId: input.sessionId,
|
|
73
|
+
parent: input.skillName,
|
|
74
|
+
phase: resolvedPhase,
|
|
75
|
+
});
|
|
76
|
+
if (!entry) return null;
|
|
77
|
+
|
|
78
|
+
const activation: LoadedSubskillActivation = {
|
|
79
|
+
plugin: entry.plugin,
|
|
80
|
+
subskillName: entry.subskillName,
|
|
81
|
+
parent: entry.parent,
|
|
82
|
+
bindsTo: entry.bindsTo,
|
|
83
|
+
phase: entry.phase,
|
|
84
|
+
activationArg: entry.activationArg,
|
|
85
|
+
filePath: entry.filePath,
|
|
86
|
+
toolPaths: entry.toolPaths,
|
|
87
|
+
};
|
|
88
|
+
const body = await readSubskillBody(activation.filePath);
|
|
89
|
+
return { block: wrapSubskillBlock(activation, body), details: activation };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function buildAgentSubskillInjection(input: {
|
|
93
|
+
cwd: string;
|
|
94
|
+
sessionId?: string;
|
|
95
|
+
agentName: string;
|
|
96
|
+
}): Promise<string> {
|
|
97
|
+
if (!(GJC_SUBSKILL_PARENT_AGENTS as readonly string[]).includes(input.agentName)) return "";
|
|
98
|
+
|
|
99
|
+
const entries = await readActiveSubskillsForParent({
|
|
100
|
+
cwd: input.cwd,
|
|
101
|
+
sessionId: input.sessionId,
|
|
102
|
+
parent: input.agentName,
|
|
103
|
+
phase: "prompt",
|
|
104
|
+
});
|
|
105
|
+
if (entries.length === 0) return "";
|
|
106
|
+
|
|
107
|
+
const blocks = await Promise.all(
|
|
108
|
+
entries.map(async entry => {
|
|
109
|
+
const body = await readSubskillBody(entry.filePath);
|
|
110
|
+
return wrapSubskillBlock(entry, body);
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
return blocks.join("");
|
|
114
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parseFrontmatter } from "@gajae-code/utils";
|
|
4
|
+
import { resolveWithinRoot } from "./paths";
|
|
5
|
+
import { parseManifest, parseSubskillFrontmatter } from "./schema";
|
|
6
|
+
import {
|
|
7
|
+
GJC_PLUGIN_MANIFEST_FILENAME,
|
|
8
|
+
GjcPluginLoadError,
|
|
9
|
+
type LoadedGjcPlugin,
|
|
10
|
+
type LoadedSubskillBinding,
|
|
11
|
+
type PhaseScopedToolBinding,
|
|
12
|
+
} from "./types";
|
|
13
|
+
import { buildParentArgMap, buildParentPhaseSet, validateBinding } from "./validation";
|
|
14
|
+
|
|
15
|
+
async function readJsonFile(filePath: string): Promise<unknown> {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(await fs.readFile(filePath, "utf8")) as unknown;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error instanceof SyntaxError) {
|
|
20
|
+
throw new GjcPluginLoadError("invalid_manifest", `Invalid GJC plugin manifest JSON at ${filePath}`, {
|
|
21
|
+
cause: error,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
throw new GjcPluginLoadError("missing_file", `Missing GJC plugin manifest at ${filePath}`, {
|
|
25
|
+
cause: error instanceof Error ? error : undefined,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function readRequiredText(filePath: string, kind: "sub-skill" | "tool"): Promise<string> {
|
|
31
|
+
try {
|
|
32
|
+
return await fs.readFile(filePath, "utf8");
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new GjcPluginLoadError("missing_file", `Missing GJC plugin ${kind} file at ${filePath}`, {
|
|
35
|
+
cause: error instanceof Error ? error : undefined,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseFrontmatterToolPaths(fm: Record<string, unknown>): string[] {
|
|
41
|
+
const raw = fm.tools;
|
|
42
|
+
if (raw === undefined) return [];
|
|
43
|
+
if (typeof raw === "string") return raw.trim() ? [raw] : [];
|
|
44
|
+
if (Array.isArray(raw) && raw.every(item => typeof item === "string")) return [...raw];
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pushToolBinding(
|
|
49
|
+
toolBindings: PhaseScopedToolBinding[],
|
|
50
|
+
plugin: string,
|
|
51
|
+
parent: string,
|
|
52
|
+
phase: string,
|
|
53
|
+
toolPath: string,
|
|
54
|
+
): void {
|
|
55
|
+
toolBindings.push({ plugin, parent, phase, toolPath });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadGjcPlugin(root: string): Promise<LoadedGjcPlugin> {
|
|
59
|
+
const pluginRoot = path.resolve(root);
|
|
60
|
+
const manifestPath = path.join(pluginRoot, GJC_PLUGIN_MANIFEST_FILENAME);
|
|
61
|
+
const manifest = parseManifest(await readJsonFile(manifestPath), manifestPath);
|
|
62
|
+
const manifestToolPaths = manifest.tools.map(rel => resolveWithinRoot(pluginRoot, rel));
|
|
63
|
+
|
|
64
|
+
for (const toolPath of manifestToolPaths) {
|
|
65
|
+
await readRequiredText(toolPath, "tool");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const bindings: LoadedSubskillBinding[] = [];
|
|
69
|
+
const toolBindings: PhaseScopedToolBinding[] = [];
|
|
70
|
+
|
|
71
|
+
for (const rel of manifest.subskills) {
|
|
72
|
+
const filePath = resolveWithinRoot(pluginRoot, rel);
|
|
73
|
+
const content = await readRequiredText(filePath, "sub-skill");
|
|
74
|
+
let parsed: { frontmatter: Record<string, unknown>; body: string };
|
|
75
|
+
try {
|
|
76
|
+
parsed = parseFrontmatter(content, { source: filePath, level: "fatal" });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw new GjcPluginLoadError("invalid_frontmatter", `Invalid GJC sub-skill frontmatter at ${filePath}`, {
|
|
79
|
+
cause: error instanceof Error ? error : undefined,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const frontmatter = parseSubskillFrontmatter(parsed.frontmatter, filePath);
|
|
83
|
+
validateBinding(frontmatter);
|
|
84
|
+
const frontmatterToolPaths = parseFrontmatterToolPaths(parsed.frontmatter).map(toolRel =>
|
|
85
|
+
resolveWithinRoot(pluginRoot, toolRel),
|
|
86
|
+
);
|
|
87
|
+
for (const toolPath of frontmatterToolPaths) {
|
|
88
|
+
await readRequiredText(toolPath, "tool");
|
|
89
|
+
}
|
|
90
|
+
const toolPaths = [...manifestToolPaths, ...frontmatterToolPaths];
|
|
91
|
+
const binding: LoadedSubskillBinding = {
|
|
92
|
+
plugin: manifest.name,
|
|
93
|
+
subskillName: frontmatter.name,
|
|
94
|
+
parent: frontmatter.binds_to,
|
|
95
|
+
bindsTo: frontmatter.binds_to,
|
|
96
|
+
phase: frontmatter.phase,
|
|
97
|
+
activationArg: frontmatter.activation_arg,
|
|
98
|
+
description: frontmatter.description,
|
|
99
|
+
filePath,
|
|
100
|
+
body: parsed.body,
|
|
101
|
+
toolPaths,
|
|
102
|
+
};
|
|
103
|
+
bindings.push(binding);
|
|
104
|
+
for (const toolPath of toolPaths) {
|
|
105
|
+
pushToolBinding(toolBindings, manifest.name, binding.parent, binding.phase, toolPath);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
buildParentArgMap(bindings);
|
|
110
|
+
buildParentPhaseSet(bindings);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
name: manifest.name,
|
|
114
|
+
version: manifest.version,
|
|
115
|
+
root: pluginRoot,
|
|
116
|
+
manifestPath,
|
|
117
|
+
bindings,
|
|
118
|
+
toolBindings,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function loadGjcPlugins(roots: readonly string[]): Promise<LoadedGjcPlugin[]> {
|
|
123
|
+
const plugins: LoadedGjcPlugin[] = [];
|
|
124
|
+
for (const root of roots) {
|
|
125
|
+
plugins.push(await loadGjcPlugin(root));
|
|
126
|
+
}
|
|
127
|
+
const bindings = plugins.flatMap(plugin => plugin.bindings);
|
|
128
|
+
buildParentArgMap(bindings);
|
|
129
|
+
buildParentPhaseSet(bindings);
|
|
130
|
+
return plugins;
|
|
131
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getAgentDir, pathIsWithin } from "@gajae-code/utils";
|
|
4
|
+
import { GJC_PLUGIN_MANIFEST_FILENAME, GjcPluginLoadError } from "./types";
|
|
5
|
+
|
|
6
|
+
export function gjcPluginUserRoot(): string {
|
|
7
|
+
return path.join(getAgentDir(), "gjc-plugins");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function gjcPluginProjectRoot(cwd: string): string {
|
|
11
|
+
return path.join(cwd, ".gjc", "gjc-plugins");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isEnoent(error: unknown): boolean {
|
|
15
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function rootContainsGjcManifest(dir: string): Promise<boolean> {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(path.join(dir, GJC_PLUGIN_MANIFEST_FILENAME));
|
|
21
|
+
return true;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (isEnoent(error)) return false;
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function discoverGjcPluginRootsIn(baseDir: string): Promise<string[]> {
|
|
29
|
+
if (await rootContainsGjcManifest(baseDir)) return [baseDir];
|
|
30
|
+
|
|
31
|
+
let entries: import("node:fs").Dirent[];
|
|
32
|
+
try {
|
|
33
|
+
entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (isEnoent(error)) return [];
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const roots = await Promise.all(
|
|
40
|
+
entries
|
|
41
|
+
.filter(entry => entry.isDirectory() || entry.isSymbolicLink())
|
|
42
|
+
.map(async entry => {
|
|
43
|
+
const dir = path.join(baseDir, entry.name);
|
|
44
|
+
return (await rootContainsGjcManifest(dir)) ? dir : null;
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return roots.filter((root): root is string => root !== null);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function discoverGjcPluginRoots({ cwd }: { cwd: string; home?: string }): Promise<string[]> {
|
|
52
|
+
const roots = await Promise.all([
|
|
53
|
+
discoverGjcPluginRootsIn(gjcPluginUserRoot()),
|
|
54
|
+
discoverGjcPluginRootsIn(gjcPluginProjectRoot(cwd)),
|
|
55
|
+
]);
|
|
56
|
+
return roots.flat();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveWithinRoot(root: string, rel: string): string {
|
|
60
|
+
const resolvedRoot = path.resolve(root);
|
|
61
|
+
const resolvedPath = path.resolve(resolvedRoot, rel);
|
|
62
|
+
if (!pathIsWithin(resolvedRoot, resolvedPath)) {
|
|
63
|
+
throw new GjcPluginLoadError("missing_file", `GJC plugin path escapes root: ${rel}`);
|
|
64
|
+
}
|
|
65
|
+
return resolvedPath;
|
|
66
|
+
}
|