syntaur 0.27.0 → 0.32.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/dashboard/dist/assets/{_basePickBy-DPBuiT9A.js → _basePickBy-CV3s3ZBR.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-B5Q4dkW3.js → _baseUniq-BTBb-kpx.js} +1 -1
- package/dashboard/dist/assets/{arc-Bp71QC_v.js → arc-DroCaru_.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CWHBISZ5.js → architectureDiagram-2XIMDMQ5-hL5g0oNx.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-D0txIHgi.js → blockDiagram-WCTKOSBZ-H-mOZOGQ.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-D_Hpnc38.js → c4Diagram-IC4MRINW-C7JTywql.js} +1 -1
- package/dashboard/dist/assets/channel-X6-lrXLw.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-D0A_A8qn.js → chunk-4BX2VUAB-C5NgL7Ud.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-DuK8QvrD.js → chunk-55IACEB6-CpqlZIZp.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-B5WfIDS6.js → chunk-FMBD7UC4-DOEJuCgN.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-D3jB_ZJP.js → chunk-JSJVCQXG-DTvwZQeC.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DtxN1mOD.js → chunk-KX2RTZJC-DtM5R3Ro.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-4fQpgivN.js → chunk-NQ4KR5QH-BRf7XeyG.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BOf9TZCT.js → chunk-QZHKN3VN-CkRK4_hm.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-D9HeEPWL.js → chunk-WL4C6EOR-D40xff82.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-DfkR91Os.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-DfkR91Os.js +1 -0
- package/dashboard/dist/assets/clone--OUSRwbL.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CpzWcyB7.js → cose-bilkent-S5V4N54A-BzAx8dWI.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CC9-omFF.js → dagre-KLK3FWXG-DqBzOGFn.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-q_F9KKPz.js → diagram-E7M64L7V-BU3Nv4BP.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-CbYvNpQB.js → diagram-IFDJBPK2-9173qxjV.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-q8XUUKRC.js → diagram-P4PSJMXO-CDO7XNao.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Q-oL35fO.js → erDiagram-INFDFZHY-DkO9AjCM.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-Cptj-2yF.js → flowDiagram-PKNHOUZH-9Fjhsq_p.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-BYmgXBad.js → ganttDiagram-A5KZAMGK-FL3oGbo9.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-DHF3w-Cn.js → gitGraphDiagram-K3NZZRJ6-FS9HpxFJ.js} +1 -1
- package/dashboard/dist/assets/{graph-Br4uG9xg.js → graph-COu71lol.js} +1 -1
- package/dashboard/dist/assets/index-BohN_jjP.css +1 -0
- package/dashboard/dist/assets/{index-dyJ_mu3x.js → index-D1FxzsMS.js} +84 -83
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Ckb3YLUI.js → infoDiagram-LFFYTUFH-Cb7nqRMJ.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DSXXm4hL.js → ishikawaDiagram-PHBUUO56-DU44jQ_t.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-D4JJ4wn_.js → journeyDiagram-4ABVD52K-Dvf5wmhX.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-DZeWPcIi.js → kanban-definition-K7BYSVSG-NNnzIiBX.js} +1 -1
- package/dashboard/dist/assets/{layout-DU5mcBKh.js → layout-BWL1q6XW.js} +1 -1
- package/dashboard/dist/assets/{linear-h7AvdT63.js → linear-BpjXUE-L.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-DIOnVuDB.js → mermaid.core-C1YuKa7V.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-BVSORv6W.js → mindmap-definition-YRQLILUH-BUS-7SSM.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BEdO084J.js → pieDiagram-SKSYHLDU-CId0D08y.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-3Dc5mQ7q.js → quadrantDiagram-337W2JSQ-lrdlvDFz.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-eu-8doSY.js → requirementDiagram-Z7DCOOCP-CijOr4BN.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-jA292hzv.js → sankeyDiagram-WA2Y5GQK-Bz63rCum.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-et31a6Tg.js → sequenceDiagram-2WXFIKYE-0ojwsRXQ.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D6MtTWaR.js → stateDiagram-RAJIS63D-DvTark-k.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CiT1CTy0.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Oa_SYaCP.js → timeline-definition-YZTLITO2-B4EQprf1.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-vrIbKmuv.js → treemap-KZPCXAKY-ht_xOxVL.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-B3UlkEHW.js → vennDiagram-LZ73GAT5-C4DqZkvq.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BLiVVy6A.js → xychartDiagram-JWTSCODW-eoymvv9D.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +449 -124
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +5766 -3927
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +1191 -1181
- package/dist/launch/index.js.map +1 -1
- package/package.json +2 -1
- package/platforms/README.md +21 -0
- package/platforms/claude-code/skills/clear-assignment/SKILL.md +2 -2
- package/platforms/claude-code/skills/log-progress/SKILL.md +29 -48
- package/platforms/claude-code/skills/plan-assignment/SKILL.md +20 -1
- package/platforms/claude-code/skills/save-session-summary/SKILL.md +28 -29
- package/platforms/claude-code/skills/set-workspace/SKILL.md +25 -41
- package/platforms/codex/skills/clear-assignment/SKILL.md +2 -2
- package/platforms/codex/skills/log-progress/SKILL.md +29 -48
- package/platforms/codex/skills/plan-assignment/SKILL.md +20 -1
- package/platforms/codex/skills/save-session-summary/SKILL.md +28 -29
- package/platforms/codex/skills/set-workspace/SKILL.md +25 -41
- package/skills/clear-assignment/SKILL.md +2 -2
- package/skills/log-progress/SKILL.md +29 -48
- package/skills/plan-assignment/SKILL.md +20 -1
- package/skills/save-session-summary/SKILL.md +28 -29
- package/skills/set-workspace/SKILL.md +25 -41
- package/dashboard/dist/assets/channel-D41AslDq.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BnKy62Yt.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BnKy62Yt.js +0 -1
- package/dashboard/dist/assets/clone-Cz7h9axV.js +0 -1
- package/dashboard/dist/assets/index-Ds1-e_jv.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-sYL-A3ib.js +0 -1
package/dist/launch/index.js
CHANGED
|
@@ -256,1147 +256,1301 @@ var init_fs_migration = __esm({
|
|
|
256
256
|
}
|
|
257
257
|
});
|
|
258
258
|
|
|
259
|
-
// src/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function canonicalizeCombo(input) {
|
|
264
|
-
if (typeof input !== "string") return "";
|
|
265
|
-
const trimmed = input.trim();
|
|
266
|
-
if (!trimmed) return "";
|
|
267
|
-
if (/\s/.test(trimmed) && !trimmed.includes("+")) {
|
|
268
|
-
return trimmed.split(/\s+/).map(canonicalizeCombo).filter((part) => part.length > 0).join(" ");
|
|
269
|
-
}
|
|
270
|
-
const parts = trimmed.split("+").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
271
|
-
if (parts.length === 0) return "";
|
|
272
|
-
if (parts.length === 1) {
|
|
273
|
-
return parts[0].toLowerCase();
|
|
274
|
-
}
|
|
275
|
-
const key = parts[parts.length - 1].toLowerCase();
|
|
276
|
-
const mods = parts.slice(0, -1).map((m) => m.toLowerCase());
|
|
277
|
-
const seen = /* @__PURE__ */ new Set();
|
|
278
|
-
const ordered = [];
|
|
279
|
-
for (const m of MODIFIER_ORDER) {
|
|
280
|
-
if (mods.includes(m) && !seen.has(m)) {
|
|
281
|
-
ordered.push(m);
|
|
282
|
-
seen.add(m);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
for (const m of mods) {
|
|
286
|
-
if (!seen.has(m)) {
|
|
287
|
-
ordered.push(m);
|
|
288
|
-
seen.add(m);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return [...ordered, key].join("+");
|
|
292
|
-
}
|
|
293
|
-
var BINDABLE_ACTION_KINDS, MODIFIER_ORDER, DEFAULT_BINDABLE_HOTKEYS;
|
|
294
|
-
var init_hotkeysCatalog = __esm({
|
|
295
|
-
"src/utils/hotkeysCatalog.ts"() {
|
|
259
|
+
// src/lifecycle/types.ts
|
|
260
|
+
var DEFAULT_STATUSES;
|
|
261
|
+
var init_types = __esm({
|
|
262
|
+
"src/lifecycle/types.ts"() {
|
|
296
263
|
"use strict";
|
|
297
|
-
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
301
|
-
"
|
|
264
|
+
DEFAULT_STATUSES = [
|
|
265
|
+
"draft",
|
|
266
|
+
"pending",
|
|
267
|
+
"ready_for_planning",
|
|
268
|
+
"ready_to_implement",
|
|
269
|
+
"in_progress",
|
|
270
|
+
"blocked",
|
|
271
|
+
"review",
|
|
272
|
+
"completed",
|
|
273
|
+
"failed"
|
|
302
274
|
];
|
|
303
|
-
MODIFIER_ORDER = ["mod", "ctrl", "alt", "shift"];
|
|
304
|
-
DEFAULT_BINDABLE_HOTKEYS = {
|
|
305
|
-
"new-workspace": canonicalizeCombo("Mod+Shift+Alt+w"),
|
|
306
|
-
"new-project": canonicalizeCombo("Mod+Shift+Alt+p"),
|
|
307
|
-
"new-todo": canonicalizeCombo("Mod+Shift+Alt+t"),
|
|
308
|
-
"new-assignment": canonicalizeCombo("Mod+Shift+Alt+a")
|
|
309
|
-
};
|
|
310
275
|
}
|
|
311
276
|
});
|
|
312
277
|
|
|
313
|
-
// src/
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
BUILTIN_AGENTS = [
|
|
319
|
-
{
|
|
320
|
-
id: "claude",
|
|
321
|
-
label: "Claude",
|
|
322
|
-
command: "claude",
|
|
323
|
-
default: true,
|
|
324
|
-
resume: { args: ["--resume", "{id}"] },
|
|
325
|
-
fork: { args: ["--resume", "{id}", "--fork-session"] }
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
id: "codex",
|
|
329
|
-
label: "Codex",
|
|
330
|
-
command: "codex",
|
|
331
|
-
resume: { args: ["resume", "{id}"] },
|
|
332
|
-
fork: { args: ["fork", "{id}"] }
|
|
333
|
-
}
|
|
334
|
-
];
|
|
335
|
-
AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
336
|
-
PROMPT_ARG_POSITIONS = ["first", "last", "none"];
|
|
278
|
+
// src/lifecycle/state-machine.ts
|
|
279
|
+
function buildTransitionTable(transitions) {
|
|
280
|
+
const table = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const t of transitions) {
|
|
282
|
+
table.set(`${t.from}:${t.command}`, t.to);
|
|
337
283
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const seen = /* @__PURE__ */ new Set();
|
|
344
|
-
const out = [];
|
|
345
|
-
for (const raw of input) {
|
|
346
|
-
if (typeof raw !== "string") continue;
|
|
347
|
-
const name = raw.trim();
|
|
348
|
-
if (name.length === 0) continue;
|
|
349
|
-
if (name.length > MAX_WORKSPACE_NAME_LENGTH) continue;
|
|
350
|
-
if (/[\r\n]/.test(name)) continue;
|
|
351
|
-
if (seen.has(name)) continue;
|
|
352
|
-
seen.add(name);
|
|
353
|
-
out.push(name);
|
|
284
|
+
return table;
|
|
285
|
+
}
|
|
286
|
+
function getTargetStatus(_from, command, table) {
|
|
287
|
+
if (!table) {
|
|
288
|
+
return DEFAULT_COMMAND_TARGETS.get(command) ?? null;
|
|
354
289
|
}
|
|
355
|
-
return
|
|
290
|
+
return table.get(command) ?? table.get(`${_from}:${command}`) ?? null;
|
|
356
291
|
}
|
|
357
|
-
var
|
|
358
|
-
var
|
|
359
|
-
"src/
|
|
292
|
+
var DEFAULT_COMMAND_TARGETS, DEFAULT_TRANSITION_TABLE;
|
|
293
|
+
var init_state_machine = __esm({
|
|
294
|
+
"src/lifecycle/state-machine.ts"() {
|
|
360
295
|
"use strict";
|
|
361
|
-
|
|
296
|
+
init_types();
|
|
297
|
+
DEFAULT_COMMAND_TARGETS = /* @__PURE__ */ new Map([
|
|
298
|
+
["start", "in_progress"],
|
|
299
|
+
["shape", "ready_for_planning"],
|
|
300
|
+
["plan-ready", "ready_to_implement"],
|
|
301
|
+
["implement", "in_progress"],
|
|
302
|
+
["block", "blocked"],
|
|
303
|
+
["unblock", "in_progress"],
|
|
304
|
+
["review", "review"],
|
|
305
|
+
["complete", "completed"],
|
|
306
|
+
["fail", "failed"],
|
|
307
|
+
["reopen", "in_progress"]
|
|
308
|
+
]);
|
|
309
|
+
DEFAULT_TRANSITION_TABLE = /* @__PURE__ */ new Map([
|
|
310
|
+
["pending:start", "in_progress"],
|
|
311
|
+
["pending:block", "blocked"],
|
|
312
|
+
["draft:shape", "ready_for_planning"],
|
|
313
|
+
["draft:start", "in_progress"],
|
|
314
|
+
["ready_for_planning:plan-ready", "ready_to_implement"],
|
|
315
|
+
["ready_for_planning:start", "in_progress"],
|
|
316
|
+
["ready_to_implement:implement", "in_progress"],
|
|
317
|
+
["in_progress:block", "blocked"],
|
|
318
|
+
["in_progress:review", "review"],
|
|
319
|
+
["in_progress:complete", "completed"],
|
|
320
|
+
["in_progress:fail", "failed"],
|
|
321
|
+
["blocked:unblock", "in_progress"],
|
|
322
|
+
["review:start", "in_progress"],
|
|
323
|
+
["review:complete", "completed"],
|
|
324
|
+
["review:fail", "failed"],
|
|
325
|
+
["completed:reopen", "in_progress"],
|
|
326
|
+
["failed:reopen", "in_progress"]
|
|
327
|
+
]);
|
|
362
328
|
}
|
|
363
329
|
});
|
|
364
330
|
|
|
365
|
-
// src/
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
function parseAgentCommand(value, agentId) {
|
|
370
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
371
|
-
throw new AgentConfigError(
|
|
372
|
-
`agent${agentId ? ` "${agentId}"` : ""} has empty command`
|
|
373
|
-
);
|
|
331
|
+
// src/lifecycle/frontmatter.ts
|
|
332
|
+
var init_frontmatter = __esm({
|
|
333
|
+
"src/lifecycle/frontmatter.ts"() {
|
|
334
|
+
"use strict";
|
|
374
335
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// src/dashboard/parser.ts
|
|
339
|
+
function extractFrontmatter(fileContent) {
|
|
340
|
+
const match = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
341
|
+
if (!match) {
|
|
342
|
+
return ["", fileContent];
|
|
378
343
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
344
|
+
const frontmatterBlock = match[1];
|
|
345
|
+
const body = fileContent.slice(match[0].length).trim();
|
|
346
|
+
return [frontmatterBlock, body];
|
|
347
|
+
}
|
|
348
|
+
function parseSimpleValue(raw) {
|
|
349
|
+
const trimmed = raw.trim();
|
|
350
|
+
if (trimmed === "null" || trimmed === "~" || trimmed === "") return null;
|
|
351
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
352
|
+
return trimmed.slice(1, -1);
|
|
383
353
|
}
|
|
384
|
-
return
|
|
354
|
+
return trimmed;
|
|
385
355
|
}
|
|
386
|
-
function
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
356
|
+
function getField(frontmatter, key) {
|
|
357
|
+
const match = frontmatter.match(new RegExp(`^${key}:\\s*(.*)$`, "m"));
|
|
358
|
+
if (!match) return null;
|
|
359
|
+
return parseSimpleValue(match[1]);
|
|
360
|
+
}
|
|
361
|
+
function getNestedField(frontmatter, parent, key) {
|
|
362
|
+
const parentRegex = new RegExp(`^${parent}:\\s*\\n((?:\\s+.*\\n?)*)`, "m");
|
|
363
|
+
const parentMatch = frontmatter.match(parentRegex);
|
|
364
|
+
if (!parentMatch) return null;
|
|
365
|
+
const block = parentMatch[1];
|
|
366
|
+
const fieldMatch = block.match(new RegExp(`^\\s+${key}:\\s*(.*)$`, "m"));
|
|
367
|
+
if (!fieldMatch) return null;
|
|
368
|
+
return parseSimpleValue(fieldMatch[1]);
|
|
369
|
+
}
|
|
370
|
+
function parseListField(frontmatter, fieldName) {
|
|
371
|
+
const inlineMatch = frontmatter.match(new RegExp(`^${fieldName}:\\s*\\[\\s*\\]`, "m"));
|
|
372
|
+
if (inlineMatch) return [];
|
|
373
|
+
const results = [];
|
|
374
|
+
const blockMatch = frontmatter.match(
|
|
375
|
+
new RegExp(`^${fieldName}:\\s*\\n((?:\\s+-\\s+.*\\n?)*)`, "m")
|
|
376
|
+
);
|
|
377
|
+
if (blockMatch) {
|
|
378
|
+
let item;
|
|
379
|
+
const regex = /^\s+-\s+(.+)$/gm;
|
|
380
|
+
while ((item = regex.exec(blockMatch[1])) !== null) {
|
|
381
|
+
results.push(item[1].trim());
|
|
407
382
|
}
|
|
408
|
-
validateSessionInvocation(agent, "resume", agent.resume);
|
|
409
|
-
validateSessionInvocation(agent, "fork", agent.fork);
|
|
410
|
-
if (agent.default) defaults++;
|
|
411
|
-
}
|
|
412
|
-
if (defaults > 1) {
|
|
413
|
-
throw new AgentConfigError(
|
|
414
|
-
`more than one agent is marked default: true (only one is allowed)`
|
|
415
|
-
);
|
|
416
383
|
}
|
|
384
|
+
return results;
|
|
417
385
|
}
|
|
418
|
-
function
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
throw new AgentConfigError(
|
|
422
|
-
`agent "${agent.id}" ${mode}.args must be an array of strings`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
for (const a of invocation.args) {
|
|
426
|
-
if (typeof a !== "string") {
|
|
427
|
-
throw new AgentConfigError(
|
|
428
|
-
`agent "${agent.id}" ${mode}.args must contain only strings`
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (invocation.command !== void 0 && (typeof invocation.command !== "string" || invocation.command.trim() === "")) {
|
|
433
|
-
throw new AgentConfigError(
|
|
434
|
-
`agent "${agent.id}" ${mode}.command must be a non-empty string when present`
|
|
435
|
-
);
|
|
386
|
+
function unquoteYamlString(value) {
|
|
387
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
388
|
+
return value.slice(1, -1);
|
|
436
389
|
}
|
|
390
|
+
return value;
|
|
437
391
|
}
|
|
438
|
-
function
|
|
392
|
+
function parseProject(fileContent) {
|
|
393
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
394
|
+
const slug = getField(fm, "slug") ?? getField(fm, "mission") ?? "";
|
|
439
395
|
return {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
agents: DEFAULT_CONFIG.agents ? DEFAULT_CONFIG.agents.map((a) => ({
|
|
455
|
-
...a,
|
|
456
|
-
...a.args ? { args: [...a.args] } : {},
|
|
457
|
-
...a.resume ? { resume: { ...a.resume, args: [...a.resume.args] } } : {},
|
|
458
|
-
...a.fork ? { fork: { ...a.fork, args: [...a.fork.args] } } : {}
|
|
459
|
-
})) : null,
|
|
460
|
-
playbooks: {
|
|
461
|
-
disabled: [...DEFAULT_CONFIG.playbooks.disabled]
|
|
462
|
-
},
|
|
463
|
-
theme: DEFAULT_CONFIG.theme ? { ...DEFAULT_CONFIG.theme } : null,
|
|
464
|
-
hotkeys: DEFAULT_CONFIG.hotkeys ? { bindings: { ...DEFAULT_CONFIG.hotkeys.bindings } } : null,
|
|
465
|
-
terminal: DEFAULT_CONFIG.terminal,
|
|
466
|
-
workspaceVisibility: {
|
|
467
|
-
hidden: [...DEFAULT_CONFIG.workspaceVisibility.hidden]
|
|
468
|
-
}
|
|
396
|
+
id: getField(fm, "id") ?? "",
|
|
397
|
+
slug,
|
|
398
|
+
title: getField(fm, "title") ?? "",
|
|
399
|
+
archived: getField(fm, "archived") === "true",
|
|
400
|
+
archivedAt: getField(fm, "archivedAt"),
|
|
401
|
+
archivedReason: getField(fm, "archivedReason"),
|
|
402
|
+
statusOverride: getField(fm, "statusOverride"),
|
|
403
|
+
created: getField(fm, "created") ?? "",
|
|
404
|
+
updated: getField(fm, "updated") ?? "",
|
|
405
|
+
tags: parseListField(fm, "tags"),
|
|
406
|
+
workspace: getField(fm, "workspace"),
|
|
407
|
+
repositories: parseListField(fm, "repositories").map(unquoteYamlString),
|
|
408
|
+
externalIds: parseExternalIds(fm),
|
|
409
|
+
body
|
|
469
410
|
};
|
|
470
411
|
}
|
|
471
|
-
function
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (colonIndex < 0) continue;
|
|
482
|
-
const key = line.slice(0, colonIndex).trim();
|
|
483
|
-
const value = line.slice(colonIndex + 1).trim();
|
|
484
|
-
if (indent === 0) {
|
|
485
|
-
if (value === "" || value === void 0) {
|
|
486
|
-
currentParent = key;
|
|
487
|
-
} else {
|
|
488
|
-
currentParent = null;
|
|
489
|
-
result[key] = value.replace(/^["']|["']$/g, "");
|
|
412
|
+
function parseStatus(fileContent) {
|
|
413
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
414
|
+
const progress = { total: 0 };
|
|
415
|
+
const progressMatch = fm.match(/^progress:\s*\n((?:\s+.*\n?)*)/m);
|
|
416
|
+
if (progressMatch) {
|
|
417
|
+
const lines = progressMatch[1].split("\n");
|
|
418
|
+
for (const line of lines) {
|
|
419
|
+
const kv = line.match(/^\s+(\w+):\s*(\d+)/);
|
|
420
|
+
if (kv) {
|
|
421
|
+
progress[kv[1]] = parseInt(kv[2], 10);
|
|
490
422
|
}
|
|
491
|
-
} else if (indent > 0 && currentParent) {
|
|
492
|
-
result[`${currentParent}.${key}`] = value.replace(/^["']|["']$/g, "");
|
|
493
423
|
}
|
|
494
424
|
}
|
|
495
|
-
return
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
return Object.keys(installedAgents).length > 0 ? { installedAgents } : {};
|
|
425
|
+
return {
|
|
426
|
+
project: getField(fm, "project") ?? "",
|
|
427
|
+
status: getField(fm, "status") ?? "pending",
|
|
428
|
+
progress,
|
|
429
|
+
needsAttention: {
|
|
430
|
+
blockedCount: parseInt(getNestedField(fm, "needsAttention", "blockedCount") ?? "0", 10),
|
|
431
|
+
failedCount: parseInt(getNestedField(fm, "needsAttention", "failedCount") ?? "0", 10),
|
|
432
|
+
openQuestions: parseInt(getNestedField(fm, "needsAttention", "openQuestions") ?? "0", 10)
|
|
433
|
+
},
|
|
434
|
+
body
|
|
435
|
+
};
|
|
508
436
|
}
|
|
509
|
-
function
|
|
510
|
-
const
|
|
511
|
-
if (
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
let currentSection = null;
|
|
521
|
-
const lines = remaining.split("\n");
|
|
522
|
-
function parseListEntry(lineIdx, baseIndent) {
|
|
437
|
+
function parseExternalIds(frontmatter) {
|
|
438
|
+
const inlineMatch = frontmatter.match(/^externalIds:\s*\[\s*\]/m);
|
|
439
|
+
if (inlineMatch) return [];
|
|
440
|
+
const results = [];
|
|
441
|
+
const blockMatch = frontmatter.match(
|
|
442
|
+
/^externalIds:\s*\n((?:\s+-\s+[\s\S]*?)(?=^\w|\n---))/m
|
|
443
|
+
);
|
|
444
|
+
if (!blockMatch) return [];
|
|
445
|
+
const itemBlocks = blockMatch[1].split(/\n\s+-\s+/).filter(Boolean);
|
|
446
|
+
for (const block of itemBlocks) {
|
|
447
|
+
const lines = block.split("\n");
|
|
523
448
|
const entry = {};
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
for (let i = lineIdx + 1; i < lines.length; i++) {
|
|
531
|
-
const next = lines[i];
|
|
532
|
-
const nextTrimmed = next.trimStart();
|
|
533
|
-
const nextIndent = next.length - nextTrimmed.length;
|
|
534
|
-
if (nextIndent <= baseIndent || nextTrimmed.startsWith("- ")) break;
|
|
535
|
-
const ci = nextTrimmed.indexOf(":");
|
|
536
|
-
if (ci > 0) {
|
|
537
|
-
entry[nextTrimmed.slice(0, ci).trim()] = nextTrimmed.slice(ci + 1).trim();
|
|
538
|
-
}
|
|
539
|
-
consumed++;
|
|
540
|
-
}
|
|
541
|
-
return { entry, consumed };
|
|
542
|
-
}
|
|
543
|
-
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
544
|
-
const line = lines[lineIdx];
|
|
545
|
-
const trimmed = line.trimStart();
|
|
546
|
-
const indent = line.length - trimmed.length;
|
|
547
|
-
if (indent === 2 && trimmed.endsWith(":")) {
|
|
548
|
-
const key = trimmed.slice(0, -1).trim();
|
|
549
|
-
if (key === "definitions") currentSection = "definitions";
|
|
550
|
-
else if (key === "order") currentSection = "order";
|
|
551
|
-
else if (key === "transitions") currentSection = "transitions";
|
|
552
|
-
else currentSection = null;
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
if (indent === 0 && trimmed.includes(":")) break;
|
|
556
|
-
if (currentSection === "order" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
557
|
-
order.push(trimmed.slice(2).trim());
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
if (currentSection === "definitions" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
561
|
-
const { entry, consumed } = parseListEntry(lineIdx, indent);
|
|
562
|
-
if (entry["id"]) {
|
|
563
|
-
statuses.push({
|
|
564
|
-
id: entry["id"],
|
|
565
|
-
label: entry["label"] ?? entry["id"],
|
|
566
|
-
description: entry["description"],
|
|
567
|
-
color: entry["color"],
|
|
568
|
-
icon: entry["icon"],
|
|
569
|
-
terminal: entry["terminal"] === "true"
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
lineIdx += consumed - 1;
|
|
573
|
-
continue;
|
|
449
|
+
for (const line of lines) {
|
|
450
|
+
const colonIdx = line.indexOf(":");
|
|
451
|
+
if (colonIdx < 0) continue;
|
|
452
|
+
const key = line.slice(0, colonIdx).trim().replace(/^-\s+/, "");
|
|
453
|
+
if (!key) continue;
|
|
454
|
+
entry[key] = parseSimpleValue(line.slice(colonIdx + 1));
|
|
574
455
|
}
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
to: entry["to"],
|
|
582
|
-
label: entry["label"],
|
|
583
|
-
description: entry["description"],
|
|
584
|
-
requiresReason: entry["requiresReason"] === "true"
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
lineIdx += consumed - 1;
|
|
588
|
-
continue;
|
|
456
|
+
if (entry["system"] && entry["id"]) {
|
|
457
|
+
results.push({
|
|
458
|
+
system: entry["system"],
|
|
459
|
+
id: entry["id"],
|
|
460
|
+
url: entry["url"] || null
|
|
461
|
+
});
|
|
589
462
|
}
|
|
590
463
|
}
|
|
591
|
-
|
|
464
|
+
return results;
|
|
465
|
+
}
|
|
466
|
+
function parseAssignmentFull(fileContent) {
|
|
467
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
592
468
|
return {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
469
|
+
id: getField(fm, "id") ?? "",
|
|
470
|
+
slug: getField(fm, "slug") ?? "",
|
|
471
|
+
title: getField(fm, "title") ?? "",
|
|
472
|
+
project: getField(fm, "project"),
|
|
473
|
+
workspaceGroup: getField(fm, "workspaceGroup"),
|
|
474
|
+
type: getField(fm, "type"),
|
|
475
|
+
status: getField(fm, "status") ?? "pending",
|
|
476
|
+
priority: getField(fm, "priority") ?? "medium",
|
|
477
|
+
assignee: getField(fm, "assignee"),
|
|
478
|
+
dependsOn: parseListField(fm, "dependsOn"),
|
|
479
|
+
links: parseListField(fm, "links"),
|
|
480
|
+
blockedReason: getField(fm, "blockedReason"),
|
|
481
|
+
workspace: {
|
|
482
|
+
repository: getNestedField(fm, "workspace", "repository"),
|
|
483
|
+
worktreePath: getNestedField(fm, "workspace", "worktreePath"),
|
|
484
|
+
branch: getNestedField(fm, "workspace", "branch"),
|
|
485
|
+
parentBranch: getNestedField(fm, "workspace", "parentBranch")
|
|
486
|
+
},
|
|
487
|
+
externalIds: parseExternalIds(fm),
|
|
488
|
+
tags: parseListField(fm, "tags"),
|
|
489
|
+
archived: getField(fm, "archived") === "true",
|
|
490
|
+
archivedAt: getField(fm, "archivedAt"),
|
|
491
|
+
archivedReason: getField(fm, "archivedReason"),
|
|
492
|
+
created: getField(fm, "created") ?? "",
|
|
493
|
+
updated: getField(fm, "updated") ?? "",
|
|
494
|
+
body
|
|
596
495
|
};
|
|
597
496
|
}
|
|
598
|
-
function
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
497
|
+
function parsePlan(fileContent) {
|
|
498
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
499
|
+
return {
|
|
500
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
501
|
+
status: getField(fm, "status") ?? "",
|
|
502
|
+
created: getField(fm, "created") ?? "",
|
|
503
|
+
updated: getField(fm, "updated") ?? "",
|
|
504
|
+
body
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function parseScratchpad(fileContent) {
|
|
508
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
509
|
+
return {
|
|
510
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
511
|
+
updated: getField(fm, "updated") ?? "",
|
|
512
|
+
body
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function parseHandoff(fileContent) {
|
|
516
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
517
|
+
return {
|
|
518
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
519
|
+
handoffCount: parseInt(getField(fm, "handoffCount") ?? "0", 10),
|
|
520
|
+
updated: getField(fm, "updated") ?? "",
|
|
521
|
+
body
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function parseDecisionRecord(fileContent) {
|
|
525
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
526
|
+
return {
|
|
527
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
528
|
+
decisionCount: parseInt(getField(fm, "decisionCount") ?? "0", 10),
|
|
529
|
+
updated: getField(fm, "updated") ?? "",
|
|
530
|
+
body
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function parseComments(fileContent) {
|
|
534
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
535
|
+
const entries = [];
|
|
536
|
+
const sections = body.split(/^## /m).slice(1);
|
|
537
|
+
for (const section of sections) {
|
|
538
|
+
const newlineIdx = section.indexOf("\n");
|
|
539
|
+
if (newlineIdx === -1) continue;
|
|
540
|
+
const id = section.slice(0, newlineIdx).trim();
|
|
541
|
+
const rest = section.slice(newlineIdx + 1);
|
|
542
|
+
const headerMatch = rest.match(
|
|
543
|
+
/^\s*\*\*Recorded:\*\*\s*(.*)\n\*\*Author:\*\*\s*(.*)\n\*\*Type:\*\*\s*(question|note|feedback)(?:\n\*\*Reply to:\*\*\s*(.*))?(?:\n\*\*Resolved:\*\*\s*(true|false))?\n+([\s\S]*)$/
|
|
544
|
+
);
|
|
545
|
+
if (!headerMatch) continue;
|
|
546
|
+
const [, timestamp, author, type, replyTo, resolvedStr, entryBody] = headerMatch;
|
|
547
|
+
const entry = {
|
|
548
|
+
id,
|
|
549
|
+
timestamp: timestamp.trim(),
|
|
550
|
+
author: author.trim(),
|
|
551
|
+
type,
|
|
552
|
+
body: entryBody.trim()
|
|
553
|
+
};
|
|
554
|
+
if (replyTo) entry.replyTo = replyTo.trim();
|
|
555
|
+
if (resolvedStr) entry.resolved = resolvedStr === "true";
|
|
556
|
+
entries.push(entry);
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
560
|
+
entryCount: parseInt(getField(fm, "entryCount") ?? "0", 10),
|
|
561
|
+
updated: getField(fm, "updated") ?? "",
|
|
562
|
+
entries,
|
|
563
|
+
body
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function parseProgress(fileContent) {
|
|
567
|
+
const [fm, body] = extractFrontmatter(fileContent);
|
|
568
|
+
const entries = [];
|
|
569
|
+
const sections = body.split(/^## /m).slice(1);
|
|
570
|
+
for (const section of sections) {
|
|
571
|
+
const newlineIdx = section.indexOf("\n");
|
|
572
|
+
if (newlineIdx === -1) continue;
|
|
573
|
+
const timestamp = section.slice(0, newlineIdx).trim();
|
|
574
|
+
const entryBody = section.slice(newlineIdx + 1).trim();
|
|
575
|
+
entries.push({ timestamp, body: entryBody });
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
assignment: getField(fm, "assignment") ?? "",
|
|
579
|
+
entryCount: parseInt(getField(fm, "entryCount") ?? "0", 10),
|
|
580
|
+
updated: getField(fm, "updated") ?? "",
|
|
581
|
+
entries,
|
|
582
|
+
body
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function extractMermaidGraph(body) {
|
|
586
|
+
const match = body.match(/```mermaid\n([\s\S]*?)```/);
|
|
587
|
+
return match ? match[1].trim() : null;
|
|
588
|
+
}
|
|
589
|
+
var init_parser = __esm({
|
|
590
|
+
"src/dashboard/parser.ts"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// src/todos/parser.ts
|
|
596
|
+
import { randomBytes } from "crypto";
|
|
597
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
598
|
+
import { resolve as resolve3 } from "path";
|
|
599
|
+
var init_parser2 = __esm({
|
|
600
|
+
"src/todos/parser.ts"() {
|
|
601
|
+
"use strict";
|
|
602
|
+
init_parser();
|
|
603
|
+
init_fs();
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// src/lifecycle/linked-todos.ts
|
|
608
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
609
|
+
import { resolve as resolve4 } from "path";
|
|
610
|
+
var init_linked_todos = __esm({
|
|
611
|
+
"src/lifecycle/linked-todos.ts"() {
|
|
612
|
+
"use strict";
|
|
613
|
+
init_parser2();
|
|
614
|
+
init_fs();
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// src/lifecycle/transitions.ts
|
|
619
|
+
import { resolve as resolve5 } from "path";
|
|
620
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
621
|
+
var init_transitions = __esm({
|
|
622
|
+
"src/lifecycle/transitions.ts"() {
|
|
623
|
+
"use strict";
|
|
624
|
+
init_fs();
|
|
625
|
+
init_timestamp();
|
|
626
|
+
init_state_machine();
|
|
627
|
+
init_frontmatter();
|
|
628
|
+
init_linked_todos();
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// src/lifecycle/index.ts
|
|
633
|
+
var init_lifecycle = __esm({
|
|
634
|
+
"src/lifecycle/index.ts"() {
|
|
635
|
+
"use strict";
|
|
636
|
+
init_types();
|
|
637
|
+
init_state_machine();
|
|
638
|
+
init_frontmatter();
|
|
639
|
+
init_transitions();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// src/utils/hotkeysCatalog.ts
|
|
644
|
+
function isBindableActionKind(value) {
|
|
645
|
+
return typeof value === "string" && BINDABLE_ACTION_KINDS.includes(value);
|
|
646
|
+
}
|
|
647
|
+
function canonicalizeCombo(input) {
|
|
648
|
+
if (typeof input !== "string") return "";
|
|
649
|
+
const trimmed = input.trim();
|
|
650
|
+
if (!trimmed) return "";
|
|
651
|
+
if (/\s/.test(trimmed) && !trimmed.includes("+")) {
|
|
652
|
+
return trimmed.split(/\s+/).map(canonicalizeCombo).filter((part) => part.length > 0).join(" ");
|
|
653
|
+
}
|
|
654
|
+
const parts = trimmed.split("+").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
655
|
+
if (parts.length === 0) return "";
|
|
656
|
+
if (parts.length === 1) {
|
|
657
|
+
return parts[0].toLowerCase();
|
|
658
|
+
}
|
|
659
|
+
const key = parts[parts.length - 1].toLowerCase();
|
|
660
|
+
const mods = parts.slice(0, -1).map((m) => m.toLowerCase());
|
|
661
|
+
const seen = /* @__PURE__ */ new Set();
|
|
662
|
+
const ordered = [];
|
|
663
|
+
for (const m of MODIFIER_ORDER) {
|
|
664
|
+
if (mods.includes(m) && !seen.has(m)) {
|
|
665
|
+
ordered.push(m);
|
|
666
|
+
seen.add(m);
|
|
619
667
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
disabled.push(raw);
|
|
628
|
-
continue;
|
|
668
|
+
}
|
|
669
|
+
for (const m of mods) {
|
|
670
|
+
if (!seen.has(m)) {
|
|
671
|
+
ordered.push(m);
|
|
672
|
+
seen.add(m);
|
|
629
673
|
}
|
|
630
674
|
}
|
|
631
|
-
return
|
|
675
|
+
return [...ordered, key].join("+");
|
|
632
676
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
677
|
+
var BINDABLE_ACTION_KINDS, MODIFIER_ORDER, DEFAULT_BINDABLE_HOTKEYS;
|
|
678
|
+
var init_hotkeysCatalog = __esm({
|
|
679
|
+
"src/utils/hotkeysCatalog.ts"() {
|
|
680
|
+
"use strict";
|
|
681
|
+
BINDABLE_ACTION_KINDS = [
|
|
682
|
+
"new-workspace",
|
|
683
|
+
"new-project",
|
|
684
|
+
"new-todo",
|
|
685
|
+
"new-assignment"
|
|
686
|
+
];
|
|
687
|
+
MODIFIER_ORDER = ["mod", "ctrl", "alt", "shift"];
|
|
688
|
+
DEFAULT_BINDABLE_HOTKEYS = {
|
|
689
|
+
"new-workspace": canonicalizeCombo("Mod+Shift+Alt+w"),
|
|
690
|
+
"new-project": canonicalizeCombo("Mod+Shift+Alt+p"),
|
|
691
|
+
"new-todo": canonicalizeCombo("Mod+Shift+Alt+t"),
|
|
692
|
+
"new-assignment": canonicalizeCombo("Mod+Shift+Alt+a")
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// src/utils/agents-schema.ts
|
|
698
|
+
var BUILTIN_AGENTS, AGENT_ID_PATTERN, PROMPT_ARG_POSITIONS;
|
|
699
|
+
var init_agents_schema = __esm({
|
|
700
|
+
"src/utils/agents-schema.ts"() {
|
|
701
|
+
"use strict";
|
|
702
|
+
BUILTIN_AGENTS = [
|
|
703
|
+
{
|
|
704
|
+
id: "claude",
|
|
705
|
+
label: "Claude",
|
|
706
|
+
command: "claude",
|
|
707
|
+
default: true,
|
|
708
|
+
resume: { args: ["--resume", "{id}"] },
|
|
709
|
+
fork: { args: ["--resume", "{id}", "--fork-session"] }
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
id: "codex",
|
|
713
|
+
label: "Codex",
|
|
714
|
+
command: "codex",
|
|
715
|
+
resume: { args: ["resume", "{id}"] },
|
|
716
|
+
fork: { args: ["fork", "{id}"] }
|
|
717
|
+
}
|
|
718
|
+
];
|
|
719
|
+
AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
720
|
+
PROMPT_ARG_POSITIONS = ["first", "last", "none"];
|
|
651
721
|
}
|
|
652
|
-
|
|
653
|
-
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// src/utils/workspace-visibility-schema.ts
|
|
725
|
+
function normalizeHiddenList(input) {
|
|
726
|
+
if (!Array.isArray(input)) return [];
|
|
727
|
+
const seen = /* @__PURE__ */ new Set();
|
|
728
|
+
const out = [];
|
|
729
|
+
for (const raw of input) {
|
|
730
|
+
if (typeof raw !== "string") continue;
|
|
731
|
+
const name = raw.trim();
|
|
732
|
+
if (name.length === 0) continue;
|
|
733
|
+
if (name.length > MAX_WORKSPACE_NAME_LENGTH) continue;
|
|
734
|
+
if (/[\r\n]/.test(name)) continue;
|
|
735
|
+
if (seen.has(name)) continue;
|
|
736
|
+
seen.add(name);
|
|
737
|
+
out.push(name);
|
|
738
|
+
}
|
|
739
|
+
return out;
|
|
654
740
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
741
|
+
var MAX_WORKSPACE_NAME_LENGTH;
|
|
742
|
+
var init_workspace_visibility_schema = __esm({
|
|
743
|
+
"src/utils/workspace-visibility-schema.ts"() {
|
|
744
|
+
"use strict";
|
|
745
|
+
MAX_WORKSPACE_NAME_LENGTH = 256;
|
|
659
746
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
if (currentSection === "hidden" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
674
|
-
const rest = trimmed.slice(2).trim();
|
|
675
|
-
if (rest.length === 0) continue;
|
|
676
|
-
let name;
|
|
677
|
-
if (rest.startsWith('"')) {
|
|
678
|
-
try {
|
|
679
|
-
name = JSON.parse(rest);
|
|
680
|
-
} catch {
|
|
681
|
-
name = rest.replace(/^["']|["']$/g, "");
|
|
682
|
-
}
|
|
683
|
-
} else {
|
|
684
|
-
name = rest;
|
|
685
|
-
}
|
|
686
|
-
hidden.push(name);
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// src/utils/config.ts
|
|
750
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
751
|
+
import { spawnSync } from "child_process";
|
|
752
|
+
import { resolve as resolve6, isAbsolute } from "path";
|
|
753
|
+
function parseAgentCommand(value, agentId) {
|
|
754
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
755
|
+
throw new AgentConfigError(
|
|
756
|
+
`agent${agentId ? ` "${agentId}"` : ""} has empty command`
|
|
757
|
+
);
|
|
689
758
|
}
|
|
690
|
-
|
|
759
|
+
const expanded = expandHome(value.trim());
|
|
760
|
+
if (isAbsolute(expanded)) {
|
|
761
|
+
return resolve6(expanded);
|
|
762
|
+
}
|
|
763
|
+
if (expanded.includes("/")) {
|
|
764
|
+
throw new AgentConfigError(
|
|
765
|
+
`agent${agentId ? ` "${agentId}"` : ""} command "${value}" is a relative path \u2014 use an absolute path or a bare binary name`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return expanded;
|
|
691
769
|
}
|
|
692
|
-
function
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const bindings = {};
|
|
701
|
-
let inBindings = false;
|
|
702
|
-
for (const line of remaining) {
|
|
703
|
-
const trimmed = line.trimStart();
|
|
704
|
-
const indent = line.length - trimmed.length;
|
|
705
|
-
if (indent === 0 && trimmed.length > 0) break;
|
|
706
|
-
if (trimmed === "") continue;
|
|
707
|
-
if (indent === 2 && trimmed === "bindings:") {
|
|
708
|
-
inBindings = true;
|
|
709
|
-
continue;
|
|
770
|
+
function validateAgentList(agents) {
|
|
771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
772
|
+
let defaults = 0;
|
|
773
|
+
for (const agent of agents) {
|
|
774
|
+
if (!AGENT_ID_PATTERN.test(agent.id)) {
|
|
775
|
+
throw new AgentConfigError(
|
|
776
|
+
`agent id "${agent.id}" is invalid \u2014 must match /^[a-z0-9][a-z0-9_-]*$/`
|
|
777
|
+
);
|
|
710
778
|
}
|
|
711
|
-
if (
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
779
|
+
if (seen.has(agent.id)) {
|
|
780
|
+
throw new AgentConfigError(`duplicate agent id "${agent.id}"`);
|
|
781
|
+
}
|
|
782
|
+
seen.add(agent.id);
|
|
783
|
+
if (!agent.label || agent.label.trim() === "") {
|
|
784
|
+
throw new AgentConfigError(`agent "${agent.id}" has empty label`);
|
|
785
|
+
}
|
|
786
|
+
parseAgentCommand(agent.command, agent.id);
|
|
787
|
+
if (agent.promptArgPosition !== void 0 && !PROMPT_ARG_POSITIONS.includes(agent.promptArgPosition)) {
|
|
788
|
+
throw new AgentConfigError(
|
|
789
|
+
`agent "${agent.id}" has invalid promptArgPosition "${agent.promptArgPosition}" \u2014 expected first|last|none`
|
|
790
|
+
);
|
|
719
791
|
}
|
|
792
|
+
validateSessionInvocation(agent, "resume", agent.resume);
|
|
793
|
+
validateSessionInvocation(agent, "fork", agent.fork);
|
|
794
|
+
if (agent.default) defaults++;
|
|
795
|
+
}
|
|
796
|
+
if (defaults > 1) {
|
|
797
|
+
throw new AgentConfigError(
|
|
798
|
+
`more than one agent is marked default: true (only one is allowed)`
|
|
799
|
+
);
|
|
720
800
|
}
|
|
721
|
-
if (Object.keys(bindings).length === 0) return null;
|
|
722
|
-
return { bindings };
|
|
723
801
|
}
|
|
724
|
-
function
|
|
725
|
-
if (
|
|
726
|
-
|
|
802
|
+
function validateSessionInvocation(agent, mode, invocation) {
|
|
803
|
+
if (invocation === void 0) return;
|
|
804
|
+
if (!Array.isArray(invocation.args)) {
|
|
805
|
+
throw new AgentConfigError(
|
|
806
|
+
`agent "${agent.id}" ${mode}.args must be an array of strings`
|
|
807
|
+
);
|
|
727
808
|
}
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
809
|
+
for (const a of invocation.args) {
|
|
810
|
+
if (typeof a !== "string") {
|
|
811
|
+
throw new AgentConfigError(
|
|
812
|
+
`agent "${agent.id}" ${mode}.args must contain only strings`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (invocation.command !== void 0 && (typeof invocation.command !== "string" || invocation.command.trim() === "")) {
|
|
817
|
+
throw new AgentConfigError(
|
|
818
|
+
`agent "${agent.id}" ${mode}.command must be a non-empty string when present`
|
|
732
819
|
);
|
|
733
|
-
return null;
|
|
734
820
|
}
|
|
735
|
-
return resolve3(expanded);
|
|
736
821
|
}
|
|
737
|
-
function
|
|
822
|
+
function cloneDefaultConfig() {
|
|
823
|
+
return {
|
|
824
|
+
...DEFAULT_CONFIG,
|
|
825
|
+
onboarding: { ...DEFAULT_CONFIG.onboarding },
|
|
826
|
+
agentDefaults: { ...DEFAULT_CONFIG.agentDefaults },
|
|
827
|
+
integrations: { ...DEFAULT_CONFIG.integrations },
|
|
828
|
+
backup: DEFAULT_CONFIG.backup ? { ...DEFAULT_CONFIG.backup } : null,
|
|
829
|
+
statuses: DEFAULT_CONFIG.statuses ? {
|
|
830
|
+
statuses: DEFAULT_CONFIG.statuses.statuses.map((s) => ({ ...s })),
|
|
831
|
+
order: [...DEFAULT_CONFIG.statuses.order],
|
|
832
|
+
transitions: DEFAULT_CONFIG.statuses.transitions.map((t) => ({ ...t }))
|
|
833
|
+
} : null,
|
|
834
|
+
types: DEFAULT_CONFIG.types ? {
|
|
835
|
+
definitions: DEFAULT_CONFIG.types.definitions.map((d) => ({ ...d })),
|
|
836
|
+
default: DEFAULT_CONFIG.types.default
|
|
837
|
+
} : null,
|
|
838
|
+
agents: DEFAULT_CONFIG.agents ? DEFAULT_CONFIG.agents.map((a) => ({
|
|
839
|
+
...a,
|
|
840
|
+
...a.args ? { args: [...a.args] } : {},
|
|
841
|
+
...a.resume ? { resume: { ...a.resume, args: [...a.resume.args] } } : {},
|
|
842
|
+
...a.fork ? { fork: { ...a.fork, args: [...a.fork.args] } } : {}
|
|
843
|
+
})) : null,
|
|
844
|
+
playbooks: {
|
|
845
|
+
disabled: [...DEFAULT_CONFIG.playbooks.disabled]
|
|
846
|
+
},
|
|
847
|
+
theme: DEFAULT_CONFIG.theme ? { ...DEFAULT_CONFIG.theme } : null,
|
|
848
|
+
hotkeys: DEFAULT_CONFIG.hotkeys ? { bindings: { ...DEFAULT_CONFIG.hotkeys.bindings } } : null,
|
|
849
|
+
terminal: DEFAULT_CONFIG.terminal,
|
|
850
|
+
workspaceVisibility: {
|
|
851
|
+
hidden: [...DEFAULT_CONFIG.workspaceVisibility.hidden]
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function parseFrontmatter(content) {
|
|
738
856
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
739
|
-
if (!match) return
|
|
740
|
-
const
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
857
|
+
if (!match) return {};
|
|
858
|
+
const result = {};
|
|
859
|
+
const lines = match[1].split("\n");
|
|
860
|
+
let currentParent = null;
|
|
861
|
+
for (const line of lines) {
|
|
862
|
+
if (line.trim() === "") continue;
|
|
863
|
+
const indent = line.length - line.trimStart().length;
|
|
864
|
+
const colonIndex = line.indexOf(":");
|
|
865
|
+
if (colonIndex < 0) continue;
|
|
866
|
+
const key = line.slice(0, colonIndex).trim();
|
|
867
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
868
|
+
if (indent === 0) {
|
|
869
|
+
if (value === "" || value === void 0) {
|
|
870
|
+
currentParent = key;
|
|
871
|
+
} else {
|
|
872
|
+
currentParent = null;
|
|
873
|
+
result[key] = value.replace(/^["']|["']$/g, "");
|
|
874
|
+
}
|
|
875
|
+
} else if (indent > 0 && currentParent) {
|
|
876
|
+
result[`${currentParent}.${key}`] = value.replace(/^["']|["']$/g, "");
|
|
758
877
|
}
|
|
759
|
-
agents.push({
|
|
760
|
-
id: current.id,
|
|
761
|
-
label: current.label,
|
|
762
|
-
command: current.command,
|
|
763
|
-
...current.args && current.args.length > 0 ? { args: current.args } : {},
|
|
764
|
-
...current.promptArgPosition ? { promptArgPosition: current.promptArgPosition } : {},
|
|
765
|
-
...current.default ? { default: true } : {},
|
|
766
|
-
...current.resolveFromShellAliases ? { resolveFromShellAliases: true } : {},
|
|
767
|
-
...current.resume ? { resume: current.resume } : {},
|
|
768
|
-
...current.fork ? { fork: current.fork } : {}
|
|
769
|
-
});
|
|
770
|
-
current = null;
|
|
771
|
-
argsCapture = null;
|
|
772
|
-
nestedKey = null;
|
|
773
|
-
nestedInvocation = null;
|
|
774
878
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
879
|
+
return result;
|
|
880
|
+
}
|
|
881
|
+
function parseInstalledAgents(fm) {
|
|
882
|
+
const prefix = "integrations.installedAgents.";
|
|
883
|
+
const installedAgents = {};
|
|
884
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
885
|
+
if (!key.startsWith(prefix)) continue;
|
|
886
|
+
const id = key.slice(prefix.length);
|
|
887
|
+
if (!id) continue;
|
|
888
|
+
const scope = value === "project" ? "project" : "global";
|
|
889
|
+
installedAgents[id] = { scope };
|
|
890
|
+
}
|
|
891
|
+
return Object.keys(installedAgents).length > 0 ? { installedAgents } : {};
|
|
892
|
+
}
|
|
893
|
+
function parseStatusConfig(content) {
|
|
894
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
895
|
+
if (!match) return null;
|
|
896
|
+
const fmBlock = match[1];
|
|
897
|
+
const statusesStart = fmBlock.match(/^statuses:\s*$/m);
|
|
898
|
+
if (!statusesStart) return null;
|
|
899
|
+
const startIdx = fmBlock.indexOf(statusesStart[0]) + statusesStart[0].length;
|
|
900
|
+
const remaining = fmBlock.slice(startIdx);
|
|
901
|
+
const statuses = [];
|
|
902
|
+
const order = [];
|
|
903
|
+
const transitions = [];
|
|
904
|
+
let currentSection = null;
|
|
905
|
+
const lines = remaining.split("\n");
|
|
906
|
+
function parseListEntry(lineIdx, baseIndent) {
|
|
907
|
+
const entry = {};
|
|
908
|
+
const firstLine = lines[lineIdx].trimStart().slice(2).trim();
|
|
909
|
+
const colonIdx = firstLine.indexOf(":");
|
|
910
|
+
if (colonIdx > 0) {
|
|
911
|
+
entry[firstLine.slice(0, colonIdx).trim()] = firstLine.slice(colonIdx + 1).trim();
|
|
912
|
+
}
|
|
913
|
+
let consumed = 1;
|
|
914
|
+
for (let i = lineIdx + 1; i < lines.length; i++) {
|
|
915
|
+
const next = lines[i];
|
|
916
|
+
const nextTrimmed = next.trimStart();
|
|
917
|
+
const nextIndent = next.length - nextTrimmed.length;
|
|
918
|
+
if (nextIndent <= baseIndent || nextTrimmed.startsWith("- ")) break;
|
|
919
|
+
const ci = nextTrimmed.indexOf(":");
|
|
920
|
+
if (ci > 0) {
|
|
921
|
+
entry[nextTrimmed.slice(0, ci).trim()] = nextTrimmed.slice(ci + 1).trim();
|
|
780
922
|
}
|
|
923
|
+
consumed++;
|
|
781
924
|
}
|
|
782
|
-
|
|
783
|
-
nestedInvocation = null;
|
|
784
|
-
argsCapture = null;
|
|
925
|
+
return { entry, consumed };
|
|
785
926
|
}
|
|
786
|
-
for (let
|
|
787
|
-
const line = lines[
|
|
927
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
928
|
+
const line = lines[lineIdx];
|
|
788
929
|
const trimmed = line.trimStart();
|
|
789
930
|
const indent = line.length - trimmed.length;
|
|
790
|
-
if (indent ===
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
argsCapture.push(decodeYamlScalar(trimmed.slice(2).trim()));
|
|
797
|
-
continue;
|
|
798
|
-
} else {
|
|
799
|
-
argsCapture = null;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
if (indent === 2 && trimmed.startsWith("- ")) {
|
|
803
|
-
closeNestedBlock();
|
|
804
|
-
flushCurrent();
|
|
805
|
-
current = {};
|
|
806
|
-
const rest = trimmed.slice(2).trim();
|
|
807
|
-
const colonIdx = rest.indexOf(":");
|
|
808
|
-
if (colonIdx > 0) {
|
|
809
|
-
const k = rest.slice(0, colonIdx).trim();
|
|
810
|
-
const v = rest.slice(colonIdx + 1).trim();
|
|
811
|
-
assignAgentField(current, k, v);
|
|
812
|
-
}
|
|
931
|
+
if (indent === 2 && trimmed.endsWith(":")) {
|
|
932
|
+
const key = trimmed.slice(0, -1).trim();
|
|
933
|
+
if (key === "definitions") currentSection = "definitions";
|
|
934
|
+
else if (key === "order") currentSection = "order";
|
|
935
|
+
else if (key === "transitions") currentSection = "transitions";
|
|
936
|
+
else currentSection = null;
|
|
813
937
|
continue;
|
|
814
938
|
}
|
|
815
|
-
if (
|
|
816
|
-
if (
|
|
817
|
-
|
|
818
|
-
if (colonIdx <= 0) continue;
|
|
819
|
-
const k = trimmed.slice(0, colonIdx).trim();
|
|
820
|
-
const v = trimmed.slice(colonIdx + 1).trim();
|
|
821
|
-
if (nestedKey === "resume" || nestedKey === "fork") {
|
|
822
|
-
if (!nestedInvocation) nestedInvocation = { args: [] };
|
|
823
|
-
if (k === "args" && v === "") {
|
|
824
|
-
nestedInvocation.args = [];
|
|
825
|
-
argsCapture = nestedInvocation.args;
|
|
826
|
-
argsBaseIndent = indent;
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
829
|
-
if (k === "command" && v !== "") {
|
|
830
|
-
nestedInvocation.command = decodeYamlScalar(v);
|
|
831
|
-
continue;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
939
|
+
if (indent === 0 && trimmed.includes(":")) break;
|
|
940
|
+
if (currentSection === "order" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
941
|
+
order.push(trimmed.slice(2).trim());
|
|
834
942
|
continue;
|
|
835
943
|
}
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
current.args = argsCapture;
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
|
-
if ((k === "resume" || k === "fork") && v === "") {
|
|
851
|
-
nestedKey = k;
|
|
852
|
-
nestedInvocation = { args: [] };
|
|
853
|
-
nestedBaseIndent = indent;
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
|
-
if (v === "" && !KNOWN_AGENT_SCALAR_FIELDS.has(k)) {
|
|
857
|
-
nestedKey = "__skip__";
|
|
858
|
-
nestedInvocation = null;
|
|
859
|
-
nestedBaseIndent = indent;
|
|
860
|
-
continue;
|
|
944
|
+
if (currentSection === "definitions" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
945
|
+
const { entry, consumed } = parseListEntry(lineIdx, indent);
|
|
946
|
+
if (entry["id"]) {
|
|
947
|
+
statuses.push({
|
|
948
|
+
id: entry["id"],
|
|
949
|
+
label: entry["label"] ?? entry["id"],
|
|
950
|
+
description: entry["description"],
|
|
951
|
+
color: entry["color"],
|
|
952
|
+
icon: entry["icon"],
|
|
953
|
+
terminal: entry["terminal"] === "true"
|
|
954
|
+
});
|
|
861
955
|
}
|
|
862
|
-
|
|
956
|
+
lineIdx += consumed - 1;
|
|
957
|
+
continue;
|
|
863
958
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
command: parseAgentCommand(agent.command, agent.id)
|
|
876
|
-
}));
|
|
877
|
-
validateAgentList(normalized);
|
|
878
|
-
return normalized;
|
|
879
|
-
} catch (err) {
|
|
880
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
881
|
-
console.warn(
|
|
882
|
-
`Warning: ~/.syntaur/config.md agents block is invalid (${msg}) \u2014 using built-in defaults`
|
|
883
|
-
);
|
|
884
|
-
return null;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
function decodeYamlScalar(value) {
|
|
888
|
-
const trimmed = value.trim();
|
|
889
|
-
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
890
|
-
const body = trimmed.slice(1, -1);
|
|
891
|
-
let out = "";
|
|
892
|
-
for (let i = 0; i < body.length; i++) {
|
|
893
|
-
const ch = body[i];
|
|
894
|
-
if (ch === "\\" && i + 1 < body.length) {
|
|
895
|
-
const next = body[i + 1];
|
|
896
|
-
switch (next) {
|
|
897
|
-
case "\\":
|
|
898
|
-
out += "\\";
|
|
899
|
-
break;
|
|
900
|
-
case '"':
|
|
901
|
-
out += '"';
|
|
902
|
-
break;
|
|
903
|
-
case "n":
|
|
904
|
-
out += "\n";
|
|
905
|
-
break;
|
|
906
|
-
case "t":
|
|
907
|
-
out += " ";
|
|
908
|
-
break;
|
|
909
|
-
case "r":
|
|
910
|
-
out += "\r";
|
|
911
|
-
break;
|
|
912
|
-
default:
|
|
913
|
-
out += next;
|
|
914
|
-
break;
|
|
915
|
-
}
|
|
916
|
-
i++;
|
|
917
|
-
continue;
|
|
959
|
+
if (currentSection === "transitions" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
960
|
+
const { entry, consumed } = parseListEntry(lineIdx, indent);
|
|
961
|
+
if (entry["from"] && entry["command"] && entry["to"]) {
|
|
962
|
+
transitions.push({
|
|
963
|
+
from: entry["from"],
|
|
964
|
+
command: entry["command"],
|
|
965
|
+
to: entry["to"],
|
|
966
|
+
label: entry["label"],
|
|
967
|
+
description: entry["description"],
|
|
968
|
+
requiresReason: entry["requiresReason"] === "true"
|
|
969
|
+
});
|
|
918
970
|
}
|
|
919
|
-
|
|
971
|
+
lineIdx += consumed - 1;
|
|
972
|
+
continue;
|
|
920
973
|
}
|
|
921
|
-
return out;
|
|
922
|
-
}
|
|
923
|
-
if (trimmed.length >= 2 && trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
924
|
-
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
925
|
-
}
|
|
926
|
-
return trimmed;
|
|
927
|
-
}
|
|
928
|
-
function assignAgentField(target, key, rawValue) {
|
|
929
|
-
const value = decodeYamlScalar(rawValue);
|
|
930
|
-
switch (key) {
|
|
931
|
-
case "id":
|
|
932
|
-
target.id = value;
|
|
933
|
-
break;
|
|
934
|
-
case "label":
|
|
935
|
-
target.label = value;
|
|
936
|
-
break;
|
|
937
|
-
case "command":
|
|
938
|
-
target.command = value;
|
|
939
|
-
break;
|
|
940
|
-
case "promptArgPosition":
|
|
941
|
-
target.promptArgPosition = value;
|
|
942
|
-
break;
|
|
943
|
-
case "default":
|
|
944
|
-
target.default = value === "true";
|
|
945
|
-
break;
|
|
946
|
-
case "resolveFromShellAliases":
|
|
947
|
-
target.resolveFromShellAliases = value === "true";
|
|
948
|
-
break;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
async function readConfig() {
|
|
952
|
-
const configPath = resolve3(syntaurRoot(), "config.md");
|
|
953
|
-
if (!await fileExists(configPath)) {
|
|
954
|
-
return cloneDefaultConfig();
|
|
955
|
-
}
|
|
956
|
-
if (!migratedConfigPaths.has(configPath)) {
|
|
957
|
-
migratedConfigPaths.add(configPath);
|
|
958
|
-
await migrateLegacyConfig(configPath);
|
|
959
|
-
}
|
|
960
|
-
const content = await readFile3(configPath, "utf-8");
|
|
961
|
-
const fm = parseFrontmatter(content);
|
|
962
|
-
if (Object.keys(fm).length === 0) {
|
|
963
|
-
console.warn("Warning: ~/.syntaur/config.md has malformed frontmatter, using defaults");
|
|
964
|
-
return cloneDefaultConfig();
|
|
965
|
-
}
|
|
966
|
-
let projectDir = fm["defaultProjectDir"] ? expandHome(String(fm["defaultProjectDir"])) : DEFAULT_CONFIG.defaultProjectDir;
|
|
967
|
-
if (!isAbsolute(projectDir)) {
|
|
968
|
-
console.warn(
|
|
969
|
-
`Warning: config.md defaultProjectDir is not an absolute path ("${fm["defaultProjectDir"]}"), using default`
|
|
970
|
-
);
|
|
971
|
-
projectDir = DEFAULT_CONFIG.defaultProjectDir;
|
|
972
974
|
}
|
|
973
|
-
|
|
975
|
+
if (statuses.length === 0) return null;
|
|
974
976
|
return {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
completed: fm["onboarding.completed"] === "true"
|
|
979
|
-
},
|
|
980
|
-
agentDefaults: {
|
|
981
|
-
trustLevel: fm["agentDefaults.trustLevel"] || DEFAULT_CONFIG.agentDefaults.trustLevel,
|
|
982
|
-
autoApprove: fm["agentDefaults.autoApprove"] === "true" || DEFAULT_CONFIG.agentDefaults.autoApprove,
|
|
983
|
-
autoCreateWorktree: AUTO_CREATE_WORKTREE_VALUES.includes(
|
|
984
|
-
fm["agentDefaults.autoCreateWorktree"]
|
|
985
|
-
) ? fm["agentDefaults.autoCreateWorktree"] : DEFAULT_CONFIG.agentDefaults.autoCreateWorktree
|
|
986
|
-
},
|
|
987
|
-
integrations: {
|
|
988
|
-
claudePluginDir: parseOptionalAbsolutePath(
|
|
989
|
-
fm["integrations.claudePluginDir"],
|
|
990
|
-
"integrations.claudePluginDir"
|
|
991
|
-
),
|
|
992
|
-
codexPluginDir: parseOptionalAbsolutePath(
|
|
993
|
-
fm["integrations.codexPluginDir"],
|
|
994
|
-
"integrations.codexPluginDir"
|
|
995
|
-
),
|
|
996
|
-
codexMarketplacePath: parseOptionalAbsolutePath(
|
|
997
|
-
fm["integrations.codexMarketplacePath"],
|
|
998
|
-
"integrations.codexMarketplacePath"
|
|
999
|
-
),
|
|
1000
|
-
...parseInstalledAgents(fm)
|
|
1001
|
-
},
|
|
1002
|
-
backup: fm["backup.repo"] || fm["backup.categories"] ? {
|
|
1003
|
-
repo: fm["backup.repo"] && fm["backup.repo"] !== "null" ? fm["backup.repo"] : null,
|
|
1004
|
-
categories: fm["backup.categories"] || "projects, playbooks, todos, servers, config",
|
|
1005
|
-
lastBackup: fm["backup.lastBackup"] && fm["backup.lastBackup"] !== "null" ? fm["backup.lastBackup"] : null,
|
|
1006
|
-
lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
|
|
1007
|
-
} : null,
|
|
1008
|
-
statuses: parseStatusConfig(content),
|
|
1009
|
-
types: null,
|
|
1010
|
-
agents: normalizeAgentsFromConfig(parseAgentsConfig(content)),
|
|
1011
|
-
playbooks: parsePlaybooksConfig(fmBlock),
|
|
1012
|
-
theme: parseThemeConfig(content),
|
|
1013
|
-
hotkeys: parseHotkeyBindingsConfig(content),
|
|
1014
|
-
terminal: (() => {
|
|
1015
|
-
try {
|
|
1016
|
-
return parseTerminalConfig(fm["terminal"]);
|
|
1017
|
-
} catch (err) {
|
|
1018
|
-
const msg = err instanceof TerminalConfigError ? err.message : String(err);
|
|
1019
|
-
console.warn(`Warning: ${msg} \u2014 falling back to default`);
|
|
1020
|
-
return null;
|
|
1021
|
-
}
|
|
1022
|
-
})(),
|
|
1023
|
-
workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock)
|
|
977
|
+
statuses,
|
|
978
|
+
order: order.length > 0 ? order : statuses.map((s) => s.id),
|
|
979
|
+
transitions
|
|
1024
980
|
};
|
|
1025
981
|
}
|
|
1026
|
-
function
|
|
1027
|
-
|
|
1028
|
-
const builtinById = new Map(BUILTIN_AGENTS.map((a) => [a.id, a]));
|
|
1029
|
-
return config.agents.map((agent) => {
|
|
1030
|
-
const builtin = builtinById.get(agent.id);
|
|
1031
|
-
if (!builtin) return agent;
|
|
1032
|
-
const resume = agent.resume ?? builtin.resume;
|
|
1033
|
-
const fork = agent.fork ?? builtin.fork;
|
|
1034
|
-
if (resume === agent.resume && fork === agent.fork) return agent;
|
|
1035
|
-
return {
|
|
1036
|
-
...agent,
|
|
1037
|
-
...resume ? { resume } : {},
|
|
1038
|
-
...fork ? { fork } : {}
|
|
1039
|
-
};
|
|
1040
|
-
});
|
|
982
|
+
function toTitleCase(s) {
|
|
983
|
+
return s.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1041
984
|
}
|
|
1042
|
-
function
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
)
|
|
1055
|
-
}
|
|
1056
|
-
return trimmed;
|
|
985
|
+
function buildDefaultStatusConfig() {
|
|
986
|
+
return {
|
|
987
|
+
statuses: DEFAULT_STATUSES.map((id) => ({
|
|
988
|
+
id,
|
|
989
|
+
label: toTitleCase(id),
|
|
990
|
+
color: DEFAULT_STATUS_COLORS[id] ?? "gray",
|
|
991
|
+
terminal: id === "completed" || id === "failed"
|
|
992
|
+
})),
|
|
993
|
+
order: [...DEFAULT_STATUSES],
|
|
994
|
+
transitions: Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
|
|
995
|
+
const [from, command] = key.split(":");
|
|
996
|
+
return { from, command, to };
|
|
997
|
+
})
|
|
998
|
+
};
|
|
1057
999
|
}
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
if (
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1000
|
+
function parsePlaybooksConfig(fmBlock) {
|
|
1001
|
+
const blockStart = fmBlock.match(/^playbooks:\s*$/m);
|
|
1002
|
+
if (!blockStart) {
|
|
1003
|
+
return { disabled: [] };
|
|
1004
|
+
}
|
|
1005
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
1006
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
1007
|
+
const disabled = [];
|
|
1008
|
+
let currentSection = null;
|
|
1009
|
+
for (const line of remaining) {
|
|
1010
|
+
const trimmed = line.trimStart();
|
|
1011
|
+
const indent = line.length - trimmed.length;
|
|
1012
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
1013
|
+
if (trimmed === "") continue;
|
|
1014
|
+
if (indent === 2 && trimmed.startsWith("disabled:")) {
|
|
1015
|
+
currentSection = "disabled";
|
|
1016
|
+
const afterColon = trimmed.slice("disabled:".length).trim();
|
|
1017
|
+
if (afterColon === "[]" || afterColon === "") {
|
|
1018
|
+
continue;
|
|
1067
1019
|
}
|
|
1020
|
+
continue;
|
|
1068
1021
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
"use strict";
|
|
1076
|
-
init_paths();
|
|
1077
|
-
init_fs();
|
|
1078
|
-
init_config();
|
|
1079
|
-
init_fs_migration();
|
|
1080
|
-
init_hotkeysCatalog();
|
|
1081
|
-
init_agents_schema();
|
|
1082
|
-
init_terminal_schema();
|
|
1083
|
-
init_workspace_visibility_schema();
|
|
1084
|
-
DEFAULT_CONFIG = {
|
|
1085
|
-
version: "2.0",
|
|
1086
|
-
defaultProjectDir: defaultProjectDir(),
|
|
1087
|
-
onboarding: {
|
|
1088
|
-
completed: false
|
|
1089
|
-
},
|
|
1090
|
-
agentDefaults: {
|
|
1091
|
-
trustLevel: "medium",
|
|
1092
|
-
autoApprove: false,
|
|
1093
|
-
autoCreateWorktree: "ask"
|
|
1094
|
-
},
|
|
1095
|
-
integrations: {
|
|
1096
|
-
claudePluginDir: null,
|
|
1097
|
-
codexPluginDir: null,
|
|
1098
|
-
codexMarketplacePath: null
|
|
1099
|
-
},
|
|
1100
|
-
backup: null,
|
|
1101
|
-
statuses: null,
|
|
1102
|
-
types: null,
|
|
1103
|
-
agents: null,
|
|
1104
|
-
playbooks: {
|
|
1105
|
-
disabled: []
|
|
1106
|
-
},
|
|
1107
|
-
theme: null,
|
|
1108
|
-
hotkeys: null,
|
|
1109
|
-
terminal: null,
|
|
1110
|
-
workspaceVisibility: {
|
|
1111
|
-
hidden: []
|
|
1022
|
+
if (currentSection === "disabled" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
1023
|
+
const raw = trimmed.slice(2).trim().replace(/^["']|["']$/g, "");
|
|
1024
|
+
if (raw.length === 0) continue;
|
|
1025
|
+
if (/\s/.test(raw)) {
|
|
1026
|
+
console.warn(`Warning: config.md playbooks.disabled entry "${raw}" contains whitespace, ignoring`);
|
|
1027
|
+
continue;
|
|
1112
1028
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
};
|
|
1117
|
-
KNOWN_AGENT_SCALAR_FIELDS = /* @__PURE__ */ new Set([
|
|
1118
|
-
"id",
|
|
1119
|
-
"label",
|
|
1120
|
-
"command",
|
|
1121
|
-
"promptArgPosition",
|
|
1122
|
-
"default",
|
|
1123
|
-
"resolveFromShellAliases"
|
|
1124
|
-
]);
|
|
1125
|
-
migratedConfigPaths = /* @__PURE__ */ new Set();
|
|
1126
|
-
TerminalConfigError = class extends Error {
|
|
1127
|
-
};
|
|
1029
|
+
disabled.push(raw);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1128
1032
|
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1033
|
+
return { disabled };
|
|
1034
|
+
}
|
|
1035
|
+
function parseThemeConfig(content) {
|
|
1036
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1037
|
+
if (!match) return null;
|
|
1038
|
+
const fmBlock = match[1];
|
|
1039
|
+
const blockStart = fmBlock.match(/^theme:\s*$/m);
|
|
1040
|
+
if (!blockStart) return null;
|
|
1041
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
1042
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
1043
|
+
let preset = null;
|
|
1044
|
+
for (const line of remaining) {
|
|
1045
|
+
const trimmed = line.trimStart();
|
|
1046
|
+
const indent = line.length - trimmed.length;
|
|
1047
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
1048
|
+
if (trimmed === "") continue;
|
|
1049
|
+
if (indent === 2 && trimmed.startsWith("preset:")) {
|
|
1050
|
+
const value = trimmed.slice("preset:".length).trim().replace(/^["']|["']$/g, "");
|
|
1051
|
+
if (value.length > 0) preset = value;
|
|
1052
|
+
}
|
|
1136
1053
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
return [frontmatterBlock, body];
|
|
1054
|
+
if (!preset) return null;
|
|
1055
|
+
return { preset };
|
|
1140
1056
|
}
|
|
1141
|
-
function
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1057
|
+
function parseWorkspaceVisibilityConfig(fmBlock) {
|
|
1058
|
+
const blockStart = fmBlock.match(/^workspaceVisibility:\s*$/m);
|
|
1059
|
+
if (!blockStart) {
|
|
1060
|
+
return { hidden: [] };
|
|
1061
|
+
}
|
|
1062
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
1063
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
1064
|
+
const hidden = [];
|
|
1065
|
+
let currentSection = null;
|
|
1066
|
+
for (const line of remaining) {
|
|
1067
|
+
const trimmed = line.trimStart();
|
|
1068
|
+
const indent = line.length - trimmed.length;
|
|
1069
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
1070
|
+
if (trimmed === "") continue;
|
|
1071
|
+
if (indent === 2 && trimmed.startsWith("hidden:")) {
|
|
1072
|
+
currentSection = "hidden";
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
if (currentSection === "hidden" && indent >= 4 && trimmed.startsWith("- ")) {
|
|
1076
|
+
const rest = trimmed.slice(2).trim();
|
|
1077
|
+
if (rest.length === 0) continue;
|
|
1078
|
+
let name;
|
|
1079
|
+
if (rest.startsWith('"')) {
|
|
1080
|
+
try {
|
|
1081
|
+
name = JSON.parse(rest);
|
|
1082
|
+
} catch {
|
|
1083
|
+
name = rest.replace(/^["']|["']$/g, "");
|
|
1084
|
+
}
|
|
1085
|
+
} else {
|
|
1086
|
+
name = rest;
|
|
1087
|
+
}
|
|
1088
|
+
hidden.push(name);
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1146
1091
|
}
|
|
1147
|
-
return
|
|
1092
|
+
return { hidden: normalizeHiddenList(hidden) };
|
|
1148
1093
|
}
|
|
1149
|
-
function
|
|
1150
|
-
const match =
|
|
1094
|
+
function parseHotkeyBindingsConfig(content) {
|
|
1095
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1151
1096
|
if (!match) return null;
|
|
1152
|
-
|
|
1097
|
+
const fmBlock = match[1];
|
|
1098
|
+
const blockStart = fmBlock.match(/^hotkeys:\s*$/m);
|
|
1099
|
+
if (!blockStart) return null;
|
|
1100
|
+
const startIdx = fmBlock.indexOf(blockStart[0]) + blockStart[0].length;
|
|
1101
|
+
const remaining = fmBlock.slice(startIdx).split("\n");
|
|
1102
|
+
const bindings = {};
|
|
1103
|
+
let inBindings = false;
|
|
1104
|
+
for (const line of remaining) {
|
|
1105
|
+
const trimmed = line.trimStart();
|
|
1106
|
+
const indent = line.length - trimmed.length;
|
|
1107
|
+
if (indent === 0 && trimmed.length > 0) break;
|
|
1108
|
+
if (trimmed === "") continue;
|
|
1109
|
+
if (indent === 2 && trimmed === "bindings:") {
|
|
1110
|
+
inBindings = true;
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (inBindings && indent === 4) {
|
|
1114
|
+
const colonIdx = trimmed.indexOf(":");
|
|
1115
|
+
if (colonIdx <= 0) continue;
|
|
1116
|
+
const rawKind = trimmed.slice(0, colonIdx).trim();
|
|
1117
|
+
const rawValue = trimmed.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
1118
|
+
if (!isBindableActionKind(rawKind)) continue;
|
|
1119
|
+
if (rawValue.length === 0) continue;
|
|
1120
|
+
bindings[rawKind] = canonicalizeCombo(rawValue);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (Object.keys(bindings).length === 0) return null;
|
|
1124
|
+
return { bindings };
|
|
1153
1125
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1126
|
+
function parseOptionalAbsolutePath(value, fieldName) {
|
|
1127
|
+
if (!value) {
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
const expanded = expandHome(String(value));
|
|
1131
|
+
if (!isAbsolute(expanded)) {
|
|
1132
|
+
console.warn(
|
|
1133
|
+
`Warning: config.md ${fieldName} is not an absolute path ("${value}"), ignoring it`
|
|
1134
|
+
);
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
return resolve6(expanded);
|
|
1162
1138
|
}
|
|
1163
|
-
function
|
|
1164
|
-
const
|
|
1165
|
-
if (
|
|
1166
|
-
const
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1139
|
+
function parseAgentsConfig(content) {
|
|
1140
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
1141
|
+
if (!match) return null;
|
|
1142
|
+
const fmBlock = match[1];
|
|
1143
|
+
const agentsStart = fmBlock.match(/^agents:\s*$/m);
|
|
1144
|
+
if (!agentsStart) return null;
|
|
1145
|
+
const startIdx = fmBlock.indexOf(agentsStart[0]) + agentsStart[0].length;
|
|
1146
|
+
const remaining = fmBlock.slice(startIdx);
|
|
1147
|
+
const lines = remaining.split("\n");
|
|
1148
|
+
const agents = [];
|
|
1149
|
+
let current = null;
|
|
1150
|
+
let argsCapture = null;
|
|
1151
|
+
let argsBaseIndent = 0;
|
|
1152
|
+
let nestedKey = null;
|
|
1153
|
+
let nestedInvocation = null;
|
|
1154
|
+
let nestedBaseIndent = 0;
|
|
1155
|
+
function flushCurrent() {
|
|
1156
|
+
if (!current) return;
|
|
1157
|
+
if (!current.id || !current.command || !current.label) {
|
|
1158
|
+
current = null;
|
|
1159
|
+
return;
|
|
1175
1160
|
}
|
|
1161
|
+
agents.push({
|
|
1162
|
+
id: current.id,
|
|
1163
|
+
label: current.label,
|
|
1164
|
+
command: current.command,
|
|
1165
|
+
...current.args && current.args.length > 0 ? { args: current.args } : {},
|
|
1166
|
+
...current.promptArgPosition ? { promptArgPosition: current.promptArgPosition } : {},
|
|
1167
|
+
...current.default ? { default: true } : {},
|
|
1168
|
+
...current.resolveFromShellAliases ? { resolveFromShellAliases: true } : {},
|
|
1169
|
+
...current.resume ? { resume: current.resume } : {},
|
|
1170
|
+
...current.fork ? { fork: current.fork } : {}
|
|
1171
|
+
});
|
|
1172
|
+
current = null;
|
|
1173
|
+
argsCapture = null;
|
|
1174
|
+
nestedKey = null;
|
|
1175
|
+
nestedInvocation = null;
|
|
1176
1176
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1177
|
+
function closeNestedBlock() {
|
|
1178
|
+
if (!nestedKey) return;
|
|
1179
|
+
if (current && (nestedKey === "resume" || nestedKey === "fork") && nestedInvocation) {
|
|
1180
|
+
if (Array.isArray(nestedInvocation.args)) {
|
|
1181
|
+
current[nestedKey] = nestedInvocation;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
nestedKey = null;
|
|
1185
|
+
nestedInvocation = null;
|
|
1186
|
+
argsCapture = null;
|
|
1182
1187
|
}
|
|
1183
|
-
|
|
1188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1189
|
+
const line = lines[i];
|
|
1190
|
+
const trimmed = line.trimStart();
|
|
1191
|
+
const indent = line.length - trimmed.length;
|
|
1192
|
+
if (indent === 0 && trimmed !== "" && !trimmed.startsWith("#")) {
|
|
1193
|
+
closeNestedBlock();
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
if (argsCapture) {
|
|
1197
|
+
if (indent > argsBaseIndent && trimmed.startsWith("- ")) {
|
|
1198
|
+
argsCapture.push(decodeYamlScalar(trimmed.slice(2).trim()));
|
|
1199
|
+
continue;
|
|
1200
|
+
} else {
|
|
1201
|
+
argsCapture = null;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
if (indent === 2 && trimmed.startsWith("- ")) {
|
|
1205
|
+
closeNestedBlock();
|
|
1206
|
+
flushCurrent();
|
|
1207
|
+
current = {};
|
|
1208
|
+
const rest = trimmed.slice(2).trim();
|
|
1209
|
+
const colonIdx = rest.indexOf(":");
|
|
1210
|
+
if (colonIdx > 0) {
|
|
1211
|
+
const k = rest.slice(0, colonIdx).trim();
|
|
1212
|
+
const v = rest.slice(colonIdx + 1).trim();
|
|
1213
|
+
assignAgentField(current, k, v);
|
|
1214
|
+
}
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (!current) continue;
|
|
1218
|
+
if (nestedKey && indent > nestedBaseIndent) {
|
|
1219
|
+
const colonIdx = trimmed.indexOf(":");
|
|
1220
|
+
if (colonIdx <= 0) continue;
|
|
1221
|
+
const k = trimmed.slice(0, colonIdx).trim();
|
|
1222
|
+
const v = trimmed.slice(colonIdx + 1).trim();
|
|
1223
|
+
if (nestedKey === "resume" || nestedKey === "fork") {
|
|
1224
|
+
if (!nestedInvocation) nestedInvocation = { args: [] };
|
|
1225
|
+
if (k === "args" && v === "") {
|
|
1226
|
+
nestedInvocation.args = [];
|
|
1227
|
+
argsCapture = nestedInvocation.args;
|
|
1228
|
+
argsBaseIndent = indent;
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (k === "command" && v !== "") {
|
|
1232
|
+
nestedInvocation.command = decodeYamlScalar(v);
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
if (nestedKey && indent <= nestedBaseIndent) {
|
|
1239
|
+
closeNestedBlock();
|
|
1240
|
+
}
|
|
1241
|
+
if (indent >= 4 && current) {
|
|
1242
|
+
const colonIdx = trimmed.indexOf(":");
|
|
1243
|
+
if (colonIdx <= 0) continue;
|
|
1244
|
+
const k = trimmed.slice(0, colonIdx).trim();
|
|
1245
|
+
const v = trimmed.slice(colonIdx + 1).trim();
|
|
1246
|
+
if (k === "args" && v === "") {
|
|
1247
|
+
argsCapture = [];
|
|
1248
|
+
argsBaseIndent = indent;
|
|
1249
|
+
current.args = argsCapture;
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
if ((k === "resume" || k === "fork") && v === "") {
|
|
1253
|
+
nestedKey = k;
|
|
1254
|
+
nestedInvocation = { args: [] };
|
|
1255
|
+
nestedBaseIndent = indent;
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (v === "" && !KNOWN_AGENT_SCALAR_FIELDS.has(k)) {
|
|
1259
|
+
nestedKey = "__skip__";
|
|
1260
|
+
nestedInvocation = null;
|
|
1261
|
+
nestedBaseIndent = indent;
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
assignAgentField(current, k, v);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
closeNestedBlock();
|
|
1268
|
+
flushCurrent();
|
|
1269
|
+
if (agents.length === 0) return [];
|
|
1270
|
+
return agents;
|
|
1184
1271
|
}
|
|
1185
|
-
function
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
externalIds: parseExternalIds(fm),
|
|
1202
|
-
body
|
|
1203
|
-
};
|
|
1272
|
+
function normalizeAgentsFromConfig(agents) {
|
|
1273
|
+
if (agents === null) return null;
|
|
1274
|
+
try {
|
|
1275
|
+
const normalized = agents.map((agent) => ({
|
|
1276
|
+
...agent,
|
|
1277
|
+
command: parseAgentCommand(agent.command, agent.id)
|
|
1278
|
+
}));
|
|
1279
|
+
validateAgentList(normalized);
|
|
1280
|
+
return normalized;
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1283
|
+
console.warn(
|
|
1284
|
+
`Warning: ~/.syntaur/config.md agents block is invalid (${msg}) \u2014 using built-in defaults`
|
|
1285
|
+
);
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1204
1288
|
}
|
|
1205
|
-
function
|
|
1206
|
-
const
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1289
|
+
function decodeYamlScalar(value) {
|
|
1290
|
+
const trimmed = value.trim();
|
|
1291
|
+
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
1292
|
+
const body = trimmed.slice(1, -1);
|
|
1293
|
+
let out = "";
|
|
1294
|
+
for (let i = 0; i < body.length; i++) {
|
|
1295
|
+
const ch = body[i];
|
|
1296
|
+
if (ch === "\\" && i + 1 < body.length) {
|
|
1297
|
+
const next = body[i + 1];
|
|
1298
|
+
switch (next) {
|
|
1299
|
+
case "\\":
|
|
1300
|
+
out += "\\";
|
|
1301
|
+
break;
|
|
1302
|
+
case '"':
|
|
1303
|
+
out += '"';
|
|
1304
|
+
break;
|
|
1305
|
+
case "n":
|
|
1306
|
+
out += "\n";
|
|
1307
|
+
break;
|
|
1308
|
+
case "t":
|
|
1309
|
+
out += " ";
|
|
1310
|
+
break;
|
|
1311
|
+
case "r":
|
|
1312
|
+
out += "\r";
|
|
1313
|
+
break;
|
|
1314
|
+
default:
|
|
1315
|
+
out += next;
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
i++;
|
|
1319
|
+
continue;
|
|
1215
1320
|
}
|
|
1321
|
+
out += ch;
|
|
1216
1322
|
}
|
|
1323
|
+
return out;
|
|
1217
1324
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
needsAttention: {
|
|
1223
|
-
blockedCount: parseInt(getNestedField(fm, "needsAttention", "blockedCount") ?? "0", 10),
|
|
1224
|
-
failedCount: parseInt(getNestedField(fm, "needsAttention", "failedCount") ?? "0", 10),
|
|
1225
|
-
openQuestions: parseInt(getNestedField(fm, "needsAttention", "openQuestions") ?? "0", 10)
|
|
1226
|
-
},
|
|
1227
|
-
body
|
|
1228
|
-
};
|
|
1325
|
+
if (trimmed.length >= 2 && trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
1326
|
+
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
1327
|
+
}
|
|
1328
|
+
return trimmed;
|
|
1229
1329
|
}
|
|
1230
|
-
function
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
system: entry["system"],
|
|
1252
|
-
id: entry["id"],
|
|
1253
|
-
url: entry["url"] || null
|
|
1254
|
-
});
|
|
1255
|
-
}
|
|
1330
|
+
function assignAgentField(target, key, rawValue) {
|
|
1331
|
+
const value = decodeYamlScalar(rawValue);
|
|
1332
|
+
switch (key) {
|
|
1333
|
+
case "id":
|
|
1334
|
+
target.id = value;
|
|
1335
|
+
break;
|
|
1336
|
+
case "label":
|
|
1337
|
+
target.label = value;
|
|
1338
|
+
break;
|
|
1339
|
+
case "command":
|
|
1340
|
+
target.command = value;
|
|
1341
|
+
break;
|
|
1342
|
+
case "promptArgPosition":
|
|
1343
|
+
target.promptArgPosition = value;
|
|
1344
|
+
break;
|
|
1345
|
+
case "default":
|
|
1346
|
+
target.default = value === "true";
|
|
1347
|
+
break;
|
|
1348
|
+
case "resolveFromShellAliases":
|
|
1349
|
+
target.resolveFromShellAliases = value === "true";
|
|
1350
|
+
break;
|
|
1256
1351
|
}
|
|
1257
|
-
return results;
|
|
1258
1352
|
}
|
|
1259
|
-
function
|
|
1260
|
-
const
|
|
1353
|
+
async function readConfig() {
|
|
1354
|
+
const configPath = resolve6(syntaurRoot(), "config.md");
|
|
1355
|
+
if (!await fileExists(configPath)) {
|
|
1356
|
+
return cloneDefaultConfig();
|
|
1357
|
+
}
|
|
1358
|
+
if (!migratedConfigPaths.has(configPath)) {
|
|
1359
|
+
migratedConfigPaths.add(configPath);
|
|
1360
|
+
await migrateLegacyConfig(configPath);
|
|
1361
|
+
}
|
|
1362
|
+
const content = await readFile5(configPath, "utf-8");
|
|
1363
|
+
const fm = parseFrontmatter(content);
|
|
1364
|
+
if (Object.keys(fm).length === 0) {
|
|
1365
|
+
console.warn("Warning: ~/.syntaur/config.md has malformed frontmatter, using defaults");
|
|
1366
|
+
return cloneDefaultConfig();
|
|
1367
|
+
}
|
|
1368
|
+
let projectDir = fm["defaultProjectDir"] ? expandHome(String(fm["defaultProjectDir"])) : DEFAULT_CONFIG.defaultProjectDir;
|
|
1369
|
+
if (!isAbsolute(projectDir)) {
|
|
1370
|
+
console.warn(
|
|
1371
|
+
`Warning: config.md defaultProjectDir is not an absolute path ("${fm["defaultProjectDir"]}"), using default`
|
|
1372
|
+
);
|
|
1373
|
+
projectDir = DEFAULT_CONFIG.defaultProjectDir;
|
|
1374
|
+
}
|
|
1375
|
+
const fmBlock = content.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? "";
|
|
1261
1376
|
return {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
workspaceGroup: getField(fm, "workspaceGroup"),
|
|
1267
|
-
type: getField(fm, "type"),
|
|
1268
|
-
status: getField(fm, "status") ?? "pending",
|
|
1269
|
-
priority: getField(fm, "priority") ?? "medium",
|
|
1270
|
-
assignee: getField(fm, "assignee"),
|
|
1271
|
-
dependsOn: parseListField(fm, "dependsOn"),
|
|
1272
|
-
links: parseListField(fm, "links"),
|
|
1273
|
-
blockedReason: getField(fm, "blockedReason"),
|
|
1274
|
-
workspace: {
|
|
1275
|
-
repository: getNestedField(fm, "workspace", "repository"),
|
|
1276
|
-
worktreePath: getNestedField(fm, "workspace", "worktreePath"),
|
|
1277
|
-
branch: getNestedField(fm, "workspace", "branch"),
|
|
1278
|
-
parentBranch: getNestedField(fm, "workspace", "parentBranch")
|
|
1377
|
+
version: fm["version"] || DEFAULT_CONFIG.version,
|
|
1378
|
+
defaultProjectDir: projectDir,
|
|
1379
|
+
onboarding: {
|
|
1380
|
+
completed: fm["onboarding.completed"] === "true"
|
|
1279
1381
|
},
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1382
|
+
agentDefaults: {
|
|
1383
|
+
trustLevel: fm["agentDefaults.trustLevel"] || DEFAULT_CONFIG.agentDefaults.trustLevel,
|
|
1384
|
+
autoApprove: fm["agentDefaults.autoApprove"] === "true" || DEFAULT_CONFIG.agentDefaults.autoApprove,
|
|
1385
|
+
autoCreateWorktree: AUTO_CREATE_WORKTREE_VALUES.includes(
|
|
1386
|
+
fm["agentDefaults.autoCreateWorktree"]
|
|
1387
|
+
) ? fm["agentDefaults.autoCreateWorktree"] : DEFAULT_CONFIG.agentDefaults.autoCreateWorktree
|
|
1388
|
+
},
|
|
1389
|
+
integrations: {
|
|
1390
|
+
claudePluginDir: parseOptionalAbsolutePath(
|
|
1391
|
+
fm["integrations.claudePluginDir"],
|
|
1392
|
+
"integrations.claudePluginDir"
|
|
1393
|
+
),
|
|
1394
|
+
codexPluginDir: parseOptionalAbsolutePath(
|
|
1395
|
+
fm["integrations.codexPluginDir"],
|
|
1396
|
+
"integrations.codexPluginDir"
|
|
1397
|
+
),
|
|
1398
|
+
codexMarketplacePath: parseOptionalAbsolutePath(
|
|
1399
|
+
fm["integrations.codexMarketplacePath"],
|
|
1400
|
+
"integrations.codexMarketplacePath"
|
|
1401
|
+
),
|
|
1402
|
+
...parseInstalledAgents(fm)
|
|
1403
|
+
},
|
|
1404
|
+
backup: fm["backup.repo"] || fm["backup.categories"] ? {
|
|
1405
|
+
repo: fm["backup.repo"] && fm["backup.repo"] !== "null" ? fm["backup.repo"] : null,
|
|
1406
|
+
categories: fm["backup.categories"] || "projects, playbooks, todos, servers, config",
|
|
1407
|
+
lastBackup: fm["backup.lastBackup"] && fm["backup.lastBackup"] !== "null" ? fm["backup.lastBackup"] : null,
|
|
1408
|
+
lastRestore: fm["backup.lastRestore"] && fm["backup.lastRestore"] !== "null" ? fm["backup.lastRestore"] : null
|
|
1409
|
+
} : null,
|
|
1410
|
+
statuses: parseStatusConfig(content),
|
|
1411
|
+
types: null,
|
|
1412
|
+
agents: normalizeAgentsFromConfig(parseAgentsConfig(content)),
|
|
1413
|
+
playbooks: parsePlaybooksConfig(fmBlock),
|
|
1414
|
+
theme: parseThemeConfig(content),
|
|
1415
|
+
hotkeys: parseHotkeyBindingsConfig(content),
|
|
1416
|
+
terminal: (() => {
|
|
1417
|
+
try {
|
|
1418
|
+
return parseTerminalConfig(fm["terminal"]);
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
const msg = err instanceof TerminalConfigError ? err.message : String(err);
|
|
1421
|
+
console.warn(`Warning: ${msg} \u2014 falling back to default`);
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
})(),
|
|
1425
|
+
workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock)
|
|
1315
1426
|
};
|
|
1316
1427
|
}
|
|
1317
|
-
function
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1428
|
+
function getAgents(config) {
|
|
1429
|
+
if (config.agents === null) return BUILTIN_AGENTS;
|
|
1430
|
+
const builtinById = new Map(BUILTIN_AGENTS.map((a) => [a.id, a]));
|
|
1431
|
+
return config.agents.map((agent) => {
|
|
1432
|
+
const builtin = builtinById.get(agent.id);
|
|
1433
|
+
if (!builtin) return agent;
|
|
1434
|
+
const resume = agent.resume ?? builtin.resume;
|
|
1435
|
+
const fork = agent.fork ?? builtin.fork;
|
|
1436
|
+
if (resume === agent.resume && fork === agent.fork) return agent;
|
|
1437
|
+
return {
|
|
1438
|
+
...agent,
|
|
1439
|
+
...resume ? { resume } : {},
|
|
1440
|
+
...fork ? { fork } : {}
|
|
1441
|
+
};
|
|
1442
|
+
});
|
|
1325
1443
|
}
|
|
1326
|
-
function
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1444
|
+
function parseTerminalConfig(value) {
|
|
1445
|
+
if (value === void 0 || value === null || value === "") return null;
|
|
1446
|
+
if (typeof value !== "string") {
|
|
1447
|
+
throw new TerminalConfigError(
|
|
1448
|
+
`terminal must be a string \u2014 got ${typeof value}`
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
const trimmed = value.trim();
|
|
1452
|
+
if (trimmed === "") return null;
|
|
1453
|
+
if (!TERMINAL_CHOICES.includes(trimmed)) {
|
|
1454
|
+
throw new TerminalConfigError(
|
|
1455
|
+
`terminal "${trimmed}" is not a known choice \u2014 expected one of ${TERMINAL_CHOICES.join("|")}`
|
|
1337
1456
|
);
|
|
1338
|
-
if (!headerMatch) continue;
|
|
1339
|
-
const [, timestamp, author, type, replyTo, resolvedStr, entryBody] = headerMatch;
|
|
1340
|
-
const entry = {
|
|
1341
|
-
id,
|
|
1342
|
-
timestamp: timestamp.trim(),
|
|
1343
|
-
author: author.trim(),
|
|
1344
|
-
type,
|
|
1345
|
-
body: entryBody.trim()
|
|
1346
|
-
};
|
|
1347
|
-
if (replyTo) entry.replyTo = replyTo.trim();
|
|
1348
|
-
if (resolvedStr) entry.resolved = resolvedStr === "true";
|
|
1349
|
-
entries.push(entry);
|
|
1350
1457
|
}
|
|
1351
|
-
return
|
|
1352
|
-
assignment: getField(fm, "assignment") ?? "",
|
|
1353
|
-
entryCount: parseInt(getField(fm, "entryCount") ?? "0", 10),
|
|
1354
|
-
updated: getField(fm, "updated") ?? "",
|
|
1355
|
-
entries,
|
|
1356
|
-
body
|
|
1357
|
-
};
|
|
1458
|
+
return trimmed;
|
|
1358
1459
|
}
|
|
1359
|
-
function
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1460
|
+
function getTerminal(config) {
|
|
1461
|
+
if (config.terminal) return config.terminal;
|
|
1462
|
+
if (process.platform === "darwin") return "terminal-app";
|
|
1463
|
+
if (process.platform === "linux") {
|
|
1464
|
+
const order = ["kitty", "alacritty", "warp"];
|
|
1465
|
+
for (const candidate of order) {
|
|
1466
|
+
const result = spawnSync("which", [candidate], { encoding: "utf-8" });
|
|
1467
|
+
if (result.status === 0 && result.stdout.trim().length > 0) {
|
|
1468
|
+
return candidate;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1369
1471
|
}
|
|
1370
|
-
return
|
|
1371
|
-
assignment: getField(fm, "assignment") ?? "",
|
|
1372
|
-
entryCount: parseInt(getField(fm, "entryCount") ?? "0", 10),
|
|
1373
|
-
updated: getField(fm, "updated") ?? "",
|
|
1374
|
-
entries,
|
|
1375
|
-
body
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
function extractMermaidGraph(body) {
|
|
1379
|
-
const match = body.match(/```mermaid\n([\s\S]*?)```/);
|
|
1380
|
-
return match ? match[1].trim() : null;
|
|
1472
|
+
return "terminal-app";
|
|
1381
1473
|
}
|
|
1382
|
-
var
|
|
1383
|
-
|
|
1474
|
+
var DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
1475
|
+
var init_config2 = __esm({
|
|
1476
|
+
"src/utils/config.ts"() {
|
|
1384
1477
|
"use strict";
|
|
1478
|
+
init_paths();
|
|
1479
|
+
init_fs();
|
|
1480
|
+
init_config();
|
|
1481
|
+
init_fs_migration();
|
|
1482
|
+
init_lifecycle();
|
|
1483
|
+
init_hotkeysCatalog();
|
|
1484
|
+
init_agents_schema();
|
|
1485
|
+
init_terminal_schema();
|
|
1486
|
+
init_workspace_visibility_schema();
|
|
1487
|
+
DEFAULT_CONFIG = {
|
|
1488
|
+
version: "2.0",
|
|
1489
|
+
defaultProjectDir: defaultProjectDir(),
|
|
1490
|
+
onboarding: {
|
|
1491
|
+
completed: false
|
|
1492
|
+
},
|
|
1493
|
+
agentDefaults: {
|
|
1494
|
+
trustLevel: "medium",
|
|
1495
|
+
autoApprove: false,
|
|
1496
|
+
autoCreateWorktree: "ask"
|
|
1497
|
+
},
|
|
1498
|
+
integrations: {
|
|
1499
|
+
claudePluginDir: null,
|
|
1500
|
+
codexPluginDir: null,
|
|
1501
|
+
codexMarketplacePath: null
|
|
1502
|
+
},
|
|
1503
|
+
backup: null,
|
|
1504
|
+
statuses: null,
|
|
1505
|
+
types: null,
|
|
1506
|
+
agents: null,
|
|
1507
|
+
playbooks: {
|
|
1508
|
+
disabled: []
|
|
1509
|
+
},
|
|
1510
|
+
theme: null,
|
|
1511
|
+
hotkeys: null,
|
|
1512
|
+
terminal: null,
|
|
1513
|
+
workspaceVisibility: {
|
|
1514
|
+
hidden: []
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
|
|
1518
|
+
AgentConfigError = class extends Error {
|
|
1519
|
+
};
|
|
1520
|
+
DEFAULT_STATUS_COLORS = {
|
|
1521
|
+
pending: "slate",
|
|
1522
|
+
in_progress: "teal",
|
|
1523
|
+
blocked: "amber",
|
|
1524
|
+
review: "violet",
|
|
1525
|
+
completed: "emerald",
|
|
1526
|
+
failed: "rose"
|
|
1527
|
+
};
|
|
1528
|
+
KNOWN_AGENT_SCALAR_FIELDS = /* @__PURE__ */ new Set([
|
|
1529
|
+
"id",
|
|
1530
|
+
"label",
|
|
1531
|
+
"command",
|
|
1532
|
+
"promptArgPosition",
|
|
1533
|
+
"default",
|
|
1534
|
+
"resolveFromShellAliases"
|
|
1535
|
+
]);
|
|
1536
|
+
migratedConfigPaths = /* @__PURE__ */ new Set();
|
|
1537
|
+
TerminalConfigError = class extends Error {
|
|
1538
|
+
};
|
|
1385
1539
|
}
|
|
1386
1540
|
});
|
|
1387
1541
|
|
|
1388
1542
|
// src/utils/assignment-resolver.ts
|
|
1389
|
-
import { resolve as
|
|
1390
|
-
import { readdir as
|
|
1543
|
+
import { resolve as resolve7 } from "path";
|
|
1544
|
+
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
1391
1545
|
async function resolveAssignmentById(projectsDir, assignmentsDir, id) {
|
|
1392
1546
|
let standaloneMatch = null;
|
|
1393
1547
|
let projectMatch = null;
|
|
1394
|
-
const standaloneDir =
|
|
1395
|
-
const standalonePath =
|
|
1548
|
+
const standaloneDir = resolve7(assignmentsDir, id);
|
|
1549
|
+
const standalonePath = resolve7(standaloneDir, "assignment.md");
|
|
1396
1550
|
if (await fileExists(standalonePath)) {
|
|
1397
1551
|
let workspaceGroup = null;
|
|
1398
1552
|
try {
|
|
1399
|
-
const content = await
|
|
1553
|
+
const content = await readFile6(standalonePath, "utf-8");
|
|
1400
1554
|
const [fm] = extractFrontmatter(content);
|
|
1401
1555
|
workspaceGroup = getField(fm, "workspaceGroup");
|
|
1402
1556
|
} catch {
|
|
@@ -1412,24 +1566,24 @@ async function resolveAssignmentById(projectsDir, assignmentsDir, id) {
|
|
|
1412
1566
|
}
|
|
1413
1567
|
if (await fileExists(projectsDir)) {
|
|
1414
1568
|
try {
|
|
1415
|
-
const projects = await
|
|
1569
|
+
const projects = await readdir3(projectsDir, { withFileTypes: true });
|
|
1416
1570
|
for (const p of projects) {
|
|
1417
1571
|
if (!p.isDirectory()) continue;
|
|
1418
1572
|
if (p.name.startsWith(".") || p.name.startsWith("_")) continue;
|
|
1419
|
-
const assignmentsPath =
|
|
1573
|
+
const assignmentsPath = resolve7(projectsDir, p.name, "assignments");
|
|
1420
1574
|
if (!await fileExists(assignmentsPath)) continue;
|
|
1421
|
-
const entries = await
|
|
1575
|
+
const entries = await readdir3(assignmentsPath, { withFileTypes: true });
|
|
1422
1576
|
for (const a of entries) {
|
|
1423
1577
|
if (!a.isDirectory()) continue;
|
|
1424
|
-
const aPath =
|
|
1578
|
+
const aPath = resolve7(assignmentsPath, a.name, "assignment.md");
|
|
1425
1579
|
if (!await fileExists(aPath)) continue;
|
|
1426
1580
|
try {
|
|
1427
|
-
const content = await
|
|
1581
|
+
const content = await readFile6(aPath, "utf-8");
|
|
1428
1582
|
const [fm] = extractFrontmatter(content);
|
|
1429
1583
|
const fileId = getField(fm, "id");
|
|
1430
1584
|
if (fileId === id) {
|
|
1431
1585
|
projectMatch = {
|
|
1432
|
-
assignmentDir:
|
|
1586
|
+
assignmentDir: resolve7(assignmentsPath, a.name),
|
|
1433
1587
|
projectSlug: p.name,
|
|
1434
1588
|
assignmentSlug: a.name,
|
|
1435
1589
|
id,
|
|
@@ -1462,133 +1616,6 @@ var init_assignment_resolver = __esm({
|
|
|
1462
1616
|
}
|
|
1463
1617
|
});
|
|
1464
1618
|
|
|
1465
|
-
// src/lifecycle/types.ts
|
|
1466
|
-
var DEFAULT_STATUSES;
|
|
1467
|
-
var init_types = __esm({
|
|
1468
|
-
"src/lifecycle/types.ts"() {
|
|
1469
|
-
"use strict";
|
|
1470
|
-
DEFAULT_STATUSES = [
|
|
1471
|
-
"draft",
|
|
1472
|
-
"pending",
|
|
1473
|
-
"ready_for_planning",
|
|
1474
|
-
"ready_to_implement",
|
|
1475
|
-
"in_progress",
|
|
1476
|
-
"blocked",
|
|
1477
|
-
"review",
|
|
1478
|
-
"completed",
|
|
1479
|
-
"failed"
|
|
1480
|
-
];
|
|
1481
|
-
}
|
|
1482
|
-
});
|
|
1483
|
-
|
|
1484
|
-
// src/lifecycle/state-machine.ts
|
|
1485
|
-
function buildTransitionTable(transitions) {
|
|
1486
|
-
const table = /* @__PURE__ */ new Map();
|
|
1487
|
-
for (const t of transitions) {
|
|
1488
|
-
table.set(`${t.from}:${t.command}`, t.to);
|
|
1489
|
-
}
|
|
1490
|
-
return table;
|
|
1491
|
-
}
|
|
1492
|
-
function getTargetStatus(_from, command, table) {
|
|
1493
|
-
if (!table) {
|
|
1494
|
-
return DEFAULT_COMMAND_TARGETS.get(command) ?? null;
|
|
1495
|
-
}
|
|
1496
|
-
return table.get(command) ?? table.get(`${_from}:${command}`) ?? null;
|
|
1497
|
-
}
|
|
1498
|
-
var DEFAULT_COMMAND_TARGETS, DEFAULT_TRANSITION_TABLE;
|
|
1499
|
-
var init_state_machine = __esm({
|
|
1500
|
-
"src/lifecycle/state-machine.ts"() {
|
|
1501
|
-
"use strict";
|
|
1502
|
-
init_types();
|
|
1503
|
-
DEFAULT_COMMAND_TARGETS = /* @__PURE__ */ new Map([
|
|
1504
|
-
["start", "in_progress"],
|
|
1505
|
-
["shape", "ready_for_planning"],
|
|
1506
|
-
["plan-ready", "ready_to_implement"],
|
|
1507
|
-
["implement", "in_progress"],
|
|
1508
|
-
["block", "blocked"],
|
|
1509
|
-
["unblock", "in_progress"],
|
|
1510
|
-
["review", "review"],
|
|
1511
|
-
["complete", "completed"],
|
|
1512
|
-
["fail", "failed"],
|
|
1513
|
-
["reopen", "in_progress"]
|
|
1514
|
-
]);
|
|
1515
|
-
DEFAULT_TRANSITION_TABLE = /* @__PURE__ */ new Map([
|
|
1516
|
-
["pending:start", "in_progress"],
|
|
1517
|
-
["pending:block", "blocked"],
|
|
1518
|
-
["draft:shape", "ready_for_planning"],
|
|
1519
|
-
["draft:start", "in_progress"],
|
|
1520
|
-
["ready_for_planning:plan-ready", "ready_to_implement"],
|
|
1521
|
-
["ready_for_planning:start", "in_progress"],
|
|
1522
|
-
["ready_to_implement:implement", "in_progress"],
|
|
1523
|
-
["in_progress:block", "blocked"],
|
|
1524
|
-
["in_progress:review", "review"],
|
|
1525
|
-
["in_progress:complete", "completed"],
|
|
1526
|
-
["in_progress:fail", "failed"],
|
|
1527
|
-
["blocked:unblock", "in_progress"],
|
|
1528
|
-
["review:start", "in_progress"],
|
|
1529
|
-
["review:complete", "completed"],
|
|
1530
|
-
["review:fail", "failed"],
|
|
1531
|
-
["completed:reopen", "in_progress"],
|
|
1532
|
-
["failed:reopen", "in_progress"]
|
|
1533
|
-
]);
|
|
1534
|
-
}
|
|
1535
|
-
});
|
|
1536
|
-
|
|
1537
|
-
// src/lifecycle/frontmatter.ts
|
|
1538
|
-
var init_frontmatter = __esm({
|
|
1539
|
-
"src/lifecycle/frontmatter.ts"() {
|
|
1540
|
-
"use strict";
|
|
1541
|
-
}
|
|
1542
|
-
});
|
|
1543
|
-
|
|
1544
|
-
// src/todos/parser.ts
|
|
1545
|
-
import { randomBytes } from "crypto";
|
|
1546
|
-
import { readFile as readFile5 } from "fs/promises";
|
|
1547
|
-
import { resolve as resolve5 } from "path";
|
|
1548
|
-
var init_parser2 = __esm({
|
|
1549
|
-
"src/todos/parser.ts"() {
|
|
1550
|
-
"use strict";
|
|
1551
|
-
init_parser();
|
|
1552
|
-
init_fs();
|
|
1553
|
-
}
|
|
1554
|
-
});
|
|
1555
|
-
|
|
1556
|
-
// src/lifecycle/linked-todos.ts
|
|
1557
|
-
import { readdir as readdir3 } from "fs/promises";
|
|
1558
|
-
import { resolve as resolve6 } from "path";
|
|
1559
|
-
var init_linked_todos = __esm({
|
|
1560
|
-
"src/lifecycle/linked-todos.ts"() {
|
|
1561
|
-
"use strict";
|
|
1562
|
-
init_parser2();
|
|
1563
|
-
init_fs();
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1566
|
-
|
|
1567
|
-
// src/lifecycle/transitions.ts
|
|
1568
|
-
import { resolve as resolve7 } from "path";
|
|
1569
|
-
import { readFile as readFile6 } from "fs/promises";
|
|
1570
|
-
var init_transitions = __esm({
|
|
1571
|
-
"src/lifecycle/transitions.ts"() {
|
|
1572
|
-
"use strict";
|
|
1573
|
-
init_fs();
|
|
1574
|
-
init_timestamp();
|
|
1575
|
-
init_state_machine();
|
|
1576
|
-
init_frontmatter();
|
|
1577
|
-
init_linked_todos();
|
|
1578
|
-
}
|
|
1579
|
-
});
|
|
1580
|
-
|
|
1581
|
-
// src/lifecycle/index.ts
|
|
1582
|
-
var init_lifecycle = __esm({
|
|
1583
|
-
"src/lifecycle/index.ts"() {
|
|
1584
|
-
"use strict";
|
|
1585
|
-
init_types();
|
|
1586
|
-
init_state_machine();
|
|
1587
|
-
init_frontmatter();
|
|
1588
|
-
init_transitions();
|
|
1589
|
-
}
|
|
1590
|
-
});
|
|
1591
|
-
|
|
1592
1619
|
// src/utils/slug.ts
|
|
1593
1620
|
var init_slug = __esm({
|
|
1594
1621
|
"src/utils/slug.ts"() {
|
|
@@ -1839,7 +1866,8 @@ function rowToSession(row) {
|
|
|
1839
1866
|
description: row.description ?? null,
|
|
1840
1867
|
transcriptPath: row.transcript_path ?? null,
|
|
1841
1868
|
pid: row.pid ?? null,
|
|
1842
|
-
pidStartedAt: row.pid_started_at ?? null
|
|
1869
|
+
pidStartedAt: row.pid_started_at ?? null,
|
|
1870
|
+
originalHeadSha: row.original_head_sha ?? null
|
|
1843
1871
|
};
|
|
1844
1872
|
}
|
|
1845
1873
|
function getSessionById(sessionId) {
|
|
@@ -1901,9 +1929,6 @@ async function computeStandaloneRecords(assignmentsDir) {
|
|
|
1901
1929
|
records.sort((left, right) => compareTimestamps(right.record.updated, left.record.updated));
|
|
1902
1930
|
return records;
|
|
1903
1931
|
}
|
|
1904
|
-
function toTitleCase(s) {
|
|
1905
|
-
return s.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1906
|
-
}
|
|
1907
1932
|
function getTransitionDefinitions(config) {
|
|
1908
1933
|
if (!config.custom) return DEFAULT_TRANSITION_DEFINITIONS;
|
|
1909
1934
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1939,19 +1964,12 @@ async function getStatusConfig() {
|
|
|
1939
1964
|
terminalStatuses: terminalSet.size > 0 ? terminalSet : /* @__PURE__ */ new Set(["completed", "failed"])
|
|
1940
1965
|
};
|
|
1941
1966
|
} else {
|
|
1967
|
+
const def = buildDefaultStatusConfig();
|
|
1942
1968
|
_cachedConfig = {
|
|
1943
1969
|
custom: false,
|
|
1944
|
-
statuses:
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
color: DEFAULT_STATUS_COLORS[id] ?? "gray",
|
|
1948
|
-
terminal: id === "completed" || id === "failed"
|
|
1949
|
-
})),
|
|
1950
|
-
order: [...DEFAULT_STATUSES],
|
|
1951
|
-
transitions: Array.from(DEFAULT_TRANSITION_TABLE.entries()).map(([key, to]) => {
|
|
1952
|
-
const [from, command] = key.split(":");
|
|
1953
|
-
return { from, command, to };
|
|
1954
|
-
}),
|
|
1970
|
+
statuses: def.statuses,
|
|
1971
|
+
order: def.order,
|
|
1972
|
+
transitions: def.transitions,
|
|
1955
1973
|
transitionTable: DEFAULT_TRANSITION_TABLE,
|
|
1956
1974
|
terminalStatuses: /* @__PURE__ */ new Set(["completed", "failed"])
|
|
1957
1975
|
};
|
|
@@ -2620,7 +2638,7 @@ function getProjectActivityTimestamp(projectUpdated, assignments) {
|
|
|
2620
2638
|
}
|
|
2621
2639
|
return latest;
|
|
2622
2640
|
}
|
|
2623
|
-
var STALE_ASSIGNMENT_MS, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS,
|
|
2641
|
+
var STALE_ASSIGNMENT_MS, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS;
|
|
2624
2642
|
var init_api = __esm({
|
|
2625
2643
|
"src/dashboard/api.ts"() {
|
|
2626
2644
|
"use strict";
|
|
@@ -2700,14 +2718,6 @@ var init_api = __esm({
|
|
|
2700
2718
|
requiresReason: false
|
|
2701
2719
|
}
|
|
2702
2720
|
];
|
|
2703
|
-
DEFAULT_STATUS_COLORS = {
|
|
2704
|
-
pending: "slate",
|
|
2705
|
-
in_progress: "teal",
|
|
2706
|
-
blocked: "amber",
|
|
2707
|
-
review: "violet",
|
|
2708
|
-
completed: "emerald",
|
|
2709
|
-
failed: "rose"
|
|
2710
|
-
};
|
|
2711
2721
|
_cachedConfig = null;
|
|
2712
2722
|
REFERENCED_BY_LIMIT = 50;
|
|
2713
2723
|
migratedProjectsDirs = /* @__PURE__ */ new Set();
|