@gajae-code/coding-agent 0.11.3 → 0.11.4
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 +24 -0
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/tool-transcript-format.d.ts +19 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/tools/ask.d.ts +39 -12
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +24 -8
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +3 -2
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +42 -210
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/session-observer-overlay.ts +10 -23
- package/src/modes/components/tool-transcript-format.ts +72 -0
- package/src/modes/components/transcript-viewer-overlay.ts +42 -2
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +228 -15
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/telegram-daemon.ts +50 -10
- package/src/session/internal/managed-session-scope.ts +15 -1
- package/src/tools/ask.ts +216 -41
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/read.ts +7 -1
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
SessionResolutionError,
|
|
43
43
|
writeSessionActivityMarker,
|
|
44
44
|
} from "./session-resolution";
|
|
45
|
+
import { classifyStateArgv, firstStateFlagValue, type StateAction, type StateArgvClassification } from "./state-argv";
|
|
45
46
|
import { renderStateGraph, type StateGraphFormat } from "./state-graph";
|
|
46
47
|
import { migrateAndPersistLegacyState, migrateWorkflowState } from "./state-migrations";
|
|
47
48
|
import {
|
|
@@ -72,7 +73,7 @@ import {
|
|
|
72
73
|
writeGuardedWorkflowEnvelopeAtomic,
|
|
73
74
|
} from "./state-writer";
|
|
74
75
|
import { assertSafePathComponent, CommandError, flagValue, hasFlag, isPlainObject } from "./workflow-cli-common";
|
|
75
|
-
import { getSkillManifest, isKnownWorkflowState, isValidTransition
|
|
76
|
+
import { getSkillManifest, isKnownWorkflowState, isValidTransition } from "./workflow-manifest";
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
79
|
* Native implementation of the `gjc state read|write|clear` command surface.
|
|
@@ -99,136 +100,9 @@ class StateCommandError extends CommandError {
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
const GRAPH_FORMATS = new Set(["ascii", "mermaid", "dot"]);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"--session-id",
|
|
106
|
-
"--thread-id",
|
|
107
|
-
"--turn-id",
|
|
108
|
-
"--to",
|
|
109
|
-
"--skill",
|
|
110
|
-
"--format",
|
|
111
|
-
"--older-than",
|
|
112
|
-
"--status",
|
|
113
|
-
"--fields",
|
|
114
|
-
"--since",
|
|
115
|
-
"--limit",
|
|
116
|
-
]);
|
|
117
|
-
const ACTION_NAMES = new Set([
|
|
118
|
-
"read",
|
|
119
|
-
"write",
|
|
120
|
-
"clear",
|
|
121
|
-
"contract",
|
|
122
|
-
"handoff",
|
|
123
|
-
"graph",
|
|
124
|
-
"prune",
|
|
125
|
-
"gc",
|
|
126
|
-
"migrate",
|
|
127
|
-
"status",
|
|
128
|
-
"doctor",
|
|
129
|
-
]);
|
|
130
|
-
const BOOLEAN_FLAGS = new Set([
|
|
131
|
-
"--json",
|
|
132
|
-
"--replace",
|
|
133
|
-
"--hard",
|
|
134
|
-
"--dry-run",
|
|
135
|
-
"--migrate",
|
|
136
|
-
"--compact",
|
|
137
|
-
"--history",
|
|
138
|
-
"--force",
|
|
139
|
-
]);
|
|
140
|
-
const VERB_SPECIFIC_FLAGS = new Set([
|
|
141
|
-
"--skill",
|
|
142
|
-
"--format",
|
|
143
|
-
"--older-than",
|
|
144
|
-
"--status",
|
|
145
|
-
"--fields",
|
|
146
|
-
"--since",
|
|
147
|
-
"--limit",
|
|
148
|
-
"--history",
|
|
149
|
-
]);
|
|
150
|
-
|
|
151
|
-
function flagName(arg: string): string | undefined {
|
|
152
|
-
if (!arg.startsWith("--")) return undefined;
|
|
153
|
-
const equalsIndex = arg.indexOf("=");
|
|
154
|
-
return equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function manifestFlagNames(action: ParsedInvocation["action"], positionalSkill: string | undefined): Set<string> {
|
|
158
|
-
const names = new Set<string>();
|
|
159
|
-
const skills =
|
|
160
|
-
positionalSkill && KNOWN_MODES.includes(positionalSkill)
|
|
161
|
-
? [positionalSkill as CanonicalGjcWorkflowSkill]
|
|
162
|
-
: CANONICAL_GJC_WORKFLOW_SKILLS;
|
|
163
|
-
for (const skill of skills) {
|
|
164
|
-
for (const arg of typedArgsFor(skill, action)) names.add(`--${arg.name}`);
|
|
165
|
-
}
|
|
166
|
-
return names;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function assertKnownFlags(args: readonly string[], parsed: ParsedInvocation): void {
|
|
170
|
-
const manifestFlags = manifestFlagNames(parsed.action, parsed.positionalSkill);
|
|
171
|
-
for (const arg of args) {
|
|
172
|
-
const flag = flagName(arg);
|
|
173
|
-
if (!flag) continue;
|
|
174
|
-
if (
|
|
175
|
-
FLAGS_WITH_VALUES.has(flag) ||
|
|
176
|
-
BOOLEAN_FLAGS.has(flag) ||
|
|
177
|
-
VERB_SPECIFIC_FLAGS.has(flag) ||
|
|
178
|
-
manifestFlags.has(flag)
|
|
179
|
-
) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
throw new StateCommandError(2, `unknown gjc state flag: ${flag}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
interface ParsedInvocation {
|
|
187
|
-
action:
|
|
188
|
-
| "read"
|
|
189
|
-
| "write"
|
|
190
|
-
| "clear"
|
|
191
|
-
| "contract"
|
|
192
|
-
| "handoff"
|
|
193
|
-
| "graph"
|
|
194
|
-
| "prune"
|
|
195
|
-
| "gc"
|
|
196
|
-
| "migrate"
|
|
197
|
-
| "status"
|
|
198
|
-
| "doctor";
|
|
199
|
-
positionalSkill?: string;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function parsePositionalArgs(args: readonly string[]): ParsedInvocation {
|
|
203
|
-
let skipNext = false;
|
|
204
|
-
const positional: string[] = [];
|
|
205
|
-
for (const arg of args) {
|
|
206
|
-
if (skipNext) {
|
|
207
|
-
skipNext = false;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (FLAGS_WITH_VALUES.has(arg)) {
|
|
211
|
-
skipNext = true;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (!arg.startsWith("-")) positional.push(arg);
|
|
215
|
-
}
|
|
216
|
-
// Documented argv shapes:
|
|
217
|
-
// gjc state read|write|clear|contract ...
|
|
218
|
-
// gjc state <skill> read|write|contract ...
|
|
219
|
-
const first = positional[0];
|
|
220
|
-
const second = positional[1];
|
|
221
|
-
if (first && ACTION_NAMES.has(first)) {
|
|
222
|
-
return { action: first as ParsedInvocation["action"], positionalSkill: second };
|
|
223
|
-
}
|
|
224
|
-
if (first && second && ACTION_NAMES.has(second)) {
|
|
225
|
-
return { action: second as ParsedInvocation["action"], positionalSkill: first };
|
|
226
|
-
}
|
|
227
|
-
// `gjc state <skill>` alone defaults to read for that skill.
|
|
228
|
-
if (first && !second) {
|
|
229
|
-
return { action: "read", positionalSkill: first };
|
|
230
|
-
}
|
|
231
|
-
return { action: "read" };
|
|
103
|
+
function assertKnownFlags(classification: StateArgvClassification): void {
|
|
104
|
+
const [unknownFlag] = classification.unknownFlags;
|
|
105
|
+
if (unknownFlag) throw new StateCommandError(2, `unknown gjc state flag: ${unknownFlag}`);
|
|
232
106
|
}
|
|
233
107
|
|
|
234
108
|
function isKnownMode(mode: string): mode is CanonicalGjcWorkflowSkill {
|
|
@@ -279,19 +153,16 @@ interface ResolvedSelectors {
|
|
|
279
153
|
// `clear` resolves like a read (explicit -> payload -> env -> latest-activity marker)
|
|
280
154
|
// per the spec: read/status/clear may fall back to the most-recent session. Commands
|
|
281
155
|
// that create or mutate new state roots still require an explicit/env session id.
|
|
282
|
-
const WRITE_SESSION_ACTIONS = new Set<
|
|
156
|
+
const WRITE_SESSION_ACTIONS = new Set<StateAction>(["write", "handoff", "prune", "migrate"]);
|
|
283
157
|
|
|
284
|
-
async function resolveSelectors(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
positionalSkill: string | undefined,
|
|
288
|
-
action: ParsedInvocation["action"],
|
|
289
|
-
): Promise<ResolvedSelectors> {
|
|
290
|
-
const payload = await readInputJson(flagValue(args, "--input"), cwd);
|
|
158
|
+
async function resolveSelectors(args: readonly string[], cwd: string, action: StateAction): Promise<ResolvedSelectors> {
|
|
159
|
+
const classification = classifyStateArgv(args);
|
|
160
|
+
const payload = await readInputJson(firstStateFlagValue(classification, "--input"), cwd);
|
|
291
161
|
|
|
162
|
+
const [modeCandidate, positionalCandidate] = classification.runtimeSelectorCandidates;
|
|
292
163
|
const candidates: Array<string | undefined> = [
|
|
293
|
-
|
|
294
|
-
|
|
164
|
+
modeCandidate?.value,
|
|
165
|
+
positionalCandidate?.value,
|
|
295
166
|
typeof payload?.mode === "string" ? (payload.mode as string).trim() || undefined : undefined,
|
|
296
167
|
typeof payload?.skill === "string" ? (payload.skill as string).trim() || undefined : undefined,
|
|
297
168
|
];
|
|
@@ -1232,12 +1103,8 @@ export async function readWorkflowStateJson(
|
|
|
1232
1103
|
return (await readJsonFile(modeStateFile(cwd, skill, session.gjcSessionId))) ?? {};
|
|
1233
1104
|
}
|
|
1234
1105
|
|
|
1235
|
-
async function handleRead(
|
|
1236
|
-
args
|
|
1237
|
-
cwd: string,
|
|
1238
|
-
positionalSkill: string | undefined,
|
|
1239
|
-
): Promise<StateCommandResult> {
|
|
1240
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
|
|
1106
|
+
async function handleRead(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1107
|
+
const selectors = await resolveSelectors(args, cwd, "read");
|
|
1241
1108
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
|
|
1242
1109
|
const fields = parseFieldsFlag(args);
|
|
1243
1110
|
if (mode) {
|
|
@@ -1276,12 +1143,8 @@ async function handleRead(
|
|
|
1276
1143
|
return { status: 0, stdout: `${JSON.stringify(existing ?? {}, null, 2)}\n` };
|
|
1277
1144
|
}
|
|
1278
1145
|
|
|
1279
|
-
async function handleStatus(
|
|
1280
|
-
args
|
|
1281
|
-
cwd: string,
|
|
1282
|
-
positionalSkill: string | undefined,
|
|
1283
|
-
): Promise<StateCommandResult> {
|
|
1284
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
|
|
1146
|
+
async function handleStatus(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1147
|
+
const selectors = await resolveSelectors(args, cwd, "read");
|
|
1285
1148
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
|
|
1286
1149
|
if (!mode) {
|
|
1287
1150
|
throw new StateCommandError(
|
|
@@ -1303,12 +1166,8 @@ async function handleStatus(
|
|
|
1303
1166
|
};
|
|
1304
1167
|
}
|
|
1305
1168
|
|
|
1306
|
-
async function handleWrite(
|
|
1307
|
-
args
|
|
1308
|
-
cwd: string,
|
|
1309
|
-
positionalSkill: string | undefined,
|
|
1310
|
-
): Promise<StateCommandResult> {
|
|
1311
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "write");
|
|
1169
|
+
async function handleWrite(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1170
|
+
const selectors = await resolveSelectors(args, cwd, "write");
|
|
1312
1171
|
const { gjcSessionId: sessionId, threadId, turnId, payload } = selectors;
|
|
1313
1172
|
if (!payload) throw new StateCommandError(2, "gjc state write requires --input '<json>'");
|
|
1314
1173
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
|
|
@@ -1465,12 +1324,8 @@ async function handleWrite(
|
|
|
1465
1324
|
);
|
|
1466
1325
|
}
|
|
1467
1326
|
|
|
1468
|
-
async function handleClear(
|
|
1469
|
-
args
|
|
1470
|
-
cwd: string,
|
|
1471
|
-
positionalSkill: string | undefined,
|
|
1472
|
-
): Promise<StateCommandResult> {
|
|
1473
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "clear");
|
|
1327
|
+
async function handleClear(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1328
|
+
const selectors = await resolveSelectors(args, cwd, "clear");
|
|
1474
1329
|
const { gjcSessionId: sessionId, threadId, turnId } = selectors;
|
|
1475
1330
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
|
|
1476
1331
|
if (!mode)
|
|
@@ -1631,12 +1486,8 @@ async function assertDeepInterviewHandoffReady(state: Record<string, unknown>):
|
|
|
1631
1486
|
* the phase remains in `skill-active-state.json` until a chain call (or
|
|
1632
1487
|
* explicit `clear`) demotes it.
|
|
1633
1488
|
*/
|
|
1634
|
-
async function handleHandoffUnlocked(
|
|
1635
|
-
args
|
|
1636
|
-
cwd: string,
|
|
1637
|
-
positionalSkill: string | undefined,
|
|
1638
|
-
): Promise<StateCommandResult> {
|
|
1639
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
|
|
1489
|
+
async function handleHandoffUnlocked(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1490
|
+
const selectors = await resolveSelectors(args, cwd, "handoff");
|
|
1640
1491
|
const { gjcSessionId: sessionId, threadId, turnId } = selectors;
|
|
1641
1492
|
const caller = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
|
|
1642
1493
|
if (!caller) {
|
|
@@ -1944,12 +1795,8 @@ async function handleHandoffUnlocked(
|
|
|
1944
1795
|
};
|
|
1945
1796
|
}
|
|
1946
1797
|
|
|
1947
|
-
async function handleHandoff(
|
|
1948
|
-
args
|
|
1949
|
-
cwd: string,
|
|
1950
|
-
positionalSkill: string | undefined,
|
|
1951
|
-
): Promise<StateCommandResult> {
|
|
1952
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
|
|
1798
|
+
async function handleHandoff(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1799
|
+
const selectors = await resolveSelectors(args, cwd, "handoff");
|
|
1953
1800
|
// Serialize concurrent handoffs on a dedicated sentinel lock, NOT on the
|
|
1954
1801
|
// derived `skill-active-state.json` cache. The inner transaction
|
|
1955
1802
|
// (applyHandoffToActiveState / syncSkillActiveState -> rebuildActiveSnapshot)
|
|
@@ -1959,15 +1806,11 @@ async function handleHandoff(
|
|
|
1959
1806
|
// `{ cwd }` so the sentinel resolves against the handoff cwd rather than
|
|
1960
1807
|
// `process.cwd()`.
|
|
1961
1808
|
const handoffLock = path.join(sessionStateDir(cwd, selectors.gjcSessionId), "handoff");
|
|
1962
|
-
return withWorkflowStateLock(handoffLock, async () => handleHandoffUnlocked(args, cwd
|
|
1809
|
+
return withWorkflowStateLock(handoffLock, async () => handleHandoffUnlocked(args, cwd), { cwd });
|
|
1963
1810
|
}
|
|
1964
1811
|
|
|
1965
|
-
async function handleContract(
|
|
1966
|
-
args
|
|
1967
|
-
cwd: string,
|
|
1968
|
-
positionalSkill: string | undefined,
|
|
1969
|
-
): Promise<StateCommandResult> {
|
|
1970
|
-
const { mode } = await resolveSelectors(args, cwd, positionalSkill, "read");
|
|
1812
|
+
async function handleContract(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
1813
|
+
const { mode } = await resolveSelectors(args, cwd, "read");
|
|
1971
1814
|
if (!mode) {
|
|
1972
1815
|
throw new StateCommandError(2, "gjc state contract requires --mode <skill>, positional <skill>, or input.skill");
|
|
1973
1816
|
}
|
|
@@ -2195,12 +2038,8 @@ async function handleGraph(
|
|
|
2195
2038
|
};
|
|
2196
2039
|
}
|
|
2197
2040
|
|
|
2198
|
-
async function handlePrune(
|
|
2199
|
-
args
|
|
2200
|
-
cwd: string,
|
|
2201
|
-
positionalSkill: string | undefined,
|
|
2202
|
-
): Promise<StateCommandResult> {
|
|
2203
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "prune");
|
|
2041
|
+
async function handlePrune(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
2042
|
+
const selectors = await resolveSelectors(args, cwd, "prune");
|
|
2204
2043
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
|
|
2205
2044
|
if (!mode) {
|
|
2206
2045
|
throw new StateCommandError(
|
|
@@ -2265,12 +2104,8 @@ async function handleGc(
|
|
|
2265
2104
|
return { status: 0, stdout: `${JSON.stringify(summary, null, 2)}\n` };
|
|
2266
2105
|
}
|
|
2267
2106
|
|
|
2268
|
-
async function handleMigrate(
|
|
2269
|
-
args
|
|
2270
|
-
cwd: string,
|
|
2271
|
-
positionalSkill: string | undefined,
|
|
2272
|
-
): Promise<StateCommandResult> {
|
|
2273
|
-
const selectors = await resolveSelectors(args, cwd, positionalSkill, "migrate");
|
|
2107
|
+
async function handleMigrate(args: readonly string[], cwd: string): Promise<StateCommandResult> {
|
|
2108
|
+
const selectors = await resolveSelectors(args, cwd, "migrate");
|
|
2274
2109
|
const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
|
|
2275
2110
|
if (!mode) {
|
|
2276
2111
|
throw new StateCommandError(
|
|
@@ -2301,34 +2136,31 @@ async function handleMigrate(
|
|
|
2301
2136
|
|
|
2302
2137
|
export async function runNativeStateCommand(args: string[], cwd = process.cwd()): Promise<StateCommandResult> {
|
|
2303
2138
|
try {
|
|
2304
|
-
const parsed =
|
|
2305
|
-
assertKnownFlags(
|
|
2306
|
-
switch (parsed.
|
|
2139
|
+
const parsed = classifyStateArgv(args);
|
|
2140
|
+
assertKnownFlags(parsed);
|
|
2141
|
+
switch (parsed.effectiveAction) {
|
|
2307
2142
|
case "read":
|
|
2308
|
-
|
|
2309
|
-
return await handleRead(args, cwd, parsed.positionalSkill);
|
|
2143
|
+
return await handleRead(args, cwd);
|
|
2310
2144
|
case "write":
|
|
2311
|
-
return await handleWrite(args, cwd
|
|
2145
|
+
return await handleWrite(args, cwd);
|
|
2312
2146
|
case "clear":
|
|
2313
|
-
return await handleClear(args, cwd
|
|
2147
|
+
return await handleClear(args, cwd);
|
|
2314
2148
|
case "contract":
|
|
2315
|
-
return await handleContract(args, cwd
|
|
2149
|
+
return await handleContract(args, cwd);
|
|
2316
2150
|
case "status":
|
|
2317
|
-
return await handleStatus(args, cwd
|
|
2151
|
+
return await handleStatus(args, cwd);
|
|
2318
2152
|
case "doctor":
|
|
2319
2153
|
return await handleDoctor(args, cwd, parsed.positionalSkill);
|
|
2320
2154
|
case "handoff":
|
|
2321
|
-
return await handleHandoff(args, cwd
|
|
2155
|
+
return await handleHandoff(args, cwd);
|
|
2322
2156
|
case "graph":
|
|
2323
2157
|
return await handleGraph(args, cwd, parsed.positionalSkill);
|
|
2324
2158
|
case "prune":
|
|
2325
|
-
return await handlePrune(args, cwd
|
|
2159
|
+
return await handlePrune(args, cwd);
|
|
2326
2160
|
case "gc":
|
|
2327
2161
|
return await handleGc(args, cwd, parsed.positionalSkill);
|
|
2328
2162
|
case "migrate":
|
|
2329
|
-
return await handleMigrate(args, cwd
|
|
2330
|
-
default:
|
|
2331
|
-
return { status: 2, stderr: `Unknown gjc state command: ${parsed.action}\n` };
|
|
2163
|
+
return await handleMigrate(args, cwd);
|
|
2332
2164
|
}
|
|
2333
2165
|
} catch (error) {
|
|
2334
2166
|
if (error instanceof CommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
|
|
@@ -59,9 +59,22 @@ const DEFAULT_ACTION_KEYS = Object.fromEntries(
|
|
|
59
59
|
|
|
60
60
|
const PASTE_DECISION_TIMEOUT_MS = 5_000;
|
|
61
61
|
const PENDING_PASTE_INPUT_MAX = 64;
|
|
62
|
+
const PENDING_PASTE_INPUT_MAX_BYTES = 256 * 1024;
|
|
62
63
|
|
|
63
64
|
type PastePendingClearReason = "timeout" | "queue-limit";
|
|
64
65
|
|
|
66
|
+
export interface PasteTextContext {
|
|
67
|
+
readonly signal: AbortSignal;
|
|
68
|
+
/** Run one synchronous composer mutation only while this paste transaction is current. */
|
|
69
|
+
commit(commit: () => boolean): boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PendingPasteDecision {
|
|
73
|
+
readonly controller: AbortController;
|
|
74
|
+
readonly pasteContent: string;
|
|
75
|
+
readonly remaining: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
/**
|
|
66
79
|
* Custom editor that handles configurable app-level shortcuts for coding-agent.
|
|
67
80
|
*/
|
|
@@ -94,9 +107,9 @@ export class CustomEditor extends Editor {
|
|
|
94
107
|
/** Called when the configured image-paste shortcut is pressed. */
|
|
95
108
|
onPasteImage?: () => Promise<boolean>;
|
|
96
109
|
/** Called before bracketed paste content is inserted. Return true to consume it. */
|
|
97
|
-
onPasteText?: (text: string) => boolean | Promise<boolean>;
|
|
98
|
-
/** Called when
|
|
99
|
-
onPastePendingInputCleared?: (reason: PastePendingClearReason,
|
|
110
|
+
onPasteText?: (text: string, context: PasteTextContext) => boolean | Promise<boolean>;
|
|
111
|
+
/** Called when pending paste handling is cancelled after restoring queued input. */
|
|
112
|
+
onPastePendingInputCleared?: (reason: PastePendingClearReason, restoredInputCount: number) => void;
|
|
100
113
|
/** Called when the configured dequeue shortcut is pressed. */
|
|
101
114
|
onDequeue?: () => void;
|
|
102
115
|
/** Called when the configured queue shortcut is pressed. */
|
|
@@ -117,7 +130,9 @@ export class CustomEditor extends Editor {
|
|
|
117
130
|
#pasteDecisionPending = false;
|
|
118
131
|
#pasteDecisionToken = 0;
|
|
119
132
|
#pasteDecisionTimeout: NodeJS.Timeout | undefined;
|
|
133
|
+
#pendingPasteDecision: PendingPasteDecision | undefined;
|
|
120
134
|
#pendingPasteInput: string[] = [];
|
|
135
|
+
#pendingPasteInputBytes = 0;
|
|
121
136
|
|
|
122
137
|
setActionKeys(action: ConfigurableEditorAction, keys: KeyId[]): void {
|
|
123
138
|
this.#actionKeys.set(action, [...keys]);
|
|
@@ -165,75 +180,127 @@ export class CustomEditor extends Editor {
|
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
182
|
|
|
168
|
-
#
|
|
183
|
+
#resetPendingPasteState(abortReason?: Error): string[] {
|
|
169
184
|
this.#clearPasteDecisionTimeout();
|
|
185
|
+
if (abortReason) this.#pendingPasteDecision?.controller.abort(abortReason);
|
|
170
186
|
this.#pasteDecisionPending = false;
|
|
171
187
|
this.#pasteDecisionToken += 1;
|
|
172
|
-
|
|
188
|
+
this.#pendingPasteDecision = undefined;
|
|
189
|
+
const queuedInput = this.#pendingPasteInput;
|
|
173
190
|
this.#pendingPasteInput = [];
|
|
174
|
-
|
|
191
|
+
this.#pendingPasteInputBytes = 0;
|
|
192
|
+
return queuedInput;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#pendingPasteEventCount(): number {
|
|
196
|
+
return this.#pendingPasteInput.length + (this.#pendingPasteDecision?.remaining ? 1 : 0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#restoreCancelledPaste(reason: PastePendingClearReason, overflowInput?: string): void {
|
|
200
|
+
const decision = this.#pendingPasteDecision;
|
|
201
|
+
const queuedInput = this.#resetPendingPasteState(new Error(`Paste handling cancelled: ${reason}`));
|
|
202
|
+
if (decision) {
|
|
203
|
+
super.handleInput(`\x1b[200~${decision.pasteContent}\x1b[201~`);
|
|
204
|
+
this.#drainPendingPasteInput(decision.remaining, queuedInput);
|
|
205
|
+
if (overflowInput !== undefined) this.handleInput(overflowInput);
|
|
206
|
+
}
|
|
207
|
+
this.onPastePendingInputCleared?.(
|
|
208
|
+
reason,
|
|
209
|
+
queuedInput.length + (decision?.remaining ? 1 : 0) + (overflowInput === undefined ? 0 : 1),
|
|
210
|
+
);
|
|
175
211
|
}
|
|
176
212
|
|
|
177
213
|
#startPasteDecisionTimeout(token: number): void {
|
|
178
214
|
this.#clearPasteDecisionTimeout();
|
|
179
215
|
this.#pasteDecisionTimeout = setTimeout(() => {
|
|
180
216
|
if (token !== this.#pasteDecisionToken) return;
|
|
181
|
-
|
|
182
|
-
this.onPastePendingInputCleared?.("timeout", droppedInputCount);
|
|
217
|
+
this.#restoreCancelledPaste("timeout");
|
|
183
218
|
}, PASTE_DECISION_TIMEOUT_MS);
|
|
184
219
|
this.#pasteDecisionTimeout.unref?.();
|
|
185
220
|
}
|
|
186
221
|
|
|
187
222
|
dispose(): void {
|
|
188
|
-
this.#
|
|
223
|
+
this.#resetPendingPasteState(new Error("Editor disposed during paste handling"));
|
|
189
224
|
this.#pasteHandler = new BracketedPasteHandler();
|
|
190
225
|
super.dispose();
|
|
191
226
|
}
|
|
192
227
|
|
|
193
|
-
#drainPendingPasteInput(initialInput
|
|
228
|
+
#drainPendingPasteInput(initialInput: string | undefined, queuedInput: readonly string[]): void {
|
|
194
229
|
if (initialInput && initialInput.length > 0) {
|
|
195
230
|
this.handleInput(initialInput);
|
|
196
231
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (nextInput === undefined) break;
|
|
200
|
-
this.handleInput(nextInput);
|
|
232
|
+
for (const input of queuedInput) {
|
|
233
|
+
this.handleInput(input);
|
|
201
234
|
}
|
|
202
235
|
}
|
|
203
236
|
|
|
204
237
|
#handleBracketedPaste(pasteContent: string, remaining: string): void {
|
|
205
|
-
const
|
|
238
|
+
const remainingBytes = Buffer.byteLength(remaining);
|
|
239
|
+
if (remainingBytes > PENDING_PASTE_INPUT_MAX_BYTES) {
|
|
240
|
+
super.handleInput(`\x1b[200~${pasteContent}\x1b[201~`);
|
|
241
|
+
if (remaining.length > 0) this.handleInput(remaining);
|
|
242
|
+
this.onPastePendingInputCleared?.("queue-limit", 1);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const token = this.#pasteDecisionToken + 1;
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
this.#pasteDecisionToken = token;
|
|
248
|
+
this.#pasteDecisionPending = true;
|
|
249
|
+
this.#pendingPasteDecision = { controller, pasteContent, remaining };
|
|
250
|
+
this.#pendingPasteInputBytes = remainingBytes;
|
|
251
|
+
let commitUsed = false;
|
|
252
|
+
|
|
253
|
+
const applyPasteResult = (handled: boolean | undefined) => {
|
|
206
254
|
if (token !== this.#pasteDecisionToken) return;
|
|
207
|
-
this.#
|
|
255
|
+
const queuedInput = this.#resetPendingPasteState();
|
|
208
256
|
if (!handled) {
|
|
209
257
|
super.handleInput(`\x1b[200~${pasteContent}\x1b[201~`);
|
|
210
258
|
}
|
|
211
|
-
this.#
|
|
212
|
-
this.#drainPendingPasteInput(remaining);
|
|
259
|
+
this.#drainPendingPasteInput(remaining, queuedInput);
|
|
213
260
|
};
|
|
214
|
-
|
|
261
|
+
|
|
262
|
+
let pasteResult: boolean | Promise<boolean> | undefined;
|
|
263
|
+
try {
|
|
264
|
+
pasteResult = this.onPasteText?.(pasteContent, {
|
|
265
|
+
signal: controller.signal,
|
|
266
|
+
commit: commit => {
|
|
267
|
+
if (
|
|
268
|
+
commitUsed ||
|
|
269
|
+
token !== this.#pasteDecisionToken ||
|
|
270
|
+
!this.#pasteDecisionPending ||
|
|
271
|
+
controller.signal.aborted
|
|
272
|
+
) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
commitUsed = true;
|
|
276
|
+
return commit();
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
} catch {
|
|
280
|
+
applyPasteResult(false);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
215
283
|
|
|
216
284
|
if (pasteResult instanceof Promise) {
|
|
217
|
-
const token = this.#pasteDecisionToken + 1;
|
|
218
|
-
this.#pasteDecisionToken = token;
|
|
219
|
-
this.#pasteDecisionPending = true;
|
|
220
285
|
this.#startPasteDecisionTimeout(token);
|
|
221
|
-
void pasteResult.then(
|
|
222
|
-
handled => applyPasteResult(token, handled),
|
|
223
|
-
() => applyPasteResult(token, false),
|
|
224
|
-
);
|
|
286
|
+
void pasteResult.then(applyPasteResult, () => applyPasteResult(false));
|
|
225
287
|
} else {
|
|
226
|
-
applyPasteResult(
|
|
288
|
+
applyPasteResult(pasteResult);
|
|
227
289
|
}
|
|
228
290
|
}
|
|
229
291
|
|
|
230
292
|
handleInput(data: string): void {
|
|
231
293
|
if (this.#pasteDecisionPending) {
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
this
|
|
294
|
+
const inputBytes = Buffer.byteLength(data);
|
|
295
|
+
if (
|
|
296
|
+
this.#pendingPasteEventCount() >= PENDING_PASTE_INPUT_MAX ||
|
|
297
|
+
this.#pendingPasteInputBytes + inputBytes > PENDING_PASTE_INPUT_MAX_BYTES
|
|
298
|
+
) {
|
|
299
|
+
this.#restoreCancelledPaste("queue-limit", data);
|
|
300
|
+
return;
|
|
236
301
|
}
|
|
302
|
+
this.#pendingPasteInput.push(data);
|
|
303
|
+
this.#pendingPasteInputBytes += inputBytes;
|
|
237
304
|
return;
|
|
238
305
|
}
|
|
239
306
|
|
|
@@ -245,8 +312,12 @@ export class CustomEditor extends Editor {
|
|
|
245
312
|
}
|
|
246
313
|
|
|
247
314
|
if (this.onPasteText) {
|
|
248
|
-
const paste =
|
|
315
|
+
const paste =
|
|
316
|
+
data === "\x1b" && !this.#pasteHandler.hasPendingFrame
|
|
317
|
+
? ({ handled: false } as const)
|
|
318
|
+
: this.#pasteHandler.process(data);
|
|
249
319
|
if (paste.handled) {
|
|
320
|
+
if (paste.leading.length > 0) this.handleInput(paste.leading);
|
|
250
321
|
if (paste.pasteContent !== undefined) {
|
|
251
322
|
this.onViewportFollowLive?.();
|
|
252
323
|
this.#handleBracketedPaste(paste.pasteContent, paste.remaining);
|
|
@@ -41,6 +41,7 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
41
41
|
credential: "",
|
|
42
42
|
models: "",
|
|
43
43
|
};
|
|
44
|
+
#submitInFlight = false;
|
|
44
45
|
#onSubmit: (input: CustomProviderWizardSubmit) => void;
|
|
45
46
|
#onCancel: () => void;
|
|
46
47
|
#onRender: () => void;
|
|
@@ -294,7 +295,7 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
294
295
|
this.#step = "credential";
|
|
295
296
|
} else if (this.#step === "confirm" || this.#step === "force-confirm") {
|
|
296
297
|
if (this.#selectedIndex === 0) {
|
|
297
|
-
this.#
|
|
298
|
+
this.#submit();
|
|
298
299
|
return;
|
|
299
300
|
}
|
|
300
301
|
this.#step = "models";
|
|
@@ -303,6 +304,30 @@ export class CustomProviderWizardComponent extends Container {
|
|
|
303
304
|
this.#onRender();
|
|
304
305
|
}
|
|
305
306
|
|
|
307
|
+
#submit(): void {
|
|
308
|
+
if (this.#submitInFlight) return;
|
|
309
|
+
this.#submitInFlight = true;
|
|
310
|
+
let submission: unknown;
|
|
311
|
+
try {
|
|
312
|
+
submission = this.#onSubmit(this.#buildInput(this.#step === "force-confirm"));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
this.#submitInFlight = false;
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
if (!(submission instanceof Promise)) {
|
|
318
|
+
this.#submitInFlight = false;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
void submission.then(
|
|
322
|
+
() => {
|
|
323
|
+
this.#submitInFlight = false;
|
|
324
|
+
},
|
|
325
|
+
() => {
|
|
326
|
+
this.#submitInFlight = false;
|
|
327
|
+
},
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
306
331
|
#buildInput(force: boolean): CustomProviderWizardSubmit {
|
|
307
332
|
const input = {
|
|
308
333
|
compatibility: this.#state.compatibility,
|