@ordewell/cli 0.7.2 → 0.8.1
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 +6 -4
- package/dist/main.js +265 -414
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -100,7 +100,7 @@ an id.
|
|
|
100
100
|
Planning:
|
|
101
101
|
ordewell Full-screen terminal UI (everything the VS Code extension does)
|
|
102
102
|
ordewell tui The same thing, named \u2014 for scripts and aliases
|
|
103
|
-
ordewell plan --goal "text" Plan a goal conversationally
|
|
103
|
+
ordewell plan --goal "text" Plan a goal conversationally
|
|
104
104
|
ordewell run Execute the last generated plan (streams status)
|
|
105
105
|
ordewell approve Sign off a plan paused for review, and continue it
|
|
106
106
|
ordewell stop Stop execution of the last session (or --server)
|
|
@@ -123,7 +123,6 @@ Tasks (<id> is an order number or a task ID):
|
|
|
123
123
|
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
124
124
|
ordewell checkpoint <id> approve|reject [reason] Answer the checkpoint a task waits at
|
|
125
125
|
ordewell cancel <id> Kill a running task
|
|
126
|
-
ordewell terminal <id> Open a real terminal attached to a task's runner
|
|
127
126
|
|
|
128
127
|
Per-task assignment (omit the value to list the options):
|
|
129
128
|
ordewell task-runner <id> [runner] Set a task's executor (re-picks its model, effort and mode)
|
|
@@ -131,6 +130,7 @@ Per-task assignment (omit the value to list the options):
|
|
|
131
130
|
ordewell task-effort <id> [level] Set a task's thinking effort ("default" to clear)
|
|
132
131
|
ordewell task-mode <id> [mode] Set a task's runner mode
|
|
133
132
|
ordewell task-deps <id> [a,b|none] Set which earlier tasks a task waits for
|
|
133
|
+
ordewell task-skills <id> [a,b|none] Set the skills attached to a task (applies-to: task); none clears them
|
|
134
134
|
ordewell task-ops <id> [on|off] Make a task an ops task (runs in your checkout once its dependencies are merged) or a change task
|
|
135
135
|
|
|
136
136
|
Planner, models and runners:
|
|
@@ -141,19 +141,14 @@ Planner, models and runners:
|
|
|
141
141
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
142
142
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
143
143
|
ordewell auto [full|guarded] Autonomy level for new sessions: Full or Guarded
|
|
144
|
-
ordewell
|
|
145
|
-
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
144
|
+
ordewell parallel [<n>] How many AI tasks run at once (default 5; applies to a live run)
|
|
146
145
|
ordewell refresh Re-discover runners and model catalogs
|
|
147
146
|
ordewell models List every provider's catalog (works without a server)
|
|
148
147
|
|
|
149
|
-
Modes:
|
|
150
|
-
ordewell tdd [on|off] Toggle Test-Driven Development mode (or show status)
|
|
151
|
-
ordewell verify [on|off] Toggle verification mode \u2014 adds a final evidence-based task that runs the full suite (or show status)
|
|
152
|
-
|
|
153
148
|
Other:
|
|
154
149
|
ordewell web Start the API server (foreground; --daemon for background)
|
|
155
150
|
ordewell setup Interactive first-run setup wizard
|
|
156
|
-
ordewell
|
|
151
|
+
ordewell skills List installed skills and shadowed workspace duplicates
|
|
157
152
|
ordewell --help Show this help
|
|
158
153
|
ordewell --version Print the installed version (alias: version, -v)
|
|
159
154
|
|
|
@@ -171,7 +166,7 @@ Plan options:
|
|
|
171
166
|
--goal "text" Task description (required)
|
|
172
167
|
--runner <id> Runner to use (repeatable; default: all enabled runners)
|
|
173
168
|
--workspace /path Workspace directory (default: cwd)
|
|
174
|
-
--no-chat One-shot plan; skips the planner dialogue
|
|
169
|
+
--no-chat One-shot plan; skips the planner dialogue
|
|
175
170
|
|
|
176
171
|
Run options:
|
|
177
172
|
--session-id <id> Execute a specific session (default: last session)
|
|
@@ -185,6 +180,10 @@ Sessions options:
|
|
|
185
180
|
--workspace /path Workspace to list sessions from (default: cwd)
|
|
186
181
|
--json Output the list as JSON
|
|
187
182
|
|
|
183
|
+
Skills options:
|
|
184
|
+
--workspace /path Workspace to read skills from (default: cwd)
|
|
185
|
+
--json Output resolved skills and shadowed duplicates as JSON
|
|
186
|
+
|
|
188
187
|
Task options (add-task):
|
|
189
188
|
--title "text" Task title (required)
|
|
190
189
|
--description "..." Task description (default: the title)
|
|
@@ -224,7 +223,7 @@ Environment:
|
|
|
224
223
|
ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
|
|
225
224
|
ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
|
|
226
225
|
ORDEWELL_RESEARCH_ENABLED true (default) or false
|
|
227
|
-
ORDEWELL_MAX_PARALLEL Default:
|
|
226
|
+
ORDEWELL_MAX_PARALLEL Default: 5, set with "ordewell parallel"
|
|
228
227
|
`);
|
|
229
228
|
}
|
|
230
229
|
|
|
@@ -247,13 +246,13 @@ function oldPidFileFor(port) {
|
|
|
247
246
|
return port === DEFAULT_PORT ? (0, import_path2.join)(OLD_CONFIG_DIR, "server.pid") : (0, import_path2.join)(OLD_CONFIG_DIR, `server-${port}.pid`);
|
|
248
247
|
}
|
|
249
248
|
function httpGet(url, timeoutMs = 2e3, headers = {}) {
|
|
250
|
-
return new Promise((
|
|
249
|
+
return new Promise((resolve4, reject) => {
|
|
251
250
|
const req = import_http.default.get(url, { timeout: timeoutMs, headers }, (res) => {
|
|
252
251
|
let data = "";
|
|
253
252
|
res.on("data", (chunk) => {
|
|
254
253
|
data += chunk.toString();
|
|
255
254
|
});
|
|
256
|
-
res.on("end", () =>
|
|
255
|
+
res.on("end", () => resolve4({ status: res.statusCode || 0, data }));
|
|
257
256
|
});
|
|
258
257
|
req.on("error", reject);
|
|
259
258
|
req.on("timeout", () => {
|
|
@@ -473,7 +472,7 @@ var ApiClient = class _ApiClient {
|
|
|
473
472
|
return res.data;
|
|
474
473
|
}
|
|
475
474
|
rawRequest(method, urlPath, body) {
|
|
476
|
-
return new Promise((
|
|
475
|
+
return new Promise((resolve4, reject) => {
|
|
477
476
|
const url = new URL(urlPath, `http://127.0.0.1:${this.port}`);
|
|
478
477
|
const configuredTimeout = Number.parseInt(process.env.ORDEWELL_HTTP_TIMEOUT_MS || "", 10);
|
|
479
478
|
const token = this.token();
|
|
@@ -498,9 +497,9 @@ var ApiClient = class _ApiClient {
|
|
|
498
497
|
});
|
|
499
498
|
res.on("end", () => {
|
|
500
499
|
try {
|
|
501
|
-
|
|
500
|
+
resolve4({ status: res.statusCode || 0, data: JSON.parse(data) });
|
|
502
501
|
} catch {
|
|
503
|
-
|
|
502
|
+
resolve4({ status: res.statusCode || 0, data });
|
|
504
503
|
}
|
|
505
504
|
});
|
|
506
505
|
});
|
|
@@ -514,17 +513,17 @@ var ApiClient = class _ApiClient {
|
|
|
514
513
|
});
|
|
515
514
|
}
|
|
516
515
|
async generatePlan(goal, runners2, workspace, sessionId = (0, import_core3.mintSessionId)()) {
|
|
517
|
-
const { plan, models, modelsByRunner } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
|
|
516
|
+
const { plan, models, modelsByRunner, notes } = await this.call("POST", `/api/plans/${sessionId}/generate`, "Plan generation failed", {
|
|
518
517
|
goal,
|
|
519
518
|
runners: runners2 || void 0,
|
|
520
519
|
workspace
|
|
521
520
|
});
|
|
522
|
-
return { sessionId, plan, models, modelsByRunner };
|
|
521
|
+
return { sessionId, plan, models, modelsByRunner, ...notes ? { notes } : {} };
|
|
523
522
|
}
|
|
524
523
|
/**
|
|
525
|
-
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`,
|
|
526
|
-
*
|
|
527
|
-
*
|
|
524
|
+
* Open the ADR-0002 planner dialogue. Unlike `generatePlan`, the returned plan
|
|
525
|
+
* may be a question (empty `tasks`, last word in `conversationHistory`) rather
|
|
526
|
+
* than a committed plan.
|
|
528
527
|
*/
|
|
529
528
|
async startConversation(sessionId, goal, runners2, workspace, allowInit) {
|
|
530
529
|
const res = await this.call("POST", `/api/plans/${sessionId}/converse/start`, "Planning failed", {
|
|
@@ -754,7 +753,7 @@ var ApiClient = class _ApiClient {
|
|
|
754
753
|
streamExecution(sessionId, onEvent, onReady) {
|
|
755
754
|
let endStream = () => {
|
|
756
755
|
};
|
|
757
|
-
const stream = new Promise((
|
|
756
|
+
const stream = new Promise((resolve4, reject) => {
|
|
758
757
|
this.executionStreams.get(sessionId)?.();
|
|
759
758
|
const socket = this.openSessionSocket(sessionId);
|
|
760
759
|
let resolved = false;
|
|
@@ -767,7 +766,7 @@ var ApiClient = class _ApiClient {
|
|
|
767
766
|
if (resolved) return;
|
|
768
767
|
resolved = true;
|
|
769
768
|
socket.close();
|
|
770
|
-
|
|
769
|
+
resolve4();
|
|
771
770
|
};
|
|
772
771
|
this.executionStreams.set(sessionId, end);
|
|
773
772
|
endStream = end;
|
|
@@ -791,7 +790,7 @@ var ApiClient = class _ApiClient {
|
|
|
791
790
|
resolved = true;
|
|
792
791
|
if (opened) {
|
|
793
792
|
socket.close();
|
|
794
|
-
|
|
793
|
+
resolve4("lost");
|
|
795
794
|
} else {
|
|
796
795
|
reject(err);
|
|
797
796
|
}
|
|
@@ -801,7 +800,7 @@ var ApiClient = class _ApiClient {
|
|
|
801
800
|
if (!opened) onReady?.(new Error("Execution stream closed before connecting"));
|
|
802
801
|
if (!resolved) {
|
|
803
802
|
resolved = true;
|
|
804
|
-
|
|
803
|
+
resolve4("lost");
|
|
805
804
|
}
|
|
806
805
|
});
|
|
807
806
|
});
|
|
@@ -829,10 +828,10 @@ var ApiClient = class _ApiClient {
|
|
|
829
828
|
*/
|
|
830
829
|
streamPlanning(sessionId, onEvent) {
|
|
831
830
|
const socket = this.openSessionSocket(sessionId);
|
|
832
|
-
const ready = new Promise((
|
|
833
|
-
socket.once("open", () =>
|
|
834
|
-
socket.once("error", () =>
|
|
835
|
-
socket.once("close", () =>
|
|
831
|
+
const ready = new Promise((resolve4) => {
|
|
832
|
+
socket.once("open", () => resolve4());
|
|
833
|
+
socket.once("error", () => resolve4());
|
|
834
|
+
socket.once("close", () => resolve4());
|
|
836
835
|
});
|
|
837
836
|
socket.on("message", (data) => {
|
|
838
837
|
try {
|
|
@@ -844,12 +843,6 @@ var ApiClient = class _ApiClient {
|
|
|
844
843
|
});
|
|
845
844
|
return { ready, close: () => socket.close() };
|
|
846
845
|
}
|
|
847
|
-
getCommands() {
|
|
848
|
-
return this.call("GET", "/api/commands", "Failed to fetch commands");
|
|
849
|
-
}
|
|
850
|
-
sendCommand(name, args = {}) {
|
|
851
|
-
return this.call("POST", `/api/commands/${name}`, `Command ${name} failed`, { args });
|
|
852
|
-
}
|
|
853
846
|
getSettings() {
|
|
854
847
|
return this.call("GET", "/api/settings", "Failed to fetch settings");
|
|
855
848
|
}
|
|
@@ -1111,10 +1104,15 @@ function hasCommittedPlan(plan) {
|
|
|
1111
1104
|
function lastPlannerMessage(plan) {
|
|
1112
1105
|
const history = "conversationHistory" in plan && plan.conversationHistory || [];
|
|
1113
1106
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
1114
|
-
if (history[i].role === "assistant") return history[i].content;
|
|
1107
|
+
if (history[i].role === "assistant" && history[i].kind !== "skill_load") return history[i].content;
|
|
1115
1108
|
}
|
|
1116
1109
|
return "(the planner returned no plan and no message)";
|
|
1117
1110
|
}
|
|
1111
|
+
function commitNotes(plan) {
|
|
1112
|
+
const history = "conversationHistory" in plan && plan.conversationHistory || [];
|
|
1113
|
+
const marker2 = history.map((e) => e.kind).lastIndexOf("plan_generated");
|
|
1114
|
+
return marker2 < 0 ? [] : history.slice(marker2 + 1).filter((e) => e.kind === "system").map((e) => e.content);
|
|
1115
|
+
}
|
|
1118
1116
|
function stdinReader(input = process.stdin) {
|
|
1119
1117
|
const rl = (0, import_readline.createInterface)({ input, output: process.stderr });
|
|
1120
1118
|
const buffered = [];
|
|
@@ -1122,9 +1120,9 @@ function stdinReader(input = process.stdin) {
|
|
|
1122
1120
|
let ended = false;
|
|
1123
1121
|
const settle = (answer) => {
|
|
1124
1122
|
if (!waiting) return false;
|
|
1125
|
-
const
|
|
1123
|
+
const resolve4 = waiting;
|
|
1126
1124
|
waiting = null;
|
|
1127
|
-
|
|
1125
|
+
resolve4(answer);
|
|
1128
1126
|
return true;
|
|
1129
1127
|
};
|
|
1130
1128
|
rl.on("line", (line) => {
|
|
@@ -1140,8 +1138,8 @@ function stdinReader(input = process.stdin) {
|
|
|
1140
1138
|
if (ended) return Promise.resolve(null);
|
|
1141
1139
|
rl.setPrompt(question);
|
|
1142
1140
|
rl.prompt();
|
|
1143
|
-
return new Promise((
|
|
1144
|
-
waiting =
|
|
1141
|
+
return new Promise((resolve4) => {
|
|
1142
|
+
waiting = resolve4;
|
|
1145
1143
|
});
|
|
1146
1144
|
},
|
|
1147
1145
|
close() {
|
|
@@ -1275,6 +1273,7 @@ async function handlePlan(subArgs, deps = {}) {
|
|
|
1275
1273
|
await stream.ready;
|
|
1276
1274
|
let plan;
|
|
1277
1275
|
let models = [];
|
|
1276
|
+
let notes = [];
|
|
1278
1277
|
if (oneShot) {
|
|
1279
1278
|
const result = await withSpinner(
|
|
1280
1279
|
"Researching codebase and building task plan",
|
|
@@ -1282,6 +1281,7 @@ async function handlePlan(subArgs, deps = {}) {
|
|
|
1282
1281
|
);
|
|
1283
1282
|
plan = result.plan;
|
|
1284
1283
|
models = result.models ?? [];
|
|
1284
|
+
notes = result.notes ?? [];
|
|
1285
1285
|
} else {
|
|
1286
1286
|
plan = await withSpinner(
|
|
1287
1287
|
"Researching codebase and building task plan",
|
|
@@ -1313,11 +1313,14 @@ ${lastPlannerMessage(plan)}
|
|
|
1313
1313
|
plan = await withSpinner("Thinking", () => api.sendConversationMessage(sessionId, reply));
|
|
1314
1314
|
}
|
|
1315
1315
|
reader?.close();
|
|
1316
|
+
notes = commitNotes(plan);
|
|
1316
1317
|
}
|
|
1317
1318
|
closeStream();
|
|
1318
1319
|
if (isTty && lastLineLength > 0) process.stderr.write("\r\x1B[2K");
|
|
1319
1320
|
process.stderr.write("\n");
|
|
1320
1321
|
printPlan(plan, sessionId, runners2, models);
|
|
1322
|
+
for (const note of notes) console.log(`
|
|
1323
|
+
${note}`);
|
|
1321
1324
|
saveLastSession(sessionId, goal, runnersOf(plan, runners2), workspace);
|
|
1322
1325
|
console.log(`
|
|
1323
1326
|
Run 'ordewell run' to execute, 'ordewell status' to inspect, or 'ordewell tui' for the full UI.`);
|
|
@@ -1520,7 +1523,7 @@ async function askYesNo(question) {
|
|
|
1520
1523
|
if (!process.stdin.isTTY) return false;
|
|
1521
1524
|
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
|
|
1522
1525
|
try {
|
|
1523
|
-
const answer = await new Promise((
|
|
1526
|
+
const answer = await new Promise((resolve4) => rl.question(`${question} [y/N] `, resolve4));
|
|
1524
1527
|
return /^y(es)?$/i.test(answer.trim());
|
|
1525
1528
|
} finally {
|
|
1526
1529
|
rl.close();
|
|
@@ -1552,7 +1555,8 @@ function toTaskView(task) {
|
|
|
1552
1555
|
taskMode: task.taskMode || void 0,
|
|
1553
1556
|
assignedModel: task.assignedModel ?? void 0,
|
|
1554
1557
|
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
|
|
1555
|
-
...task.ops ? { ops: true } : {}
|
|
1558
|
+
...task.ops ? { ops: true } : {},
|
|
1559
|
+
...task.skills?.length ? { skills: [...task.skills] } : {}
|
|
1556
1560
|
};
|
|
1557
1561
|
}
|
|
1558
1562
|
function taskViews(plan) {
|
|
@@ -1602,13 +1606,13 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1602
1606
|
let pausedAtGate = false;
|
|
1603
1607
|
let noteBlocked = () => {
|
|
1604
1608
|
};
|
|
1605
|
-
const blockedSeen = new Promise((
|
|
1606
|
-
noteBlocked =
|
|
1609
|
+
const blockedSeen = new Promise((resolve4) => {
|
|
1610
|
+
noteBlocked = resolve4;
|
|
1607
1611
|
});
|
|
1608
1612
|
let settleReady = () => {
|
|
1609
1613
|
};
|
|
1610
|
-
const streamReady = new Promise((
|
|
1611
|
-
settleReady = (error) => error ? reject(error) :
|
|
1614
|
+
const streamReady = new Promise((resolve4, reject) => {
|
|
1615
|
+
settleReady = (error) => error ? reject(error) : resolve4();
|
|
1612
1616
|
});
|
|
1613
1617
|
const stream = api.streamExecution(sessionId, (event) => {
|
|
1614
1618
|
if (event.type === "task_started") {
|
|
@@ -2031,100 +2035,60 @@ function handleSetup(subArgs) {
|
|
|
2031
2035
|
console.log("Review and edit the file, then run: ordewell tui");
|
|
2032
2036
|
}
|
|
2033
2037
|
|
|
2034
|
-
// src/commands/
|
|
2038
|
+
// src/commands/skills.ts
|
|
2035
2039
|
var import_core15 = require("@ordewell/core");
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
if (
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
)
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}
|
|
2066
|
-
if (
|
|
2067
|
-
|
|
2068
|
-
if (!source) {
|
|
2069
|
-
console.error("Usage: ordewell plugins install <path|github:user/repo|name>");
|
|
2070
|
-
process.exit(1);
|
|
2071
|
-
}
|
|
2072
|
-
try {
|
|
2073
|
-
const resolved = (0, import_core15.classifyPluginSource)(source);
|
|
2074
|
-
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
2075
|
-
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
2076
|
-
} catch (err) {
|
|
2077
|
-
console.error(`Install failed: ${err.message}`);
|
|
2078
|
-
process.exit(1);
|
|
2079
|
-
}
|
|
2080
|
-
return;
|
|
2081
|
-
}
|
|
2082
|
-
if (sub === "remove") {
|
|
2083
|
-
const name = subArgs[1];
|
|
2084
|
-
if (!name) {
|
|
2085
|
-
console.error("Usage: ordewell plugins remove <name>");
|
|
2086
|
-
process.exit(1);
|
|
2087
|
-
}
|
|
2088
|
-
try {
|
|
2089
|
-
registry.remove(name);
|
|
2090
|
-
console.log(`Removed plugin: ${name}`);
|
|
2091
|
-
} catch (err) {
|
|
2092
|
-
console.error(`Remove failed: ${err.message}`);
|
|
2093
|
-
process.exit(1);
|
|
2094
|
-
}
|
|
2040
|
+
var import_path5 = require("path");
|
|
2041
|
+
function invocation(skill) {
|
|
2042
|
+
if (skill.userInvocable && skill.modelInvocable) return "both";
|
|
2043
|
+
if (skill.userInvocable) return "user";
|
|
2044
|
+
if (skill.modelInvocable) return "model";
|
|
2045
|
+
return "none";
|
|
2046
|
+
}
|
|
2047
|
+
function handleSkills(subArgs) {
|
|
2048
|
+
if (positionals(subArgs).length > 0) {
|
|
2049
|
+
fail("Usage: ordewell skills [--workspace /path] [--json]");
|
|
2050
|
+
}
|
|
2051
|
+
const workspace = (0, import_path5.resolve)(flag(subArgs, "--workspace") || process.cwd());
|
|
2052
|
+
const catalog = (0, import_core15.createSkillsService)(workspace, new import_core15.EnvConfig().workspaceRepos).readCatalog();
|
|
2053
|
+
const skills = catalog.skills.map((skill) => ({
|
|
2054
|
+
name: skill.name,
|
|
2055
|
+
scope: skill.source,
|
|
2056
|
+
appliesTo: skill.appliesTo,
|
|
2057
|
+
invocation: invocation(skill),
|
|
2058
|
+
path: (0, import_core15.abbreviateHome)(skill.path)
|
|
2059
|
+
}));
|
|
2060
|
+
const shadowed = catalog.shadowed.map(({ skill, shadowedBy }) => ({
|
|
2061
|
+
name: skill.name,
|
|
2062
|
+
path: (0, import_core15.abbreviateHome)(skill.path),
|
|
2063
|
+
shadowedBy: { scope: shadowedBy.source, path: (0, import_core15.abbreviateHome)(shadowedBy.path) }
|
|
2064
|
+
}));
|
|
2065
|
+
const invalid = catalog.invalid.map((skill) => ({
|
|
2066
|
+
name: skill.name,
|
|
2067
|
+
path: (0, import_core15.abbreviateHome)(skill.path),
|
|
2068
|
+
reason: skill.reason
|
|
2069
|
+
}));
|
|
2070
|
+
if (hasFlag(subArgs, "--json")) {
|
|
2071
|
+
console.log(JSON.stringify({ skills, shadowed, invalid }, null, 2));
|
|
2095
2072
|
return;
|
|
2096
2073
|
}
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
const
|
|
2104
|
-
console.log(`Created plugin skeleton: ${dir}`);
|
|
2105
|
-
console.log(` Edit ${dir}/manifest.json to configure your runner`);
|
|
2106
|
-
console.log(` Then install with: ordewell plugins install ${dir}`);
|
|
2074
|
+
const skipped = [
|
|
2075
|
+
...shadowed.map((skill) => `workspace skill "${skill.name}" shadowed by the ${skill.shadowedBy.scope} copy at ${skill.shadowedBy.path} \xB7 ${skill.path}`),
|
|
2076
|
+
...invalid.map((skill) => `skill folder "${skill.name}" skipped: ${skill.reason} \xB7 ${skill.path}`)
|
|
2077
|
+
];
|
|
2078
|
+
if (skills.length === 0) {
|
|
2079
|
+
console.log("No skills installed.");
|
|
2080
|
+
for (const line of skipped) console.log(line);
|
|
2107
2081
|
return;
|
|
2108
2082
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
const api = new ApiClient(port);
|
|
2119
|
-
if (action === "on" || action === "off") {
|
|
2120
|
-
const result = await api.sendCommand("tdd", { action });
|
|
2121
|
-
const state = result.settings?.tdd;
|
|
2122
|
-
console.log(`TDD mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
2123
|
-
} else {
|
|
2124
|
-
const settings = await api.getSettings();
|
|
2125
|
-
const tdd = settings.tdd;
|
|
2126
|
-
console.log(`TDD mode: ${tdd?.enabled ? "ON" : "OFF"}`);
|
|
2127
|
-
console.log("Usage: ordewell tdd [on|off]");
|
|
2083
|
+
const headers = ["Name", "Scope", "Applies-to", "Invocation", "SKILL.md"];
|
|
2084
|
+
const rows = skills.map((skill) => [skill.name, skill.scope, skill.appliesTo, skill.invocation, skill.path]);
|
|
2085
|
+
const widths = headers.map((header, i) => Math.max(header.length, ...rows.map((row) => row[i].length)));
|
|
2086
|
+
const format = (row) => row.map((cell, i) => i === row.length - 1 ? cell : cell.padEnd(widths[i])).join(" ");
|
|
2087
|
+
console.log(format(headers));
|
|
2088
|
+
for (const row of rows) console.log(format(row));
|
|
2089
|
+
if (skipped.length > 0) {
|
|
2090
|
+
console.log("");
|
|
2091
|
+
for (const line of skipped) console.log(line);
|
|
2128
2092
|
}
|
|
2129
2093
|
}
|
|
2130
2094
|
|
|
@@ -2214,39 +2178,6 @@ async function handleShow(args, api) {
|
|
|
2214
2178
|
}
|
|
2215
2179
|
}
|
|
2216
2180
|
|
|
2217
|
-
// src/commands/verify.ts
|
|
2218
|
-
async function handleVerify(subArgs, api) {
|
|
2219
|
-
const action = subArgs[0];
|
|
2220
|
-
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
2221
|
-
const client = api || new ApiClient(port);
|
|
2222
|
-
if (action === "on" || action === "off") {
|
|
2223
|
-
const result = await client.sendCommand("verify", { action });
|
|
2224
|
-
const state = result.settings?.verification;
|
|
2225
|
-
console.log(`Verification mode: ${state?.enabled ? "ON" : "OFF"}`);
|
|
2226
|
-
} else {
|
|
2227
|
-
const settings = await client.getSettings();
|
|
2228
|
-
const verification = settings.verification;
|
|
2229
|
-
console.log(`Verification mode: ${verification?.enabled ? "ON" : "OFF"}`);
|
|
2230
|
-
console.log("Usage: ordewell verify [on|off]");
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
// src/commands/transport.ts
|
|
2235
|
-
var describe = (transport) => `Runner transport: ${transport === "terminal" ? "terminal" : "structured"} \u2014 applies from the next run`;
|
|
2236
|
-
async function handleTransport(subArgs, api) {
|
|
2237
|
-
const action = subArgs[0];
|
|
2238
|
-
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
2239
|
-
const client = api || new ApiClient(port);
|
|
2240
|
-
if (action === "terminal" || action === "structured") {
|
|
2241
|
-
const result = await client.sendCommand("transport", { action });
|
|
2242
|
-
console.log(describe(result.settings?.runnerTransport));
|
|
2243
|
-
} else {
|
|
2244
|
-
const settings = await client.getSettings();
|
|
2245
|
-
console.log(describe(settings.runnerTransport));
|
|
2246
|
-
console.log("Usage: ordewell transport [terminal|structured]");
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2249
|
-
|
|
2250
2181
|
// src/commands/task-control.ts
|
|
2251
2182
|
var import_core16 = require("@ordewell/core");
|
|
2252
2183
|
var PAST_TENSE = {
|
|
@@ -3267,130 +3198,8 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3267
3198
|
});
|
|
3268
3199
|
}
|
|
3269
3200
|
|
|
3270
|
-
// src/tui/terminalLauncher.ts
|
|
3271
|
-
var import_child_process3 = require("child_process");
|
|
3272
|
-
var import_util = require("util");
|
|
3273
|
-
var import_core22 = require("@ordewell/core");
|
|
3274
|
-
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3275
|
-
var defaultExecFile = async (command, args) => {
|
|
3276
|
-
const { stdout, stderr } = await execFileAsync(command, args);
|
|
3277
|
-
return { stdout: String(stdout), stderr: String(stderr) };
|
|
3278
|
-
};
|
|
3279
|
-
var defaultSpawn = (command, args) => {
|
|
3280
|
-
(0, import_child_process3.spawn)(command, args, { detached: true, stdio: "ignore" }).unref();
|
|
3281
|
-
};
|
|
3282
|
-
var defaultWhich = (bin) => {
|
|
3283
|
-
try {
|
|
3284
|
-
(0, import_child_process3.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
|
|
3285
|
-
return true;
|
|
3286
|
-
} catch {
|
|
3287
|
-
return false;
|
|
3288
|
-
}
|
|
3289
|
-
};
|
|
3290
|
-
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3291
|
-
const platform = deps.platform ?? process.platform;
|
|
3292
|
-
const env = deps.env ?? process.env;
|
|
3293
|
-
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core22.hasTmux;
|
|
3294
|
-
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3295
|
-
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3296
|
-
const which = deps.which ?? defaultWhich;
|
|
3297
|
-
if (!hasTmuxImpl()) {
|
|
3298
|
-
return { ok: false, message: "No terminal window for this task: tmux is not installed. Install tmux to get one for terminal-transport tasks; a structured task has its log view instead." };
|
|
3299
|
-
}
|
|
3300
|
-
const session = (0, import_core22.tmuxSessionName)(port);
|
|
3301
|
-
const socket = (0, import_core22.tmuxSocketName)(port);
|
|
3302
|
-
const base = (0, import_core22.tmuxWindowName)(taskId, planSessionId);
|
|
3303
|
-
let windows;
|
|
3304
|
-
try {
|
|
3305
|
-
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
3306
|
-
windows = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3307
|
-
} catch {
|
|
3308
|
-
return { ok: false, message: "No terminal session found for this daemon \u2014 is it still running?" };
|
|
3309
|
-
}
|
|
3310
|
-
const window = latestAttempt(windows, base);
|
|
3311
|
-
if (!window) {
|
|
3312
|
-
return { ok: false, message: "This task has no live terminal and no saved log \u2014 it may still be pending." };
|
|
3313
|
-
}
|
|
3314
|
-
const manualHint = `tmux -L ${socket} attach -t ${session}:${window}`;
|
|
3315
|
-
const viewer = `${session}-view-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3316
|
-
const attachCmd = `tmux -L ${socket} new-session -d -s ${viewer} -t ${session} \\; set-option -t ${viewer} destroy-unattached on \\; select-window -t ${viewer}:${window} \\; attach-session -t ${viewer}`;
|
|
3317
|
-
const opened = launchTerminal(platform, env, which, spawnImpl, attachCmd);
|
|
3318
|
-
if (!opened) {
|
|
3319
|
-
return { ok: false, message: `Couldn't open a terminal window automatically \u2014 attach manually: ${manualHint}` };
|
|
3320
|
-
}
|
|
3321
|
-
return { ok: true, message: "Opened a terminal for this task." };
|
|
3322
|
-
}
|
|
3323
|
-
function latestAttempt(windows, base) {
|
|
3324
|
-
let best = null;
|
|
3325
|
-
let bestAttempt = 0;
|
|
3326
|
-
for (const name of windows) {
|
|
3327
|
-
if (name === base && bestAttempt < 1) {
|
|
3328
|
-
best = name;
|
|
3329
|
-
bestAttempt = 1;
|
|
3330
|
-
continue;
|
|
3331
|
-
}
|
|
3332
|
-
const suffix = name.startsWith(`${base}-`) ? Number(name.slice(base.length + 1)) : NaN;
|
|
3333
|
-
if (Number.isInteger(suffix) && suffix > bestAttempt) {
|
|
3334
|
-
best = name;
|
|
3335
|
-
bestAttempt = suffix;
|
|
3336
|
-
}
|
|
3337
|
-
}
|
|
3338
|
-
return best;
|
|
3339
|
-
}
|
|
3340
|
-
function launchTerminal(platform, env, which, spawnImpl, attachCmd) {
|
|
3341
|
-
if (platform === "darwin") {
|
|
3342
|
-
const script = `tell application "Terminal" to do script "${attachCmd.replace(/"/g, '\\"')}"`;
|
|
3343
|
-
spawnImpl("osascript", ["-e", script]);
|
|
3344
|
-
return true;
|
|
3345
|
-
}
|
|
3346
|
-
if (platform === "win32") {
|
|
3347
|
-
if (which("wt.exe")) {
|
|
3348
|
-
spawnImpl("wt.exe", ["-w", "0", "nt", "bash", "-lc", attachCmd]);
|
|
3349
|
-
return true;
|
|
3350
|
-
}
|
|
3351
|
-
if (which("cmd.exe")) {
|
|
3352
|
-
spawnImpl("cmd.exe", ["/c", "start", '""', "bash", "-lc", attachCmd]);
|
|
3353
|
-
return true;
|
|
3354
|
-
}
|
|
3355
|
-
return false;
|
|
3356
|
-
}
|
|
3357
|
-
if (!env.DISPLAY && !env.WAYLAND_DISPLAY) return false;
|
|
3358
|
-
const candidates = [];
|
|
3359
|
-
if (env.TERMINAL) candidates.push([env.TERMINAL, ["-e", "bash", "-lc", attachCmd]]);
|
|
3360
|
-
candidates.push(
|
|
3361
|
-
["x-terminal-emulator", ["-e", "bash", "-lc", attachCmd]],
|
|
3362
|
-
["gnome-terminal", ["--", "bash", "-lc", attachCmd]],
|
|
3363
|
-
["konsole", ["-e", "bash", "-lc", attachCmd]],
|
|
3364
|
-
["xfce4-terminal", ["-e", `bash -lc ${JSON.stringify(attachCmd)}`]],
|
|
3365
|
-
["xterm", ["-e", "bash", "-lc", attachCmd]]
|
|
3366
|
-
);
|
|
3367
|
-
for (const [bin, args] of candidates) {
|
|
3368
|
-
if (which(bin)) {
|
|
3369
|
-
spawnImpl(bin, args);
|
|
3370
|
-
return true;
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
return false;
|
|
3374
|
-
}
|
|
3375
|
-
|
|
3376
|
-
// src/commands/terminal.ts
|
|
3377
|
-
async function handleTerminal(subArgs, injectedApi) {
|
|
3378
|
-
await withResolvedTask(
|
|
3379
|
-
subArgs,
|
|
3380
|
-
"Usage: ordewell terminal <task-id-or-order> [--session-id <id>]",
|
|
3381
|
-
injectedApi,
|
|
3382
|
-
async (_api, sessionId, taskId) => {
|
|
3383
|
-
const result = await openTaskTerminal(resolvePort(subArgs), sessionId, taskId);
|
|
3384
|
-
if (!result.ok) {
|
|
3385
|
-
console.error(result.message);
|
|
3386
|
-
process.exit(1);
|
|
3387
|
-
}
|
|
3388
|
-
console.log(result.message);
|
|
3389
|
-
}
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
|
-
|
|
3393
3201
|
// src/commands/task-assign.ts
|
|
3202
|
+
var import_core22 = require("@ordewell/core");
|
|
3394
3203
|
var import_plan_utils = require("@ordewell/core/plan-utils");
|
|
3395
3204
|
|
|
3396
3205
|
// src/tui/taskAssignment.ts
|
|
@@ -3430,12 +3239,13 @@ function modesForTask(modesByRunner, task) {
|
|
|
3430
3239
|
}
|
|
3431
3240
|
|
|
3432
3241
|
// src/commands/task-assign.ts
|
|
3433
|
-
async function withTask(subArgs, usage, injectedApi, run) {
|
|
3242
|
+
async function withTask(subArgs, usage, injectedApi, run, { joinValue = false } = {}) {
|
|
3434
3243
|
await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId, plan) => {
|
|
3435
3244
|
const tasks = taskViews(plan);
|
|
3436
3245
|
const task = tasks.find((t) => t.id === taskId);
|
|
3437
3246
|
if (!task) fail(`Task not found in the plan: ${taskId}`);
|
|
3438
|
-
const
|
|
3247
|
+
const [, ...rest] = positionals(subArgs);
|
|
3248
|
+
const value = joinValue ? rest.join(" ") || void 0 : rest[0];
|
|
3439
3249
|
await run(api, sessionId, task, tasks, value);
|
|
3440
3250
|
});
|
|
3441
3251
|
}
|
|
@@ -3591,6 +3401,35 @@ Dependencies \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
|
3591
3401
|
console.log(dependencies.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} now depends on ${dependencies.length} task${dependencies.length === 1 ? "" : "s"}.` : `Task ${(0, import_plan_utils.taskRef)(task)} no longer depends on anything.`);
|
|
3592
3402
|
});
|
|
3593
3403
|
}
|
|
3404
|
+
var SKILLS_USAGE = "Usage: ordewell task-skills <task-id-or-order> [<name> [<name> \u2026]|<name,name,\u2026>|none] [--session-id <id>] [--workspace <path>]";
|
|
3405
|
+
async function handleTaskSkills(subArgs, injectedApi) {
|
|
3406
|
+
await withTask(subArgs, SKILLS_USAGE, injectedApi, async (api, sessionId, task, _tasks, value) => {
|
|
3407
|
+
if (task.type !== "ai") fail("Manual tasks do not run an executor, so they take no skills.");
|
|
3408
|
+
const catalog = (0, import_core22.createSkillsService)(flag(subArgs, "--workspace") || process.cwd(), new import_core22.EnvConfig().workspaceRepos).listSkills().filter((s) => s.appliesTo === "task");
|
|
3409
|
+
if (!value) {
|
|
3410
|
+
console.log(`
|
|
3411
|
+
Skills \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3412
|
+
`);
|
|
3413
|
+
if (catalog.length === 0) console.log(" No task skills found (applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/).");
|
|
3414
|
+
for (const s of catalog) {
|
|
3415
|
+
console.log(` ${task.skills?.includes(s.name) ? "*" : " "} ${s.name}${s.description ? ` ${s.description}` : ""}`);
|
|
3416
|
+
}
|
|
3417
|
+
for (const name of task.skills ?? []) {
|
|
3418
|
+
if (!catalog.some((s) => s.name === name)) console.log(` * ${name} (not found)`);
|
|
3419
|
+
}
|
|
3420
|
+
console.log(`
|
|
3421
|
+
* = attached. ${SKILLS_USAGE}`);
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
const skills = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
|
|
3425
|
+
const unknown = skills.filter((name) => !catalog.some((s) => s.name === name));
|
|
3426
|
+
if (unknown.length > 0) {
|
|
3427
|
+
fail(`No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}.`, `Task skills: ${catalog.map((s) => s.name).join(", ") || "none"}`);
|
|
3428
|
+
}
|
|
3429
|
+
await api.updateTask(sessionId, task.id, { skills });
|
|
3430
|
+
console.log(skills.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils.taskRef)(task)} has no skills attached.`);
|
|
3431
|
+
}, { joinValue: true });
|
|
3432
|
+
}
|
|
3594
3433
|
var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
|
|
3595
3434
|
async function handleTaskOps(subArgs, injectedApi) {
|
|
3596
3435
|
await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
|
|
@@ -3761,12 +3600,12 @@ var SLASH_COMMANDS = [
|
|
|
3761
3600
|
{ name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
|
|
3762
3601
|
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
3763
3602
|
{ name: "checkpoint", usage: "/checkpoint <id> approve|reject [reason]", description: "Answer the checkpoint a task waits at: approve lets it go on, reject tells it why not", category: "tasks" },
|
|
3764
|
-
{ name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
|
|
3765
3603
|
{ name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
|
|
3766
3604
|
{ name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
|
|
3767
3605
|
{ name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
|
|
3768
3606
|
{ name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
|
|
3769
3607
|
{ name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
|
|
3608
|
+
{ name: "task-skills", usage: "/task-skills <id> [a,b|none]", description: "Choose or set the skills attached to a task; none clears them", category: "tasks" },
|
|
3770
3609
|
{ name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Make a task an ops task \u2014 run in your checkout once its dependencies are merged \u2014 or a change task", category: "tasks" },
|
|
3771
3610
|
// Models & providers
|
|
3772
3611
|
{ name: "planner", usage: "/planner [<provider>]", description: "Choose who plans \u2014 an API provider or a coding agent (no API key)", category: "models" },
|
|
@@ -3776,12 +3615,8 @@ var SLASH_COMMANDS = [
|
|
|
3776
3615
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3777
3616
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3778
3617
|
{ name: "auto", usage: "/auto [full|guarded]", description: "Autonomy level for new plans: Full or Guarded", category: "models" },
|
|
3779
|
-
{ name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
|
|
3780
3618
|
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3781
3619
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3782
|
-
// Skills
|
|
3783
|
-
{ name: "tdd", usage: "/tdd [on|off]", description: "Test-Driven Development mode", category: "skills" },
|
|
3784
|
-
{ name: "verify", usage: "/verify [on|off]", description: "Verification mode \u2014 final evidence-based task that runs the suite", category: "skills" },
|
|
3785
3620
|
// Sessions
|
|
3786
3621
|
{ name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
|
|
3787
3622
|
{ name: "fork", usage: "/fork", description: "Continue in a copy of this conversation and its tasks; the original stays as it is", category: "session" },
|
|
@@ -3802,11 +3637,11 @@ var skillByName = /* @__PURE__ */ new Map();
|
|
|
3802
3637
|
function registerSkillCommands(skills) {
|
|
3803
3638
|
const map = /* @__PURE__ */ new Map();
|
|
3804
3639
|
for (const skill of skills) {
|
|
3805
|
-
if (BY_NAME.has(skill.name)) continue;
|
|
3640
|
+
if (BY_NAME.has(skill.name) || skill.userInvocable === false) continue;
|
|
3806
3641
|
map.set(skill.name, {
|
|
3807
3642
|
name: skill.name,
|
|
3808
3643
|
usage: `/${skill.name}`,
|
|
3809
|
-
description: skill.description,
|
|
3644
|
+
description: skill.appliesTo === "task" ? `task skill \xB7 ${skill.description}` : skill.description,
|
|
3810
3645
|
category: "skills",
|
|
3811
3646
|
source: "skill"
|
|
3812
3647
|
});
|
|
@@ -4001,9 +3836,29 @@ var ROLE_PREFIX = {
|
|
|
4001
3836
|
function messageLines(block, cols) {
|
|
4002
3837
|
const text = sanitize(block.text);
|
|
4003
3838
|
const room = Math.max(1, cols - 2);
|
|
4004
|
-
const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : wrap(text, room);
|
|
3839
|
+
const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : block.skills ? skillPaintedLines(text, block.skills, room) : wrap(text, room);
|
|
4005
3840
|
return wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block.role](line) : ` ${line}`);
|
|
4006
3841
|
}
|
|
3842
|
+
function skillPaintedLines(text, skills, room) {
|
|
3843
|
+
const tokens = (0, import_core23.loadedSkillTokens)(text, skills);
|
|
3844
|
+
return wrapLines(text, room).map(({ line, start }) => {
|
|
3845
|
+
let painted = "";
|
|
3846
|
+
let at = 0;
|
|
3847
|
+
for (const token of tokens) {
|
|
3848
|
+
const from = Math.max(token.start - start, at);
|
|
3849
|
+
const to = Math.min(token.end - start, line.length);
|
|
3850
|
+
if (to <= from) continue;
|
|
3851
|
+
painted += line.slice(at, from) + style.cyan(line.slice(from, to));
|
|
3852
|
+
at = to;
|
|
3853
|
+
}
|
|
3854
|
+
return painted + line.slice(at);
|
|
3855
|
+
});
|
|
3856
|
+
}
|
|
3857
|
+
function skillLoadLine(block, cols) {
|
|
3858
|
+
const head = block.attaches ? `\u25CF /${sanitize(block.name)} will be attached to fitting tasks \xB7 ` : block.invokedBy === "planner" ? `\u25CF ${sanitize(block.name)} skill loaded by planner \xB7 ` : `\u25CF /${sanitize(block.name)} skill loaded \xB7 `;
|
|
3859
|
+
const line = truncate(`${head}${truncatePath(sanitize(block.path), Math.max(1, cols - width(head)))}`, cols);
|
|
3860
|
+
return `${style.green("\u25CF")}${style.grey(line.slice(1))}`;
|
|
3861
|
+
}
|
|
4007
3862
|
function thinkingLines(block, cols, detailAll) {
|
|
4008
3863
|
const text = sanitize(block.text).trim();
|
|
4009
3864
|
const head = `\u2234 Thinking (${countOf(text ? text.split(/\s+/).length : 0, "word")})`;
|
|
@@ -4047,7 +3902,7 @@ function truncatePath(path, max) {
|
|
|
4047
3902
|
if (width(char + tail) > max - 1) break;
|
|
4048
3903
|
tail = char + tail;
|
|
4049
3904
|
}
|
|
4050
|
-
const slash = tail.
|
|
3905
|
+
const slash = tail.search(/[\\/]/);
|
|
4051
3906
|
return `\u2026${slash > 0 ? tail.slice(slash) : tail}`;
|
|
4052
3907
|
}
|
|
4053
3908
|
function commandHeader(block, cols, detailAll) {
|
|
@@ -4201,6 +4056,8 @@ function blockLines(block, cols, detailAll) {
|
|
|
4201
4056
|
return [approvalLine(block, cols)];
|
|
4202
4057
|
case "plan":
|
|
4203
4058
|
return [planLine(block, cols)];
|
|
4059
|
+
case "skill_load":
|
|
4060
|
+
return [skillLoadLine(block, cols)];
|
|
4204
4061
|
// Pinned under the pane rather than drawn in it — see `tokenLine`.
|
|
4205
4062
|
case "usage":
|
|
4206
4063
|
return [];
|
|
@@ -4216,11 +4073,11 @@ function drawn(block, cols, detailAll) {
|
|
|
4216
4073
|
}
|
|
4217
4074
|
function conversationLines(blocks, cols, detailAll, answering) {
|
|
4218
4075
|
const lines = [];
|
|
4219
|
-
for (const block of blocks) {
|
|
4076
|
+
for (const [i, block] of blocks.entries()) {
|
|
4220
4077
|
if (block.type === "usage") continue;
|
|
4221
4078
|
lines.push(...drawn(block, cols, detailAll));
|
|
4222
4079
|
if (block === answering) lines.push(approvalKeysLine(block, cols));
|
|
4223
|
-
lines.push("");
|
|
4080
|
+
if (blocks[i + 1]?.type !== "skill_load") lines.push("");
|
|
4224
4081
|
}
|
|
4225
4082
|
return lines;
|
|
4226
4083
|
}
|
|
@@ -4249,10 +4106,6 @@ function tokenLine(blocks, cols) {
|
|
|
4249
4106
|
|
|
4250
4107
|
// src/tui/state.ts
|
|
4251
4108
|
var import_core24 = require("@ordewell/core");
|
|
4252
|
-
var SKILL_IDS = ["tdd", "verify"];
|
|
4253
|
-
function noSkills() {
|
|
4254
|
-
return { tdd: false, verify: false };
|
|
4255
|
-
}
|
|
4256
4109
|
function visibleItems(picker2) {
|
|
4257
4110
|
const filter = picker2.filter.trim().toLowerCase();
|
|
4258
4111
|
if (!filter) return picker2.items;
|
|
@@ -4313,12 +4166,12 @@ function initialState(overrides = {}) {
|
|
|
4313
4166
|
taskEditor: null,
|
|
4314
4167
|
scroll: 0,
|
|
4315
4168
|
planScroll: null,
|
|
4316
|
-
skills: noSkills(),
|
|
4317
4169
|
runners: [],
|
|
4318
4170
|
sessions: [],
|
|
4319
4171
|
rewindTargets: null,
|
|
4320
4172
|
models: [],
|
|
4321
4173
|
modesByRunner: {},
|
|
4174
|
+
taskSkills: [],
|
|
4322
4175
|
orchestratorModels: [],
|
|
4323
4176
|
providerErrors: {},
|
|
4324
4177
|
orchestratorModel: "",
|
|
@@ -4328,7 +4181,6 @@ function initialState(overrides = {}) {
|
|
|
4328
4181
|
configuredProviders: [],
|
|
4329
4182
|
allowlist: {},
|
|
4330
4183
|
autonomous: true,
|
|
4331
|
-
runnerTransport: "structured",
|
|
4332
4184
|
mouseCapture: true,
|
|
4333
4185
|
selection: null,
|
|
4334
4186
|
workspace: process.cwd(),
|
|
@@ -4403,12 +4255,13 @@ function footerHints(state) {
|
|
|
4403
4255
|
}
|
|
4404
4256
|
return [
|
|
4405
4257
|
...escHint ? [escHint] : [],
|
|
4406
|
-
"
|
|
4258
|
+
"\u2192 expand/edit",
|
|
4407
4259
|
"R runner",
|
|
4408
4260
|
"o model",
|
|
4409
4261
|
"e effort",
|
|
4410
4262
|
"M mode",
|
|
4411
4263
|
"D deps",
|
|
4264
|
+
"K skills",
|
|
4412
4265
|
"O ops",
|
|
4413
4266
|
"f start",
|
|
4414
4267
|
"E run plan",
|
|
@@ -4418,7 +4271,7 @@ function footerHints(state) {
|
|
|
4418
4271
|
"s skip",
|
|
4419
4272
|
"a add",
|
|
4420
4273
|
"d remove",
|
|
4421
|
-
"
|
|
4274
|
+
"enter log",
|
|
4422
4275
|
...resolveHint,
|
|
4423
4276
|
"pgup/pgdn scroll",
|
|
4424
4277
|
"tab chat"
|
|
@@ -4739,11 +4592,11 @@ function taskLines(state, planRow, index, cols, orderOf) {
|
|
|
4739
4592
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4740
4593
|
const runner = task.assignedRunner ?? "";
|
|
4741
4594
|
const bodyPad = parent ? " " : " ";
|
|
4742
|
-
const
|
|
4743
|
-
const
|
|
4744
|
-
const meta = [activity, runner,
|
|
4595
|
+
const activity = row.approvals ? `${inRow(row.approvals)} \u2014 enter opens it` : row.status === "quiet" ? "quiet" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
|
|
4596
|
+
const skills = (task.skills ?? []).join(" \xB7 ");
|
|
4597
|
+
const meta = [activity, runner, model].filter(Boolean).join(" \xB7 ");
|
|
4745
4598
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4746
|
-
if (
|
|
4599
|
+
if (skills) lines.push(style.grey(truncate(`${bodyPad}skills: ${skills}`, cols)));
|
|
4747
4600
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4748
4601
|
if (row.conflict) {
|
|
4749
4602
|
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
@@ -4798,7 +4651,7 @@ function taskLines(state, planRow, index, cols, orderOf) {
|
|
|
4798
4651
|
lines.push(...taskText("Depends on", row.dependencies.join(", "), cols, bodyPad));
|
|
4799
4652
|
}
|
|
4800
4653
|
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
4801
|
-
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
4654
|
+
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps \xB7 K skills`, cols)));
|
|
4802
4655
|
}
|
|
4803
4656
|
return { lines, editorLine };
|
|
4804
4657
|
}
|
|
@@ -4843,7 +4696,7 @@ function helpLayout(rows, cols) {
|
|
|
4843
4696
|
);
|
|
4844
4697
|
body.push(
|
|
4845
4698
|
"",
|
|
4846
|
-
style.bold("In a task view (
|
|
4699
|
+
style.bold("In a task view (enter on a task)"),
|
|
4847
4700
|
style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the selected queued message"),
|
|
4848
4701
|
style.grey(" ctrl-n/ctrl-p select a queued message \xB7 ctrl-r removes it \xB7 ctrl-x interrupts"),
|
|
4849
4702
|
style.grey(" ctrl-y allows a tool request (its keys sit under it) \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note"),
|
|
@@ -4865,7 +4718,7 @@ function render(state) {
|
|
|
4865
4718
|
const rowsForBody = bodyRows(state);
|
|
4866
4719
|
const body = state.overlay ? renderOverlay(state, rowsForBody, cols) : renderBody(state, rowsForBody, cols);
|
|
4867
4720
|
const lines = [
|
|
4868
|
-
|
|
4721
|
+
renderAutonomy(state, cols),
|
|
4869
4722
|
...body,
|
|
4870
4723
|
renderStatus(state, cols),
|
|
4871
4724
|
...renderInput(state),
|
|
@@ -4950,13 +4803,9 @@ function fit(lines, rows, anchor) {
|
|
|
4950
4803
|
const blanks = Array(rows - lines.length).fill("");
|
|
4951
4804
|
return anchor === "bottom" ? [...blanks, ...lines] : [...lines, ...blanks];
|
|
4952
4805
|
}
|
|
4953
|
-
function
|
|
4954
|
-
const badges = SKILL_IDS.map(
|
|
4955
|
-
(id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
|
|
4956
|
-
);
|
|
4806
|
+
function renderAutonomy(state, cols) {
|
|
4957
4807
|
const auto = state.autonomous ? style.yellow("\u25CF Full") : style.green("\u25CF Guarded");
|
|
4958
|
-
|
|
4959
|
-
return truncate([...badges, auto, ...terminal].join(" "), cols);
|
|
4808
|
+
return truncate(auto, cols);
|
|
4960
4809
|
}
|
|
4961
4810
|
function scrolledBackMark(state) {
|
|
4962
4811
|
return state.focus !== "plan" && state.scroll > 0 ? "\u2191 scrolled back \xB7 pgdn for live" : "";
|
|
@@ -5374,6 +5223,17 @@ function pickerItemsFor(state, action) {
|
|
|
5374
5223
|
detail: candidate.status === "completed" ? "already completed" : void 0
|
|
5375
5224
|
}));
|
|
5376
5225
|
}
|
|
5226
|
+
if (action.kind === "set-task-skills") {
|
|
5227
|
+
const task = findTask(state.tasks, action.taskId);
|
|
5228
|
+
if (!task) return [];
|
|
5229
|
+
const known = new Set(state.taskSkills.map((s) => s.name));
|
|
5230
|
+
return [
|
|
5231
|
+
...state.taskSkills.map((s) => ({ id: s.name, label: s.name, detail: s.description || void 0 })),
|
|
5232
|
+
// Attached but not in the catalog (created by an earlier task's worktree): keep it
|
|
5233
|
+
// selectable so confirming the picker does not silently detach it.
|
|
5234
|
+
...(task.skills ?? []).filter((name) => !known.has(name)).map((name) => ({ id: name, label: `${name} (not found)`, detail: "not found in the catalog" }))
|
|
5235
|
+
];
|
|
5236
|
+
}
|
|
5377
5237
|
if (action.kind === "set-task-mode") {
|
|
5378
5238
|
const task = findTask(state.tasks, action.taskId);
|
|
5379
5239
|
if (!task) return [];
|
|
@@ -5526,11 +5386,6 @@ function openTaskView(state, sessionId, taskId) {
|
|
|
5526
5386
|
};
|
|
5527
5387
|
return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
|
|
5528
5388
|
}
|
|
5529
|
-
function openTaskTerminalOrView(state, sessionId, taskId) {
|
|
5530
|
-
const task = findTask(state.tasks, taskId);
|
|
5531
|
-
if (task?.transport?.kind === "structured") return openTaskView(state, sessionId, taskId);
|
|
5532
|
-
return step(state, [{ type: "openTaskTerminal", sessionId, taskId }]);
|
|
5533
|
-
}
|
|
5534
5389
|
function continueTaskStep(state, sessionId, taskId, text) {
|
|
5535
5390
|
const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
|
|
5536
5391
|
const watch = state.status !== "executing";
|
|
@@ -5575,13 +5430,13 @@ function taskLogArrived(state, action) {
|
|
|
5575
5430
|
function announceApprovals(state, action) {
|
|
5576
5431
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5577
5432
|
const task = findTask(state.tasks, action.taskId);
|
|
5578
|
-
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core28.runnerToolSubject)(event.tool, event.args)} \u2014
|
|
5433
|
+
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core28.runnerToolSubject)(event.tool, event.args)} \u2014 enter on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
|
|
5579
5434
|
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5580
5435
|
}
|
|
5581
5436
|
function announceCheckpoint(state, action) {
|
|
5582
5437
|
const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
|
|
5583
5438
|
const label = task ? `Task ${task.order}` : action.title;
|
|
5584
|
-
const where =
|
|
5439
|
+
const where = "enter on it to answer";
|
|
5585
5440
|
const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core28.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
|
|
5586
5441
|
return { ...spoken, scroll: state.scroll };
|
|
5587
5442
|
}
|
|
@@ -5742,6 +5597,34 @@ function openTaskDepsPicker(state, task) {
|
|
|
5742
5597
|
}
|
|
5743
5598
|
});
|
|
5744
5599
|
}
|
|
5600
|
+
function openTaskSkillsPicker(state, task) {
|
|
5601
|
+
if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
|
|
5602
|
+
const action = { kind: "set-task-skills", taskId: task.id };
|
|
5603
|
+
const items = pickerItemsFor(state, action);
|
|
5604
|
+
if (items.length === 0) {
|
|
5605
|
+
return fail3(state, "No task skills found. A skill with applies-to: task in .ordewell/skills/ or ~/.ordewell/skills/ shows up here.");
|
|
5606
|
+
}
|
|
5607
|
+
return step({
|
|
5608
|
+
...state,
|
|
5609
|
+
overlay: {
|
|
5610
|
+
kind: "picker",
|
|
5611
|
+
picker: picker(`Skills \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
|
|
5612
|
+
hint: "Attached skills go into this task's prompt when it starts.",
|
|
5613
|
+
multi: true,
|
|
5614
|
+
chosen: task.skills ?? []
|
|
5615
|
+
})
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
5618
|
+
}
|
|
5619
|
+
function assignTaskSkills(state, sessionId, task, skills) {
|
|
5620
|
+
return step(state, [{
|
|
5621
|
+
type: "updateTask",
|
|
5622
|
+
sessionId,
|
|
5623
|
+
taskId: task.id,
|
|
5624
|
+
changes: { skills },
|
|
5625
|
+
message: skills.length > 0 ? `Task ${(0, import_plan_utils3.taskRef)(task)} skills set to ${skills.join(", ")}.` : `Task ${(0, import_plan_utils3.taskRef)(task)} has no skills attached.`
|
|
5626
|
+
}]);
|
|
5627
|
+
}
|
|
5745
5628
|
function openTaskModePicker(state, task) {
|
|
5746
5629
|
if (task.type !== "ai") return fail3(state, "Manual tasks do not have an executor mode.");
|
|
5747
5630
|
const modes = modesForTask(state.modesByRunner, task);
|
|
@@ -5882,6 +5765,20 @@ function taskEffortCommand(state, args) {
|
|
|
5882
5765
|
return assignTaskEffort(state, sessionId, task, value);
|
|
5883
5766
|
});
|
|
5884
5767
|
}
|
|
5768
|
+
function taskSkillsCommand(state, args) {
|
|
5769
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
5770
|
+
const task = findTask(state.tasks, taskId);
|
|
5771
|
+
if (args.length < 2) return openTaskSkillsPicker(state, task);
|
|
5772
|
+
if (task.type !== "ai") return fail3(state, "Manual tasks do not run an executor, so they take no skills.");
|
|
5773
|
+
const value = args.slice(1).join(" ");
|
|
5774
|
+
const names = value.toLowerCase() === "none" ? [] : [...new Set(value.toLowerCase().split(/[,\s]+/).filter(Boolean))];
|
|
5775
|
+
const unknown = names.filter((name) => !state.taskSkills.some((s) => s.name === name));
|
|
5776
|
+
if (unknown.length > 0) {
|
|
5777
|
+
return fail3(state, `No task skill named ${unknown.map((n) => `"${n}"`).join(", ")}. Task skills: ${state.taskSkills.map((s) => s.name).join(", ") || "none"}.`);
|
|
5778
|
+
}
|
|
5779
|
+
return assignTaskSkills(state, sessionId, task, names);
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5885
5782
|
function taskOpsCommand(state, args) {
|
|
5886
5783
|
return taskCommand(state, args[0], (_sessionId, taskId) => {
|
|
5887
5784
|
const task = findTask(state.tasks, taskId);
|
|
@@ -5952,7 +5849,8 @@ function handlePlanKey(state, key) {
|
|
|
5952
5849
|
const row = selectedPlanRow(state);
|
|
5953
5850
|
if (!row) return step(state);
|
|
5954
5851
|
const task = row.task;
|
|
5955
|
-
if (key.name === "enter"
|
|
5852
|
+
if (key.name === "enter" && state.sessionId) return openTaskView(state, state.sessionId, task.id);
|
|
5853
|
+
if (key.name === "right") {
|
|
5956
5854
|
const hasSubtasks = (task.subtasks?.length ?? 0) > 0;
|
|
5957
5855
|
if (hasSubtasks && state.expandedTaskId !== task.id) {
|
|
5958
5856
|
return step(settlePlan({ ...state, expandedTaskId: task.id, taskEditor: null }));
|
|
@@ -5974,7 +5872,6 @@ function handlePlanKey(state, key) {
|
|
|
5974
5872
|
}
|
|
5975
5873
|
if (key.char === "O") return toggleTaskOps(state, task, row.parent !== null);
|
|
5976
5874
|
if (key.char === "d") return confirmRemoveTask(state, task);
|
|
5977
|
-
if (key.char === "t") return openTaskTerminalOrView(state, state.sessionId, task.id);
|
|
5978
5875
|
if (key.char === "x" && task.isolation?.state === "conflict") {
|
|
5979
5876
|
return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id }]);
|
|
5980
5877
|
}
|
|
@@ -5983,6 +5880,7 @@ function handlePlanKey(state, key) {
|
|
|
5983
5880
|
if (key.char === "e") return openTaskEffortPicker(state, task);
|
|
5984
5881
|
if (key.char === "M") return openTaskModePicker(state, task);
|
|
5985
5882
|
if (key.char === "D") return openTaskDepsPicker(state, task);
|
|
5883
|
+
if (key.char === "K") return openTaskSkillsPicker(state, task);
|
|
5986
5884
|
return step(state);
|
|
5987
5885
|
}
|
|
5988
5886
|
function handleTaskEditKey(state, task, editor, key) {
|
|
@@ -6053,13 +5951,10 @@ function plannerEscape(state) {
|
|
|
6053
5951
|
|
|
6054
5952
|
// src/tui/reducers/commands.ts
|
|
6055
5953
|
var KNOWN_PROVIDERS = Object.keys(import_core31.ALL_PROVIDERS);
|
|
5954
|
+
var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
5955
|
+
var skillInTaskView = (state, name) => fail3(state, `/${name} is a skill, and skills aren't loaded in a task's chat. To give a task a skill, use /task-skills <id> <name>.`);
|
|
6056
5956
|
function runCommand(state, { name, args }) {
|
|
6057
|
-
if (!findCommand(name))
|
|
6058
|
-
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
6059
|
-
}
|
|
6060
|
-
if (SKILL_IDS.includes(name)) {
|
|
6061
|
-
return toggleSkill(state, name, args[0]);
|
|
6062
|
-
}
|
|
5957
|
+
if (!findCommand(name)) return unknownCommand(state, name);
|
|
6063
5958
|
switch (name) {
|
|
6064
5959
|
case "help":
|
|
6065
5960
|
return step({ ...state, overlay: { kind: "help", scroll: 0 } });
|
|
@@ -6093,8 +5988,6 @@ function runCommand(state, { name, args }) {
|
|
|
6093
5988
|
return runners(state, args);
|
|
6094
5989
|
case "auto":
|
|
6095
5990
|
return setAutonomous(state, args[0]);
|
|
6096
|
-
case "transport":
|
|
6097
|
-
return setTransport(state, args[0]);
|
|
6098
5991
|
case "parallel":
|
|
6099
5992
|
return setMaxParallel(state, args[0]);
|
|
6100
5993
|
case "mouse":
|
|
@@ -6131,8 +6024,6 @@ function runCommand(state, { name, args }) {
|
|
|
6131
6024
|
args[0],
|
|
6132
6025
|
(sessionId, taskId) => step(state, [{ type: "removeTask", sessionId, taskId }])
|
|
6133
6026
|
);
|
|
6134
|
-
case "terminal":
|
|
6135
|
-
return taskCommand(state, args[0], (sessionId, taskId) => openTaskTerminalOrView(state, sessionId, taskId));
|
|
6136
6027
|
case "continue":
|
|
6137
6028
|
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6138
6029
|
const text = args.slice(1).join(" ").trim();
|
|
@@ -6157,6 +6048,8 @@ function runCommand(state, { name, args }) {
|
|
|
6157
6048
|
return taskModeCommand(state, args);
|
|
6158
6049
|
case "task-ops":
|
|
6159
6050
|
return taskOpsCommand(state, args);
|
|
6051
|
+
case "task-skills":
|
|
6052
|
+
return taskSkillsCommand(state, args);
|
|
6160
6053
|
case "task-deps":
|
|
6161
6054
|
return taskCommand(
|
|
6162
6055
|
state,
|
|
@@ -6190,11 +6083,6 @@ function resolveToggle(arg, current) {
|
|
|
6190
6083
|
if (value === void 0) return !current;
|
|
6191
6084
|
return null;
|
|
6192
6085
|
}
|
|
6193
|
-
function toggleSkill(state, skill, arg) {
|
|
6194
|
-
const enabled = resolveToggle(arg, state.skills[skill]);
|
|
6195
|
-
if (enabled === null) return fail3(state, `Usage: /${skill} [on|off]`);
|
|
6196
|
-
return step(state, [{ type: "command", name: skill, action: enabled ? "on" : "off" }]);
|
|
6197
|
-
}
|
|
6198
6086
|
function requestNewSession(state) {
|
|
6199
6087
|
const hasContent = state.tasks.length > 0 || state.conversation.blocks.length > 0 || state.goal !== "" || state.status !== "idle";
|
|
6200
6088
|
if (!state.sessionId || !hasContent) return newSession(state);
|
|
@@ -6406,11 +6294,6 @@ function setAutonomous(state, arg) {
|
|
|
6406
6294
|
if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
|
|
6407
6295
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6408
6296
|
}
|
|
6409
|
-
function setTransport(state, arg) {
|
|
6410
|
-
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6411
|
-
if (!(0, import_core31.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6412
|
-
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6413
|
-
}
|
|
6414
6297
|
function setMouseCapture(state, arg) {
|
|
6415
6298
|
const enabled = resolveToggle(arg, state.mouseCapture);
|
|
6416
6299
|
if (enabled === null) return fail3(state, "Usage: /mouse [on|off]");
|
|
@@ -6588,6 +6471,11 @@ function choose(state, picker2, item) {
|
|
|
6588
6471
|
message: picker2.chosen.length > 0 ? `Task ${(0, import_core32.taskRef)(task)} now depends on ${picker2.chosen.length} task${picker2.chosen.length === 1 ? "" : "s"}.` : `Task ${(0, import_core32.taskRef)(task)} no longer depends on anything.`
|
|
6589
6472
|
}]);
|
|
6590
6473
|
}
|
|
6474
|
+
if (picker2.action.kind === "set-task-skills") {
|
|
6475
|
+
const task = findTask(state.tasks, picker2.action.taskId);
|
|
6476
|
+
if (!task || !state.sessionId) return step(closed);
|
|
6477
|
+
return assignTaskSkills(closed, state.sessionId, task, picker2.chosen);
|
|
6478
|
+
}
|
|
6591
6479
|
if (!item) return step(state);
|
|
6592
6480
|
if (item.disabled) {
|
|
6593
6481
|
return step(say(state, "error", item.detail ? `${item.label} \u2014 ${item.detail}` : `${item.label} is not available.`));
|
|
@@ -6705,28 +6593,17 @@ function toTaskView2(t, index) {
|
|
|
6705
6593
|
} : void 0,
|
|
6706
6594
|
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => wireOrder(a) - wireOrder(b)).map((s, i) => toTaskView2(asRecord(s), i)) : void 0,
|
|
6707
6595
|
...t.ops === true ? { ops: true } : {},
|
|
6596
|
+
...Array.isArray(t.skills) && t.skills.length > 0 ? { skills: t.skills.map((name) => planLabel(name)) } : {},
|
|
6708
6597
|
...Array.isArray(t.forcedPastGate) && t.forcedPastGate.length > 0 ? { forcedPastGate: t.forcedPastGate.map((title) => planLabel(title)) } : {}
|
|
6709
6598
|
};
|
|
6710
6599
|
}
|
|
6711
|
-
var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
|
|
6712
6600
|
function applySettings(state, settings) {
|
|
6713
|
-
const sources = {
|
|
6714
|
-
tdd: settings.tdd,
|
|
6715
|
-
verify: settings.verification
|
|
6716
|
-
};
|
|
6717
|
-
const skills = { ...state.skills };
|
|
6718
|
-
for (const id of SKILL_IDS) {
|
|
6719
|
-
const flag2 = enabledFlag(sources[id]);
|
|
6720
|
-
if (flag2 !== void 0) skills[id] = flag2;
|
|
6721
|
-
}
|
|
6722
6601
|
return {
|
|
6723
6602
|
...state,
|
|
6724
|
-
skills,
|
|
6725
6603
|
orchestratorModel: typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : state.orchestratorModel,
|
|
6726
6604
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6727
6605
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6728
6606
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6729
|
-
runnerTransport: (0, import_core33.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6730
6607
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6731
6608
|
};
|
|
6732
6609
|
}
|
|
@@ -6873,16 +6750,15 @@ function reduceAction(state, action) {
|
|
|
6873
6750
|
if (update === void 0) return t;
|
|
6874
6751
|
const idleSince = update.idleSince ?? null;
|
|
6875
6752
|
const isolation = update.isolation ?? t.isolation;
|
|
6876
|
-
const transport = update.transport;
|
|
6877
6753
|
const awaitingReason = update.awaitingReason;
|
|
6878
6754
|
const checkpoint = update.checkpoint;
|
|
6879
6755
|
const continuable = update.continuable === true;
|
|
6880
6756
|
const awaitingApproval = update.awaitingApproval;
|
|
6881
6757
|
const mergeGate = update.mergeGate;
|
|
6882
6758
|
const forcedPastGate = update.forcedPastGate;
|
|
6883
|
-
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) ||
|
|
6759
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
6884
6760
|
changed = true;
|
|
6885
|
-
return { ...t, status: update.status, idleSince, isolation,
|
|
6761
|
+
return { ...t, status: update.status, idleSince, isolation, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
6886
6762
|
}
|
|
6887
6763
|
return t;
|
|
6888
6764
|
});
|
|
@@ -7023,9 +6899,6 @@ function reduceAction(state, action) {
|
|
|
7023
6899
|
return step(disarmStop(state));
|
|
7024
6900
|
}
|
|
7025
6901
|
}
|
|
7026
|
-
function sameTransport(a, b) {
|
|
7027
|
-
return a?.kind === b?.kind && a?.fallback === b?.fallback;
|
|
7028
|
-
}
|
|
7029
6902
|
function sameList(a, b) {
|
|
7030
6903
|
return (a ?? []).join("\0") === (b ?? []).join("\0");
|
|
7031
6904
|
}
|
|
@@ -7095,7 +6968,11 @@ function submit(state) {
|
|
|
7095
6968
|
if (!text) return step(state);
|
|
7096
6969
|
const cleared = { ...state, editor: commit(state.editor) };
|
|
7097
6970
|
const command = parseSlash(text);
|
|
7098
|
-
if (command
|
|
6971
|
+
if (command) {
|
|
6972
|
+
const known = findCommand(command.name);
|
|
6973
|
+
if (known && known.source !== "skill") return runCommand(cleared, command);
|
|
6974
|
+
if (state.taskView) return known ? skillInTaskView(cleared, command.name) : unknownCommand(cleared, command.name);
|
|
6975
|
+
}
|
|
7099
6976
|
if (state.taskView) {
|
|
7100
6977
|
if (!state.sessionId) return step(state);
|
|
7101
6978
|
const taskId = state.taskView.taskId;
|
|
@@ -7162,7 +7039,7 @@ function createApp(deps) {
|
|
|
7162
7039
|
}
|
|
7163
7040
|
|
|
7164
7041
|
// src/tui/effects.ts
|
|
7165
|
-
var
|
|
7042
|
+
var import_child_process3 = require("child_process");
|
|
7166
7043
|
var import_core36 = require("@ordewell/core");
|
|
7167
7044
|
|
|
7168
7045
|
// src/tui/inbound.ts
|
|
@@ -7173,6 +7050,7 @@ var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
7173
7050
|
"planner_turn_ended",
|
|
7174
7051
|
"planner_text_delta",
|
|
7175
7052
|
"planner_thinking_delta",
|
|
7053
|
+
"planner_skill_loaded",
|
|
7176
7054
|
"planner_text_retracted",
|
|
7177
7055
|
"plan_token",
|
|
7178
7056
|
"plan_generated",
|
|
@@ -7380,7 +7258,6 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7380
7258
|
status: String(task.status),
|
|
7381
7259
|
idleSince: task.idleSince ?? null,
|
|
7382
7260
|
isolation: task.isolation,
|
|
7383
|
-
transport: task.transport,
|
|
7384
7261
|
awaitingReason: (0, import_core35.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7385
7262
|
...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
|
|
7386
7263
|
...task.continuable ? { continuable: true } : {},
|
|
@@ -7589,18 +7466,6 @@ async function perform(effect, deps) {
|
|
|
7589
7466
|
if (effect.answer === "approve") await api.approveTaskCheckpoint(effect.sessionId, effect.taskId);
|
|
7590
7467
|
else await api.rejectTaskCheckpoint(effect.sessionId, effect.taskId, effect.reason);
|
|
7591
7468
|
return;
|
|
7592
|
-
// A task with a saved log ran structured, whatever its `transport` says
|
|
7593
|
-
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7594
|
-
case "openTaskTerminal": {
|
|
7595
|
-
const saved = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace).catch(() => []);
|
|
7596
|
-
if (saved.length > 0) {
|
|
7597
|
-
dispatch({ type: "taskViewRequested", sessionId: effect.sessionId, taskId: effect.taskId });
|
|
7598
|
-
return;
|
|
7599
|
-
}
|
|
7600
|
-
const result = await deps.openTerminal(effect.sessionId, effect.taskId);
|
|
7601
|
-
dispatch(result.ok ? { type: "notice", message: result.message } : { type: "failed", message: result.message });
|
|
7602
|
-
return;
|
|
7603
|
-
}
|
|
7604
7469
|
// The saved log is read off disk (ADR-0018, P1) so the view opens with the
|
|
7605
7470
|
// task's history; the live `task_log` stream then catches it up in place.
|
|
7606
7471
|
case "openTaskLog": {
|
|
@@ -7652,18 +7517,6 @@ async function perform(effect, deps) {
|
|
|
7652
7517
|
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7653
7518
|
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
7654
7519
|
return;
|
|
7655
|
-
case "command": {
|
|
7656
|
-
const result = await api.sendCommand(effect.name, { action: effect.action });
|
|
7657
|
-
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7658
|
-
dispatch({ type: "notice", message: `${effect.name} is ${effect.action}.` });
|
|
7659
|
-
return;
|
|
7660
|
-
}
|
|
7661
|
-
case "setTransport": {
|
|
7662
|
-
const result = await api.sendCommand("transport", { action: effect.transport });
|
|
7663
|
-
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7664
|
-
dispatch({ type: "notice", message: `Runner transport is ${effect.transport} \u2014 it applies from the next run.` });
|
|
7665
|
-
return;
|
|
7666
|
-
}
|
|
7667
7520
|
case "setModel":
|
|
7668
7521
|
await api.updateSettings({ orchestratorModel: effect.modelId });
|
|
7669
7522
|
deps.setEnvVar("ORCHESTRATOR_MODEL", effect.modelId);
|
|
@@ -7859,7 +7712,7 @@ async function perform(effect, deps) {
|
|
|
7859
7712
|
}
|
|
7860
7713
|
}
|
|
7861
7714
|
var pipeToClipboardDefault = (command, text) => {
|
|
7862
|
-
(0,
|
|
7715
|
+
(0, import_child_process3.execSync)(command, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
7863
7716
|
};
|
|
7864
7717
|
function osc52(text) {
|
|
7865
7718
|
return `\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`;
|
|
@@ -7885,8 +7738,8 @@ function copySelection(deps, text) {
|
|
|
7885
7738
|
async function withExecutionStream(deps, sessionId, request) {
|
|
7886
7739
|
let settleReady = () => {
|
|
7887
7740
|
};
|
|
7888
|
-
const streamReady = new Promise((
|
|
7889
|
-
settleReady = (error) => error ? reject(error) :
|
|
7741
|
+
const streamReady = new Promise((resolve4, reject) => {
|
|
7742
|
+
settleReady = (error) => error ? reject(error) : resolve4();
|
|
7890
7743
|
});
|
|
7891
7744
|
const inbound = inboundFor(deps.api, deps.dispatch, sessionId);
|
|
7892
7745
|
const stream = deps.api.streamExecution(sessionId, inbound.execution(), settleReady);
|
|
@@ -7928,7 +7781,7 @@ function restoredChat(plan, sessionId) {
|
|
|
7928
7781
|
function lastAssistantMessage(plan) {
|
|
7929
7782
|
const history = plan?.conversationHistory ?? [];
|
|
7930
7783
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
7931
|
-
if (history[i].role === "assistant") {
|
|
7784
|
+
if (history[i].role === "assistant" && history[i].kind !== "skill_load") {
|
|
7932
7785
|
const { content, timestamp } = history[i];
|
|
7933
7786
|
return { content, ...timestamp ? { timestamp } : {} };
|
|
7934
7787
|
}
|
|
@@ -8272,7 +8125,9 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8272
8125
|
);
|
|
8273
8126
|
process.exit(1);
|
|
8274
8127
|
}
|
|
8275
|
-
|
|
8128
|
+
const discoveredSkills = (0, import_core37.createSkillsService)(workspace, new import_core37.EnvConfig().workspaceRepos).listSkills();
|
|
8129
|
+
registerSkillCommands(discoveredSkills);
|
|
8130
|
+
const taskSkills = discoveredSkills.filter((s) => s.appliesTo === "task").map((s) => ({ name: s.name, description: s.description }));
|
|
8276
8131
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8277
8132
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8278
8133
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8282,7 +8137,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8282
8137
|
const autoMode = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
8283
8138
|
const mouseCapture = process.env.ORDEWELL_TUI_MOUSE !== "false" && process.env.ORDEWELL_TUI_MOUSE !== "0";
|
|
8284
8139
|
const app = createApp({
|
|
8285
|
-
initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture },
|
|
8140
|
+
initial: { workspace, autonomous: autoMode !== "false" && autoMode !== "0", mouseCapture, taskSkills },
|
|
8286
8141
|
draw: (frame2) => terminal?.draw(frame2),
|
|
8287
8142
|
perform: (effect) => runEffect(effect, {
|
|
8288
8143
|
api,
|
|
@@ -8294,7 +8149,6 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8294
8149
|
writeEnvVar(findEnvFile(), key, value);
|
|
8295
8150
|
process.env[key] = value;
|
|
8296
8151
|
},
|
|
8297
|
-
openTerminal: (sessionId, taskId) => openTaskTerminal(port, sessionId, taskId),
|
|
8298
8152
|
setMouseCapture: (enabled) => terminal?.setMouse(enabled),
|
|
8299
8153
|
// The alt screen is up with the cursor hidden, so an OSC 52 write shows
|
|
8300
8154
|
// nothing — the terminal reads it and answers by taking the clipboard.
|
|
@@ -8308,7 +8162,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8308
8162
|
}),
|
|
8309
8163
|
onExit: () => shutdown(0),
|
|
8310
8164
|
// The same pointer `ordewell plan` leaves, so `ordewell handoff`,
|
|
8311
|
-
//
|
|
8165
|
+
// The one-shot commands act on the session this
|
|
8312
8166
|
// TUI is showing instead of saying there is none.
|
|
8313
8167
|
onSessionChange: (sessionId, state) => {
|
|
8314
8168
|
try {
|
|
@@ -8366,12 +8220,9 @@ var COMMANDS = {
|
|
|
8366
8220
|
key: handleKey,
|
|
8367
8221
|
runners: handleRunners,
|
|
8368
8222
|
auto: handleAuto,
|
|
8369
|
-
transport: handleTransport,
|
|
8370
8223
|
refresh: handleRefresh,
|
|
8371
8224
|
setup: handleSetup,
|
|
8372
|
-
|
|
8373
|
-
"tdd": handleTdd,
|
|
8374
|
-
"verify": handleVerify,
|
|
8225
|
+
skills: handleSkills,
|
|
8375
8226
|
"mark-complete": handleMarkComplete,
|
|
8376
8227
|
complete: handleMarkComplete,
|
|
8377
8228
|
uncomplete: handleUncomplete,
|
|
@@ -8389,8 +8240,8 @@ var COMMANDS = {
|
|
|
8389
8240
|
"task-effort": handleTaskEffort,
|
|
8390
8241
|
"task-mode": handleTaskMode,
|
|
8391
8242
|
"task-deps": handleTaskDeps,
|
|
8243
|
+
"task-skills": handleTaskSkills,
|
|
8392
8244
|
"task-ops": handleTaskOps,
|
|
8393
|
-
terminal: handleTerminal,
|
|
8394
8245
|
"sessions": handleSessions,
|
|
8395
8246
|
fork: handleFork,
|
|
8396
8247
|
compact: handleCompact,
|
|
@@ -8401,10 +8252,10 @@ var COMMANDS = {
|
|
|
8401
8252
|
|
|
8402
8253
|
// src/version.ts
|
|
8403
8254
|
var import_fs7 = require("fs");
|
|
8404
|
-
var
|
|
8255
|
+
var import_path6 = require("path");
|
|
8405
8256
|
function cliVersion() {
|
|
8406
8257
|
try {
|
|
8407
|
-
const pkg = JSON.parse((0, import_fs7.readFileSync)((0,
|
|
8258
|
+
const pkg = JSON.parse((0, import_fs7.readFileSync)((0, import_path6.join)(__dirname, "..", "package.json"), "utf8"));
|
|
8408
8259
|
const version = pkg.version;
|
|
8409
8260
|
return typeof version === "string" && version ? version : "unknown";
|
|
8410
8261
|
} catch {
|