@carljia/omd-dsh 0.1.5 → 0.1.8
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/README.md +18 -11
- package/cordis.patch.yml +10 -0
- package/lib/boot.d.ts +21 -0
- package/lib/boot.js +40 -0
- package/lib/cli.js +16 -573
- package/lib/index.d.ts +2 -2
- package/lib/index.js +11 -10
- package/lib/mode.js +1 -4
- package/lib/plan.js +1 -4
- package/lib/shared.d.ts +25 -0
- package/lib/shared.js +28 -0
- package/lib/startwork.js +1 -4
- package/lib/sync.d.ts +53 -0
- package/lib/sync.js +345 -0
- package/lib/task.js +5 -7
- package/lib/vendor/omd-mode-switch.mjs +967 -127
- package/lib/vendor/omd-mode.mjs +889 -172
- package/lib/vendor/omd-plan.mjs +95 -177
- package/lib/vendor/omd-start-work.mjs +96 -132
- package/lib/vendor/omd-task.mjs +20735 -264
- package/lib/vendor/omd-ulw.mjs +44 -49
- package/lib/vendor/shared.js +28 -0
- package/package.json +16 -5
- package/presets/omd-chat/agent.cordis.yml +1 -1
- package/presets/omd-executor/agent.cordis.yml +1 -1
- package/presets/omd-explorer/agent.cordis.yml +1 -1
- package/presets/omd-librarian/agent.cordis.yml +1 -1
- package/presets/omd-planner/agent.cordis.yml +1 -1
- package/presets/omd-reviewer/agent.cordis.yml +1 -1
- package/presets/omd-ultraworker/agent.cordis.yml +1 -1
package/lib/vendor/omd-plan.mjs
CHANGED
|
@@ -1,199 +1,117 @@
|
|
|
1
|
+
// lib/plan.js
|
|
1
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import { basename, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* planner mode. Three jobs, all scoped to the planner preset:
|
|
9
|
-
* 1. auto-activate plan mode for the top-level agent, so the plan:policy
|
|
10
|
-
* section renders and exit_plan_mode works (DSH leaves plan state
|
|
11
|
-
* inactive until /plan or a programmatic set);
|
|
12
|
-
* 2. enforce a .md-only write guard so the planner stays read-only except
|
|
13
|
-
* for markdown files;
|
|
14
|
-
* 3. wrap `tools/post-execute` and intercept a successful `exit_plan_mode`
|
|
15
|
-
* approval: the approved plan text is written into the workspace's plan
|
|
16
|
-
* directory (a fixed, code-level convention -- never mentioned in any
|
|
17
|
-
* persona/prompt text), and the tool result content is enriched with the
|
|
18
|
-
* saved file name so the planner's fixed Start Work final step can hand
|
|
19
|
-
* it to the user.
|
|
20
|
-
*
|
|
21
|
-
* Plan directory convention (hardcoded here and in omd-start-work only):
|
|
22
|
-
* <session cwd>/.omd/plans/<slug>-<timestamp>.md
|
|
23
|
-
* The slug derives from the plan's first markdown heading; a timestamp
|
|
24
|
-
* suffix keeps repeated interviews from overwriting each other.
|
|
25
|
-
*/
|
|
26
|
-
/** Cordis plugin name. */
|
|
27
|
-
const name = "omd-plan";
|
|
28
|
-
/** No service injection: this row only registers scoped event listeners. */
|
|
29
|
-
const inject = [];
|
|
30
|
-
/** Plan directory segments relative to the session workspace root (cwd). */
|
|
31
|
-
const PLAN_DIR_SEGMENTS = [".omd", "plans"];
|
|
32
|
-
/** The exit tool whose approved plan we persist. */
|
|
33
|
-
const EXIT_PLAN_MODE = "exit_plan_mode";
|
|
34
|
-
/** Maximum slug length (characters). */
|
|
35
|
-
const SLUG_MAX = 48;
|
|
36
|
-
/** The plan's first markdown heading (any level), or undefined when it has none. */
|
|
4
|
+
var name = "omd-plan";
|
|
5
|
+
var inject = [];
|
|
6
|
+
var PLAN_DIR_SEGMENTS = [".omd", "plans"];
|
|
7
|
+
var EXIT_PLAN_MODE = "exit_plan_mode";
|
|
8
|
+
var SLUG_MAX = 48;
|
|
37
9
|
function firstHeading(plan) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
10
|
+
for (const line of plan.split("\n")) {
|
|
11
|
+
const match = /^#{1,6}\s+(.+?)\s*$/.exec(line);
|
|
12
|
+
if (match)
|
|
13
|
+
return match[1];
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
44
16
|
}
|
|
45
|
-
/** Derive a filesystem-safe slug from the plan title. */
|
|
46
17
|
function slugify(title) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.toLowerCase()
|
|
50
|
-
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
51
|
-
.replace(/^-+|-+$/g, "")
|
|
52
|
-
.slice(0, SLUG_MAX);
|
|
53
|
-
return slug === "" ? "plan" : slug;
|
|
18
|
+
const slug = String(title).normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, SLUG_MAX);
|
|
19
|
+
return slug === "" ? "plan" : slug;
|
|
54
20
|
}
|
|
55
|
-
/** Compact local-ish UTC timestamp for the file name: YYYYMMDD-HHmmss. */
|
|
56
21
|
function timestamp() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
pad(d.getUTCMonth() + 1) +
|
|
61
|
-
pad(d.getUTCDate()) +
|
|
62
|
-
"-" +
|
|
63
|
-
pad(d.getUTCHours()) +
|
|
64
|
-
pad(d.getUTCMinutes()) +
|
|
65
|
-
pad(d.getUTCSeconds()));
|
|
22
|
+
const d = /* @__PURE__ */ new Date();
|
|
23
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
24
|
+
return d.getUTCFullYear() + pad(d.getUTCMonth() + 1) + pad(d.getUTCDate()) + "-" + pad(d.getUTCHours()) + pad(d.getUTCMinutes()) + pad(d.getUTCSeconds());
|
|
66
25
|
}
|
|
67
|
-
/** Write the plan into <cwd>/.omd/plans/ and return the absolute file path. */
|
|
68
26
|
async function savePlan(cwd, plan) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
27
|
+
const dir = join(cwd, ...PLAN_DIR_SEGMENTS);
|
|
28
|
+
await mkdir(dir, { recursive: true });
|
|
29
|
+
const file = join(dir, slugify(firstHeading(plan) ?? "") + "-" + timestamp() + ".md");
|
|
30
|
+
await writeFile(file, plan, "utf8");
|
|
31
|
+
return file;
|
|
74
32
|
}
|
|
75
|
-
/** Display path used in result enrichment and messages (forward slashes). */
|
|
76
33
|
function displayPath(saved) {
|
|
77
|
-
|
|
34
|
+
return PLAN_DIR_SEGMENTS.join("/") + "/" + basename(saved);
|
|
78
35
|
}
|
|
79
|
-
/** Subagents never own the plan review -- only the top-level planner does. */
|
|
80
36
|
function isSubagent(agent) {
|
|
81
|
-
|
|
82
|
-
agent !== null &&
|
|
83
|
-
agent.options !== undefined &&
|
|
84
|
-
agent.options !== null &&
|
|
85
|
-
typeof agent.options.subagentDepth === "number" &&
|
|
86
|
-
agent.options.subagentDepth > 0);
|
|
37
|
+
return agent !== void 0 && agent !== null && agent.options !== void 0 && agent.options !== null && typeof agent.options.subagentDepth === "number" && agent.options.subagentDepth > 0;
|
|
87
38
|
}
|
|
88
|
-
/** Fold the session's plan/mode events (last one wins); mirror mode.ts. */
|
|
89
39
|
function planModeActive(events) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
40
|
+
let active = false;
|
|
41
|
+
for (const event of events ?? []) {
|
|
42
|
+
if (event !== void 0 && event.type === "plan/mode") {
|
|
43
|
+
active = event.data !== void 0 && event.data !== null && event.data.active === true;
|
|
95
44
|
}
|
|
96
|
-
|
|
45
|
+
}
|
|
46
|
+
return active;
|
|
97
47
|
}
|
|
98
48
|
function apply(ctx) {
|
|
99
|
-
|
|
100
|
-
|
|
49
|
+
ctx.on("tools/pre-execute", async (exec, next) => {
|
|
50
|
+
const toolName = exec !== void 0 && exec !== null ? exec.name : void 0;
|
|
51
|
+
if (toolName !== "write" && toolName !== "edit")
|
|
52
|
+
return await next();
|
|
53
|
+
const filePath = exec.arguments !== void 0 && exec.arguments !== null && typeof exec.arguments.file_path === "string" ? exec.arguments.file_path : void 0;
|
|
54
|
+
if (filePath !== void 0 && filePath.toLowerCase().endsWith(".md"))
|
|
55
|
+
return await next();
|
|
56
|
+
return {
|
|
57
|
+
kind: "deny",
|
|
58
|
+
reason: "omd-plan: the planner preset may write or edit only .md files (refusing " + toolName + " on a non-.md path)"
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
ctx.on("agent/pre-step", async ({ agent }, next) => {
|
|
62
|
+
if (agent !== void 0 && agent !== null && !isSubagent(agent) && agent.session !== void 0 && agent.session !== null) {
|
|
63
|
+
if (!planModeActive(agent.session.events)) {
|
|
64
|
+
agent.session.append("plan/mode", { active: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return await next();
|
|
68
|
+
});
|
|
69
|
+
ctx.on("tools/post-execute", async (exec, result, next) => {
|
|
70
|
+
const decision = await next();
|
|
71
|
+
if (decision.kind !== "accept" || decision.value !== void 0)
|
|
72
|
+
return decision;
|
|
73
|
+
if (exec === void 0 || exec.name !== EXIT_PLAN_MODE)
|
|
74
|
+
return decision;
|
|
75
|
+
if (result.isError)
|
|
76
|
+
return decision;
|
|
77
|
+
const agent = exec.agent;
|
|
78
|
+
if (agent === void 0 || isSubagent(agent))
|
|
79
|
+
return decision;
|
|
80
|
+
const args = exec.arguments;
|
|
81
|
+
const plan = args !== void 0 && args !== null && typeof args.plan === "string" ? args.plan : void 0;
|
|
82
|
+
if (plan === void 0)
|
|
83
|
+
return decision;
|
|
84
|
+
const cwd = agent.session !== void 0 && agent.session.header !== void 0 && typeof agent.session.header.cwd === "string" ? agent.session.header.cwd : "";
|
|
85
|
+
if (cwd === "") {
|
|
86
|
+
return withNotice(decision, result, {
|
|
87
|
+
type: "text",
|
|
88
|
+
text: "The approved plan could NOT be saved automatically: this session has no workspace directory. Ask the user how to proceed."
|
|
89
|
+
});
|
|
101
90
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
? exec.arguments.file_path
|
|
116
|
-
: undefined;
|
|
117
|
-
if (filePath !== undefined && filePath.toLowerCase().endsWith(".md"))
|
|
118
|
-
return await next();
|
|
119
|
-
return {
|
|
120
|
-
kind: "deny",
|
|
121
|
-
reason: "omd-plan: the planner preset may write or edit only .md files (refusing " +
|
|
122
|
-
toolName +
|
|
123
|
-
" on a non-.md path)",
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
// Auto-activate plan mode for the top-level planner agent. DSH leaves plan
|
|
127
|
-
// state inactive until /plan or a programmatic set, so without this the
|
|
128
|
-
// plan:policy section never renders and exit_plan_mode fails with "only
|
|
129
|
-
// available in plan mode". Mirror mode.ts's direct log append (no narration)
|
|
130
|
-
// so the planner session is in plan mode from its first request onward.
|
|
131
|
-
ctx.on("agent/pre-step", async ({ agent }, next) => {
|
|
132
|
-
if (agent !== undefined && agent !== null && !isSubagent(agent) && agent.session !== undefined && agent.session !== null) {
|
|
133
|
-
if (!planModeActive(agent.session.events)) {
|
|
134
|
-
agent.session.append("plan/mode", { active: true });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return await next();
|
|
138
|
-
});
|
|
139
|
-
ctx.on("tools/post-execute", async (exec, result, next) => {
|
|
140
|
-
const decision = await next();
|
|
141
|
-
if (decision.kind !== "accept" || decision.value !== undefined)
|
|
142
|
-
return decision;
|
|
143
|
-
if (exec === undefined || exec.name !== EXIT_PLAN_MODE)
|
|
144
|
-
return decision;
|
|
145
|
-
if (result.isError)
|
|
146
|
-
return decision;
|
|
147
|
-
const agent = exec.agent;
|
|
148
|
-
if (agent === undefined || isSubagent(agent))
|
|
149
|
-
return decision;
|
|
150
|
-
const args = exec.arguments;
|
|
151
|
-
const plan = args !== undefined && args !== null && typeof args.plan === "string" ? args.plan : undefined;
|
|
152
|
-
if (plan === undefined)
|
|
153
|
-
return decision;
|
|
154
|
-
const cwd = agent.session !== undefined &&
|
|
155
|
-
agent.session.header !== undefined &&
|
|
156
|
-
typeof agent.session.header.cwd === "string"
|
|
157
|
-
? agent.session.header.cwd
|
|
158
|
-
: "";
|
|
159
|
-
if (cwd === "") {
|
|
160
|
-
// No workspace root to save into: fail closed but tell the model, so
|
|
161
|
-
// the planner does not promise a file name it never produced.
|
|
162
|
-
return withNotice(decision, result, {
|
|
163
|
-
type: "text",
|
|
164
|
-
text: "The approved plan could NOT be saved automatically: this session has no workspace directory. Ask the user how to proceed.",
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
try {
|
|
168
|
-
const saved = await savePlan(cwd, plan);
|
|
169
|
-
return withNotice(decision, result, {
|
|
170
|
-
type: "text",
|
|
171
|
-
text: "Plan saved to " +
|
|
172
|
-
displayPath(saved) +
|
|
173
|
-
". Start work: run /start-work " +
|
|
174
|
-
basename(saved) +
|
|
175
|
-
" in an omd-executor session, or switch this session with /mode omd-executor and continue here.",
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
return withNotice(decision, result, {
|
|
180
|
-
type: "text",
|
|
181
|
-
text: "The approved plan could NOT be saved automatically: " +
|
|
182
|
-
(error instanceof Error ? error.message : String(error)) +
|
|
183
|
-
". Ask the user how to proceed.",
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
});
|
|
91
|
+
try {
|
|
92
|
+
const saved = await savePlan(cwd, plan);
|
|
93
|
+
return withNotice(decision, result, {
|
|
94
|
+
type: "text",
|
|
95
|
+
text: "Plan saved to " + displayPath(saved) + ". Start work: run /start-work " + basename(saved) + " in an omd-executor session, or switch this session with /mode omd-executor and continue here."
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return withNotice(decision, result, {
|
|
99
|
+
type: "text",
|
|
100
|
+
text: "The approved plan could NOT be saved automatically: " + (error instanceof Error ? error.message : String(error)) + ". Ask the user how to proceed."
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
187
104
|
}
|
|
188
|
-
/** Keep the accepted decision, appending one text block to its content. */
|
|
189
105
|
function withNotice(decision, result, block) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: {}),
|
|
197
|
-
};
|
|
106
|
+
const base = Array.isArray(decision.content) ? decision.content : result.content ?? [];
|
|
107
|
+
return {
|
|
108
|
+
kind: "accept",
|
|
109
|
+
content: [...base, block],
|
|
110
|
+
...decision.additionalContexts !== void 0 ? { additionalContexts: decision.additionalContexts } : {}
|
|
111
|
+
};
|
|
198
112
|
}
|
|
199
|
-
export {
|
|
113
|
+
export {
|
|
114
|
+
apply,
|
|
115
|
+
inject,
|
|
116
|
+
name
|
|
117
|
+
};
|
|
@@ -1,147 +1,111 @@
|
|
|
1
|
+
// lib/startwork.js
|
|
1
2
|
import { access, readFile } from "node:fs/promises";
|
|
2
3
|
import { isAbsolute, join } from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* omd-start-work: human-facing `/start-work` command -- the "start work"
|
|
8
|
-
* trigger at the end of the OMD planning workflow. It resolves the named
|
|
9
|
-
* plan file inside the workspace's plan directory (a fixed, code-level
|
|
10
|
-
* convention -- never mentioned in any persona/prompt text), arms a goal
|
|
11
|
-
* whose objective references the plan's absolute path, and goal
|
|
12
|
-
* auto-continuation then drives the agent to execute the plan without
|
|
13
|
-
* further input.
|
|
14
|
-
*/
|
|
15
|
-
/** Cordis plugin name. */
|
|
16
|
-
const name = "omd-start-work";
|
|
17
|
-
/** The goal domain is already required by tool-goal in the same preset. */
|
|
18
|
-
const inject = ["goals"];
|
|
19
|
-
/** Plan directory segments relative to the session workspace root (cwd). */
|
|
20
|
-
const PLAN_DIR_SEGMENTS = [".omd", "plans"];
|
|
21
|
-
/** The prefix accepted when a user pastes the full relative plan path. */
|
|
22
|
-
const PLAN_DIR_PREFIX = ".omd/plans/";
|
|
23
|
-
/** Plan directory for one workspace root. */
|
|
4
|
+
var name = "omd-start-work";
|
|
5
|
+
var inject = ["goals"];
|
|
6
|
+
var PLAN_DIR_SEGMENTS = [".omd", "plans"];
|
|
7
|
+
var PLAN_DIR_PREFIX = ".omd/plans/";
|
|
24
8
|
function plansDir(cwd) {
|
|
25
|
-
|
|
9
|
+
return join(cwd, ...PLAN_DIR_SEGMENTS);
|
|
26
10
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolve the user's file-name input to a candidate path inside the plan
|
|
29
|
-
* directory. Accepts a bare file name ("foo.md" / "foo") or the full
|
|
30
|
-
* relative path (".omd/plans/foo.md"); rejects absolute paths and anything
|
|
31
|
-
* that would escape the plan directory.
|
|
32
|
-
*/
|
|
33
11
|
function resolveCandidate(cwd, input) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
12
|
+
const trimmed = String(input).trim().replace(/\\/g, "/");
|
|
13
|
+
if (trimmed === "" || isAbsolute(trimmed))
|
|
14
|
+
return void 0;
|
|
15
|
+
let name2 = trimmed.replace(/^\.\//, "");
|
|
16
|
+
if (name2.includes("/")) {
|
|
17
|
+
if (!name2.startsWith(PLAN_DIR_PREFIX))
|
|
18
|
+
return void 0;
|
|
19
|
+
name2 = name2.slice(PLAN_DIR_PREFIX.length);
|
|
20
|
+
}
|
|
21
|
+
if (name2 === "" || name2.includes("/") || name2 === "." || name2 === "..")
|
|
22
|
+
return void 0;
|
|
23
|
+
if (name2.startsWith("."))
|
|
24
|
+
return void 0;
|
|
25
|
+
return join(plansDir(cwd), name2);
|
|
48
26
|
}
|
|
49
|
-
/** One /start-work invocation through the goal domain. */
|
|
50
27
|
async function executeStartWork(ctx, invocation) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
: "";
|
|
58
|
-
if (cwd === "") {
|
|
59
|
-
return {
|
|
60
|
-
kind: "error",
|
|
61
|
-
text: "This session has no workspace directory; /start-work needs one to find the plan file.",
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
const candidate = resolveCandidate(cwd, invocation.rawInput);
|
|
65
|
-
if (candidate === undefined) {
|
|
66
|
-
return {
|
|
67
|
-
kind: "error",
|
|
68
|
-
text: "Usage: /start-work <plan file name> — the file must live inside " + PLAN_DIR_SEGMENTS.join("/") + "/.",
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
let file = candidate;
|
|
72
|
-
const usable = async (path) => {
|
|
73
|
-
try {
|
|
74
|
-
await access(path);
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
28
|
+
const agent = invocation.agent;
|
|
29
|
+
const cwd = agent !== void 0 && agent.session !== void 0 && agent.session.header !== void 0 && typeof agent.session.header.cwd === "string" ? agent.session.header.cwd : "";
|
|
30
|
+
if (cwd === "") {
|
|
31
|
+
return {
|
|
32
|
+
kind: "error",
|
|
33
|
+
text: "This session has no workspace directory; /start-work needs one to find the plan file."
|
|
80
34
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
text = await readFile(file, "utf8");
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
return {
|
|
96
|
-
kind: "error",
|
|
97
|
-
text: "Cannot read the plan file: " + (error instanceof Error ? error.message : String(error)),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (text.trim() === "") {
|
|
101
|
-
return { kind: "error", text: "The plan file is empty." };
|
|
102
|
-
}
|
|
35
|
+
}
|
|
36
|
+
const candidate = resolveCandidate(cwd, invocation.rawInput);
|
|
37
|
+
if (candidate === void 0) {
|
|
38
|
+
return {
|
|
39
|
+
kind: "error",
|
|
40
|
+
text: "Usage: /start-work <plan file name> \u2014 the file must live inside " + PLAN_DIR_SEGMENTS.join("/") + "/."
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
let file = candidate;
|
|
44
|
+
const usable = async (path) => {
|
|
103
45
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
text: `A goal is already ${current.phase}. Run /goal clear first, then /start-work <plan file name>.`,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
ctx.goals.create(agent, {
|
|
112
|
-
objective: "Execute the approved plan file at " +
|
|
113
|
-
file +
|
|
114
|
-
". Read the file in full, then carry out every step autonomously: implement, verify, and iterate until the plan's goal and success criteria are met. Work through goal continuation rounds until done.",
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
kind: "success",
|
|
118
|
-
text: "Start work armed — executing the plan to completion.\nPlan: " +
|
|
119
|
-
PLAN_DIR_SEGMENTS.join("/") +
|
|
120
|
-
"/" +
|
|
121
|
-
file.slice(plansDir(cwd).length + 1),
|
|
122
|
-
};
|
|
46
|
+
await access(path);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
123
50
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
51
|
+
};
|
|
52
|
+
if (!await usable(file) && !file.endsWith(".md") && await usable(file + ".md")) {
|
|
53
|
+
file = file + ".md";
|
|
54
|
+
} else if (!await usable(file)) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "error",
|
|
57
|
+
text: "Plan file not found: " + PLAN_DIR_SEGMENTS.join("/") + "/" + file.slice(plansDir(cwd).length + 1)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
let text;
|
|
61
|
+
try {
|
|
62
|
+
text = await readFile(file, "utf8");
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
kind: "error",
|
|
66
|
+
text: "Cannot read the plan file: " + (error instanceof Error ? error.message : String(error))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (text.trim() === "") {
|
|
70
|
+
return { kind: "error", text: "The plan file is empty." };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const current = ctx.goals.get(agent);
|
|
74
|
+
if (current !== void 0 && current.phase !== "complete") {
|
|
75
|
+
return {
|
|
76
|
+
kind: "error",
|
|
77
|
+
text: `A goal is already ${current.phase}. Run /goal clear first, then /start-work <plan file name>.`
|
|
78
|
+
};
|
|
129
79
|
}
|
|
80
|
+
ctx.goals.create(agent, {
|
|
81
|
+
objective: "Execute the approved plan file at " + file + ". Read the file in full, then carry out every step autonomously: implement, verify, and iterate until the plan's goal and success criteria are met. Work through goal continuation rounds until done."
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
kind: "success",
|
|
85
|
+
text: "Start work armed \u2014 executing the plan to completion.\nPlan: " + PLAN_DIR_SEGMENTS.join("/") + "/" + file.slice(plansDir(cwd).length + 1)
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
kind: "error",
|
|
90
|
+
text: "start-work failed: " + (error instanceof Error ? error.message : String(error))
|
|
91
|
+
};
|
|
92
|
+
}
|
|
130
93
|
}
|
|
131
94
|
function apply(ctx) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
images: false,
|
|
142
|
-
},
|
|
143
|
-
handler: (invocation) => executeStartWork(ctx, invocation),
|
|
144
|
-
});
|
|
95
|
+
ctx.inject(["commands"], (commandCtx) => {
|
|
96
|
+
commandCtx.commands.register({
|
|
97
|
+
name: "start-work",
|
|
98
|
+
description: "start work: arm a goal that executes the named plan file to completion",
|
|
99
|
+
input: {
|
|
100
|
+
hint: "<plan file name>",
|
|
101
|
+
images: false
|
|
102
|
+
},
|
|
103
|
+
handler: (invocation) => executeStartWork(ctx, invocation)
|
|
145
104
|
});
|
|
105
|
+
});
|
|
146
106
|
}
|
|
147
|
-
export {
|
|
107
|
+
export {
|
|
108
|
+
apply,
|
|
109
|
+
inject,
|
|
110
|
+
name
|
|
111
|
+
};
|