syntaur 0.44.1 → 0.46.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/.claude-plugin/plugin.json +1 -1
- package/dashboard/dist/assets/{_basePickBy-n0wU3YiR.js → _basePickBy-RQBuJKcX.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-CZE21ua2.js → _baseUniq-_J7s4kD3.js} +1 -1
- package/dashboard/dist/assets/{arc-Dd_UCPQq.js → arc-_9SyUgKQ.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-mgIxQnzX.js → architectureDiagram-2XIMDMQ5-C8LeFMgr.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-DhlycMw2.js → blockDiagram-WCTKOSBZ-gMh0EPEh.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-CuWqqKYM.js → c4Diagram-IC4MRINW-cHwecwLI.js} +1 -1
- package/dashboard/dist/assets/channel-C36dnl_e.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-CTP72FfB.js → chunk-4BX2VUAB-Bb2anYuQ.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-CE7fO2_0.js → chunk-55IACEB6-DYIRGzA1.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-DBsJdRy7.js → chunk-FMBD7UC4-sgRWBbaF.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-1dES-fUJ.js → chunk-JSJVCQXG-DlYKMl_j.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-U98s2l2I.js → chunk-KX2RTZJC-D0YDLAOF.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-DAy7YeTS.js → chunk-NQ4KR5QH-D-Y-CUx6.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-BeQRHiY8.js → chunk-QZHKN3VN-D7FpSvb5.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-Bt94wrMs.js → chunk-WL4C6EOR-CtXgQLdS.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-BsoGa6_a.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-BsoGa6_a.js +1 -0
- package/dashboard/dist/assets/clone-Bz6jW3OY.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-CdQh3kdW.js → cose-bilkent-S5V4N54A-YbTaohoJ.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CKWb9fD7.js → dagre-KLK3FWXG-CMtwGAnP.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-Dlkqi8ga.js → diagram-E7M64L7V-D8wBMBAX.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-HhRKBN6J.js → diagram-IFDJBPK2-DfudLpiJ.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-B70t50AJ.js → diagram-P4PSJMXO-CyMy61wE.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-Cc6Lz8R-.js → erDiagram-INFDFZHY-BlB4ZQl9.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-UfEconjz.js → flowDiagram-PKNHOUZH-DbhDQJM3.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-B13deOQz.js → ganttDiagram-A5KZAMGK-DJFqteNi.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-CHRxKecy.js → gitGraphDiagram-K3NZZRJ6-D8etA_mm.js} +1 -1
- package/dashboard/dist/assets/{graph-BO4rYEQo.js → graph-Ce86jeZn.js} +1 -1
- package/dashboard/dist/assets/index-DRng26Jg.js +567 -0
- package/dashboard/dist/assets/index-DzHQIE2n.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-DYhPRnW_.js → infoDiagram-LFFYTUFH-Cx35U-h8.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-uCkY17Z8.js → ishikawaDiagram-PHBUUO56-C04Y2nj8.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DT3-91Dx.js → journeyDiagram-4ABVD52K-D8-cxbxE.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CbNSU1jT.js → kanban-definition-K7BYSVSG-DVKqMylP.js} +1 -1
- package/dashboard/dist/assets/{layout-wdZYENqD.js → layout-98xZDpgu.js} +1 -1
- package/dashboard/dist/assets/{linear-DP2LUzjc.js → linear-0jk_IwAc.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-glRfe02B.js → mermaid.core-C337VWfr.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Bq2Qe7iv.js → mindmap-definition-YRQLILUH-8sNYGYEP.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-BnG8dbQb.js → pieDiagram-SKSYHLDU-afcmzHxf.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-CBvIkuCW.js → quadrantDiagram-337W2JSQ-B4RjcpOq.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-CsHJZ9D_.js → requirementDiagram-Z7DCOOCP-CRavU6cI.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DioS3JH6.js → sankeyDiagram-WA2Y5GQK-DFomU3z-.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-_noTQrZH.js → sequenceDiagram-2WXFIKYE-CGKO7nmK.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-K3R1Eh9N.js → stateDiagram-RAJIS63D-BjFI1K8h.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BtxefYKD.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Bb3u4Ahz.js → timeline-definition-YZTLITO2-BBo8XJFG.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-BLiQz7AY.js → treemap-KZPCXAKY-COd6i6TE.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-BLsoQIAw.js → vennDiagram-LZ73GAT5-CGQweQ36.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-Dm1juAkc.js → xychartDiagram-JWTSCODW-mfJ5So7N.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +1617 -300
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +2314 -1586
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +21 -1
- package/dist/launch/index.js +685 -111
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/SESSION-ID-RESOLUTION.md +41 -4
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/claude-code/hooks/session-cleanup.sh +25 -64
- package/platforms/claude-code/hooks/session-start.sh +35 -109
- package/platforms/claude-code/skills/track-session/SKILL.md +12 -60
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/codex/skills/track-session/SKILL.md +12 -60
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
- package/skills/track-session/SKILL.md +12 -60
- package/dashboard/dist/assets/channel-BLQNOcNZ.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-Mx6la8yG.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-Mx6la8yG.js +0 -1
- package/dashboard/dist/assets/clone-58HOBY7h.js +0 -1
- package/dashboard/dist/assets/index-BKdHsXLj.js +0 -566
- package/dashboard/dist/assets/index-D1f1wB-7.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BJes_eiS.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -2374,7 +2374,11 @@ var init_fields = __esm({
|
|
|
2374
2374
|
tags: { kind: "list" },
|
|
2375
2375
|
archived: { kind: "bool" },
|
|
2376
2376
|
title: { kind: "substring" },
|
|
2377
|
-
search
|
|
2377
|
+
// `search` reads a dedicated `searchText` haystack when the item provides one
|
|
2378
|
+
// (so the dashboard can match title + slug + project like its filter box),
|
|
2379
|
+
// falling back to `title` when absent. Backward-compatible: title-only when no
|
|
2380
|
+
// searchText. The `title` field stays title-only.
|
|
2381
|
+
search: { kind: "substring", get: (i) => i["searchText"] ?? i["title"] },
|
|
2378
2382
|
created: { kind: "timestamp" },
|
|
2379
2383
|
updated: { kind: "timestamp" },
|
|
2380
2384
|
completedat: { kind: "timestamp", get: (i) => i["completedAt"] },
|
|
@@ -2997,6 +3001,20 @@ var init_parser3 = __esm({
|
|
|
2997
3001
|
});
|
|
2998
3002
|
|
|
2999
3003
|
// src/utils/query/index.ts
|
|
3004
|
+
function compileQuery(input, registry = ASSIGNMENT_FIELDS) {
|
|
3005
|
+
const parsed = parseQuery(input);
|
|
3006
|
+
if (!parsed.ast) return { query: null, errors: parsed.errors };
|
|
3007
|
+
try {
|
|
3008
|
+
const predicate = compileNode(parsed.ast, registry);
|
|
3009
|
+
return { query: { predicate, ast: parsed.ast }, errors: [] };
|
|
3010
|
+
} catch (err) {
|
|
3011
|
+
if (err instanceof CompileError) return { query: null, errors: err.errors };
|
|
3012
|
+
throw err;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
function validateQuery(input, registry = ASSIGNMENT_FIELDS) {
|
|
3016
|
+
return compileQuery(input, registry).errors;
|
|
3017
|
+
}
|
|
3000
3018
|
var init_query = __esm({
|
|
3001
3019
|
"src/utils/query/index.ts"() {
|
|
3002
3020
|
"use strict";
|
|
@@ -3009,17 +3027,7 @@ var init_query = __esm({
|
|
|
3009
3027
|
}
|
|
3010
3028
|
});
|
|
3011
3029
|
|
|
3012
|
-
// src/
|
|
3013
|
-
var derive_exports = {};
|
|
3014
|
-
__export(derive_exports, {
|
|
3015
|
-
DERIVE_FIELDS: () => DERIVE_FIELDS,
|
|
3016
|
-
acceptFactDeclarations: () => acceptFactDeclarations,
|
|
3017
|
-
buildDeriveRegistry: () => buildDeriveRegistry,
|
|
3018
|
-
buildQueryRegistry: () => buildQueryRegistry,
|
|
3019
|
-
deriveDimensions: () => deriveDimensions,
|
|
3020
|
-
factFieldNames: () => factFieldNames,
|
|
3021
|
-
validateDeriveCondition: () => validateDeriveCondition
|
|
3022
|
-
});
|
|
3030
|
+
// src/utils/fact-registry.ts
|
|
3023
3031
|
function factFieldNames(decl) {
|
|
3024
3032
|
const name = decl.name;
|
|
3025
3033
|
const exportNames = {
|
|
@@ -3086,85 +3094,63 @@ function buildQueryRegistry(accepted) {
|
|
|
3086
3094
|
for (const decl of accepted) addFactFields(registry, decl);
|
|
3087
3095
|
return registry;
|
|
3088
3096
|
}
|
|
3089
|
-
function
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3097
|
+
function queryFieldNames(declarations) {
|
|
3098
|
+
const builtins = [
|
|
3099
|
+
"status",
|
|
3100
|
+
"priority",
|
|
3101
|
+
"type",
|
|
3102
|
+
"assignee",
|
|
3103
|
+
"project",
|
|
3104
|
+
"tag",
|
|
3105
|
+
"tags",
|
|
3106
|
+
"archived",
|
|
3107
|
+
"title",
|
|
3108
|
+
"search",
|
|
3109
|
+
"created",
|
|
3110
|
+
"updated",
|
|
3111
|
+
"completedAt",
|
|
3112
|
+
"statusAge",
|
|
3113
|
+
"phase",
|
|
3114
|
+
"disposition",
|
|
3115
|
+
"phaseAge",
|
|
3116
|
+
"hasRealObjective",
|
|
3117
|
+
"acRealTotal",
|
|
3118
|
+
"acRealChecked",
|
|
3119
|
+
"acAllChecked",
|
|
3120
|
+
"planExists",
|
|
3121
|
+
"planApproved",
|
|
3122
|
+
"workspaceSet",
|
|
3123
|
+
"implementationStarted",
|
|
3124
|
+
"depsSatisfied",
|
|
3125
|
+
"unresolvedQuestions",
|
|
3126
|
+
"progressStaleDays",
|
|
3127
|
+
"blocked",
|
|
3128
|
+
"parked",
|
|
3129
|
+
"reviewRequested",
|
|
3130
|
+
"pinned"
|
|
3131
|
+
];
|
|
3132
|
+
const custom = [];
|
|
3133
|
+
for (const decl of declarations) {
|
|
3134
|
+
const names = factFieldNames(decl);
|
|
3135
|
+
if (decl.type === "attestation") {
|
|
3136
|
+
custom.push(
|
|
3137
|
+
names.exports.fact,
|
|
3138
|
+
names.exports.approved,
|
|
3139
|
+
names.exports.changesRequested,
|
|
3140
|
+
names.exports.by,
|
|
3141
|
+
names.exports.approvedBy
|
|
3142
|
+
);
|
|
3111
3143
|
} else {
|
|
3112
|
-
|
|
3113
|
-
if (!parsed.ast) {
|
|
3114
|
-
throw new CompileError(parsed.errors);
|
|
3115
|
-
}
|
|
3116
|
-
pred = compileNode(parsed.ast, registry);
|
|
3144
|
+
custom.push(names.exports.fact);
|
|
3117
3145
|
}
|
|
3118
|
-
cache2.set(when, pred);
|
|
3119
3146
|
}
|
|
3120
|
-
return
|
|
3147
|
+
return [...builtins, ...custom];
|
|
3121
3148
|
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
if (terminalStatuses.has(currentStatus)) return null;
|
|
3126
|
-
const ctx = { now: 0 };
|
|
3127
|
-
const item = facts;
|
|
3128
|
-
let phase = derive.phaseLadder[0]?.phase ?? currentStatus;
|
|
3129
|
-
let nextAction = derive.phaseLadder[0]?.next ?? null;
|
|
3130
|
-
for (let i = derive.phaseLadder.length - 1; i >= 0; i--) {
|
|
3131
|
-
const rung = derive.phaseLadder[i];
|
|
3132
|
-
if (compiledWhen(registry, rung.when)(item, ctx)) {
|
|
3133
|
-
phase = rung.phase;
|
|
3134
|
-
nextAction = rung.next ?? null;
|
|
3135
|
-
break;
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
let disposition = "active";
|
|
3139
|
-
for (const rule of derive.disposition) {
|
|
3140
|
-
if (rule.when === null || compiledWhen(registry, rule.when)(item, ctx)) {
|
|
3141
|
-
disposition = rule.is;
|
|
3142
|
-
break;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
let derivedStatus;
|
|
3146
|
-
switch (disposition) {
|
|
3147
|
-
case "parked":
|
|
3148
|
-
derivedStatus = knownStatusIds.has(derive.headline.parked) ? derive.headline.parked : phase;
|
|
3149
|
-
break;
|
|
3150
|
-
case "blocked":
|
|
3151
|
-
derivedStatus = knownStatusIds.has(derive.headline.blocked) ? derive.headline.blocked : phase;
|
|
3152
|
-
break;
|
|
3153
|
-
default:
|
|
3154
|
-
derivedStatus = phase;
|
|
3155
|
-
}
|
|
3156
|
-
let status = derivedStatus;
|
|
3157
|
-
if (override && override.status && !terminalStatuses.has(override.status) && knownStatusIds.has(override.status)) {
|
|
3158
|
-
status = override.status;
|
|
3159
|
-
}
|
|
3160
|
-
return { phase, disposition, derivedStatus, status, nextAction };
|
|
3161
|
-
}
|
|
3162
|
-
var DERIVE_FIELDS, conditionCache;
|
|
3163
|
-
var init_derive = __esm({
|
|
3164
|
-
"src/lifecycle/derive.ts"() {
|
|
3149
|
+
var DERIVE_FIELDS;
|
|
3150
|
+
var init_fact_registry = __esm({
|
|
3151
|
+
"src/utils/fact-registry.ts"() {
|
|
3165
3152
|
"use strict";
|
|
3166
3153
|
init_query();
|
|
3167
|
-
init_query();
|
|
3168
3154
|
DERIVE_FIELDS = {
|
|
3169
3155
|
hasrealobjective: { kind: "bool", get: (i) => i["hasRealObjective"] },
|
|
3170
3156
|
acrealtotal: { kind: "number", get: (i) => i["acRealTotal"] },
|
|
@@ -3181,7 +3167,6 @@ var init_derive = __esm({
|
|
|
3181
3167
|
reviewrequested: { kind: "bool", get: (i) => i["reviewRequested"] },
|
|
3182
3168
|
pinned: { kind: "bool" }
|
|
3183
3169
|
};
|
|
3184
|
-
conditionCache = /* @__PURE__ */ new WeakMap();
|
|
3185
3170
|
}
|
|
3186
3171
|
});
|
|
3187
3172
|
|
|
@@ -3381,6 +3366,7 @@ function cloneDefaultConfig() {
|
|
|
3381
3366
|
...DEFAULT_CONFIG,
|
|
3382
3367
|
onboarding: { ...DEFAULT_CONFIG.onboarding },
|
|
3383
3368
|
agentDefaults: { ...DEFAULT_CONFIG.agentDefaults },
|
|
3369
|
+
session: { ...DEFAULT_CONFIG.session },
|
|
3384
3370
|
integrations: { ...DEFAULT_CONFIG.integrations },
|
|
3385
3371
|
backup: DEFAULT_CONFIG.backup ? { ...DEFAULT_CONFIG.backup } : null,
|
|
3386
3372
|
statuses: DEFAULT_CONFIG.statuses ? {
|
|
@@ -4730,6 +4716,11 @@ async function readConfig() {
|
|
|
4730
4716
|
fm["agentDefaults.autoCreateWorktree"]
|
|
4731
4717
|
) ? fm["agentDefaults.autoCreateWorktree"] : DEFAULT_CONFIG.agentDefaults.autoCreateWorktree
|
|
4732
4718
|
},
|
|
4719
|
+
session: {
|
|
4720
|
+
autoTrack: SESSION_AUTO_TRACK_VALUES.includes(
|
|
4721
|
+
fm["session.autoTrack"]
|
|
4722
|
+
) ? fm["session.autoTrack"] : DEFAULT_CONFIG.session.autoTrack
|
|
4723
|
+
},
|
|
4733
4724
|
integrations: {
|
|
4734
4725
|
claudePluginDir: parseOptionalAbsolutePath(
|
|
4735
4726
|
fm["integrations.claudePluginDir"],
|
|
@@ -4829,7 +4820,7 @@ async function updateAgentsConfig(mutation, options = {}) {
|
|
|
4829
4820
|
await writeAgentsConfig(next);
|
|
4830
4821
|
return { previous, next, written: true };
|
|
4831
4822
|
}
|
|
4832
|
-
var DEFAULT_DERIVE_CONFIG, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
4823
|
+
var DEFAULT_DERIVE_CONFIG, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, SESSION_AUTO_TRACK_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
4833
4824
|
var init_config2 = __esm({
|
|
4834
4825
|
"src/utils/config.ts"() {
|
|
4835
4826
|
"use strict";
|
|
@@ -4841,7 +4832,7 @@ var init_config2 = __esm({
|
|
|
4841
4832
|
init_hotkeysCatalog();
|
|
4842
4833
|
init_agents_schema();
|
|
4843
4834
|
init_slug();
|
|
4844
|
-
|
|
4835
|
+
init_fact_registry();
|
|
4845
4836
|
init_query();
|
|
4846
4837
|
init_terminal_schema();
|
|
4847
4838
|
init_workspace_visibility_schema();
|
|
@@ -4895,6 +4886,9 @@ var init_config2 = __esm({
|
|
|
4895
4886
|
autoApprove: false,
|
|
4896
4887
|
autoCreateWorktree: "ask"
|
|
4897
4888
|
},
|
|
4889
|
+
session: {
|
|
4890
|
+
autoTrack: "all"
|
|
4891
|
+
},
|
|
4898
4892
|
integrations: {
|
|
4899
4893
|
claudePluginDir: null,
|
|
4900
4894
|
codexPluginDir: null,
|
|
@@ -4915,6 +4909,7 @@ var init_config2 = __esm({
|
|
|
4915
4909
|
}
|
|
4916
4910
|
};
|
|
4917
4911
|
AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
|
|
4912
|
+
SESSION_AUTO_TRACK_VALUES = ["all", "workspaces-only", "off"];
|
|
4918
4913
|
AgentConfigError = class extends Error {
|
|
4919
4914
|
};
|
|
4920
4915
|
DEFAULT_STATUS_COLORS = {
|
|
@@ -4942,6 +4937,103 @@ var init_config2 = __esm({
|
|
|
4942
4937
|
}
|
|
4943
4938
|
});
|
|
4944
4939
|
|
|
4940
|
+
// src/lifecycle/derive.ts
|
|
4941
|
+
var derive_exports = {};
|
|
4942
|
+
__export(derive_exports, {
|
|
4943
|
+
DERIVE_FIELDS: () => DERIVE_FIELDS,
|
|
4944
|
+
acceptFactDeclarations: () => acceptFactDeclarations,
|
|
4945
|
+
addFactFields: () => addFactFields,
|
|
4946
|
+
buildDeriveRegistry: () => buildDeriveRegistry,
|
|
4947
|
+
buildQueryRegistry: () => buildQueryRegistry,
|
|
4948
|
+
deriveDimensions: () => deriveDimensions,
|
|
4949
|
+
factFieldNames: () => factFieldNames,
|
|
4950
|
+
queryFieldNames: () => queryFieldNames,
|
|
4951
|
+
validateDeriveCondition: () => validateDeriveCondition
|
|
4952
|
+
});
|
|
4953
|
+
function validateDeriveCondition(when, registry = DERIVE_FIELDS) {
|
|
4954
|
+
if (when === "*") return null;
|
|
4955
|
+
const parsed = parseQuery(when);
|
|
4956
|
+
if (!parsed.ast) return parsed.errors[0]?.message ?? "unparseable condition";
|
|
4957
|
+
try {
|
|
4958
|
+
compileNode(parsed.ast, registry);
|
|
4959
|
+
return null;
|
|
4960
|
+
} catch (err) {
|
|
4961
|
+
if (err instanceof CompileError) return err.errors[0]?.message ?? "invalid condition";
|
|
4962
|
+
throw err;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
function compiledWhen(registry, when) {
|
|
4966
|
+
let cache2 = conditionCache.get(registry);
|
|
4967
|
+
if (!cache2) {
|
|
4968
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
4969
|
+
conditionCache.set(registry, cache2);
|
|
4970
|
+
}
|
|
4971
|
+
let pred = cache2.get(when);
|
|
4972
|
+
if (!pred) {
|
|
4973
|
+
if (when === "*") {
|
|
4974
|
+
pred = () => true;
|
|
4975
|
+
} else {
|
|
4976
|
+
const parsed = parseQuery(when);
|
|
4977
|
+
if (!parsed.ast) {
|
|
4978
|
+
throw new CompileError(parsed.errors);
|
|
4979
|
+
}
|
|
4980
|
+
pred = compileNode(parsed.ast, registry);
|
|
4981
|
+
}
|
|
4982
|
+
cache2.set(when, pred);
|
|
4983
|
+
}
|
|
4984
|
+
return pred;
|
|
4985
|
+
}
|
|
4986
|
+
function deriveDimensions(input) {
|
|
4987
|
+
const { facts, derive, currentStatus, terminalStatuses, knownStatusIds, override } = input;
|
|
4988
|
+
const registry = input.registry ?? DERIVE_FIELDS;
|
|
4989
|
+
if (terminalStatuses.has(currentStatus)) return null;
|
|
4990
|
+
const ctx = { now: 0 };
|
|
4991
|
+
const item = facts;
|
|
4992
|
+
let phase = derive.phaseLadder[0]?.phase ?? currentStatus;
|
|
4993
|
+
let nextAction = derive.phaseLadder[0]?.next ?? null;
|
|
4994
|
+
for (let i = derive.phaseLadder.length - 1; i >= 0; i--) {
|
|
4995
|
+
const rung = derive.phaseLadder[i];
|
|
4996
|
+
if (compiledWhen(registry, rung.when)(item, ctx)) {
|
|
4997
|
+
phase = rung.phase;
|
|
4998
|
+
nextAction = rung.next ?? null;
|
|
4999
|
+
break;
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
let disposition = "active";
|
|
5003
|
+
for (const rule of derive.disposition) {
|
|
5004
|
+
if (rule.when === null || compiledWhen(registry, rule.when)(item, ctx)) {
|
|
5005
|
+
disposition = rule.is;
|
|
5006
|
+
break;
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
let derivedStatus;
|
|
5010
|
+
switch (disposition) {
|
|
5011
|
+
case "parked":
|
|
5012
|
+
derivedStatus = knownStatusIds.has(derive.headline.parked) ? derive.headline.parked : phase;
|
|
5013
|
+
break;
|
|
5014
|
+
case "blocked":
|
|
5015
|
+
derivedStatus = knownStatusIds.has(derive.headline.blocked) ? derive.headline.blocked : phase;
|
|
5016
|
+
break;
|
|
5017
|
+
default:
|
|
5018
|
+
derivedStatus = phase;
|
|
5019
|
+
}
|
|
5020
|
+
let status = derivedStatus;
|
|
5021
|
+
if (override && override.status && !terminalStatuses.has(override.status) && knownStatusIds.has(override.status)) {
|
|
5022
|
+
status = override.status;
|
|
5023
|
+
}
|
|
5024
|
+
return { phase, disposition, derivedStatus, status, nextAction };
|
|
5025
|
+
}
|
|
5026
|
+
var conditionCache;
|
|
5027
|
+
var init_derive = __esm({
|
|
5028
|
+
"src/lifecycle/derive.ts"() {
|
|
5029
|
+
"use strict";
|
|
5030
|
+
init_query();
|
|
5031
|
+
init_fact_registry();
|
|
5032
|
+
init_fact_registry();
|
|
5033
|
+
conditionCache = /* @__PURE__ */ new WeakMap();
|
|
5034
|
+
}
|
|
5035
|
+
});
|
|
5036
|
+
|
|
4945
5037
|
// src/utils/playbooks.ts
|
|
4946
5038
|
import { resolve as resolve7 } from "path";
|
|
4947
5039
|
import { readdir as readdir3, readFile as readFile6, unlink } from "fs/promises";
|
|
@@ -5678,6 +5770,15 @@ var init_help = __esm({
|
|
|
5678
5770
|
});
|
|
5679
5771
|
|
|
5680
5772
|
// src/dashboard/session-db.ts
|
|
5773
|
+
var session_db_exports = {};
|
|
5774
|
+
__export(session_db_exports, {
|
|
5775
|
+
closeSessionDb: () => closeSessionDb,
|
|
5776
|
+
getSessionDb: () => getSessionDb,
|
|
5777
|
+
initSessionDb: () => initSessionDb,
|
|
5778
|
+
isSessionDbInitialized: () => isSessionDbInitialized,
|
|
5779
|
+
migrateFromMarkdown: () => migrateFromMarkdown,
|
|
5780
|
+
resetSessionDb: () => resetSessionDb
|
|
5781
|
+
});
|
|
5681
5782
|
import Database from "better-sqlite3";
|
|
5682
5783
|
import { resolve as resolve9 } from "path";
|
|
5683
5784
|
import { readdir as readdir5 } from "fs/promises";
|
|
@@ -5822,6 +5923,9 @@ function initSessionDb(dbPath) {
|
|
|
5822
5923
|
db.exec(POST_MIGRATION_INDEXES_SQL);
|
|
5823
5924
|
return db;
|
|
5824
5925
|
}
|
|
5926
|
+
function isSessionDbInitialized() {
|
|
5927
|
+
return db !== null;
|
|
5928
|
+
}
|
|
5825
5929
|
function getSessionDb() {
|
|
5826
5930
|
if (!db) {
|
|
5827
5931
|
throw new Error(
|
|
@@ -5836,6 +5940,9 @@ function closeSessionDb() {
|
|
|
5836
5940
|
db = null;
|
|
5837
5941
|
}
|
|
5838
5942
|
}
|
|
5943
|
+
function resetSessionDb() {
|
|
5944
|
+
db = null;
|
|
5945
|
+
}
|
|
5839
5946
|
async function migrateFromMarkdown(projectsDir) {
|
|
5840
5947
|
const database = getSessionDb();
|
|
5841
5948
|
const count = database.prepare("SELECT COUNT(*) as count FROM sessions").get();
|
|
@@ -5866,8 +5973,8 @@ async function migrateFromMarkdown(projectsDir) {
|
|
|
5866
5973
|
return allSessions.length;
|
|
5867
5974
|
}
|
|
5868
5975
|
async function parseMarkdownSessionsIndex(filePath, projectSlug) {
|
|
5869
|
-
const { readFile:
|
|
5870
|
-
const raw2 = await
|
|
5976
|
+
const { readFile: readFile25 } = await import("fs/promises");
|
|
5977
|
+
const raw2 = await readFile25(filePath, "utf-8");
|
|
5871
5978
|
const sessions = [];
|
|
5872
5979
|
const lines = raw2.split("\n");
|
|
5873
5980
|
let inTable = false;
|
|
@@ -5957,7 +6064,7 @@ function rowToSession(row) {
|
|
|
5957
6064
|
originalHeadSha: row.original_head_sha ?? null
|
|
5958
6065
|
};
|
|
5959
6066
|
}
|
|
5960
|
-
async function appendSession(_projectDir, session) {
|
|
6067
|
+
async function appendSession(_projectDir, session, opts) {
|
|
5961
6068
|
const db4 = getSessionDb();
|
|
5962
6069
|
db4.prepare(`
|
|
5963
6070
|
INSERT INTO sessions (session_id, project_slug, assignment_slug, agent, started, status, path, description, transcript_path, pid, pid_started_at, original_head_sha)
|
|
@@ -5966,7 +6073,11 @@ async function appendSession(_projectDir, session) {
|
|
|
5966
6073
|
project_slug = COALESCE(NULLIF(excluded.project_slug, ''), project_slug),
|
|
5967
6074
|
assignment_slug = COALESCE(NULLIF(excluded.assignment_slug, ''), assignment_slug),
|
|
5968
6075
|
agent = excluded.agent,
|
|
5969
|
-
status = CASE
|
|
6076
|
+
status = CASE
|
|
6077
|
+
WHEN status = 'completed' THEN status
|
|
6078
|
+
WHEN status = 'stopped' AND NOT (? AND excluded.status = 'active') THEN status
|
|
6079
|
+
ELSE excluded.status
|
|
6080
|
+
END,
|
|
5970
6081
|
path = COALESCE(NULLIF(excluded.path, ''), path),
|
|
5971
6082
|
description = COALESCE(NULLIF(excluded.description, ''), description),
|
|
5972
6083
|
transcript_path = COALESCE(NULLIF(excluded.transcript_path, ''), transcript_path),
|
|
@@ -5986,15 +6097,16 @@ async function appendSession(_projectDir, session) {
|
|
|
5986
6097
|
session.transcriptPath ?? null,
|
|
5987
6098
|
session.pid ?? null,
|
|
5988
6099
|
session.pidStartedAt ?? null,
|
|
5989
|
-
session.originalHeadSha ?? null
|
|
6100
|
+
session.originalHeadSha ?? null,
|
|
6101
|
+
opts?.reviveStopped ? 1 : 0
|
|
5990
6102
|
);
|
|
5991
6103
|
}
|
|
5992
|
-
async function updateSessionStatus(_projectDir, sessionId, status) {
|
|
6104
|
+
async function updateSessionStatus(_projectDir, sessionId, status, endedAt) {
|
|
5993
6105
|
const db4 = getSessionDb();
|
|
5994
6106
|
const isTerminal = status === "completed" || status === "stopped";
|
|
5995
6107
|
const result = isTerminal ? db4.prepare(
|
|
5996
|
-
"UPDATE sessions SET status = ?, ended = datetime('now'), updated_at = datetime('now') WHERE session_id = ?"
|
|
5997
|
-
).run(status, sessionId) : db4.prepare(
|
|
6108
|
+
"UPDATE sessions SET status = ?, ended = COALESCE(?, datetime('now')), updated_at = datetime('now') WHERE session_id = ?"
|
|
6109
|
+
).run(status, endedAt ?? null, sessionId) : db4.prepare(
|
|
5998
6110
|
"UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE session_id = ?"
|
|
5999
6111
|
).run(status, sessionId);
|
|
6000
6112
|
return result.changes > 0;
|
|
@@ -6303,8 +6415,8 @@ function scanKey(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
6303
6415
|
return `${serversDir2}\0${projectsDir}\0${assignmentsDir2 ?? ""}`;
|
|
6304
6416
|
}
|
|
6305
6417
|
function delay(ms) {
|
|
6306
|
-
return new Promise((
|
|
6307
|
-
const timer2 = setTimeout(
|
|
6418
|
+
return new Promise((resolve38) => {
|
|
6419
|
+
const timer2 = setTimeout(resolve38, ms);
|
|
6308
6420
|
if (typeof timer2.unref === "function") {
|
|
6309
6421
|
timer2.unref();
|
|
6310
6422
|
}
|
|
@@ -7359,7 +7471,8 @@ async function getStatusConfig() {
|
|
|
7359
7471
|
derive: config.statuses.derive ?? null,
|
|
7360
7472
|
facts: config.statuses.facts ?? null,
|
|
7361
7473
|
factDeclarations: accepted,
|
|
7362
|
-
deriveRegistry: buildDeriveRegistry(accepted)
|
|
7474
|
+
deriveRegistry: buildDeriveRegistry(accepted),
|
|
7475
|
+
queryRegistry: buildQueryRegistry(accepted)
|
|
7363
7476
|
};
|
|
7364
7477
|
} else {
|
|
7365
7478
|
const def = buildDefaultStatusConfig();
|
|
@@ -7373,7 +7486,8 @@ async function getStatusConfig() {
|
|
|
7373
7486
|
derive: null,
|
|
7374
7487
|
facts: null,
|
|
7375
7488
|
factDeclarations: [],
|
|
7376
|
-
deriveRegistry: buildDeriveRegistry([])
|
|
7489
|
+
deriveRegistry: buildDeriveRegistry([]),
|
|
7490
|
+
queryRegistry: buildQueryRegistry([])
|
|
7377
7491
|
};
|
|
7378
7492
|
}
|
|
7379
7493
|
return _cachedConfig;
|
|
@@ -7682,14 +7796,30 @@ async function listArchived(projectsDir, assignmentsDir2) {
|
|
|
7682
7796
|
return { projects, assignments: individuallyArchived };
|
|
7683
7797
|
}
|
|
7684
7798
|
async function toStandaloneBoardItem(sr) {
|
|
7685
|
-
const
|
|
7799
|
+
const config = await getStatusConfig();
|
|
7800
|
+
const { terminalStatuses } = config;
|
|
7801
|
+
let facts;
|
|
7802
|
+
try {
|
|
7803
|
+
const { computeFacts: computeFacts2 } = await Promise.resolve().then(() => (init_facts(), facts_exports));
|
|
7804
|
+
facts = await computeFacts2({
|
|
7805
|
+
assignmentDir: sr.assignmentDir,
|
|
7806
|
+
frontmatter: sr.record,
|
|
7807
|
+
body: sr.record.body,
|
|
7808
|
+
projectDir: null,
|
|
7809
|
+
terminalStatuses,
|
|
7810
|
+
declarations: config.factDeclarations
|
|
7811
|
+
});
|
|
7812
|
+
} catch (err) {
|
|
7813
|
+
console.warn(`toStandaloneBoardItem: computeFacts failed for ${sr.assignmentDir}:`, err);
|
|
7814
|
+
}
|
|
7686
7815
|
return {
|
|
7687
7816
|
...toAssignmentSummary(sr.record, terminalStatuses),
|
|
7688
7817
|
projectSlug: null,
|
|
7689
7818
|
projectTitle: null,
|
|
7690
7819
|
blockedReason: sr.record.blockedReason,
|
|
7691
7820
|
projectWorkspace: sr.record.workspaceGroup ?? null,
|
|
7692
|
-
availableTransitions: await getStandaloneAvailableTransitions(sr.record)
|
|
7821
|
+
availableTransitions: await getStandaloneAvailableTransitions(sr.record),
|
|
7822
|
+
facts
|
|
7693
7823
|
};
|
|
7694
7824
|
}
|
|
7695
7825
|
async function getStandaloneAvailableTransitions(assignment) {
|
|
@@ -8576,7 +8706,24 @@ function toAssignmentSummary(assignment, terminalStatuses) {
|
|
|
8576
8706
|
};
|
|
8577
8707
|
}
|
|
8578
8708
|
async function toAssignmentBoardItem(projectsDir, projectRecord, assignment) {
|
|
8579
|
-
const
|
|
8709
|
+
const config = await getStatusConfig();
|
|
8710
|
+
const { terminalStatuses } = config;
|
|
8711
|
+
const assignmentDir = resolve14(projectRecord.projectPath, "assignments", assignment.slug);
|
|
8712
|
+
const projectDir = projectRecord.projectPath;
|
|
8713
|
+
let facts;
|
|
8714
|
+
try {
|
|
8715
|
+
const { computeFacts: computeFacts2 } = await Promise.resolve().then(() => (init_facts(), facts_exports));
|
|
8716
|
+
facts = await computeFacts2({
|
|
8717
|
+
assignmentDir,
|
|
8718
|
+
frontmatter: assignment,
|
|
8719
|
+
body: assignment.body,
|
|
8720
|
+
projectDir,
|
|
8721
|
+
terminalStatuses,
|
|
8722
|
+
declarations: config.factDeclarations
|
|
8723
|
+
});
|
|
8724
|
+
} catch (err) {
|
|
8725
|
+
console.warn(`toAssignmentBoardItem: computeFacts failed for ${assignmentDir}:`, err);
|
|
8726
|
+
}
|
|
8580
8727
|
return {
|
|
8581
8728
|
...toAssignmentSummary(assignment, terminalStatuses),
|
|
8582
8729
|
projectSlug: projectRecord.summary.slug,
|
|
@@ -8588,7 +8735,8 @@ async function toAssignmentBoardItem(projectsDir, projectRecord, assignment) {
|
|
|
8588
8735
|
projectRecord.summary.slug,
|
|
8589
8736
|
assignment.slug,
|
|
8590
8737
|
assignment
|
|
8591
|
-
)
|
|
8738
|
+
),
|
|
8739
|
+
facts
|
|
8592
8740
|
};
|
|
8593
8741
|
}
|
|
8594
8742
|
function buildDependencyGraph(assignments) {
|
|
@@ -9206,6 +9354,431 @@ var init_api = __esm({
|
|
|
9206
9354
|
}
|
|
9207
9355
|
});
|
|
9208
9356
|
|
|
9357
|
+
// src/templates/cursor-rules.ts
|
|
9358
|
+
function renderCursorProtocol() {
|
|
9359
|
+
return `---
|
|
9360
|
+
description: Syntaur protocol rules for multi-agent coordination
|
|
9361
|
+
globs:
|
|
9362
|
+
alwaysApply: true
|
|
9363
|
+
---
|
|
9364
|
+
|
|
9365
|
+
# Syntaur Protocol
|
|
9366
|
+
|
|
9367
|
+
You are working within the Syntaur protocol for multi-agent project coordination.
|
|
9368
|
+
|
|
9369
|
+
## Directory Structure
|
|
9370
|
+
|
|
9371
|
+
\`\`\`
|
|
9372
|
+
~/.syntaur/
|
|
9373
|
+
config.md
|
|
9374
|
+
projects/
|
|
9375
|
+
<project-slug>/
|
|
9376
|
+
manifest.md # Derived: root navigation (read-only)
|
|
9377
|
+
project.md # Human-authored: project overview (read-only)
|
|
9378
|
+
_index-assignments.md # Derived (read-only)
|
|
9379
|
+
_index-plans.md # Derived (read-only)
|
|
9380
|
+
_index-decisions.md # Derived (read-only)
|
|
9381
|
+
_status.md # Derived (read-only)
|
|
9382
|
+
assignments/
|
|
9383
|
+
<assignment-slug>/
|
|
9384
|
+
assignment.md # Agent-writable: source of truth for state (includes ## Todos)
|
|
9385
|
+
plan*.md # Agent-writable: versioned implementation plans (optional, one per ## Todos entry)
|
|
9386
|
+
progress.md # Agent-writable, append-only: timestamped progress log
|
|
9387
|
+
comments.md # CLI-mediated: threaded questions/notes/feedback (via \`syntaur comment\`)
|
|
9388
|
+
scratchpad.md # Agent-writable: working notes
|
|
9389
|
+
handoff.md # Agent-writable: append-only cross-ticket outbound at completion
|
|
9390
|
+
decision-record.md # Agent-writable: append-only decision log
|
|
9391
|
+
sessions/
|
|
9392
|
+
<session-id>/
|
|
9393
|
+
summary.md # Agent-writable: per-session continuity (single doc, overwritten)
|
|
9394
|
+
resources/
|
|
9395
|
+
_index.md # Derived (read-only)
|
|
9396
|
+
<resource-slug>.md # Shared-writable
|
|
9397
|
+
memories/
|
|
9398
|
+
_index.md # Derived (read-only)
|
|
9399
|
+
<memory-slug>.md # Shared-writable
|
|
9400
|
+
assignments/
|
|
9401
|
+
<assignment-id>/ # Standalone assignments \u2014 folder = UUID, \`project: null\`, slug display-only
|
|
9402
|
+
assignment.md
|
|
9403
|
+
plan*.md
|
|
9404
|
+
progress.md
|
|
9405
|
+
comments.md
|
|
9406
|
+
scratchpad.md
|
|
9407
|
+
handoff.md
|
|
9408
|
+
decision-record.md
|
|
9409
|
+
sessions/<session-id>/summary.md # Per-session continuity (same as project-nested)
|
|
9410
|
+
\`\`\`
|
|
9411
|
+
|
|
9412
|
+
## Write Boundary Rules (CRITICAL)
|
|
9413
|
+
|
|
9414
|
+
### Files you may WRITE:
|
|
9415
|
+
1. **Your assignment folder** -- only the assignment you are currently working on:
|
|
9416
|
+
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\` (cross-ticket outbound at completion), \`decision-record.md\`
|
|
9417
|
+
- \`sessions/<session-id>/summary.md\` -- per-session continuity (single doc per session id, overwritten on save). Distinct from \`handoff.md\`.
|
|
9418
|
+
- Path (project-nested): \`~/.syntaur/projects/<project>/assignments/<your-assignment>/\`
|
|
9419
|
+
- Path (standalone): \`~/.syntaur/assignments/<your-assignment-uuid>/\`
|
|
9420
|
+
2. **Shared resources and memories** at the project level:
|
|
9421
|
+
- \`~/.syntaur/projects/<project>/resources/<slug>.md\`
|
|
9422
|
+
- \`~/.syntaur/projects/<project>/memories/<slug>.md\`
|
|
9423
|
+
3. **Your workspace** -- source code files in the current working directory (the directory where this adapter file lives). If your assignment's frontmatter specifies a \`workspace\` field, read it at runtime to determine the exact boundary.
|
|
9424
|
+
|
|
9425
|
+
> **Note:** The \`setup-adapter\` command does not parse assignment frontmatter for workspace paths. Workspace boundaries are resolved by the agent at runtime by reading \`assignment.md\` frontmatter. If no \`workspace\` field is set, treat the current working directory as your workspace.
|
|
9426
|
+
|
|
9427
|
+
### Files written only via CLI (never edit directly):
|
|
9428
|
+
- \`comments.md\` (any assignment) -- use \`syntaur comment <slug-or-uuid> "body" [--type question|note|feedback] [--reply-to <id>]\`
|
|
9429
|
+
- Another assignment's \`## Todos\` section -- use \`syntaur request <source> <target> "text"\` to request cross-assignment work
|
|
9430
|
+
|
|
9431
|
+
### Files you must NEVER write:
|
|
9432
|
+
1. \`project.md\` -- human-authored, read-only
|
|
9433
|
+
2. \`manifest.md\` -- derived, rebuilt by tooling
|
|
9434
|
+
3. Any file prefixed with \`_\` -- derived
|
|
9435
|
+
4. Other agents' assignment folders (except via the CLI-mediated channels above)
|
|
9436
|
+
5. Any files outside your workspace boundary
|
|
9437
|
+
|
|
9438
|
+
## Assignment Lifecycle
|
|
9439
|
+
|
|
9440
|
+
| Status | Meaning |
|
|
9441
|
+
|--------|---------|
|
|
9442
|
+
| \`pending\` | Not yet started |
|
|
9443
|
+
| \`in_progress\` | Actively being worked on |
|
|
9444
|
+
| \`blocked\` | Manually blocked (requires blockedReason) |
|
|
9445
|
+
| \`review\` | Work complete, awaiting review |
|
|
9446
|
+
| \`completed\` | Done |
|
|
9447
|
+
| \`failed\` | Could not be completed |
|
|
9448
|
+
|
|
9449
|
+
## Valid State Transitions
|
|
9450
|
+
|
|
9451
|
+
| From | Command | To |
|
|
9452
|
+
|------|---------|-----|
|
|
9453
|
+
| pending | start | in_progress |
|
|
9454
|
+
| pending | block | blocked |
|
|
9455
|
+
| in_progress | block | blocked |
|
|
9456
|
+
| in_progress | review | review |
|
|
9457
|
+
| in_progress | complete | completed |
|
|
9458
|
+
| in_progress | fail | failed |
|
|
9459
|
+
| blocked | unblock | in_progress |
|
|
9460
|
+
| review | start | in_progress |
|
|
9461
|
+
| review | complete | completed |
|
|
9462
|
+
| review | fail | failed |
|
|
9463
|
+
|
|
9464
|
+
## Lifecycle Commands
|
|
9465
|
+
|
|
9466
|
+
Use the \`syntaur\` CLI for state transitions and coordination:
|
|
9467
|
+
- \`syntaur assign <slug> --agent <name> --project <project>\` -- set assignee
|
|
9468
|
+
- \`syntaur start <slug> --project <project>\` -- pending -> in_progress
|
|
9469
|
+
- \`syntaur review <slug> --project <project>\` -- in_progress -> review
|
|
9470
|
+
- \`syntaur complete <slug> --project <project>\` -- in_progress/review -> completed
|
|
9471
|
+
- \`syntaur block <slug> --project <project> --reason <text>\` -- block an assignment
|
|
9472
|
+
- \`syntaur unblock <slug> --project <project>\` -- unblock
|
|
9473
|
+
- \`syntaur fail <slug> --project <project>\` -- mark as failed
|
|
9474
|
+
- \`syntaur create-assignment "Title" [--type <type>] [--project <slug> | --one-off]\` -- create project-nested or standalone assignment
|
|
9475
|
+
- \`syntaur comment <slug-or-uuid> "body" --type question|note|feedback [--reply-to <id>]\` -- append to \`comments.md\` (questions support resolve toggle via dashboard)
|
|
9476
|
+
- \`syntaur request <source> <target> "text"\` -- append a todo to another assignment's \`## Todos\` annotated \`(from: <source>)\`
|
|
9477
|
+
|
|
9478
|
+
## Playbooks
|
|
9479
|
+
|
|
9480
|
+
Playbooks are user-defined behavioral rules stored in \`~/.syntaur/playbooks/\`. Read the playbook manifest before starting work:
|
|
9481
|
+
|
|
9482
|
+
\`\`\`bash
|
|
9483
|
+
cat ~/.syntaur/playbooks/manifest.md
|
|
9484
|
+
\`\`\`
|
|
9485
|
+
|
|
9486
|
+
Follow the rules in each playbook. They take precedence over default conventions when they conflict.
|
|
9487
|
+
|
|
9488
|
+
## Conventions
|
|
9489
|
+
|
|
9490
|
+
- Assignment frontmatter is the single source of truth for state. \`project\` is the containing project slug (\`null\` for standalone); \`type\` is a classification validated against \`config.md\` \`types.definitions\` when present.
|
|
9491
|
+
- Slugs are lowercase, hyphen-separated. Standalone assignment folders are named by UUID; \`slug\` is display-only in that case.
|
|
9492
|
+
- Always read \`project.md\` at the project level (when project-nested) before starting work.
|
|
9493
|
+
- Append timestamped entries to \`progress.md\` (never to \`assignment.md\`).
|
|
9494
|
+
- Record questions, notes, and feedback via \`syntaur comment\`. Never edit \`comments.md\` directly.
|
|
9495
|
+
- To route work to another assignment, use \`syntaur request\`.
|
|
9496
|
+
- Commit frequently with messages referencing the assignment slug.
|
|
9497
|
+
`;
|
|
9498
|
+
}
|
|
9499
|
+
function renderCursorAssignment(params2) {
|
|
9500
|
+
return `---
|
|
9501
|
+
description: Syntaur assignment context for ${params2.projectSlug}/${params2.assignmentSlug}
|
|
9502
|
+
globs:
|
|
9503
|
+
alwaysApply: true
|
|
9504
|
+
---
|
|
9505
|
+
|
|
9506
|
+
# Current Assignment Context
|
|
9507
|
+
|
|
9508
|
+
- **Project:** ${params2.projectSlug}
|
|
9509
|
+
- **Assignment:** ${params2.assignmentSlug}
|
|
9510
|
+
- **Project directory:** ${params2.projectDir}
|
|
9511
|
+
- **Assignment directory:** ${params2.assignmentDir}
|
|
9512
|
+
|
|
9513
|
+
## Reading Order
|
|
9514
|
+
|
|
9515
|
+
Before starting work, read these files in order:
|
|
9516
|
+
1. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
|
|
9517
|
+
2. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
|
|
9518
|
+
3. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
9519
|
+
4. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
9520
|
+
5. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
9521
|
+
6. \`${params2.assignmentDir}/handoff.md\` -- cross-ticket outbound history (entries from prior agents/humans handing this assignment off)
|
|
9522
|
+
7. The latest \`${params2.assignmentDir}/sessions/<sid>/summary.md\` if present -- previous-session continuity (selected by \`summary.md\` file mtime; read it for "what was done / what's next" before resuming work in flight)
|
|
9523
|
+
|
|
9524
|
+
## Your Writable Files
|
|
9525
|
+
|
|
9526
|
+
You may write directly to these files inside your assignment folder:
|
|
9527
|
+
- \`${params2.assignmentDir}/assignment.md\`
|
|
9528
|
+
- \`${params2.assignmentDir}/plan*.md\` (0 or more versioned plan files, e.g., \`plan.md\`, \`plan-v2.md\`)
|
|
9529
|
+
- \`${params2.assignmentDir}/progress.md\` (append timestamped entries, newest first)
|
|
9530
|
+
- \`${params2.assignmentDir}/scratchpad.md\`
|
|
9531
|
+
- \`${params2.assignmentDir}/handoff.md\`
|
|
9532
|
+
- \`${params2.assignmentDir}/decision-record.md\`
|
|
9533
|
+
- \`${params2.assignmentDir}/sessions/<session-id>/summary.md\` (per-session continuity)
|
|
9534
|
+
|
|
9535
|
+
Do NOT edit \`${params2.assignmentDir}/comments.md\` directly \u2014 use \`syntaur comment\`. Do NOT edit other assignments' files \u2014 use \`syntaur request\` for cross-assignment todos.
|
|
9536
|
+
|
|
9537
|
+
And source code files in your workspace. Read the \`workspace\` field from your assignment's frontmatter to determine the exact boundary. If not set, the current working directory is your workspace.
|
|
9538
|
+
`;
|
|
9539
|
+
}
|
|
9540
|
+
var init_cursor_rules = __esm({
|
|
9541
|
+
"src/templates/cursor-rules.ts"() {
|
|
9542
|
+
"use strict";
|
|
9543
|
+
}
|
|
9544
|
+
});
|
|
9545
|
+
|
|
9546
|
+
// src/templates/codex-agents.ts
|
|
9547
|
+
function renderCodexAgents(params2) {
|
|
9548
|
+
return `# Syntaur Protocol -- Agent Instructions
|
|
9549
|
+
|
|
9550
|
+
This project uses the Syntaur protocol for multi-agent project coordination.
|
|
9551
|
+
|
|
9552
|
+
## Current Assignment
|
|
9553
|
+
|
|
9554
|
+
- **Project:** ${params2.projectSlug}
|
|
9555
|
+
- **Assignment:** ${params2.assignmentSlug}
|
|
9556
|
+
- **Project directory:** ${params2.projectDir}
|
|
9557
|
+
- **Assignment directory:** ${params2.assignmentDir}
|
|
9558
|
+
|
|
9559
|
+
## Preferred Workflow
|
|
9560
|
+
|
|
9561
|
+
If the global Syntaur Codex plugin is installed, prefer these workflows instead of ad hoc protocol edits:
|
|
9562
|
+
|
|
9563
|
+
- \`syntaur-operator\` agent -- use for broad Syntaur protocol work or when a task spans multiple lifecycle steps
|
|
9564
|
+
- \`syntaur-protocol\` -- background protocol and write-boundary rules
|
|
9565
|
+
- \`create-project\` -- scaffold a project
|
|
9566
|
+
- \`create-assignment\` -- create a new assignment (use \`--type <bug|feature|chore|...>\` to classify; use \`--one-off\` to create a standalone assignment at \`~/.syntaur/assignments/<uuid>/\` with no parent project)
|
|
9567
|
+
- \`grab-assignment\` -- claim work, create \`.syntaur/context.json\`, and register a session
|
|
9568
|
+
- \`plan-assignment\` -- write a versioned plan file (\`plan.md\`, \`plan-v2.md\`, ...) and link it from the \`## Todos\` section of \`assignment.md\`
|
|
9569
|
+
- \`complete-assignment\` -- write the cross-ticket \`handoff.md\` entry, append a final entry to \`progress.md\`, close the session, and transition state
|
|
9570
|
+
- \`save-session-summary\` -- write per-session continuity at \`<assignmentDir>/sessions/<sessionId>/summary.md\` for resume across sessions of the same agent. Codex has no \`PreCompact\` hook event \u2014 invoke this manually before compaction or session end.
|
|
9571
|
+
- \`capture-artifacts\` -- capture typed proof artifacts (screenshot/video/asciinema/http/text) for the active assignment. Criterion linkage is optional. Run \`syntaur proof build\` to render \`proof.html\`.
|
|
9572
|
+
- \`resume-session\` -- counterpart to \`save-session-summary\`; loads the latest summary, \`.syntaur/context.json\`, and any open handoff so a fresh session re-orients without re-reading the transcript
|
|
9573
|
+
- \`replan\` -- bump the active assignment to a new \`plan-v<N>.md\` per the Plan Versioning playbook (CLI does file ops, skill writes the body)
|
|
9574
|
+
- \`syntaur-worktree\` -- atomic worktree creation under \`<repository>/.worktrees/<branch>\` plus assign + start + context binding in one move
|
|
9575
|
+
- \`add-resource\` -- register a project-level resource (link to dashboard / doc / ticket); CLI regenerates \`_index.md\` server-side
|
|
9576
|
+
- \`add-memory\` -- capture a project-level Syntaur memory; CLI regenerates \`_index.md\` server-side (distinct from user-global Claude Code auto-memory)
|
|
9577
|
+
- \`list-assignments\` -- cross-project listing with filters by status, project, tag, age (scriptable; not the interactive \`browse\` TUI)
|
|
9578
|
+
- \`log-progress\` -- append a timestamped entry to the active \`progress.md\` and bump frontmatter (Keep Records Updated playbook)
|
|
9579
|
+
- \`set-workspace\` -- populate the four \`workspace.*\` fields in \`assignment.md\`; validates via \`syntaur doctor --assignment --json\` before writing
|
|
9580
|
+
- \`track-session\` -- manage tracked tmux sessions for the dashboard
|
|
9581
|
+
|
|
9582
|
+
If the plugin is unavailable, follow the same workflow manually with the \`syntaur\` CLI and keep the protocol files current yourself.
|
|
9583
|
+
|
|
9584
|
+
## Reading Order
|
|
9585
|
+
|
|
9586
|
+
Before starting work, read these files in order:
|
|
9587
|
+
1. \`${params2.projectDir}/manifest.md\` -- root navigation entry point (project-nested assignments only)
|
|
9588
|
+
2. \`${params2.projectDir}/project.md\` -- project overview and goals (project-nested assignments only)
|
|
9589
|
+
3. \`${params2.assignmentDir}/assignment.md\` -- your assignment details, acceptance criteria, todos, current status. Frontmatter now includes \`project: <slug> | null\` (null for standalone) and \`type: <classification> | null\`.
|
|
9590
|
+
4. any \`${params2.assignmentDir}/plan*.md\` files linked from active todos in the \`## Todos\` section (may be 0, 1, or many)
|
|
9591
|
+
5. \`${params2.assignmentDir}/progress.md\` -- reverse-chron progress log (if present)
|
|
9592
|
+
6. \`${params2.assignmentDir}/comments.md\` -- threaded questions/notes/feedback (if present)
|
|
9593
|
+
7. \`${params2.assignmentDir}/handoff.md\` -- cross-ticket outbound history (entries from prior agents/humans handing this assignment off)
|
|
9594
|
+
8. The latest \`${params2.assignmentDir}/sessions/<sid>/summary.md\` if present -- previous-session continuity (read it for "what was done / what's next" before resuming work in flight)
|
|
9595
|
+
|
|
9596
|
+
## Context File
|
|
9597
|
+
|
|
9598
|
+
- Treat \`.syntaur/context.json\` in the current working directory as the active assignment context when it exists.
|
|
9599
|
+
- Use that file to resolve the workspace boundary, assignment path, and project path (the active assignment binding). The active session id, however, is resolved from *your* running process -- prefer \`$CLAUDE_CODE_SESSION_ID\` (or the peer \`OPENCODE_SESSION_ID\` / \`PI_SESSION_ID\`), otherwise run \`syntaur session resolve-id\`; the \`sessionId\` scalar in context.json is only a clobberable legacy hint, not authoritative.
|
|
9600
|
+
- If there is no context file yet and you are supposed to work on an assignment, claim or set up the assignment before editing code.
|
|
9601
|
+
|
|
9602
|
+
## Directory Structure
|
|
9603
|
+
|
|
9604
|
+
\`\`\`
|
|
9605
|
+
~/.syntaur/
|
|
9606
|
+
config.md
|
|
9607
|
+
projects/
|
|
9608
|
+
<project-slug>/
|
|
9609
|
+
manifest.md # Derived: root navigation (read-only)
|
|
9610
|
+
project.md # Human-authored: project overview (read-only)
|
|
9611
|
+
_index-assignments.md # Derived (read-only)
|
|
9612
|
+
_index-plans.md # Derived (read-only)
|
|
9613
|
+
_index-decisions.md # Derived (read-only)
|
|
9614
|
+
_status.md # Derived (read-only)
|
|
9615
|
+
assignments/
|
|
9616
|
+
<assignment-slug>/
|
|
9617
|
+
assignment.md # Agent-writable: source of truth for state (includes ## Todos)
|
|
9618
|
+
plan*.md # Agent-writable: versioned implementation plans (optional, one per ## Todos entry)
|
|
9619
|
+
progress.md # Agent-writable, append-only: timestamped progress log
|
|
9620
|
+
comments.md # CLI-mediated: threaded questions/notes/feedback (via \`syntaur comment\`)
|
|
9621
|
+
scratchpad.md # Agent-writable: working notes
|
|
9622
|
+
handoff.md # Agent-writable: append-only cross-ticket outbound at completion
|
|
9623
|
+
decision-record.md # Agent-writable: append-only decision log
|
|
9624
|
+
sessions/
|
|
9625
|
+
<session-id>/
|
|
9626
|
+
summary.md # Agent-writable: per-session continuity (single doc, overwritten)
|
|
9627
|
+
resources/
|
|
9628
|
+
_index.md # Derived (read-only)
|
|
9629
|
+
<resource-slug>.md # Shared-writable
|
|
9630
|
+
memories/
|
|
9631
|
+
_index.md # Derived (read-only)
|
|
9632
|
+
<memory-slug>.md # Shared-writable
|
|
9633
|
+
assignments/
|
|
9634
|
+
<assignment-id>/ # Standalone assignments \u2014 folder = UUID, \`project: null\`, slug display-only
|
|
9635
|
+
assignment.md
|
|
9636
|
+
plan*.md
|
|
9637
|
+
progress.md
|
|
9638
|
+
comments.md
|
|
9639
|
+
scratchpad.md
|
|
9640
|
+
handoff.md
|
|
9641
|
+
decision-record.md
|
|
9642
|
+
sessions/<session-id>/summary.md # Per-session continuity (same as project-nested)
|
|
9643
|
+
\`\`\`
|
|
9644
|
+
|
|
9645
|
+
## Write Boundary Rules (CRITICAL)
|
|
9646
|
+
|
|
9647
|
+
### Files you may WRITE:
|
|
9648
|
+
1. **Your assignment folder** -- only the assignment you are currently working on:
|
|
9649
|
+
- \`assignment.md\`, \`plan*.md\` (0 or more versioned plan files), \`progress.md\`, \`scratchpad.md\`, \`handoff.md\` (cross-ticket outbound at completion), \`decision-record.md\`
|
|
9650
|
+
- \`sessions/<session-id>/summary.md\` -- per-session continuity (single doc per session id, overwritten on save). Distinct from \`handoff.md\`.
|
|
9651
|
+
- Path: \`${params2.assignmentDir}/\`
|
|
9652
|
+
2. **Shared resources and memories** at the project level:
|
|
9653
|
+
- \`${params2.projectDir}/resources/<slug>.md\`
|
|
9654
|
+
- \`${params2.projectDir}/memories/<slug>.md\`
|
|
9655
|
+
3. **Your workspace** -- source code files in the current working directory (the directory where this AGENTS.md lives). If your assignment's frontmatter specifies a \`workspace\` field, read it at runtime to determine the exact boundary.
|
|
9656
|
+
|
|
9657
|
+
> **Note:** Workspace boundaries are resolved by the agent at runtime by reading \`assignment.md\` frontmatter. If no \`workspace\` field is set, treat the current working directory as your workspace.
|
|
9658
|
+
|
|
9659
|
+
### Files written only via CLI (never edit directly):
|
|
9660
|
+
- \`comments.md\` (any assignment) -- use \`syntaur comment <slug-or-uuid> "body" [--type question|note|feedback] [--reply-to <id>]\`
|
|
9661
|
+
- Another assignment's \`## Todos\` section -- use \`syntaur request <source> <target> "text"\` to request cross-assignment work
|
|
9662
|
+
|
|
9663
|
+
### Files you must NEVER write:
|
|
9664
|
+
1. \`project.md\` -- human-authored, read-only
|
|
9665
|
+
2. \`manifest.md\` -- derived, rebuilt by tooling
|
|
9666
|
+
3. Any file prefixed with \`_\` -- derived
|
|
9667
|
+
4. Other agents' assignment folders (except via the CLI-mediated channels above)
|
|
9668
|
+
5. Any files outside your workspace boundary
|
|
9669
|
+
|
|
9670
|
+
## Assignment Lifecycle
|
|
9671
|
+
|
|
9672
|
+
| Status | Meaning |
|
|
9673
|
+
|--------|---------|
|
|
9674
|
+
| \`pending\` | Not yet started |
|
|
9675
|
+
| \`in_progress\` | Actively being worked on |
|
|
9676
|
+
| \`blocked\` | Manually blocked (requires blockedReason) |
|
|
9677
|
+
| \`review\` | Work complete, awaiting review |
|
|
9678
|
+
| \`completed\` | Done |
|
|
9679
|
+
| \`failed\` | Could not be completed |
|
|
9680
|
+
|
|
9681
|
+
## Valid State Transitions
|
|
9682
|
+
|
|
9683
|
+
| From | Command | To |
|
|
9684
|
+
|------|---------|-----|
|
|
9685
|
+
| pending | start | in_progress |
|
|
9686
|
+
| pending | block | blocked |
|
|
9687
|
+
| in_progress | block | blocked |
|
|
9688
|
+
| in_progress | review | review |
|
|
9689
|
+
| in_progress | complete | completed |
|
|
9690
|
+
| in_progress | fail | failed |
|
|
9691
|
+
| blocked | unblock | in_progress |
|
|
9692
|
+
| review | start | in_progress |
|
|
9693
|
+
| review | complete | completed |
|
|
9694
|
+
| review | fail | failed |
|
|
9695
|
+
|
|
9696
|
+
## Lifecycle Commands
|
|
9697
|
+
|
|
9698
|
+
Use the \`syntaur\` CLI for state transitions and coordination:
|
|
9699
|
+
- \`syntaur assign ${params2.assignmentSlug} --agent <name> --project ${params2.projectSlug}\` -- set assignee
|
|
9700
|
+
- \`syntaur start ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- pending -> in_progress
|
|
9701
|
+
- \`syntaur review ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress -> review
|
|
9702
|
+
- \`syntaur complete ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- in_progress/review -> completed
|
|
9703
|
+
- \`syntaur block ${params2.assignmentSlug} --project ${params2.projectSlug} --reason <text>\` -- block
|
|
9704
|
+
- \`syntaur unblock ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- unblock
|
|
9705
|
+
- \`syntaur fail ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- mark as failed
|
|
9706
|
+
- \`syntaur comment ${params2.assignmentSlug} "body" --type question|note|feedback [--reply-to <id>]\` -- append to \`comments.md\` (use for all Q&A; questions support resolve toggle)
|
|
9707
|
+
- \`syntaur request ${params2.assignmentSlug} <target-slug-or-uuid> "text"\` -- append a todo to another assignment's \`## Todos\` annotated \`(from: ${params2.assignmentSlug})\`
|
|
9708
|
+
- \`syntaur capture --kind <screenshot|video|asciinema|http|text> [--file <path>] [--criterion <index>] [--note <text>] [--transcribe] ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- record a proof artifact. \`--kind=text\` requires \`--note\` and forbids \`--file\`. Criterion linkage is optional. \`--transcribe\` is video-only and writes a sibling \`<id>.transcript.md\` (requires \`ELEVENLABS_API_KEY\` + \`ffmpeg\`).
|
|
9709
|
+
- \`syntaur proof build ${params2.assignmentSlug} --project ${params2.projectSlug}\` -- render \`proof.html\` and \`proof.md\` at the assignment dir. Atomic overwrite \u2014 safe to re-run.
|
|
9710
|
+
|
|
9711
|
+
## Troubleshooting
|
|
9712
|
+
|
|
9713
|
+
If Syntaur state looks inconsistent (missing files, stale manifests, unexpected hook blocks), run \`syntaur doctor\` to diagnose. Use \`--json\` for structured output.
|
|
9714
|
+
|
|
9715
|
+
## Playbooks
|
|
9716
|
+
|
|
9717
|
+
Playbooks are user-defined behavioral rules stored in \`~/.syntaur/playbooks/\`. Before starting work, read the playbook manifest and then each referenced playbook:
|
|
9718
|
+
|
|
9719
|
+
\`\`\`bash
|
|
9720
|
+
cat ~/.syntaur/playbooks/manifest.md
|
|
9721
|
+
\`\`\`
|
|
9722
|
+
|
|
9723
|
+
Read each linked playbook and follow the rules in its body section. The \`when_to_use\` field tells you when each playbook applies. Playbooks take precedence over default conventions when they conflict.
|
|
9724
|
+
|
|
9725
|
+
## Conventions
|
|
9726
|
+
|
|
9727
|
+
- Assignment frontmatter is the single source of truth for state. \`project\` is the containing project slug (\`null\` for standalone); \`type\` is a classification validated against \`config.md\` \`types.definitions\` when present.
|
|
9728
|
+
- Slugs are lowercase, hyphen-separated. For standalone assignments, \`slug\` is display-only; the folder is named by the UUID.
|
|
9729
|
+
- Always read \`project.md\` at the project level (when project-nested) before starting work.
|
|
9730
|
+
- Keep \`assignment.md\` acceptance criteria and \`## Todos\` updated as work lands; append timestamped entries to \`progress.md\` (never to \`assignment.md\`).
|
|
9731
|
+
- Keep active plan file(s) current after planning changes. Write \`handoff.md\` (via \`complete-assignment\`) at the cross-ticket boundary; write \`sessions/<sid>/summary.md\` (via \`/save-session-summary\`) before compaction or before ending a session mid-assignment so a future session can resume cleanly.
|
|
9732
|
+
- When requirements shift, supersede the prior plan todo (\`- [x] ~~...~~ (superseded by plan-v<N>)\`) and write a new plan file instead of rewriting the old one.
|
|
9733
|
+
- Record questions, notes, and feedback via \`syntaur comment\`. Never edit \`comments.md\` directly. Resolve questions via the dashboard UI (toggle on the question entry).
|
|
9734
|
+
- To route work to another assignment, use \`syntaur request\`.
|
|
9735
|
+
- Commit frequently with messages referencing the assignment slug.
|
|
9736
|
+
`;
|
|
9737
|
+
}
|
|
9738
|
+
var init_codex_agents = __esm({
|
|
9739
|
+
"src/templates/codex-agents.ts"() {
|
|
9740
|
+
"use strict";
|
|
9741
|
+
}
|
|
9742
|
+
});
|
|
9743
|
+
|
|
9744
|
+
// src/templates/opencode-config.ts
|
|
9745
|
+
function renderOpenCodeConfig(params2) {
|
|
9746
|
+
const config = {
|
|
9747
|
+
instructions: [
|
|
9748
|
+
`Read AGENTS.md in this directory for Syntaur protocol (v2.0) instructions.`,
|
|
9749
|
+
`Read ${params2.projectDir}/project.md for project overview (project-nested assignments only).`,
|
|
9750
|
+
`Append timestamped progress entries to the assignment's progress.md (not to assignment.md).`,
|
|
9751
|
+
`Use 'syntaur comment <slug-or-uuid> "body" --type question|note|feedback' to append to comments.md \u2014 never edit it directly.`,
|
|
9752
|
+
`Use 'syntaur request <source> <target> "text"' to append a todo to another assignment's ## Todos.`,
|
|
9753
|
+
`Assignment folders are project-nested at ~/.syntaur/projects/<slug>/assignments/<aslug>/ or standalone at ~/.syntaur/assignments/<uuid>/ (project: null, slug display-only).`
|
|
9754
|
+
]
|
|
9755
|
+
};
|
|
9756
|
+
return JSON.stringify(config, null, 2) + "\n";
|
|
9757
|
+
}
|
|
9758
|
+
var init_opencode_config = __esm({
|
|
9759
|
+
"src/templates/opencode-config.ts"() {
|
|
9760
|
+
"use strict";
|
|
9761
|
+
}
|
|
9762
|
+
});
|
|
9763
|
+
|
|
9764
|
+
// src/templates/hermes-soul.ts
|
|
9765
|
+
function renderHermesSoul(params2) {
|
|
9766
|
+
const body = renderCodexAgents(params2);
|
|
9767
|
+
return `# SOUL -- Syntaur Protocol Operator
|
|
9768
|
+
|
|
9769
|
+
This agent follows the Syntaur protocol for multi-agent project coordination.
|
|
9770
|
+
Hermes loads this file as part of its identity / system context; treat the
|
|
9771
|
+
Write Boundary Rules and Lifecycle sections below as binding.
|
|
9772
|
+
|
|
9773
|
+
${body}`;
|
|
9774
|
+
}
|
|
9775
|
+
var init_hermes_soul = __esm({
|
|
9776
|
+
"src/templates/hermes-soul.ts"() {
|
|
9777
|
+
"use strict";
|
|
9778
|
+
init_codex_agents();
|
|
9779
|
+
}
|
|
9780
|
+
});
|
|
9781
|
+
|
|
9209
9782
|
// src/lifecycle/recompute.ts
|
|
9210
9783
|
var recompute_exports = {};
|
|
9211
9784
|
__export(recompute_exports, {
|
|
@@ -9445,20 +10018,370 @@ var LOCK_FILE, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_MAX_WAITS, CAS_RETRIES, MIGRATI
|
|
|
9445
10018
|
var init_recompute = __esm({
|
|
9446
10019
|
"src/lifecycle/recompute.ts"() {
|
|
9447
10020
|
"use strict";
|
|
9448
|
-
init_config2();
|
|
9449
|
-
init_fs();
|
|
9450
|
-
init_paths();
|
|
9451
|
-
init_timestamp();
|
|
9452
|
-
init_facts();
|
|
9453
|
-
init_derive();
|
|
9454
|
-
init_frontmatter();
|
|
9455
|
-
init_types();
|
|
9456
|
-
LOCK_FILE = ".derive.lock";
|
|
9457
|
-
LOCK_STALE_MS = 3e4;
|
|
9458
|
-
LOCK_WAIT_MS = 50;
|
|
9459
|
-
LOCK_MAX_WAITS = 100;
|
|
9460
|
-
CAS_RETRIES = 3;
|
|
9461
|
-
MIGRATION_MARKER = "derive-migrated";
|
|
10021
|
+
init_config2();
|
|
10022
|
+
init_fs();
|
|
10023
|
+
init_paths();
|
|
10024
|
+
init_timestamp();
|
|
10025
|
+
init_facts();
|
|
10026
|
+
init_derive();
|
|
10027
|
+
init_frontmatter();
|
|
10028
|
+
init_types();
|
|
10029
|
+
LOCK_FILE = ".derive.lock";
|
|
10030
|
+
LOCK_STALE_MS = 3e4;
|
|
10031
|
+
LOCK_WAIT_MS = 50;
|
|
10032
|
+
LOCK_MAX_WAITS = 100;
|
|
10033
|
+
CAS_RETRIES = 3;
|
|
10034
|
+
MIGRATION_MARKER = "derive-migrated";
|
|
10035
|
+
}
|
|
10036
|
+
});
|
|
10037
|
+
|
|
10038
|
+
// src/utils/transcript.ts
|
|
10039
|
+
import { open as open2 } from "fs/promises";
|
|
10040
|
+
async function derivePathFromTranscript(transcriptPath) {
|
|
10041
|
+
if (!transcriptPath) return null;
|
|
10042
|
+
let handle;
|
|
10043
|
+
try {
|
|
10044
|
+
handle = await open2(transcriptPath, "r");
|
|
10045
|
+
} catch {
|
|
10046
|
+
return null;
|
|
10047
|
+
}
|
|
10048
|
+
try {
|
|
10049
|
+
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
10050
|
+
let buffer = "";
|
|
10051
|
+
let scanned = 0;
|
|
10052
|
+
for await (const chunk of stream) {
|
|
10053
|
+
buffer += chunk;
|
|
10054
|
+
let nl = buffer.indexOf("\n");
|
|
10055
|
+
while (nl !== -1) {
|
|
10056
|
+
const line = buffer.slice(0, nl);
|
|
10057
|
+
buffer = buffer.slice(nl + 1);
|
|
10058
|
+
const cwd = extractCwd(line);
|
|
10059
|
+
if (cwd) {
|
|
10060
|
+
stream.destroy();
|
|
10061
|
+
return cwd;
|
|
10062
|
+
}
|
|
10063
|
+
scanned++;
|
|
10064
|
+
if (scanned >= MAX_LINES_SCANNED) {
|
|
10065
|
+
stream.destroy();
|
|
10066
|
+
return null;
|
|
10067
|
+
}
|
|
10068
|
+
nl = buffer.indexOf("\n");
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
if (buffer.length > 0) {
|
|
10072
|
+
const cwd = extractCwd(buffer);
|
|
10073
|
+
if (cwd) return cwd;
|
|
10074
|
+
}
|
|
10075
|
+
return null;
|
|
10076
|
+
} finally {
|
|
10077
|
+
await handle.close().catch(() => {
|
|
10078
|
+
});
|
|
10079
|
+
}
|
|
10080
|
+
}
|
|
10081
|
+
function extractCwd(line) {
|
|
10082
|
+
const trimmed = line.trim();
|
|
10083
|
+
if (trimmed.length === 0 || trimmed[0] !== "{") return null;
|
|
10084
|
+
try {
|
|
10085
|
+
const parsed = JSON.parse(trimmed);
|
|
10086
|
+
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
10087
|
+
return parsed.cwd;
|
|
10088
|
+
}
|
|
10089
|
+
} catch {
|
|
10090
|
+
}
|
|
10091
|
+
return null;
|
|
10092
|
+
}
|
|
10093
|
+
var MAX_LINES_SCANNED;
|
|
10094
|
+
var init_transcript = __esm({
|
|
10095
|
+
"src/utils/transcript.ts"() {
|
|
10096
|
+
"use strict";
|
|
10097
|
+
MAX_LINES_SCANNED = 50;
|
|
10098
|
+
}
|
|
10099
|
+
});
|
|
10100
|
+
|
|
10101
|
+
// src/utils/process-info.ts
|
|
10102
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
10103
|
+
function captureProcessStartedAt(pid) {
|
|
10104
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
10105
|
+
try {
|
|
10106
|
+
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
10107
|
+
encoding: "utf8",
|
|
10108
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
10109
|
+
});
|
|
10110
|
+
const trimmed = out.trim();
|
|
10111
|
+
return trimmed === "" ? null : trimmed;
|
|
10112
|
+
} catch {
|
|
10113
|
+
return null;
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
var init_process_info = __esm({
|
|
10117
|
+
"src/utils/process-info.ts"() {
|
|
10118
|
+
"use strict";
|
|
10119
|
+
}
|
|
10120
|
+
});
|
|
10121
|
+
|
|
10122
|
+
// src/usage/cwd-extractor.ts
|
|
10123
|
+
import { open as open3, readdir as readdir11, stat as stat2 } from "fs/promises";
|
|
10124
|
+
import { join as join3 } from "path";
|
|
10125
|
+
import { homedir as homedir3 } from "os";
|
|
10126
|
+
async function extractClaudeSessionMeta(jsonlPath) {
|
|
10127
|
+
const cwd = await derivePathFromTranscript(jsonlPath);
|
|
10128
|
+
if (!cwd) return null;
|
|
10129
|
+
const basename6 = jsonlPath.split("/").pop() ?? "";
|
|
10130
|
+
const sessionId = basename6.replace(/\.jsonl$/, "");
|
|
10131
|
+
if (!sessionId) return null;
|
|
10132
|
+
const startTs = await readFirstTimestamp(jsonlPath);
|
|
10133
|
+
const endTs = await readLastTimestamp(jsonlPath);
|
|
10134
|
+
return {
|
|
10135
|
+
tool: "claude",
|
|
10136
|
+
sessionId,
|
|
10137
|
+
cwd,
|
|
10138
|
+
startTs,
|
|
10139
|
+
endTs,
|
|
10140
|
+
path: jsonlPath
|
|
10141
|
+
};
|
|
10142
|
+
}
|
|
10143
|
+
async function extractCodexSessionMeta(jsonlPath) {
|
|
10144
|
+
let handle;
|
|
10145
|
+
try {
|
|
10146
|
+
handle = await open3(jsonlPath, "r");
|
|
10147
|
+
} catch {
|
|
10148
|
+
return null;
|
|
10149
|
+
}
|
|
10150
|
+
try {
|
|
10151
|
+
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
10152
|
+
let buffer = "";
|
|
10153
|
+
let firstLine = null;
|
|
10154
|
+
for await (const chunk of stream) {
|
|
10155
|
+
buffer += chunk;
|
|
10156
|
+
const nl = buffer.indexOf("\n");
|
|
10157
|
+
if (nl !== -1) {
|
|
10158
|
+
firstLine = buffer.slice(0, nl);
|
|
10159
|
+
stream.destroy();
|
|
10160
|
+
break;
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
if (!firstLine && buffer.length > 0) firstLine = buffer;
|
|
10164
|
+
if (!firstLine) return null;
|
|
10165
|
+
let parsed;
|
|
10166
|
+
try {
|
|
10167
|
+
parsed = JSON.parse(firstLine);
|
|
10168
|
+
} catch {
|
|
10169
|
+
return null;
|
|
10170
|
+
}
|
|
10171
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
10172
|
+
const obj = parsed;
|
|
10173
|
+
if (obj.type !== "session_meta") return null;
|
|
10174
|
+
const timestamp = typeof obj.timestamp === "string" ? obj.timestamp : null;
|
|
10175
|
+
const payload = obj.payload;
|
|
10176
|
+
const id = payload && typeof payload.id === "string" ? payload.id : null;
|
|
10177
|
+
const cwd = payload && typeof payload.cwd === "string" ? payload.cwd : null;
|
|
10178
|
+
if (!timestamp || !id || !cwd) return null;
|
|
10179
|
+
const endTs = await readLastTimestamp(jsonlPath) ?? timestamp;
|
|
10180
|
+
return {
|
|
10181
|
+
tool: "codex",
|
|
10182
|
+
sessionId: id,
|
|
10183
|
+
cwd,
|
|
10184
|
+
startTs: timestamp,
|
|
10185
|
+
endTs,
|
|
10186
|
+
path: jsonlPath
|
|
10187
|
+
};
|
|
10188
|
+
} finally {
|
|
10189
|
+
await handle.close().catch(() => {
|
|
10190
|
+
});
|
|
10191
|
+
}
|
|
10192
|
+
}
|
|
10193
|
+
async function* walkClaudeProjects(opts = {}) {
|
|
10194
|
+
const root = expandHome(opts.root ?? "~/.claude/projects");
|
|
10195
|
+
const dirs = await listDirSafe(root);
|
|
10196
|
+
for (const dirent of dirs) {
|
|
10197
|
+
if (!dirent.isDirectory) continue;
|
|
10198
|
+
const dirPath = join3(root, dirent.name);
|
|
10199
|
+
const files = await listDirSafe(dirPath);
|
|
10200
|
+
let cachedCwd = null;
|
|
10201
|
+
for (const f of files) {
|
|
10202
|
+
if (!f.isFile || !f.name.endsWith(".jsonl")) continue;
|
|
10203
|
+
const filePath = join3(dirPath, f.name);
|
|
10204
|
+
if (opts.sinceMtimeMs !== void 0) {
|
|
10205
|
+
const mtime = await mtimeMs(filePath);
|
|
10206
|
+
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
10207
|
+
}
|
|
10208
|
+
let meta;
|
|
10209
|
+
if (cachedCwd) {
|
|
10210
|
+
const sessionId = f.name.replace(/\.jsonl$/, "");
|
|
10211
|
+
const startTs = await readFirstTimestamp(filePath);
|
|
10212
|
+
const endTs = await readLastTimestamp(filePath);
|
|
10213
|
+
meta = { tool: "claude", sessionId, cwd: cachedCwd, startTs, endTs, path: filePath };
|
|
10214
|
+
} else {
|
|
10215
|
+
meta = await extractClaudeSessionMeta(filePath);
|
|
10216
|
+
if (meta) cachedCwd = meta.cwd;
|
|
10217
|
+
}
|
|
10218
|
+
if (meta) yield meta;
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
}
|
|
10222
|
+
async function* walkCodexSessions(opts = {}) {
|
|
10223
|
+
const root = resolveCodexSessionsRoot(opts.root);
|
|
10224
|
+
for await (const filePath of walkJsonlRecursive(root)) {
|
|
10225
|
+
const basename6 = filePath.split("/").pop() ?? "";
|
|
10226
|
+
if (!basename6.endsWith(".jsonl")) continue;
|
|
10227
|
+
if (opts.sinceMtimeMs !== void 0) {
|
|
10228
|
+
const mtime = await mtimeMs(filePath);
|
|
10229
|
+
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
10230
|
+
}
|
|
10231
|
+
const meta = await extractCodexSessionMeta(filePath);
|
|
10232
|
+
if (meta) yield meta;
|
|
10233
|
+
}
|
|
10234
|
+
}
|
|
10235
|
+
function resolveCodexSessionsRoot(override) {
|
|
10236
|
+
if (override) return expandHome(override);
|
|
10237
|
+
const fromSessionsEnv = process.env.CODEX_SESSIONS_DIR;
|
|
10238
|
+
if (fromSessionsEnv && fromSessionsEnv.length > 0) return expandHome(fromSessionsEnv);
|
|
10239
|
+
const fromHomeEnv = process.env.CODEX_HOME;
|
|
10240
|
+
if (fromHomeEnv && fromHomeEnv.length > 0) return join3(expandHome(fromHomeEnv), "sessions");
|
|
10241
|
+
return join3(homedir3(), ".codex", "sessions");
|
|
10242
|
+
}
|
|
10243
|
+
async function listDirSafe(path) {
|
|
10244
|
+
try {
|
|
10245
|
+
const entries = await readdir11(path, { withFileTypes: true });
|
|
10246
|
+
return entries.map((e) => ({
|
|
10247
|
+
name: e.name,
|
|
10248
|
+
isFile: e.isFile(),
|
|
10249
|
+
isDirectory: e.isDirectory()
|
|
10250
|
+
}));
|
|
10251
|
+
} catch {
|
|
10252
|
+
return [];
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
async function* walkJsonlRecursive(root) {
|
|
10256
|
+
const stack = [root];
|
|
10257
|
+
while (stack.length > 0) {
|
|
10258
|
+
const current = stack.pop();
|
|
10259
|
+
const entries = await listDirSafe(current);
|
|
10260
|
+
for (const e of entries) {
|
|
10261
|
+
const full = join3(current, e.name);
|
|
10262
|
+
if (e.isDirectory) {
|
|
10263
|
+
stack.push(full);
|
|
10264
|
+
} else if (e.isFile && e.name.endsWith(".jsonl")) {
|
|
10265
|
+
yield full;
|
|
10266
|
+
}
|
|
10267
|
+
}
|
|
10268
|
+
}
|
|
10269
|
+
}
|
|
10270
|
+
async function mtimeMs(path) {
|
|
10271
|
+
try {
|
|
10272
|
+
const s = await stat2(path);
|
|
10273
|
+
return s.mtimeMs;
|
|
10274
|
+
} catch {
|
|
10275
|
+
return null;
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10278
|
+
async function readFirstTimestamp(path) {
|
|
10279
|
+
let handle;
|
|
10280
|
+
try {
|
|
10281
|
+
handle = await open3(path, "r");
|
|
10282
|
+
} catch {
|
|
10283
|
+
return null;
|
|
10284
|
+
}
|
|
10285
|
+
try {
|
|
10286
|
+
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
10287
|
+
let buffer = "";
|
|
10288
|
+
let scanned = 0;
|
|
10289
|
+
for await (const chunk of stream) {
|
|
10290
|
+
buffer += chunk;
|
|
10291
|
+
let nl = buffer.indexOf("\n");
|
|
10292
|
+
while (nl !== -1) {
|
|
10293
|
+
const line = buffer.slice(0, nl);
|
|
10294
|
+
buffer = buffer.slice(nl + 1);
|
|
10295
|
+
const ts = extractTimestamp(line);
|
|
10296
|
+
if (ts) {
|
|
10297
|
+
stream.destroy();
|
|
10298
|
+
return ts;
|
|
10299
|
+
}
|
|
10300
|
+
scanned++;
|
|
10301
|
+
if (scanned >= SCAN_LINE_CAP) {
|
|
10302
|
+
stream.destroy();
|
|
10303
|
+
return null;
|
|
10304
|
+
}
|
|
10305
|
+
nl = buffer.indexOf("\n");
|
|
10306
|
+
}
|
|
10307
|
+
}
|
|
10308
|
+
if (buffer.length > 0) return extractTimestamp(buffer);
|
|
10309
|
+
return null;
|
|
10310
|
+
} finally {
|
|
10311
|
+
await handle.close().catch(() => {
|
|
10312
|
+
});
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
async function readLastTimestamp(path) {
|
|
10316
|
+
let handle;
|
|
10317
|
+
try {
|
|
10318
|
+
handle = await open3(path, "r");
|
|
10319
|
+
} catch {
|
|
10320
|
+
return null;
|
|
10321
|
+
}
|
|
10322
|
+
try {
|
|
10323
|
+
const stats = await handle.stat();
|
|
10324
|
+
const size = stats.size;
|
|
10325
|
+
if (size === 0) return null;
|
|
10326
|
+
for (const windowBytes of [TAIL_READ_BYTES, TAIL_READ_BYTES_MAX]) {
|
|
10327
|
+
const start = Math.max(0, size - windowBytes);
|
|
10328
|
+
const length = size - start;
|
|
10329
|
+
const buf = Buffer.alloc(length);
|
|
10330
|
+
await handle.read(buf, 0, length, start);
|
|
10331
|
+
const text = buf.toString("utf-8");
|
|
10332
|
+
const lines = text.split("\n");
|
|
10333
|
+
if (start > 0) lines.shift();
|
|
10334
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
10335
|
+
const ts = extractTimestamp(lines[i]);
|
|
10336
|
+
if (ts) return ts;
|
|
10337
|
+
}
|
|
10338
|
+
if (start === 0) break;
|
|
10339
|
+
}
|
|
10340
|
+
return null;
|
|
10341
|
+
} finally {
|
|
10342
|
+
await handle.close().catch(() => {
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
function extractTimestamp(line) {
|
|
10347
|
+
const trimmed = line.trim();
|
|
10348
|
+
if (trimmed.length === 0 || trimmed[0] !== "{") return null;
|
|
10349
|
+
try {
|
|
10350
|
+
const parsed = JSON.parse(trimmed);
|
|
10351
|
+
if (typeof parsed.timestamp === "string" && parsed.timestamp.length > 0) {
|
|
10352
|
+
return parsed.timestamp;
|
|
10353
|
+
}
|
|
10354
|
+
} catch {
|
|
10355
|
+
}
|
|
10356
|
+
return null;
|
|
10357
|
+
}
|
|
10358
|
+
var SCAN_LINE_CAP, TAIL_READ_BYTES, TAIL_READ_BYTES_MAX;
|
|
10359
|
+
var init_cwd_extractor = __esm({
|
|
10360
|
+
"src/usage/cwd-extractor.ts"() {
|
|
10361
|
+
"use strict";
|
|
10362
|
+
init_paths();
|
|
10363
|
+
init_transcript();
|
|
10364
|
+
SCAN_LINE_CAP = 50;
|
|
10365
|
+
TAIL_READ_BYTES = 8 * 1024;
|
|
10366
|
+
TAIL_READ_BYTES_MAX = 64 * 1024;
|
|
10367
|
+
}
|
|
10368
|
+
});
|
|
10369
|
+
|
|
10370
|
+
// src/utils/session-id.ts
|
|
10371
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
10372
|
+
import { mkdirSync, readFileSync, statSync as statSync3, writeFileSync } from "fs";
|
|
10373
|
+
import { homedir as homedir4 } from "os";
|
|
10374
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
10375
|
+
function isSafeSessionId(value) {
|
|
10376
|
+
return typeof value === "string" && value.length > 0 && value.length <= 256 && SAFE_SESSION_ID.test(value);
|
|
10377
|
+
}
|
|
10378
|
+
var SAFE_SESSION_ID;
|
|
10379
|
+
var init_session_id = __esm({
|
|
10380
|
+
"src/utils/session-id.ts"() {
|
|
10381
|
+
"use strict";
|
|
10382
|
+
init_process_info();
|
|
10383
|
+
init_cwd_extractor();
|
|
10384
|
+
SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
9462
10385
|
}
|
|
9463
10386
|
});
|
|
9464
10387
|
|
|
@@ -9509,6 +10432,420 @@ var init_assignment_todos = __esm({
|
|
|
9509
10432
|
}
|
|
9510
10433
|
});
|
|
9511
10434
|
|
|
10435
|
+
// src/targets/renderers.ts
|
|
10436
|
+
var RENDERERS;
|
|
10437
|
+
var init_renderers = __esm({
|
|
10438
|
+
"src/targets/renderers.ts"() {
|
|
10439
|
+
"use strict";
|
|
10440
|
+
init_cursor_rules();
|
|
10441
|
+
init_codex_agents();
|
|
10442
|
+
init_opencode_config();
|
|
10443
|
+
init_hermes_soul();
|
|
10444
|
+
RENDERERS = {
|
|
10445
|
+
codexAgents: (ctx) => renderCodexAgents(ctx),
|
|
10446
|
+
cursorProtocol: () => renderCursorProtocol(),
|
|
10447
|
+
cursorAssignment: (ctx) => renderCursorAssignment(ctx),
|
|
10448
|
+
openCodeConfig: (ctx) => renderOpenCodeConfig({ projectDir: ctx.projectDir }),
|
|
10449
|
+
hermesSoul: (ctx) => renderHermesSoul(ctx)
|
|
10450
|
+
};
|
|
10451
|
+
}
|
|
10452
|
+
});
|
|
10453
|
+
|
|
10454
|
+
// src/targets/user-descriptors.ts
|
|
10455
|
+
import { resolve as resolve34 } from "path";
|
|
10456
|
+
import { readFile as readFile23, readdir as readdir16 } from "fs/promises";
|
|
10457
|
+
var VALID_RENDERER_KEYS;
|
|
10458
|
+
var init_user_descriptors = __esm({
|
|
10459
|
+
"src/targets/user-descriptors.ts"() {
|
|
10460
|
+
"use strict";
|
|
10461
|
+
init_fs();
|
|
10462
|
+
init_paths();
|
|
10463
|
+
init_renderers();
|
|
10464
|
+
VALID_RENDERER_KEYS = new Set(Object.keys(RENDERERS));
|
|
10465
|
+
}
|
|
10466
|
+
});
|
|
10467
|
+
|
|
10468
|
+
// src/targets/registry.ts
|
|
10469
|
+
import { homedir as homedir6 } from "os";
|
|
10470
|
+
import { join as join8, resolve as resolve35 } from "path";
|
|
10471
|
+
function home(...segments) {
|
|
10472
|
+
return resolve35(homedir6(), ...segments);
|
|
10473
|
+
}
|
|
10474
|
+
function hermesHome() {
|
|
10475
|
+
const env = process.env.HERMES_HOME;
|
|
10476
|
+
return env && env.length > 0 ? resolve35(env) : home(".hermes");
|
|
10477
|
+
}
|
|
10478
|
+
function hermesSkillsDir() {
|
|
10479
|
+
return resolve35(hermesHome(), "skills");
|
|
10480
|
+
}
|
|
10481
|
+
function codexHome() {
|
|
10482
|
+
const env = process.env.CODEX_HOME;
|
|
10483
|
+
return env && env.length > 0 ? resolve35(env) : home(".codex");
|
|
10484
|
+
}
|
|
10485
|
+
function toDiscovered(meta) {
|
|
10486
|
+
if (!meta) return null;
|
|
10487
|
+
return {
|
|
10488
|
+
sessionId: meta.sessionId,
|
|
10489
|
+
cwd: meta.cwd,
|
|
10490
|
+
startedAt: meta.startTs,
|
|
10491
|
+
endedAt: meta.endTs,
|
|
10492
|
+
transcriptPath: meta.path
|
|
10493
|
+
};
|
|
10494
|
+
}
|
|
10495
|
+
var detectDir, claudeSessions, codexSessions, AGENT_TARGETS, AGENT_TARGETS_BY_ID;
|
|
10496
|
+
var init_registry = __esm({
|
|
10497
|
+
"src/targets/registry.ts"() {
|
|
10498
|
+
"use strict";
|
|
10499
|
+
init_fs();
|
|
10500
|
+
init_cwd_extractor();
|
|
10501
|
+
init_user_descriptors();
|
|
10502
|
+
detectDir = (dir) => () => fileExists(dir);
|
|
10503
|
+
claudeSessions = {
|
|
10504
|
+
globs: (root) => [join8(root ?? home(".claude", "projects"), "*", "*.jsonl")],
|
|
10505
|
+
parse: async (file) => toDiscovered(await extractClaudeSessionMeta(file)),
|
|
10506
|
+
walk: async function* (opts = {}) {
|
|
10507
|
+
for await (const meta of walkClaudeProjects({ root: opts.root, sinceMtimeMs: opts.sinceMtimeMs })) {
|
|
10508
|
+
const d = toDiscovered(meta);
|
|
10509
|
+
if (d) yield d;
|
|
10510
|
+
}
|
|
10511
|
+
}
|
|
10512
|
+
};
|
|
10513
|
+
codexSessions = {
|
|
10514
|
+
globs: (root) => [join8(root ?? resolveCodexSessionsRoot(), "**", "*.jsonl")],
|
|
10515
|
+
parse: async (file) => toDiscovered(await extractCodexSessionMeta(file)),
|
|
10516
|
+
walk: async function* (opts = {}) {
|
|
10517
|
+
for await (const meta of walkCodexSessions({ root: opts.root, sinceMtimeMs: opts.sinceMtimeMs })) {
|
|
10518
|
+
const d = toDiscovered(meta);
|
|
10519
|
+
if (d) yield d;
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
};
|
|
10523
|
+
AGENT_TARGETS = [
|
|
10524
|
+
{
|
|
10525
|
+
id: "cursor",
|
|
10526
|
+
displayName: "Cursor",
|
|
10527
|
+
skillsShAgentId: "cursor",
|
|
10528
|
+
detect: detectDir(home(".cursor")),
|
|
10529
|
+
skillsDir: { global: home(".cursor", "skills") },
|
|
10530
|
+
instructions: {
|
|
10531
|
+
files: [
|
|
10532
|
+
{ path: ".cursor/rules/syntaur-protocol.mdc", renderer: "cursorProtocol" },
|
|
10533
|
+
{ path: ".cursor/rules/syntaur-assignment.mdc", renderer: "cursorAssignment" }
|
|
10534
|
+
]
|
|
10535
|
+
}
|
|
10536
|
+
},
|
|
10537
|
+
{
|
|
10538
|
+
// codex is BOTH an adapter (writes AGENTS.md) AND a native plugin.
|
|
10539
|
+
id: "codex",
|
|
10540
|
+
displayName: "Codex",
|
|
10541
|
+
skillsShAgentId: "codex",
|
|
10542
|
+
nativePlugin: "codex",
|
|
10543
|
+
detect: detectDir(codexHome()),
|
|
10544
|
+
skillsDir: { global: resolve35(codexHome(), "skills") },
|
|
10545
|
+
instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] },
|
|
10546
|
+
sessions: codexSessions
|
|
10547
|
+
},
|
|
10548
|
+
{
|
|
10549
|
+
id: "opencode",
|
|
10550
|
+
displayName: "OpenCode",
|
|
10551
|
+
skillsShAgentId: "opencode",
|
|
10552
|
+
detect: detectDir(home(".config", "opencode")),
|
|
10553
|
+
skillsDir: { global: home(".config", "opencode", "skills") },
|
|
10554
|
+
instructions: {
|
|
10555
|
+
files: [
|
|
10556
|
+
{ path: "AGENTS.md", renderer: "codexAgents" },
|
|
10557
|
+
{ path: "opencode.json", renderer: "openCodeConfig" }
|
|
10558
|
+
]
|
|
10559
|
+
}
|
|
10560
|
+
},
|
|
10561
|
+
{
|
|
10562
|
+
// claude has NO adapter today (not in the old SUPPORTED_FRAMEWORKS) — the
|
|
10563
|
+
// full plugin path owns its skills/hooks/commands. Native-plugin only.
|
|
10564
|
+
id: "claude",
|
|
10565
|
+
displayName: "Claude Code",
|
|
10566
|
+
skillsShAgentId: "claude-code",
|
|
10567
|
+
nativePlugin: "claude",
|
|
10568
|
+
detect: detectDir(home(".claude")),
|
|
10569
|
+
skillsDir: { global: home(".claude", "skills") },
|
|
10570
|
+
sessions: claudeSessions
|
|
10571
|
+
},
|
|
10572
|
+
{
|
|
10573
|
+
id: "pi",
|
|
10574
|
+
displayName: "Pi",
|
|
10575
|
+
skillsShAgentId: "pi",
|
|
10576
|
+
detect: detectDir(home(".pi")),
|
|
10577
|
+
skillsDir: { global: home(".pi", "agent", "skills") },
|
|
10578
|
+
instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] },
|
|
10579
|
+
tier3: {
|
|
10580
|
+
kind: "pi-extension",
|
|
10581
|
+
source: "platforms/pi/extensions/syntaur",
|
|
10582
|
+
installDir: () => home(".pi", "agent", "extensions", "syntaur"),
|
|
10583
|
+
entry: "index.ts"
|
|
10584
|
+
}
|
|
10585
|
+
},
|
|
10586
|
+
{
|
|
10587
|
+
id: "openclaw",
|
|
10588
|
+
displayName: "OpenClaw",
|
|
10589
|
+
skillsShAgentId: "openclaw",
|
|
10590
|
+
detect: detectDir(home(".openclaw")),
|
|
10591
|
+
skillsDir: { global: home(".openclaw", "skills") },
|
|
10592
|
+
instructions: { files: [{ path: "AGENTS.md", renderer: "codexAgents" }] },
|
|
10593
|
+
// OpenClaw runs on pi-coding-agent (design memo), so it reuses the pi
|
|
10594
|
+
// extension SOURCE; only the install dir differs.
|
|
10595
|
+
tier3: {
|
|
10596
|
+
kind: "pi-extension",
|
|
10597
|
+
source: "platforms/pi/extensions/syntaur",
|
|
10598
|
+
installDir: () => home(".openclaw", "extensions", "syntaur"),
|
|
10599
|
+
entry: "index.ts"
|
|
10600
|
+
}
|
|
10601
|
+
},
|
|
10602
|
+
{
|
|
10603
|
+
id: "hermes",
|
|
10604
|
+
displayName: "Hermes Agent",
|
|
10605
|
+
skillsShAgentId: "hermes-agent",
|
|
10606
|
+
detect: () => fileExists(hermesHome()),
|
|
10607
|
+
skillsDir: { global: hermesSkillsDir() },
|
|
10608
|
+
instructions: { files: [{ path: "SOUL.md", renderer: "hermesSoul" }] },
|
|
10609
|
+
tier3: {
|
|
10610
|
+
kind: "hermes-plugin",
|
|
10611
|
+
source: "platforms/hermes/plugins/syntaur",
|
|
10612
|
+
installDir: () => resolve35(hermesHome(), "plugins", "syntaur"),
|
|
10613
|
+
entry: "plugin.yaml"
|
|
10614
|
+
}
|
|
10615
|
+
}
|
|
10616
|
+
];
|
|
10617
|
+
AGENT_TARGETS_BY_ID = Object.fromEntries(
|
|
10618
|
+
AGENT_TARGETS.map((t) => [t.id, t])
|
|
10619
|
+
);
|
|
10620
|
+
}
|
|
10621
|
+
});
|
|
10622
|
+
|
|
10623
|
+
// src/sessions/scanner.ts
|
|
10624
|
+
var scanner_exports2 = {};
|
|
10625
|
+
__export(scanner_exports2, {
|
|
10626
|
+
scanSessions: () => scanSessions
|
|
10627
|
+
});
|
|
10628
|
+
import { execFile as execFile3, execFileSync as execFileSync4 } from "child_process";
|
|
10629
|
+
import { promisify as promisify3 } from "util";
|
|
10630
|
+
import { statSync as statSync4 } from "fs";
|
|
10631
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
10632
|
+
import { resolve as resolve36 } from "path";
|
|
10633
|
+
function emptySummary() {
|
|
10634
|
+
return { discovered: 0, inserted: 0, revived: 0, swept: 0, skipped: 0, changed: false };
|
|
10635
|
+
}
|
|
10636
|
+
function defaultStatMtimeMs(path) {
|
|
10637
|
+
try {
|
|
10638
|
+
return statSync4(path).mtimeMs;
|
|
10639
|
+
} catch {
|
|
10640
|
+
return null;
|
|
10641
|
+
}
|
|
10642
|
+
}
|
|
10643
|
+
function defaultIsPidAlive(pid) {
|
|
10644
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
10645
|
+
try {
|
|
10646
|
+
process.kill(pid, 0);
|
|
10647
|
+
return true;
|
|
10648
|
+
} catch (err) {
|
|
10649
|
+
return err.code === "EPERM";
|
|
10650
|
+
}
|
|
10651
|
+
}
|
|
10652
|
+
function defaultPidStartedAt(pid) {
|
|
10653
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
10654
|
+
try {
|
|
10655
|
+
const out = execFileSync4("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
10656
|
+
encoding: "utf8",
|
|
10657
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
10658
|
+
});
|
|
10659
|
+
const trimmed = out.trim();
|
|
10660
|
+
return trimmed === "" ? null : trimmed;
|
|
10661
|
+
} catch {
|
|
10662
|
+
return null;
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
async function defaultOpenFiles(files) {
|
|
10666
|
+
const open5 = /* @__PURE__ */ new Set();
|
|
10667
|
+
for (let i = 0; i < files.length; i += LSOF_CHUNK) {
|
|
10668
|
+
const chunk = files.slice(i, i + LSOF_CHUNK);
|
|
10669
|
+
let stdout = "";
|
|
10670
|
+
try {
|
|
10671
|
+
const result = await execFileAsync("lsof", ["-Fn", "--", ...chunk], {
|
|
10672
|
+
maxBuffer: 8 * 1024 * 1024
|
|
10673
|
+
});
|
|
10674
|
+
stdout = result.stdout;
|
|
10675
|
+
} catch (err) {
|
|
10676
|
+
const maybe = err.stdout;
|
|
10677
|
+
stdout = typeof maybe === "string" ? maybe : "";
|
|
10678
|
+
}
|
|
10679
|
+
for (const line of stdout.split("\n")) {
|
|
10680
|
+
if (line.startsWith("n") && line.length > 1) open5.add(line.slice(1));
|
|
10681
|
+
}
|
|
10682
|
+
}
|
|
10683
|
+
return open5;
|
|
10684
|
+
}
|
|
10685
|
+
async function readContextLink(cwd, cache2) {
|
|
10686
|
+
if (cache2.has(cwd)) return cache2.get(cwd);
|
|
10687
|
+
let link = null;
|
|
10688
|
+
const path = resolve36(cwd, ".syntaur", "context.json");
|
|
10689
|
+
if (await fileExists(path)) {
|
|
10690
|
+
try {
|
|
10691
|
+
const parsed = JSON.parse(await readFile24(path, "utf-8"));
|
|
10692
|
+
link = {
|
|
10693
|
+
projectSlug: typeof parsed.projectSlug === "string" ? parsed.projectSlug : null,
|
|
10694
|
+
assignmentSlug: typeof parsed.assignmentSlug === "string" ? parsed.assignmentSlug : null
|
|
10695
|
+
};
|
|
10696
|
+
} catch {
|
|
10697
|
+
link = { projectSlug: null, assignmentSlug: null };
|
|
10698
|
+
}
|
|
10699
|
+
}
|
|
10700
|
+
cache2.set(cwd, link);
|
|
10701
|
+
return link;
|
|
10702
|
+
}
|
|
10703
|
+
function readWatermark() {
|
|
10704
|
+
const db4 = getSessionDb();
|
|
10705
|
+
const row = db4.prepare("SELECT value FROM meta WHERE key = ?").get(WATERMARK_KEY);
|
|
10706
|
+
if (!row) return null;
|
|
10707
|
+
const parsed = Number.parseInt(row.value, 10);
|
|
10708
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
10709
|
+
}
|
|
10710
|
+
function writeWatermark(ms) {
|
|
10711
|
+
const db4 = getSessionDb();
|
|
10712
|
+
db4.prepare(
|
|
10713
|
+
"INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
10714
|
+
).run(WATERMARK_KEY, String(ms));
|
|
10715
|
+
}
|
|
10716
|
+
async function scanSessions(opts = {}, deps = {}) {
|
|
10717
|
+
const summary = emptySummary();
|
|
10718
|
+
const autoTrack = deps.autoTrack ?? (await readConfig()).session.autoTrack;
|
|
10719
|
+
if (autoTrack === "off") return summary;
|
|
10720
|
+
const now = deps.now ?? (() => Date.now());
|
|
10721
|
+
const statMtimeMs = deps.statMtimeMs ?? defaultStatMtimeMs;
|
|
10722
|
+
const openFiles = deps.openFiles ?? defaultOpenFiles;
|
|
10723
|
+
const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
|
|
10724
|
+
const pidStartedAt = deps.pidStartedAt ?? defaultPidStartedAt;
|
|
10725
|
+
const targets = (deps.targets ?? AGENT_TARGETS).filter((t) => t.sessions !== void 0);
|
|
10726
|
+
const scanStartMs = now();
|
|
10727
|
+
const watermark = opts.full ? null : readWatermark();
|
|
10728
|
+
const discovered = [];
|
|
10729
|
+
for (const target of targets) {
|
|
10730
|
+
const walk = target.sessions.walk({
|
|
10731
|
+
root: deps.roots?.[target.id],
|
|
10732
|
+
sinceMtimeMs: watermark ?? void 0
|
|
10733
|
+
});
|
|
10734
|
+
for await (const session of walk) {
|
|
10735
|
+
if (!isSafeSessionId(session.sessionId)) continue;
|
|
10736
|
+
discovered.push({ ...session, agent: target.id });
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
summary.discovered = discovered.length;
|
|
10740
|
+
const openSet = await openFiles(discovered.map((d) => d.transcriptPath));
|
|
10741
|
+
const contextCache = /* @__PURE__ */ new Map();
|
|
10742
|
+
for (const d of discovered) {
|
|
10743
|
+
const link = await readContextLink(d.cwd, contextCache);
|
|
10744
|
+
if (autoTrack === "workspaces-only" && link === null) {
|
|
10745
|
+
summary.skipped += 1;
|
|
10746
|
+
continue;
|
|
10747
|
+
}
|
|
10748
|
+
const mtime = statMtimeMs(d.transcriptPath);
|
|
10749
|
+
const heldOpen = openSet.has(d.transcriptPath);
|
|
10750
|
+
const isLive = heldOpen || mtime !== null && now() - mtime < FRESH_MTIME_MS;
|
|
10751
|
+
const prev = getSessionById(d.sessionId);
|
|
10752
|
+
const status = isLive ? "active" : prev?.status ?? "stopped";
|
|
10753
|
+
const started = d.startedAt ?? (mtime !== null ? new Date(mtime).toISOString() : new Date(now()).toISOString());
|
|
10754
|
+
await appendSession(
|
|
10755
|
+
"",
|
|
10756
|
+
{
|
|
10757
|
+
sessionId: d.sessionId,
|
|
10758
|
+
projectSlug: link?.projectSlug ?? null,
|
|
10759
|
+
assignmentSlug: link?.assignmentSlug ?? null,
|
|
10760
|
+
agent: d.agent,
|
|
10761
|
+
started,
|
|
10762
|
+
status,
|
|
10763
|
+
path: d.cwd,
|
|
10764
|
+
description: null,
|
|
10765
|
+
transcriptPath: d.transcriptPath,
|
|
10766
|
+
pid: null,
|
|
10767
|
+
pidStartedAt: null,
|
|
10768
|
+
originalHeadSha: null
|
|
10769
|
+
},
|
|
10770
|
+
// Narrow revival rule: only LIVE-PROCESS evidence (a process holding the
|
|
10771
|
+
// transcript open) may flip a stopped row back to active. mtime freshness
|
|
10772
|
+
// alone must not — a session stopped moments ago by its SessionEnd hook
|
|
10773
|
+
// still has a fresh transcript for up to 5 minutes and would flap back to
|
|
10774
|
+
// active. `completed` always sticks (appendSession enforces).
|
|
10775
|
+
{ reviveStopped: heldOpen }
|
|
10776
|
+
);
|
|
10777
|
+
if (!isLive) {
|
|
10778
|
+
const after = getSessionById(d.sessionId);
|
|
10779
|
+
if (after && after.status === "stopped" && !after.ended) {
|
|
10780
|
+
const endedAt = d.endedAt ?? (mtime !== null ? new Date(mtime).toISOString() : void 0);
|
|
10781
|
+
await updateSessionStatus("", d.sessionId, "stopped", endedAt);
|
|
10782
|
+
}
|
|
10783
|
+
}
|
|
10784
|
+
if (!prev) {
|
|
10785
|
+
summary.inserted += 1;
|
|
10786
|
+
summary.changed = true;
|
|
10787
|
+
} else {
|
|
10788
|
+
if (prev.status === "stopped" && heldOpen) {
|
|
10789
|
+
summary.revived += 1;
|
|
10790
|
+
summary.changed = true;
|
|
10791
|
+
}
|
|
10792
|
+
if (link?.projectSlug && !prev.projectSlug || link?.assignmentSlug && !prev.assignmentSlug) {
|
|
10793
|
+
summary.changed = true;
|
|
10794
|
+
}
|
|
10795
|
+
}
|
|
10796
|
+
}
|
|
10797
|
+
const db4 = getSessionDb();
|
|
10798
|
+
const activeRows = db4.prepare("SELECT session_id, pid, pid_started_at, transcript_path FROM sessions WHERE status = 'active'").all();
|
|
10799
|
+
const sweepCandidates = [];
|
|
10800
|
+
for (const row of activeRows) {
|
|
10801
|
+
if (row.pid !== null) {
|
|
10802
|
+
const alive = isPidAlive(row.pid) && (!row.pid_started_at || (pidStartedAt(row.pid) ?? row.pid_started_at) === row.pid_started_at);
|
|
10803
|
+
if (alive) continue;
|
|
10804
|
+
}
|
|
10805
|
+
if (row.transcript_path) {
|
|
10806
|
+
sweepCandidates.push({ sessionId: row.session_id, transcriptPath: row.transcript_path });
|
|
10807
|
+
} else if (row.pid !== null) {
|
|
10808
|
+
sweepCandidates.push({ sessionId: row.session_id, transcriptPath: null });
|
|
10809
|
+
}
|
|
10810
|
+
}
|
|
10811
|
+
const sweepOpenSet = await openFiles(
|
|
10812
|
+
sweepCandidates.map((c) => c.transcriptPath).filter((p) => p !== null)
|
|
10813
|
+
);
|
|
10814
|
+
for (const candidate of sweepCandidates) {
|
|
10815
|
+
if (candidate.transcriptPath) {
|
|
10816
|
+
if (sweepOpenSet.has(candidate.transcriptPath)) continue;
|
|
10817
|
+
const mtime = statMtimeMs(candidate.transcriptPath);
|
|
10818
|
+
if (mtime !== null && now() - mtime < FRESH_MTIME_MS) continue;
|
|
10819
|
+
const endedAt = mtime !== null ? new Date(mtime).toISOString() : void 0;
|
|
10820
|
+
if (await updateSessionStatus("", candidate.sessionId, "stopped", endedAt)) {
|
|
10821
|
+
summary.swept += 1;
|
|
10822
|
+
summary.changed = true;
|
|
10823
|
+
}
|
|
10824
|
+
} else if (await updateSessionStatus("", candidate.sessionId, "stopped")) {
|
|
10825
|
+
summary.swept += 1;
|
|
10826
|
+
summary.changed = true;
|
|
10827
|
+
}
|
|
10828
|
+
}
|
|
10829
|
+
writeWatermark(scanStartMs);
|
|
10830
|
+
return summary;
|
|
10831
|
+
}
|
|
10832
|
+
var execFileAsync, FRESH_MTIME_MS, LSOF_CHUNK, WATERMARK_KEY;
|
|
10833
|
+
var init_scanner2 = __esm({
|
|
10834
|
+
"src/sessions/scanner.ts"() {
|
|
10835
|
+
"use strict";
|
|
10836
|
+
init_fs();
|
|
10837
|
+
init_config2();
|
|
10838
|
+
init_session_id();
|
|
10839
|
+
init_registry();
|
|
10840
|
+
init_session_db();
|
|
10841
|
+
init_agent_sessions();
|
|
10842
|
+
execFileAsync = promisify3(execFile3);
|
|
10843
|
+
FRESH_MTIME_MS = 5 * 60 * 1e3;
|
|
10844
|
+
LSOF_CHUNK = 64;
|
|
10845
|
+
WATERMARK_KEY = "sessions_scan_last_ms";
|
|
10846
|
+
}
|
|
10847
|
+
});
|
|
10848
|
+
|
|
9512
10849
|
// src/dashboard/server.ts
|
|
9513
10850
|
init_paths();
|
|
9514
10851
|
init_api();
|
|
@@ -9516,7 +10853,7 @@ init_assignment_resolver();
|
|
|
9516
10853
|
init_agent_sessions();
|
|
9517
10854
|
import express from "express";
|
|
9518
10855
|
import { createServer } from "http";
|
|
9519
|
-
import { resolve as
|
|
10856
|
+
import { resolve as resolve37 } from "path";
|
|
9520
10857
|
import { writeFile as writeFile8, unlink as unlink8 } from "fs/promises";
|
|
9521
10858
|
import { WebSocketServer, WebSocket } from "ws";
|
|
9522
10859
|
|
|
@@ -9811,11 +11148,9 @@ function createWatcher(options) {
|
|
|
9811
11148
|
debounceKey,
|
|
9812
11149
|
setTimeout(() => {
|
|
9813
11150
|
pendingEvents.delete(debounceKey);
|
|
9814
|
-
const
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
};
|
|
9818
|
-
onMessage(message);
|
|
11151
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11152
|
+
onMessage({ type: "leases-updated", timestamp });
|
|
11153
|
+
onMessage({ type: "agent-sessions-updated", timestamp });
|
|
9819
11154
|
}, debounceMs)
|
|
9820
11155
|
);
|
|
9821
11156
|
};
|
|
@@ -10115,6 +11450,9 @@ function isViewPrefsDefaults(file) {
|
|
|
10115
11450
|
// src/dashboard/api-saved-views.ts
|
|
10116
11451
|
import { Router } from "express";
|
|
10117
11452
|
|
|
11453
|
+
// src/utils/view-filters-query.ts
|
|
11454
|
+
init_query();
|
|
11455
|
+
|
|
10118
11456
|
// src/utils/saved-views-schema.ts
|
|
10119
11457
|
var TABLE_COLUMN_IDS = [
|
|
10120
11458
|
"title",
|
|
@@ -10208,6 +11546,7 @@ function isViewFilters(value) {
|
|
|
10208
11546
|
if (obj.activity !== void 0 && !isActivity(obj.activity)) return false;
|
|
10209
11547
|
if (obj.dateRange !== void 0 && !isDateRange(obj.dateRange)) return false;
|
|
10210
11548
|
if (obj.search !== void 0 && typeof obj.search !== "string") return false;
|
|
11549
|
+
if (obj.query !== void 0 && typeof obj.query !== "string") return false;
|
|
10211
11550
|
return true;
|
|
10212
11551
|
}
|
|
10213
11552
|
function isSavedViewConfig(value) {
|
|
@@ -10393,6 +11732,8 @@ function withTwoLocks(keyA, keyB, fn) {
|
|
|
10393
11732
|
}
|
|
10394
11733
|
|
|
10395
11734
|
// src/dashboard/api-saved-views.ts
|
|
11735
|
+
init_api();
|
|
11736
|
+
init_query();
|
|
10396
11737
|
var SAVED_VIEWS_LOCK = "sv:global";
|
|
10397
11738
|
function validateCreateBody(body) {
|
|
10398
11739
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
@@ -10479,6 +11820,15 @@ function createSavedViewsRouter() {
|
|
|
10479
11820
|
res.status(400).json({ error: result.error });
|
|
10480
11821
|
return;
|
|
10481
11822
|
}
|
|
11823
|
+
const queryStr = result.value.config.filters.query;
|
|
11824
|
+
if (typeof queryStr === "string" && queryStr.length > 0) {
|
|
11825
|
+
const statusConfig = await getStatusConfig();
|
|
11826
|
+
const queryErrors = validateQuery(queryStr, statusConfig.queryRegistry);
|
|
11827
|
+
if (queryErrors.length > 0) {
|
|
11828
|
+
res.status(400).json({ errors: queryErrors });
|
|
11829
|
+
return;
|
|
11830
|
+
}
|
|
11831
|
+
}
|
|
10482
11832
|
try {
|
|
10483
11833
|
const file = await withLock(SAVED_VIEWS_LOCK, async () => {
|
|
10484
11834
|
const current = await readSavedViewsFile();
|
|
@@ -10503,6 +11853,15 @@ function createSavedViewsRouter() {
|
|
|
10503
11853
|
res.status(400).json({ error: result.error });
|
|
10504
11854
|
return;
|
|
10505
11855
|
}
|
|
11856
|
+
const patchQueryStr = result.value.config?.filters.query;
|
|
11857
|
+
if (typeof patchQueryStr === "string" && patchQueryStr.length > 0) {
|
|
11858
|
+
const statusConfig = await getStatusConfig();
|
|
11859
|
+
const queryErrors = validateQuery(patchQueryStr, statusConfig.queryRegistry);
|
|
11860
|
+
if (queryErrors.length > 0) {
|
|
11861
|
+
res.status(400).json({ errors: queryErrors });
|
|
11862
|
+
return;
|
|
11863
|
+
}
|
|
11864
|
+
}
|
|
10506
11865
|
try {
|
|
10507
11866
|
const outcome = await withLock(SAVED_VIEWS_LOCK, async () => {
|
|
10508
11867
|
const current = await readSavedViewsFile();
|
|
@@ -11395,6 +12754,12 @@ tags: []
|
|
|
11395
12754
|
`;
|
|
11396
12755
|
}
|
|
11397
12756
|
|
|
12757
|
+
// src/templates/index.ts
|
|
12758
|
+
init_cursor_rules();
|
|
12759
|
+
init_codex_agents();
|
|
12760
|
+
init_opencode_config();
|
|
12761
|
+
init_hermes_soul();
|
|
12762
|
+
|
|
11398
12763
|
// src/dashboard/api-write.ts
|
|
11399
12764
|
init_lifecycle();
|
|
11400
12765
|
init_parser();
|
|
@@ -14119,86 +15484,11 @@ function createServersRouter(serversDir2, projectsDir, assignmentsDir2) {
|
|
|
14119
15484
|
// src/dashboard/api-agent-sessions.ts
|
|
14120
15485
|
init_agent_sessions();
|
|
14121
15486
|
init_fs();
|
|
15487
|
+
init_transcript();
|
|
14122
15488
|
import { Router as Router4 } from "express";
|
|
14123
15489
|
import { resolve as resolve21 } from "path";
|
|
14124
|
-
|
|
14125
|
-
// src/utils/transcript.ts
|
|
14126
|
-
import { open as open2 } from "fs/promises";
|
|
14127
|
-
var MAX_LINES_SCANNED = 50;
|
|
14128
|
-
async function derivePathFromTranscript(transcriptPath) {
|
|
14129
|
-
if (!transcriptPath) return null;
|
|
14130
|
-
let handle;
|
|
14131
|
-
try {
|
|
14132
|
-
handle = await open2(transcriptPath, "r");
|
|
14133
|
-
} catch {
|
|
14134
|
-
return null;
|
|
14135
|
-
}
|
|
14136
|
-
try {
|
|
14137
|
-
const stream = handle.createReadStream({ encoding: "utf-8" });
|
|
14138
|
-
let buffer = "";
|
|
14139
|
-
let scanned = 0;
|
|
14140
|
-
for await (const chunk of stream) {
|
|
14141
|
-
buffer += chunk;
|
|
14142
|
-
let nl = buffer.indexOf("\n");
|
|
14143
|
-
while (nl !== -1) {
|
|
14144
|
-
const line = buffer.slice(0, nl);
|
|
14145
|
-
buffer = buffer.slice(nl + 1);
|
|
14146
|
-
const cwd = extractCwd(line);
|
|
14147
|
-
if (cwd) {
|
|
14148
|
-
stream.destroy();
|
|
14149
|
-
return cwd;
|
|
14150
|
-
}
|
|
14151
|
-
scanned++;
|
|
14152
|
-
if (scanned >= MAX_LINES_SCANNED) {
|
|
14153
|
-
stream.destroy();
|
|
14154
|
-
return null;
|
|
14155
|
-
}
|
|
14156
|
-
nl = buffer.indexOf("\n");
|
|
14157
|
-
}
|
|
14158
|
-
}
|
|
14159
|
-
if (buffer.length > 0) {
|
|
14160
|
-
const cwd = extractCwd(buffer);
|
|
14161
|
-
if (cwd) return cwd;
|
|
14162
|
-
}
|
|
14163
|
-
return null;
|
|
14164
|
-
} finally {
|
|
14165
|
-
await handle.close().catch(() => {
|
|
14166
|
-
});
|
|
14167
|
-
}
|
|
14168
|
-
}
|
|
14169
|
-
function extractCwd(line) {
|
|
14170
|
-
const trimmed = line.trim();
|
|
14171
|
-
if (trimmed.length === 0 || trimmed[0] !== "{") return null;
|
|
14172
|
-
try {
|
|
14173
|
-
const parsed = JSON.parse(trimmed);
|
|
14174
|
-
if (typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
14175
|
-
return parsed.cwd;
|
|
14176
|
-
}
|
|
14177
|
-
} catch {
|
|
14178
|
-
}
|
|
14179
|
-
return null;
|
|
14180
|
-
}
|
|
14181
|
-
|
|
14182
|
-
// src/dashboard/api-agent-sessions.ts
|
|
14183
15490
|
init_config2();
|
|
14184
|
-
|
|
14185
|
-
// src/utils/process-info.ts
|
|
14186
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
14187
|
-
function captureProcessStartedAt(pid) {
|
|
14188
|
-
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
14189
|
-
try {
|
|
14190
|
-
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
14191
|
-
encoding: "utf8",
|
|
14192
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
14193
|
-
});
|
|
14194
|
-
const trimmed = out.trim();
|
|
14195
|
-
return trimmed === "" ? null : trimmed;
|
|
14196
|
-
} catch {
|
|
14197
|
-
return null;
|
|
14198
|
-
}
|
|
14199
|
-
}
|
|
14200
|
-
|
|
14201
|
-
// src/dashboard/api-agent-sessions.ts
|
|
15491
|
+
init_process_info();
|
|
14202
15492
|
init_git_worktree();
|
|
14203
15493
|
init_cwd();
|
|
14204
15494
|
function createAgentSessionsRouter(projectsDir, broadcast, assignmentsDir2) {
|
|
@@ -15092,13 +16382,20 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
15092
16382
|
env: process.env,
|
|
15093
16383
|
agentId: agent.id,
|
|
15094
16384
|
fallbackWarning,
|
|
15095
|
-
shellFallbackWarning
|
|
16385
|
+
shellFallbackWarning,
|
|
16386
|
+
// Resume continues the SAME session id; fork mints a new one in-agent.
|
|
16387
|
+
session: { sessionId: (input.mode ?? "resume") === "resume" ? session.sessionId : null }
|
|
15096
16388
|
};
|
|
15097
16389
|
}
|
|
15098
16390
|
|
|
15099
16391
|
// src/launch/execute.ts
|
|
15100
16392
|
import { spawn as spawn3 } from "child_process";
|
|
15101
|
-
import {
|
|
16393
|
+
import { homedir as homedir5 } from "os";
|
|
16394
|
+
import { basename as basename4, join as join5, resolve as resolve23 } from "path";
|
|
16395
|
+
init_fs();
|
|
16396
|
+
init_config2();
|
|
16397
|
+
init_session_id();
|
|
16398
|
+
init_process_info();
|
|
15102
16399
|
var CMUX_BUNDLE_ID = "com.cmuxterm.app";
|
|
15103
16400
|
var CMUX_READINESS_MAX_MS = 20 * 250;
|
|
15104
16401
|
var CMUX_LAUNCH_TIMEOUT_MS = CMUX_READINESS_MAX_MS + 3e3;
|
|
@@ -15123,8 +16420,8 @@ function buildShellCommandLine(plan) {
|
|
|
15123
16420
|
init_paths();
|
|
15124
16421
|
init_fs();
|
|
15125
16422
|
import { fileURLToPath } from "url";
|
|
15126
|
-
import { dirname as
|
|
15127
|
-
import { realpathSync, readFileSync, mkdirSync } from "fs";
|
|
16423
|
+
import { dirname as dirname6, resolve as resolve24, join as join6 } from "path";
|
|
16424
|
+
import { realpathSync, readFileSync as readFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
15128
16425
|
|
|
15129
16426
|
// src/dashboard/api-launch-preflight.ts
|
|
15130
16427
|
init_assignment_resolver();
|
|
@@ -15401,32 +16698,32 @@ import { Router as Router9 } from "express";
|
|
|
15401
16698
|
// src/utils/status-config-resolution.ts
|
|
15402
16699
|
init_frontmatter();
|
|
15403
16700
|
import { readFile as readFile18, writeFile as writeFile5, rm as rm2 } from "fs/promises";
|
|
15404
|
-
import { dirname as
|
|
16701
|
+
import { dirname as dirname7 } from "path";
|
|
15405
16702
|
|
|
15406
16703
|
// src/utils/assignment-walk.ts
|
|
15407
16704
|
init_fs();
|
|
15408
|
-
import { resolve as
|
|
15409
|
-
import { readdir as
|
|
16705
|
+
import { resolve as resolve25 } from "path";
|
|
16706
|
+
import { readdir as readdir12 } from "fs/promises";
|
|
15410
16707
|
async function listAssignmentsByProject(projectsDir, standaloneDir) {
|
|
15411
16708
|
const result = {
|
|
15412
16709
|
withAssignmentMd: [],
|
|
15413
16710
|
orphanFolders: []
|
|
15414
16711
|
};
|
|
15415
16712
|
if (await fileExists(projectsDir)) {
|
|
15416
|
-
const projects = await
|
|
16713
|
+
const projects = await readdir12(projectsDir, { withFileTypes: true });
|
|
15417
16714
|
for (const m of projects) {
|
|
15418
16715
|
if (!m.isDirectory()) continue;
|
|
15419
16716
|
if (m.name.startsWith(".") || m.name.startsWith("_")) continue;
|
|
15420
|
-
const assignmentsDir2 =
|
|
16717
|
+
const assignmentsDir2 = resolve25(projectsDir, m.name, "assignments");
|
|
15421
16718
|
if (!await fileExists(assignmentsDir2)) continue;
|
|
15422
|
-
const entries = await
|
|
16719
|
+
const entries = await readdir12(assignmentsDir2, { withFileTypes: true });
|
|
15423
16720
|
for (const a of entries) {
|
|
15424
16721
|
if (!a.isDirectory()) continue;
|
|
15425
16722
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
15426
|
-
const assignmentDir =
|
|
15427
|
-
const assignmentMd =
|
|
16723
|
+
const assignmentDir = resolve25(assignmentsDir2, a.name);
|
|
16724
|
+
const assignmentMd = resolve25(assignmentDir, "assignment.md");
|
|
15428
16725
|
const entry = {
|
|
15429
|
-
projectDir:
|
|
16726
|
+
projectDir: resolve25(projectsDir, m.name),
|
|
15430
16727
|
projectSlug: m.name,
|
|
15431
16728
|
assignmentDir,
|
|
15432
16729
|
assignmentSlug: a.name,
|
|
@@ -15441,12 +16738,12 @@ async function listAssignmentsByProject(projectsDir, standaloneDir) {
|
|
|
15441
16738
|
}
|
|
15442
16739
|
}
|
|
15443
16740
|
if (standaloneDir !== null && await fileExists(standaloneDir)) {
|
|
15444
|
-
const entries = await
|
|
16741
|
+
const entries = await readdir12(standaloneDir, { withFileTypes: true });
|
|
15445
16742
|
for (const a of entries) {
|
|
15446
16743
|
if (!a.isDirectory()) continue;
|
|
15447
16744
|
if (a.name.startsWith(".") || a.name.startsWith("_")) continue;
|
|
15448
|
-
const assignmentDir =
|
|
15449
|
-
const assignmentMd =
|
|
16745
|
+
const assignmentDir = resolve25(standaloneDir, a.name);
|
|
16746
|
+
const assignmentMd = resolve25(assignmentDir, "assignment.md");
|
|
15450
16747
|
const entry = {
|
|
15451
16748
|
projectDir: standaloneDir,
|
|
15452
16749
|
projectSlug: null,
|
|
@@ -15638,7 +16935,7 @@ async function applyStatusResolutions(resolutions, affected, validTargets) {
|
|
|
15638
16935
|
} catch {
|
|
15639
16936
|
continue;
|
|
15640
16937
|
}
|
|
15641
|
-
const assignmentDir =
|
|
16938
|
+
const assignmentDir = dirname7(a.path);
|
|
15642
16939
|
try {
|
|
15643
16940
|
await rm2(assignmentDir, { recursive: true, force: true });
|
|
15644
16941
|
deleted++;
|
|
@@ -15771,7 +17068,8 @@ function createStatusConfigRouter(projectsDir, assignmentsDir2) {
|
|
|
15771
17068
|
statuses: config.statuses,
|
|
15772
17069
|
order: config.order,
|
|
15773
17070
|
transitions: config.transitions,
|
|
15774
|
-
custom: config.custom
|
|
17071
|
+
custom: config.custom,
|
|
17072
|
+
factDeclarations: config.factDeclarations
|
|
15775
17073
|
});
|
|
15776
17074
|
} catch (error) {
|
|
15777
17075
|
console.error("Error getting status config:", error);
|
|
@@ -15955,7 +17253,7 @@ import { Router as Router10 } from "express";
|
|
|
15955
17253
|
init_paths();
|
|
15956
17254
|
import Database2 from "better-sqlite3";
|
|
15957
17255
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
15958
|
-
import { resolve as
|
|
17256
|
+
import { resolve as resolve26 } from "path";
|
|
15959
17257
|
var db2 = null;
|
|
15960
17258
|
var LEASE_SCHEMA_VERSION = "1";
|
|
15961
17259
|
var SCHEMA_SQL2 = `
|
|
@@ -16042,7 +17340,7 @@ function isBusyError(err) {
|
|
|
16042
17340
|
}
|
|
16043
17341
|
function initLeasesDb(dbPath) {
|
|
16044
17342
|
if (db2) return db2;
|
|
16045
|
-
const finalPath = dbPath ??
|
|
17343
|
+
const finalPath = dbPath ?? resolve26(syntaurRoot(), "syntaur.db");
|
|
16046
17344
|
db2 = new Database2(finalPath);
|
|
16047
17345
|
db2.pragma("journal_mode = WAL");
|
|
16048
17346
|
db2.pragma("busy_timeout = 5000");
|
|
@@ -16191,7 +17489,7 @@ import { Router as Router11 } from "express";
|
|
|
16191
17489
|
// src/db/usage-db.ts
|
|
16192
17490
|
init_paths();
|
|
16193
17491
|
import Database3 from "better-sqlite3";
|
|
16194
|
-
import { resolve as
|
|
17492
|
+
import { resolve as resolve27 } from "path";
|
|
16195
17493
|
var db3 = null;
|
|
16196
17494
|
var USAGE_SCHEMA_VERSION = "1";
|
|
16197
17495
|
var SCHEMA_SQL3 = `
|
|
@@ -16248,7 +17546,7 @@ CREATE INDEX IF NOT EXISTS idx_usage_daily_day
|
|
|
16248
17546
|
`;
|
|
16249
17547
|
function initUsageDb(dbPath) {
|
|
16250
17548
|
if (db3) return db3;
|
|
16251
|
-
const finalPath = dbPath ??
|
|
17549
|
+
const finalPath = dbPath ?? resolve27(syntaurRoot(), "syntaur.db");
|
|
16252
17550
|
db3 = new Database3(finalPath);
|
|
16253
17551
|
db3.pragma("journal_mode = WAL");
|
|
16254
17552
|
db3.pragma("busy_timeout = 5000");
|
|
@@ -16507,7 +17805,7 @@ init_slug();
|
|
|
16507
17805
|
init_timestamp();
|
|
16508
17806
|
init_fs();
|
|
16509
17807
|
import { Router as Router12 } from "express";
|
|
16510
|
-
import { resolve as
|
|
17808
|
+
import { resolve as resolve28 } from "path";
|
|
16511
17809
|
import { readFile as readFile19 } from "fs/promises";
|
|
16512
17810
|
init_playbooks();
|
|
16513
17811
|
function statusForPlaybookError(code) {
|
|
@@ -16590,7 +17888,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
16590
17888
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
16591
17889
|
return;
|
|
16592
17890
|
}
|
|
16593
|
-
const filePath =
|
|
17891
|
+
const filePath = resolve28(playbooksDir2, resolved.filename);
|
|
16594
17892
|
const content = await readFile19(filePath, "utf-8");
|
|
16595
17893
|
res.json({
|
|
16596
17894
|
documentType: "playbook",
|
|
@@ -16616,7 +17914,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
16616
17914
|
return;
|
|
16617
17915
|
}
|
|
16618
17916
|
await ensureDir(playbooksDir2);
|
|
16619
|
-
const filePath =
|
|
17917
|
+
const filePath = resolve28(playbooksDir2, `${slug}.md`);
|
|
16620
17918
|
if (await fileExists(filePath)) {
|
|
16621
17919
|
res.status(409).json({ error: `Playbook "${slug}" already exists` });
|
|
16622
17920
|
return;
|
|
@@ -16640,7 +17938,7 @@ function createPlaybooksRouter(playbooksDir2) {
|
|
|
16640
17938
|
res.status(404).json({ error: `Playbook "${req.params.slug}" not found` });
|
|
16641
17939
|
return;
|
|
16642
17940
|
}
|
|
16643
|
-
const filePath =
|
|
17941
|
+
const filePath = resolve28(playbooksDir2, resolved.filename);
|
|
16644
17942
|
await writeFileForce(filePath, content);
|
|
16645
17943
|
await rebuildPlaybookManifest(playbooksDir2);
|
|
16646
17944
|
res.json({ slug: resolved.slug, path: filePath });
|
|
@@ -16688,8 +17986,8 @@ init_parser2();
|
|
|
16688
17986
|
init_fs();
|
|
16689
17987
|
init_paths();
|
|
16690
17988
|
import { Router as Router14 } from "express";
|
|
16691
|
-
import { readdir as
|
|
16692
|
-
import { resolve as resolvePath, dirname as
|
|
17989
|
+
import { readdir as readdir14 } from "fs/promises";
|
|
17990
|
+
import { resolve as resolvePath, dirname as dirname9 } from "path";
|
|
16693
17991
|
import { rename as rename6, mkdir as mkdir4 } from "fs/promises";
|
|
16694
17992
|
init_slug();
|
|
16695
17993
|
|
|
@@ -16699,7 +17997,7 @@ init_parser2();
|
|
|
16699
17997
|
// src/commands/create-assignment.ts
|
|
16700
17998
|
init_slug();
|
|
16701
17999
|
init_timestamp();
|
|
16702
|
-
import { resolve as
|
|
18000
|
+
import { resolve as resolve29 } from "path";
|
|
16703
18001
|
init_paths();
|
|
16704
18002
|
init_fs();
|
|
16705
18003
|
init_config2();
|
|
@@ -16777,14 +18075,14 @@ async function createAssignmentCommand(title, options) {
|
|
|
16777
18075
|
if (options.oneOff) {
|
|
16778
18076
|
const standaloneRoot = assignmentsDir();
|
|
16779
18077
|
folderName = id;
|
|
16780
|
-
assignmentDir =
|
|
18078
|
+
assignmentDir = resolve29(standaloneRoot, folderName);
|
|
16781
18079
|
projectSlug = null;
|
|
16782
18080
|
await ensureDir(standaloneRoot);
|
|
16783
18081
|
} else {
|
|
16784
18082
|
const baseDir = options.dir ? expandHome(options.dir) : config.defaultProjectDir;
|
|
16785
18083
|
projectSlug = options.project;
|
|
16786
|
-
const projectDir =
|
|
16787
|
-
const projectMdPath =
|
|
18084
|
+
const projectDir = resolve29(baseDir, projectSlug);
|
|
18085
|
+
const projectMdPath = resolve29(projectDir, "project.md");
|
|
16788
18086
|
if (!await fileExists(projectDir) || !await fileExists(projectMdPath)) {
|
|
16789
18087
|
throw new Error(
|
|
16790
18088
|
`Project "${projectSlug}" not found at ${projectDir}.
|
|
@@ -16792,9 +18090,9 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
16792
18090
|
);
|
|
16793
18091
|
}
|
|
16794
18092
|
if (dependsOn.length > 0) {
|
|
16795
|
-
const depDirBase =
|
|
18093
|
+
const depDirBase = resolve29(projectDir, "assignments");
|
|
16796
18094
|
for (const dep of dependsOn) {
|
|
16797
|
-
const depDir =
|
|
18095
|
+
const depDir = resolve29(depDirBase, dep);
|
|
16798
18096
|
if (!await fileExists(depDir)) {
|
|
16799
18097
|
console.warn(
|
|
16800
18098
|
`Warning: dependency "${dep}" does not exist in project "${projectSlug}" yet.`
|
|
@@ -16803,7 +18101,7 @@ Run 'syntaur create-project' first or use --one-off.`
|
|
|
16803
18101
|
}
|
|
16804
18102
|
}
|
|
16805
18103
|
folderName = assignmentSlug;
|
|
16806
|
-
assignmentDir =
|
|
18104
|
+
assignmentDir = resolve29(projectDir, "assignments", folderName);
|
|
16807
18105
|
}
|
|
16808
18106
|
if (await fileExists(assignmentDir)) {
|
|
16809
18107
|
throw new Error(
|
|
@@ -16815,7 +18113,7 @@ Use --slug to specify a different slug.`
|
|
|
16815
18113
|
const companionAssignmentRef = projectSlug === null ? id : assignmentSlug;
|
|
16816
18114
|
const files = [
|
|
16817
18115
|
[
|
|
16818
|
-
|
|
18116
|
+
resolve29(assignmentDir, "assignment.md"),
|
|
16819
18117
|
renderAssignment({
|
|
16820
18118
|
id,
|
|
16821
18119
|
slug: assignmentSlug,
|
|
@@ -16833,35 +18131,35 @@ Use --slug to specify a different slug.`
|
|
|
16833
18131
|
})
|
|
16834
18132
|
],
|
|
16835
18133
|
[
|
|
16836
|
-
|
|
18134
|
+
resolve29(assignmentDir, "scratchpad.md"),
|
|
16837
18135
|
renderScratchpad({
|
|
16838
18136
|
assignmentSlug: companionAssignmentRef,
|
|
16839
18137
|
timestamp
|
|
16840
18138
|
})
|
|
16841
18139
|
],
|
|
16842
18140
|
[
|
|
16843
|
-
|
|
18141
|
+
resolve29(assignmentDir, "handoff.md"),
|
|
16844
18142
|
renderHandoff({
|
|
16845
18143
|
assignmentSlug: companionAssignmentRef,
|
|
16846
18144
|
timestamp
|
|
16847
18145
|
})
|
|
16848
18146
|
],
|
|
16849
18147
|
[
|
|
16850
|
-
|
|
18148
|
+
resolve29(assignmentDir, "decision-record.md"),
|
|
16851
18149
|
renderDecisionRecord({
|
|
16852
18150
|
assignmentSlug: companionAssignmentRef,
|
|
16853
18151
|
timestamp
|
|
16854
18152
|
})
|
|
16855
18153
|
],
|
|
16856
18154
|
[
|
|
16857
|
-
|
|
18155
|
+
resolve29(assignmentDir, "progress.md"),
|
|
16858
18156
|
renderProgress({
|
|
16859
18157
|
assignment: companionAssignmentRef,
|
|
16860
18158
|
timestamp
|
|
16861
18159
|
})
|
|
16862
18160
|
],
|
|
16863
18161
|
[
|
|
16864
|
-
|
|
18162
|
+
resolve29(assignmentDir, "comments.md"),
|
|
16865
18163
|
renderComments({
|
|
16866
18164
|
assignment: companionAssignmentRef,
|
|
16867
18165
|
timestamp
|
|
@@ -17034,8 +18332,8 @@ init_api();
|
|
|
17034
18332
|
import { raw } from "express";
|
|
17035
18333
|
|
|
17036
18334
|
// src/todos/attachments.ts
|
|
17037
|
-
import { mkdir as mkdir3, readdir as
|
|
17038
|
-
import { resolve as
|
|
18335
|
+
import { mkdir as mkdir3, readdir as readdir13, stat as stat3, rename as rename5, rm as rm3, unlink as unlink6, writeFile as writeFile6, cp } from "fs/promises";
|
|
18336
|
+
import { resolve as resolve30, basename as basename5, dirname as dirname8, extname } from "path";
|
|
17039
18337
|
|
|
17040
18338
|
// src/utils/proof-artifact-id.ts
|
|
17041
18339
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -17122,16 +18420,16 @@ function sanitizeAttachmentName(name) {
|
|
|
17122
18420
|
return n;
|
|
17123
18421
|
}
|
|
17124
18422
|
function attachmentsRootDir(todosDir2) {
|
|
17125
|
-
return
|
|
18423
|
+
return resolve30(todosDir2, "attachments");
|
|
17126
18424
|
}
|
|
17127
18425
|
function attachmentDirFor(todosDir2, scopeId, todoId) {
|
|
17128
18426
|
assertScope(scopeId);
|
|
17129
18427
|
assertTodoId(todoId);
|
|
17130
|
-
return
|
|
18428
|
+
return resolve30(attachmentsRootDir(todosDir2), scopeId, todoId);
|
|
17131
18429
|
}
|
|
17132
18430
|
async function dirExists(p) {
|
|
17133
18431
|
try {
|
|
17134
|
-
return (await
|
|
18432
|
+
return (await stat3(p)).isDirectory();
|
|
17135
18433
|
} catch {
|
|
17136
18434
|
return false;
|
|
17137
18435
|
}
|
|
@@ -17141,7 +18439,7 @@ async function writeAttachment(todosDir2, scopeId, todoId, originalName, bytes)
|
|
|
17141
18439
|
await mkdir3(dir, { recursive: true });
|
|
17142
18440
|
const id = generateArtifactId();
|
|
17143
18441
|
const filename = sanitizeAttachmentName(originalName);
|
|
17144
|
-
await writeFile6(
|
|
18442
|
+
await writeFile6(resolve30(dir, `${id}__${filename}`), bytes);
|
|
17145
18443
|
return {
|
|
17146
18444
|
id,
|
|
17147
18445
|
filename,
|
|
@@ -17154,7 +18452,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
17154
18452
|
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
17155
18453
|
let names;
|
|
17156
18454
|
try {
|
|
17157
|
-
names = await
|
|
18455
|
+
names = await readdir13(dir);
|
|
17158
18456
|
} catch {
|
|
17159
18457
|
return [];
|
|
17160
18458
|
}
|
|
@@ -17166,7 +18464,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
17166
18464
|
if (!ATTACHMENT_ID_RE.test(id)) continue;
|
|
17167
18465
|
const filename = stored.slice(sep2 + 2);
|
|
17168
18466
|
try {
|
|
17169
|
-
const st = await
|
|
18467
|
+
const st = await stat3(resolve30(dir, stored));
|
|
17170
18468
|
if (!st.isFile()) continue;
|
|
17171
18469
|
out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
|
|
17172
18470
|
} catch {
|
|
@@ -17177,10 +18475,10 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
17177
18475
|
}
|
|
17178
18476
|
async function readScopeAttachments(todosDir2, scopeId) {
|
|
17179
18477
|
assertScope(scopeId);
|
|
17180
|
-
const scopeDir =
|
|
18478
|
+
const scopeDir = resolve30(attachmentsRootDir(todosDir2), scopeId);
|
|
17181
18479
|
let todoIds;
|
|
17182
18480
|
try {
|
|
17183
|
-
todoIds = await
|
|
18481
|
+
todoIds = await readdir13(scopeDir);
|
|
17184
18482
|
} catch {
|
|
17185
18483
|
return {};
|
|
17186
18484
|
}
|
|
@@ -17197,7 +18495,7 @@ async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
|
|
|
17197
18495
|
const dir = attachmentDirFor(todosDir2, scopeId, todoId);
|
|
17198
18496
|
let names;
|
|
17199
18497
|
try {
|
|
17200
|
-
names = await
|
|
18498
|
+
names = await readdir13(dir);
|
|
17201
18499
|
} catch {
|
|
17202
18500
|
return null;
|
|
17203
18501
|
}
|
|
@@ -17205,7 +18503,7 @@ async function resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId) {
|
|
|
17205
18503
|
const stored = names.find((n) => n.startsWith(prefix));
|
|
17206
18504
|
if (!stored) return null;
|
|
17207
18505
|
const filename = stored.slice(prefix.length);
|
|
17208
|
-
return { path:
|
|
18506
|
+
return { path: resolve30(dir, stored), filename, mime: mimeForName(filename) };
|
|
17209
18507
|
}
|
|
17210
18508
|
async function deleteAttachment(todosDir2, scopeId, todoId, attachmentId) {
|
|
17211
18509
|
const resolved = await resolveAttachmentFile(todosDir2, scopeId, todoId, attachmentId);
|
|
@@ -17225,7 +18523,7 @@ async function moveAttachments(srcTodosDir, srcScopeId, dstTodosDir, dstScopeId,
|
|
|
17225
18523
|
const src = attachmentDirFor(srcTodosDir, srcScopeId, todoId);
|
|
17226
18524
|
if (!await dirExists(src)) return;
|
|
17227
18525
|
const dst = attachmentDirFor(dstTodosDir, dstScopeId, todoId);
|
|
17228
|
-
await mkdir3(
|
|
18526
|
+
await mkdir3(dirname8(dst), { recursive: true });
|
|
17229
18527
|
try {
|
|
17230
18528
|
await rename5(src, dst);
|
|
17231
18529
|
} catch (err) {
|
|
@@ -17501,7 +18799,7 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
17501
18799
|
router.get("/", async (_req, res) => {
|
|
17502
18800
|
try {
|
|
17503
18801
|
await ensureDir(todosDir2);
|
|
17504
|
-
const files = await
|
|
18802
|
+
const files = await readdir14(todosDir2).catch(() => []);
|
|
17505
18803
|
const workspaces = [];
|
|
17506
18804
|
for (const file of files) {
|
|
17507
18805
|
if (typeof file !== "string") continue;
|
|
@@ -17617,8 +18915,8 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
17617
18915
|
router.post("/:workspace/archive", async (req, res) => {
|
|
17618
18916
|
try {
|
|
17619
18917
|
const { archivePath: archivePath2 } = await Promise.resolve().then(() => (init_parser2(), parser_exports));
|
|
17620
|
-
const { resolve:
|
|
17621
|
-
const { readFile:
|
|
18918
|
+
const { resolve: resolve38 } = await import("path");
|
|
18919
|
+
const { readFile: readFile25 } = await import("fs/promises");
|
|
17622
18920
|
const { writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
17623
18921
|
const workspace = getWorkspaceParam(req.params.workspace);
|
|
17624
18922
|
const outcome = await wsLock(workspace, async () => {
|
|
@@ -17634,10 +18932,10 @@ function createTodosRouter(todosDir2, broadcast, projectsDir) {
|
|
|
17634
18932
|
(e) => e.itemIds.every((id) => completedIds.has(id))
|
|
17635
18933
|
);
|
|
17636
18934
|
const archFile = archivePath2(todosDir2, workspace, checklist.archiveInterval);
|
|
17637
|
-
await ensureDir(
|
|
18935
|
+
await ensureDir(resolve38(todosDir2, "archive"));
|
|
17638
18936
|
let archContent = "";
|
|
17639
18937
|
if (await fileExists(archFile)) {
|
|
17640
|
-
archContent = await
|
|
18938
|
+
archContent = await readFile25(archFile, "utf-8");
|
|
17641
18939
|
archContent = archContent.trimEnd() + "\n\n";
|
|
17642
18940
|
} else {
|
|
17643
18941
|
archContent = `---
|
|
@@ -17926,7 +19224,7 @@ workspace: ${workspace}
|
|
|
17926
19224
|
const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config2(), config_exports));
|
|
17927
19225
|
const { assignmentsDir: assignmentsDirFn } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
17928
19226
|
const { fileExists: fileExists2, writeFileForce: writeFileForce2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
17929
|
-
const { readFile:
|
|
19227
|
+
const { readFile: readFile25 } = await import("fs/promises");
|
|
17930
19228
|
const { appendTodosToAssignmentBody: appendTodosToAssignmentBody2, touchAssignmentUpdated: touchAssignmentUpdated2 } = await Promise.resolve().then(() => (init_assignment_todos(), assignment_todos_exports));
|
|
17931
19229
|
const { nowTimestamp: nowTimestamp3 } = await Promise.resolve().then(() => (init_timestamp(), timestamp_exports));
|
|
17932
19230
|
let assignmentRef;
|
|
@@ -17947,7 +19245,7 @@ workspace: ${workspace}
|
|
|
17947
19245
|
}
|
|
17948
19246
|
const assignmentMdPath = resolvePath2(assignmentDir, "assignment.md");
|
|
17949
19247
|
if (!await fileExists2(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
17950
|
-
let content = await
|
|
19248
|
+
let content = await readFile25(assignmentMdPath, "utf-8");
|
|
17951
19249
|
content = appendTodosToAssignmentBody2(
|
|
17952
19250
|
content,
|
|
17953
19251
|
items.map((it) => ({
|
|
@@ -18064,7 +19362,7 @@ workspace: ${workspace}
|
|
|
18064
19362
|
return { status: 409, error: "attachments already exist in target" };
|
|
18065
19363
|
}
|
|
18066
19364
|
if (item.planDir && newPlanDir) {
|
|
18067
|
-
await mkdir4(
|
|
19365
|
+
await mkdir4(dirname9(newPlanDir), { recursive: true });
|
|
18068
19366
|
await rename6(item.planDir, newPlanDir);
|
|
18069
19367
|
item.planDir = newPlanDir;
|
|
18070
19368
|
}
|
|
@@ -18143,7 +19441,7 @@ init_paths();
|
|
|
18143
19441
|
init_slug();
|
|
18144
19442
|
import { Router as Router15 } from "express";
|
|
18145
19443
|
import { mkdir as mkdir5, readFile as readFile20, rename as rename7 } from "fs/promises";
|
|
18146
|
-
import { resolve as
|
|
19444
|
+
import { resolve as resolve31, dirname as dirname10 } from "path";
|
|
18147
19445
|
init_api();
|
|
18148
19446
|
var WORKSPACE_REGEX2 = /^[a-z0-9_][a-z0-9-]*$/;
|
|
18149
19447
|
function touchItem4(item) {
|
|
@@ -18159,7 +19457,7 @@ function params(req) {
|
|
|
18159
19457
|
return req.params;
|
|
18160
19458
|
}
|
|
18161
19459
|
async function projectExists(projectsDir, slug) {
|
|
18162
|
-
return fileExists(
|
|
19460
|
+
return fileExists(resolve31(projectsDir, slug, "project.md"));
|
|
18163
19461
|
}
|
|
18164
19462
|
async function ensureProjectTodosDir(projectsDir, slug) {
|
|
18165
19463
|
const todosDir2 = projectTodosDir(projectsDir, slug);
|
|
@@ -18176,7 +19474,7 @@ async function ensureProjectTodosDir(projectsDir, slug) {
|
|
|
18176
19474
|
throw err;
|
|
18177
19475
|
}
|
|
18178
19476
|
try {
|
|
18179
|
-
await mkdir5(
|
|
19477
|
+
await mkdir5(resolve31(todosDir2, "archive"), { recursive: false });
|
|
18180
19478
|
} catch (err) {
|
|
18181
19479
|
const code = err.code;
|
|
18182
19480
|
if (code === "EEXIST") return;
|
|
@@ -18839,15 +20137,15 @@ workspace: ${slug}
|
|
|
18839
20137
|
if (tg.includes("/")) {
|
|
18840
20138
|
const parts = tg.split("/");
|
|
18841
20139
|
if (parts.length !== 2) return { error: `Invalid target.assignment "${tg}"` };
|
|
18842
|
-
assignmentDir =
|
|
20140
|
+
assignmentDir = resolve31(projectsDir, parts[0], "assignments", parts[1]);
|
|
18843
20141
|
assignmentRef = `${parts[0]}/${parts[1]}`;
|
|
18844
20142
|
} else if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(tg)) {
|
|
18845
|
-
assignmentDir =
|
|
20143
|
+
assignmentDir = resolve31(assignmentsDirFn(), tg);
|
|
18846
20144
|
assignmentRef = tg;
|
|
18847
20145
|
} else {
|
|
18848
20146
|
return { error: `Invalid target.assignment "${tg}"` };
|
|
18849
20147
|
}
|
|
18850
|
-
const assignmentMdPath =
|
|
20148
|
+
const assignmentMdPath = resolve31(assignmentDir, "assignment.md");
|
|
18851
20149
|
if (!await fileExists(assignmentMdPath)) return { error: `Target assignment not found: ${assignmentMdPath}` };
|
|
18852
20150
|
let content = await readFile20(assignmentMdPath, "utf-8");
|
|
18853
20151
|
content = appendTodosToAssignmentBody2(
|
|
@@ -18988,7 +20286,7 @@ workspace: ${slug}
|
|
|
18988
20286
|
return { status: 409, error: "attachments already exist in target" };
|
|
18989
20287
|
}
|
|
18990
20288
|
if (item.planDir && newPlanDir) {
|
|
18991
|
-
await mkdir5(
|
|
20289
|
+
await mkdir5(dirname10(newPlanDir), { recursive: true });
|
|
18992
20290
|
await rename7(item.planDir, newPlanDir);
|
|
18993
20291
|
item.planDir = newPlanDir;
|
|
18994
20292
|
}
|
|
@@ -19052,7 +20350,7 @@ workspace: ${slug}
|
|
|
19052
20350
|
|
|
19053
20351
|
// src/dashboard/api-bundles.ts
|
|
19054
20352
|
import { Router as Router16 } from "express";
|
|
19055
|
-
import { readdir as
|
|
20353
|
+
import { readdir as readdir15 } from "fs/promises";
|
|
19056
20354
|
|
|
19057
20355
|
// src/todos/bundle-parser.ts
|
|
19058
20356
|
init_parser();
|
|
@@ -19176,7 +20474,7 @@ function createBundlesRouter(todosDir2, broadcast) {
|
|
|
19176
20474
|
try {
|
|
19177
20475
|
await ensureDir(todosDir2);
|
|
19178
20476
|
const bundles = await readBundles(todosDir2);
|
|
19179
|
-
const workspaceFiles = await
|
|
20477
|
+
const workspaceFiles = await readdir15(todosDir2).catch(() => []);
|
|
19180
20478
|
const itemsByKey = /* @__PURE__ */ new Map();
|
|
19181
20479
|
for (const f of workspaceFiles) {
|
|
19182
20480
|
if (typeof f !== "string") continue;
|
|
@@ -19229,7 +20527,7 @@ init_fs();
|
|
|
19229
20527
|
init_paths();
|
|
19230
20528
|
init_slug();
|
|
19231
20529
|
import { Router as Router17 } from "express";
|
|
19232
|
-
import { resolve as
|
|
20530
|
+
import { resolve as resolve32 } from "path";
|
|
19233
20531
|
init_parser2();
|
|
19234
20532
|
function deriveStatus2(bundle, items) {
|
|
19235
20533
|
const members = bundle.todoIds.map((id) => items.find((i) => i.id === id)).filter((i) => i !== void 0);
|
|
@@ -19271,7 +20569,7 @@ function createProjectBundlesRouter(projectsDir, broadcast) {
|
|
|
19271
20569
|
router.get("/", async (req, res) => {
|
|
19272
20570
|
try {
|
|
19273
20571
|
const slug = getProjectIdParam2(req.params.projectId);
|
|
19274
|
-
const projectMd =
|
|
20572
|
+
const projectMd = resolve32(projectsDir, slug, "project.md");
|
|
19275
20573
|
if (!await fileExists(projectMd)) {
|
|
19276
20574
|
notFound2(res, slug);
|
|
19277
20575
|
return;
|
|
@@ -19300,8 +20598,8 @@ init_fs();
|
|
|
19300
20598
|
init_config2();
|
|
19301
20599
|
import { execFile as execFile2 } from "child_process";
|
|
19302
20600
|
import { promisify as promisify2 } from "util";
|
|
19303
|
-
import { cp as cp2, mkdtemp, rm as rm4, readFile as readFile22, writeFile as writeFile7, unlink as unlink7, stat as
|
|
19304
|
-
import { resolve as
|
|
20601
|
+
import { cp as cp2, mkdtemp, rm as rm4, readFile as readFile22, writeFile as writeFile7, unlink as unlink7, stat as stat4, open as open4, rename as rename8 } from "fs/promises";
|
|
20602
|
+
import { resolve as resolve33, join as join7 } from "path";
|
|
19305
20603
|
import { tmpdir } from "os";
|
|
19306
20604
|
var exec2 = promisify2(execFile2);
|
|
19307
20605
|
var VALID_CATEGORIES = ["projects", "playbooks", "todos", "servers", "config"];
|
|
@@ -19341,7 +20639,7 @@ async function resolveCategoryPath(category) {
|
|
|
19341
20639
|
case "servers":
|
|
19342
20640
|
return { sourcePath: serversDir(), repoPath: "servers", isFile: false };
|
|
19343
20641
|
case "config":
|
|
19344
|
-
return { sourcePath:
|
|
20642
|
+
return { sourcePath: resolve33(syntaurRoot(), "config.md"), repoPath: "config.md", isFile: true };
|
|
19345
20643
|
}
|
|
19346
20644
|
}
|
|
19347
20645
|
async function checkGitInstalled() {
|
|
@@ -19352,10 +20650,10 @@ async function checkGitInstalled() {
|
|
|
19352
20650
|
}
|
|
19353
20651
|
}
|
|
19354
20652
|
async function acquireLock2() {
|
|
19355
|
-
const lockPath =
|
|
20653
|
+
const lockPath = resolve33(syntaurRoot(), LOCK_FILE_NAME);
|
|
19356
20654
|
await ensureDir(syntaurRoot());
|
|
19357
20655
|
try {
|
|
19358
|
-
const handle = await
|
|
20656
|
+
const handle = await open4(lockPath, "wx");
|
|
19359
20657
|
await handle.write(String(process.pid));
|
|
19360
20658
|
await handle.close();
|
|
19361
20659
|
return lockPath;
|
|
@@ -19394,12 +20692,12 @@ async function cloneOrInit(repoUrl, destDir) {
|
|
|
19394
20692
|
}
|
|
19395
20693
|
async function copyRecursive(src, dest) {
|
|
19396
20694
|
if (!await fileExists(src)) return;
|
|
19397
|
-
const s = await
|
|
20695
|
+
const s = await stat4(src);
|
|
19398
20696
|
if (s.isDirectory()) {
|
|
19399
20697
|
await ensureDir(dest);
|
|
19400
20698
|
await cp2(src, dest, { recursive: true, force: true });
|
|
19401
20699
|
} else {
|
|
19402
|
-
await ensureDir(
|
|
20700
|
+
await ensureDir(resolve33(dest, ".."));
|
|
19403
20701
|
await cp2(src, dest, { force: true });
|
|
19404
20702
|
}
|
|
19405
20703
|
}
|
|
@@ -19431,11 +20729,11 @@ async function backupToGithub(overrides) {
|
|
|
19431
20729
|
let tmpDir = null;
|
|
19432
20730
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
19433
20731
|
try {
|
|
19434
|
-
tmpDir = await mkdtemp(
|
|
20732
|
+
tmpDir = await mkdtemp(join7(tmpdir(), "syntaur-backup-"));
|
|
19435
20733
|
await cloneOrInit(repo, tmpDir);
|
|
19436
20734
|
for (const category of categories) {
|
|
19437
20735
|
const { sourcePath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
19438
|
-
const destPath =
|
|
20736
|
+
const destPath = join7(tmpDir, repoPath);
|
|
19439
20737
|
if (isFile) {
|
|
19440
20738
|
await rm4(destPath, { force: true });
|
|
19441
20739
|
} else {
|
|
@@ -19447,7 +20745,7 @@ async function backupToGithub(overrides) {
|
|
|
19447
20745
|
}
|
|
19448
20746
|
if (category === "config") {
|
|
19449
20747
|
const sanitized = await readSanitizedConfig(sourcePath);
|
|
19450
|
-
await ensureDir(
|
|
20748
|
+
await ensureDir(resolve33(destPath, ".."));
|
|
19451
20749
|
await writeFile7(destPath, sanitized, "utf-8");
|
|
19452
20750
|
} else {
|
|
19453
20751
|
await copyRecursive(sourcePath, destPath);
|
|
@@ -19501,7 +20799,7 @@ async function backupToGithub(overrides) {
|
|
|
19501
20799
|
}
|
|
19502
20800
|
async function safeRestoreCategory(localPath, repoSrcPath, isFile) {
|
|
19503
20801
|
if (isFile) {
|
|
19504
|
-
await ensureDir(
|
|
20802
|
+
await ensureDir(resolve33(localPath, ".."));
|
|
19505
20803
|
await cp2(repoSrcPath, localPath, { force: true });
|
|
19506
20804
|
return;
|
|
19507
20805
|
}
|
|
@@ -19563,7 +20861,7 @@ async function restoreFromGithub(overrides) {
|
|
|
19563
20861
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
19564
20862
|
try {
|
|
19565
20863
|
await updateBackupConfig({ lastRestore: timestamp });
|
|
19566
|
-
tmpDir = await mkdtemp(
|
|
20864
|
+
tmpDir = await mkdtemp(join7(tmpdir(), "syntaur-restore-"));
|
|
19567
20865
|
await cloneOrInit(repo, tmpDir);
|
|
19568
20866
|
for (const category of categories) {
|
|
19569
20867
|
if (category === "config") {
|
|
@@ -19572,7 +20870,7 @@ async function restoreFromGithub(overrides) {
|
|
|
19572
20870
|
}
|
|
19573
20871
|
try {
|
|
19574
20872
|
const { sourcePath: localPath, repoPath, isFile } = await resolveCategoryPath(category);
|
|
19575
|
-
const repoSrcPath =
|
|
20873
|
+
const repoSrcPath = join7(tmpDir, repoPath);
|
|
19576
20874
|
if (!await fileExists(repoSrcPath)) {
|
|
19577
20875
|
console.warn(`Category "${category}" not found in backup repo, skipping.`);
|
|
19578
20876
|
continue;
|
|
@@ -19602,7 +20900,7 @@ async function restoreFromGithub(overrides) {
|
|
|
19602
20900
|
}
|
|
19603
20901
|
async function getBackupStatus() {
|
|
19604
20902
|
const config = await readConfig();
|
|
19605
|
-
const lockPath =
|
|
20903
|
+
const lockPath = resolve33(syntaurRoot(), LOCK_FILE_NAME);
|
|
19606
20904
|
const locked = await fileExists(lockPath);
|
|
19607
20905
|
return {
|
|
19608
20906
|
repo: config.backup?.repo ?? null,
|
|
@@ -19763,7 +21061,7 @@ async function stopAutodiscovery() {
|
|
|
19763
21061
|
function runReconcile() {
|
|
19764
21062
|
if (activeReconcile || !savedOptions) return;
|
|
19765
21063
|
const opts = savedOptions;
|
|
19766
|
-
activeReconcile = reconcile(opts.serversDir, opts.projectsDir, opts.excludePids, opts.assignmentsDir).catch((err) => {
|
|
21064
|
+
activeReconcile = reconcile(opts.serversDir, opts.projectsDir, opts.excludePids, opts.assignmentsDir, opts.onAgentSessionsChanged).catch((err) => {
|
|
19767
21065
|
console.error("[autodiscovery] reconcile failed:", err);
|
|
19768
21066
|
}).finally(() => {
|
|
19769
21067
|
activeReconcile = null;
|
|
@@ -19884,7 +21182,7 @@ async function isProcessAlive(pid) {
|
|
|
19884
21182
|
return false;
|
|
19885
21183
|
}
|
|
19886
21184
|
}
|
|
19887
|
-
async function reconcile(serversDir2, projectsDir, excludePids, assignmentsDir2) {
|
|
21185
|
+
async function reconcile(serversDir2, projectsDir, excludePids, assignmentsDir2, onAgentSessionsChanged) {
|
|
19888
21186
|
const names = await listSessionFiles(serversDir2);
|
|
19889
21187
|
const existingFiles = /* @__PURE__ */ new Map();
|
|
19890
21188
|
for (const name of names) {
|
|
@@ -19901,6 +21199,16 @@ async function reconcile(serversDir2, projectsDir, excludePids, assignmentsDir2)
|
|
|
19901
21199
|
if (tmuxChanged || processChanged || cleanupChanged) {
|
|
19902
21200
|
clearScanCache();
|
|
19903
21201
|
}
|
|
21202
|
+
const { isSessionDbInitialized: isSessionDbInitialized2 } = await Promise.resolve().then(() => (init_session_db(), session_db_exports));
|
|
21203
|
+
if (isSessionDbInitialized2()) {
|
|
21204
|
+
try {
|
|
21205
|
+
const { scanSessions: scanSessions2 } = await Promise.resolve().then(() => (init_scanner2(), scanner_exports2));
|
|
21206
|
+
const summary = await scanSessions2({});
|
|
21207
|
+
if (summary.changed) onAgentSessionsChanged?.();
|
|
21208
|
+
} catch (err) {
|
|
21209
|
+
console.error("[autodiscovery] session scan failed:", err);
|
|
21210
|
+
}
|
|
21211
|
+
}
|
|
19904
21212
|
}
|
|
19905
21213
|
|
|
19906
21214
|
// src/dashboard/server.ts
|
|
@@ -19950,7 +21258,7 @@ function createDashboardServer(options) {
|
|
|
19950
21258
|
(async () => {
|
|
19951
21259
|
try {
|
|
19952
21260
|
const configResult = await migrateLegacyConfig(
|
|
19953
|
-
|
|
21261
|
+
resolve37(syntaurRoot(), "config.md")
|
|
19954
21262
|
);
|
|
19955
21263
|
const projectResult = await migrateLegacyProjectFiles(projectsDir);
|
|
19956
21264
|
const summary = summarizeMigration(projectResult, configResult);
|
|
@@ -20468,14 +21776,14 @@ function createDashboardServer(options) {
|
|
|
20468
21776
|
app.use("/api/backup", createBackupRouter());
|
|
20469
21777
|
if (serveStaticUi && dashboardDistPath) {
|
|
20470
21778
|
const sendOpts = { dotfiles: "allow" };
|
|
20471
|
-
app.use("/assets", express.static(
|
|
21779
|
+
app.use("/assets", express.static(resolve37(dashboardDistPath, "assets"), sendOpts));
|
|
20472
21780
|
app.use(express.static(dashboardDistPath, { ...sendOpts, index: false, fallthrough: true }));
|
|
20473
21781
|
app.get("{*path}", async (req, res) => {
|
|
20474
21782
|
if (req.path.startsWith("/api") || req.path === "/ws" || req.path.startsWith("/assets")) {
|
|
20475
21783
|
res.status(404).json({ error: "Not Found" });
|
|
20476
21784
|
return;
|
|
20477
21785
|
}
|
|
20478
|
-
const indexPath =
|
|
21786
|
+
const indexPath = resolve37(dashboardDistPath, "index.html");
|
|
20479
21787
|
if (!await fileExists(indexPath)) {
|
|
20480
21788
|
res.status(503).send(
|
|
20481
21789
|
'Dashboard not built. Run "npm run build:dashboard" first.'
|
|
@@ -20509,8 +21817,8 @@ function createDashboardServer(options) {
|
|
|
20509
21817
|
if (!await migrationGate()) return;
|
|
20510
21818
|
try {
|
|
20511
21819
|
const context = await resolveDeriveContext2();
|
|
20512
|
-
const projectDir = projectSlug ?
|
|
20513
|
-
const path = projectDir ?
|
|
21820
|
+
const projectDir = projectSlug ? resolve37(projectsDir, projectSlug) : null;
|
|
21821
|
+
const path = projectDir ? resolve37(projectDir, "assignments", assignmentSlug, "assignment.md") : resolve37(assignmentsDir2, assignmentSlug, "assignment.md");
|
|
20514
21822
|
if (!await fileExists(path)) return;
|
|
20515
21823
|
const result = await recomputeAndWrite2(path, {
|
|
20516
21824
|
cause: "derive",
|
|
@@ -20546,8 +21854,8 @@ function createDashboardServer(options) {
|
|
|
20546
21854
|
serversDir: serversDir2,
|
|
20547
21855
|
playbooksDir: playbooksDir2,
|
|
20548
21856
|
todosDir: todosDir2,
|
|
20549
|
-
dbPath:
|
|
20550
|
-
configPath:
|
|
21857
|
+
dbPath: resolve37(syntaurRoot(), "syntaur.db"),
|
|
21858
|
+
configPath: resolve37(syntaurRoot(), "config.md"),
|
|
20551
21859
|
onMessage: broadcast,
|
|
20552
21860
|
onAssignmentChanged: (projectSlug, assignmentSlug) => {
|
|
20553
21861
|
void recomputeOne(projectSlug, assignmentSlug);
|
|
@@ -20558,7 +21866,16 @@ function createDashboardServer(options) {
|
|
|
20558
21866
|
}
|
|
20559
21867
|
});
|
|
20560
21868
|
void sweepAll("boot-reconcile");
|
|
20561
|
-
startAutodiscovery({
|
|
21869
|
+
startAutodiscovery({
|
|
21870
|
+
serversDir: serversDir2,
|
|
21871
|
+
projectsDir,
|
|
21872
|
+
assignmentsDir: assignmentsDir2,
|
|
21873
|
+
excludePids: /* @__PURE__ */ new Set([process.pid]),
|
|
21874
|
+
// Same WS frame the REST mutations emit, so the UI refreshes when the
|
|
21875
|
+
// session scan inserts/revives/sweeps rows. Autodiscovery's immediate
|
|
21876
|
+
// first run covers "scan at dashboard start".
|
|
21877
|
+
onAgentSessionsChanged: () => broadcast({ type: "agent-sessions-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
21878
|
+
});
|
|
20562
21879
|
return new Promise((resolvePromise, reject) => {
|
|
20563
21880
|
server.on("error", (err) => {
|
|
20564
21881
|
if (err.code === "EADDRINUSE") {
|
|
@@ -20570,7 +21887,7 @@ function createDashboardServer(options) {
|
|
|
20570
21887
|
}
|
|
20571
21888
|
});
|
|
20572
21889
|
server.listen(port, () => {
|
|
20573
|
-
const portFile =
|
|
21890
|
+
const portFile = resolve37(syntaurRoot(), "dashboard-port");
|
|
20574
21891
|
writeFile8(portFile, String(port), "utf-8").catch(() => {
|
|
20575
21892
|
});
|
|
20576
21893
|
resolvePromise();
|
|
@@ -20589,7 +21906,7 @@ function createDashboardServer(options) {
|
|
|
20589
21906
|
client.terminate();
|
|
20590
21907
|
}
|
|
20591
21908
|
clients.clear();
|
|
20592
|
-
const portFile =
|
|
21909
|
+
const portFile = resolve37(syntaurRoot(), "dashboard-port");
|
|
20593
21910
|
await unlink8(portFile).catch(() => {
|
|
20594
21911
|
});
|
|
20595
21912
|
server.closeAllConnections?.();
|