@ordewell/cli 0.11.1 → 0.11.3
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/AUTHORS.md +4 -0
- package/README.md +1 -1
- package/dist/main.js +142 -41
- package/package.json +3 -3
package/AUTHORS.md
CHANGED
|
@@ -27,6 +27,10 @@ record of who built what, rather than `git log`.
|
|
|
27
27
|
- [@haltingstate](https://github.com/haltingstate) — made the terminal UI's
|
|
28
28
|
stepper say what a saved step does next, instead of encoding it in a
|
|
29
29
|
sentinel index (#123).
|
|
30
|
+
- [@Logic769](https://github.com/Logic769) — made `/resume` an alias of
|
|
31
|
+
`/sessions` in the planner chat (#139).
|
|
32
|
+
- Hanbin Tan ([@hskl18](https://github.com/hskl18)) — documented how
|
|
33
|
+
planners and runners get their models and credentials (#140).
|
|
30
34
|
|
|
31
35
|
Thank you. Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
32
36
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ and model assigned to each AI task. With worktree isolation enabled, code tasks
|
|
|
6
6
|
run in separate worktrees; ops and script tasks run at the workspace root.
|
|
7
7
|
Ordewell records task outcomes and combines code changes for review and merging.
|
|
8
8
|
|
|
9
|
-
This README describes 0.11.
|
|
9
|
+
This README describes 0.11.3. See the [published releases](https://github.com/ordewell/ordewell/releases)
|
|
10
10
|
for versions available to install and their release notes.
|
|
11
11
|
|
|
12
12
|
## Installation
|
package/dist/main.js
CHANGED
|
@@ -188,7 +188,7 @@ Planner, models and runners:
|
|
|
188
188
|
ordewell trailer [on|off] (headless) End Ordewell's commits with "Planned-with: Ordewell"; set only here or in settings.json
|
|
189
189
|
ordewell autorun [on|off] (headless) Start each plan the planner submits, without review; /settings covers it
|
|
190
190
|
ordewell parallel [<n>] (headless) How many AI tasks run at once, a live run included; /settings covers it
|
|
191
|
-
ordewell refresh Re-
|
|
191
|
+
ordewell refresh Re-check runners; reread settings and cached models
|
|
192
192
|
ordewell models List every provider's catalog (works without a server)
|
|
193
193
|
|
|
194
194
|
Other:
|
|
@@ -792,7 +792,7 @@ var ApiClient = class _ApiClient {
|
|
|
792
792
|
}
|
|
793
793
|
/** The full provider catalog the daemon has discovered (used by the TUI model picker). */
|
|
794
794
|
getModels() {
|
|
795
|
-
return this.call("GET",
|
|
795
|
+
return this.call("GET", `/api/models?workspace=${encodeURIComponent(this.workspace)}`, "Failed to fetch models");
|
|
796
796
|
}
|
|
797
797
|
setRunnerEnabled(runner, enabled) {
|
|
798
798
|
return this.call("PUT", `/api/runners/${runner}`, `Failed to ${enabled ? "enable" : "disable"} ${runner}`, { enabled });
|
|
@@ -1140,16 +1140,19 @@ var ApiClient = class _ApiClient {
|
|
|
1140
1140
|
socket.close();
|
|
1141
1141
|
} };
|
|
1142
1142
|
}
|
|
1143
|
+
// Every settings call names this workspace, as `getModels` does: the
|
|
1144
|
+
// planner's model is judged against its runner catalog, which its own
|
|
1145
|
+
// CLAUDE_CONFIG_DIR can make another account's than the daemon's cwd.
|
|
1143
1146
|
getSettings(sessionId) {
|
|
1144
|
-
const
|
|
1145
|
-
return this.call("GET",
|
|
1147
|
+
const session = sessionId ? `sessionId=${encodeURIComponent(sessionId)}&` : "";
|
|
1148
|
+
return this.call("GET", `/api/settings?${session}workspace=${encodeURIComponent(this.workspace)}`, "Failed to fetch settings");
|
|
1146
1149
|
}
|
|
1147
1150
|
/** Save run settings (ADR-0030); with `sessionId`, that session's run takes them at once. */
|
|
1148
1151
|
updateRunSettings(changes, sessionId) {
|
|
1149
|
-
return this.call("PATCH", "/api/settings", "Change run settings failed", { run: changes, ...sessionId ? { sessionId } : {} });
|
|
1152
|
+
return this.call("PATCH", "/api/settings", "Change run settings failed", { run: changes, ...sessionId ? { sessionId } : {}, workspace: this.workspace });
|
|
1150
1153
|
}
|
|
1151
1154
|
updateSettings(changes) {
|
|
1152
|
-
return this.call("PATCH", "/api/settings", "Failed to update settings", changes);
|
|
1155
|
+
return this.call("PATCH", "/api/settings", "Failed to update settings", { ...changes, workspace: this.workspace });
|
|
1153
1156
|
}
|
|
1154
1157
|
};
|
|
1155
1158
|
|
|
@@ -4995,6 +4998,7 @@ function initialState(overrides = {}) {
|
|
|
4995
4998
|
runSettings: { ...import_core31.RUN_SETTINGS_DEFAULTS },
|
|
4996
4999
|
runSettingsKnown: false,
|
|
4997
5000
|
settingsRead: 0,
|
|
5001
|
+
runSettingsRead: 0,
|
|
4998
5002
|
plannerKnown: false,
|
|
4999
5003
|
runnersKnown: false,
|
|
5000
5004
|
providersKnown: false,
|
|
@@ -5283,7 +5287,7 @@ var SLASH_COMMANDS = [
|
|
|
5283
5287
|
{ name: "runners", usage: "/runners [<id> on|off]", description: `Enable or disable runners (${runnerIdList()})`, category: "models" },
|
|
5284
5288
|
{ name: "login", usage: "/login [<runner>]", description: "Sign the active coding agent in, or the one named, in this terminal", category: "models" },
|
|
5285
5289
|
{ name: "settings", usage: "/settings", description: "Run settings: autonomy, supervisor, merging, autorun and parallel tasks", category: "models" },
|
|
5286
|
-
{ name: "refresh", usage: "/refresh", description: "Re-
|
|
5290
|
+
{ name: "refresh", usage: "/refresh", description: "Re-check runners; reread settings and cached models", category: "models" },
|
|
5287
5291
|
// Sessions
|
|
5288
5292
|
{ name: "sessions", usage: "/sessions", description: "List saved sessions", category: "session" },
|
|
5289
5293
|
{ name: "fork", usage: "/fork", description: "Continue in a copy of this conversation and its tasks; the original stays as it is", category: "session" },
|
|
@@ -5298,7 +5302,27 @@ var SLASH_COMMANDS = [
|
|
|
5298
5302
|
{ name: "mouse", usage: "/mouse [on|off]", description: "Wheel scrolling and drag-select, per-pane in the body and full-width in the composer (on), or the terminal's own selection (off)", category: "system" },
|
|
5299
5303
|
{ name: "quit", usage: "/quit", description: "Exit the TUI", category: "system" }
|
|
5300
5304
|
];
|
|
5301
|
-
var
|
|
5305
|
+
var COMMAND_ALIASES = [
|
|
5306
|
+
// Editors call reopening a saved session "resume"; here it must open the same
|
|
5307
|
+
// saved-session picker as /sessions, not restart a task or a runner.
|
|
5308
|
+
{ name: "resume", target: "sessions", description: "Alias for /sessions: open a saved session, not restart a task" }
|
|
5309
|
+
];
|
|
5310
|
+
function slashCommandTable() {
|
|
5311
|
+
const table2 = [];
|
|
5312
|
+
for (const command of SLASH_COMMANDS) {
|
|
5313
|
+
table2.push(command);
|
|
5314
|
+
for (const alias of COMMAND_ALIASES) {
|
|
5315
|
+
if (alias.target !== command.name) continue;
|
|
5316
|
+
table2.push({ name: alias.name, usage: `/${alias.name}`, description: alias.description, category: command.category });
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
return table2;
|
|
5320
|
+
}
|
|
5321
|
+
var BY_NAME = new Map(slashCommandTable().map((c) => [c.name, c]));
|
|
5322
|
+
function canonicalCommandName(name) {
|
|
5323
|
+
const key = name.replace(/^\//, "").toLowerCase();
|
|
5324
|
+
return COMMAND_ALIASES.find((a) => a.name === key)?.target ?? key;
|
|
5325
|
+
}
|
|
5302
5326
|
var skillCommands = [];
|
|
5303
5327
|
var skillByName = /* @__PURE__ */ new Map();
|
|
5304
5328
|
function registerSkillCommands(skills) {
|
|
@@ -5369,7 +5393,7 @@ function composerCompletions(token, runner) {
|
|
|
5369
5393
|
return tokenCompletions(token).map((c) => c.name);
|
|
5370
5394
|
}
|
|
5371
5395
|
function tokenCompletions(token) {
|
|
5372
|
-
const pool = token.start === 0 ? [...
|
|
5396
|
+
const pool = token.start === 0 ? [...slashCommandTable(), ...skillCommands] : skillCommands;
|
|
5373
5397
|
return pool.filter((c) => c.name.startsWith(token.name));
|
|
5374
5398
|
}
|
|
5375
5399
|
|
|
@@ -5929,6 +5953,8 @@ function chatActivity(state) {
|
|
|
5929
5953
|
const own = findTask(state.tasks, tv.taskId);
|
|
5930
5954
|
const task = state.tasks.find((t) => t.subtasks?.some((s) => s.id === tv.taskId)) ?? own;
|
|
5931
5955
|
if (!task) return null;
|
|
5956
|
+
const attaching = (0, import_core38.attachingLabel)(task);
|
|
5957
|
+
if (attaching) return { label: `${attaching}\u2026`, busy: true };
|
|
5932
5958
|
if ((0, import_core31.isTaskRunning)(task) || own && (0, import_core31.isTaskRunning)(own)) return { label: "Working\u2026", busy: true };
|
|
5933
5959
|
const at = task.status === "awaiting_user" && task.awaitingReason === "usage-limit" && task.resumeAt ? new Date(task.resumeAt) : null;
|
|
5934
5960
|
if (at && !Number.isNaN(at.getTime())) return { label: `paused \xB7 resumes ${(0, import_core38.resumeTime)(at)}`, busy: false };
|
|
@@ -6404,7 +6430,7 @@ function taskLines(state, planRow, index, cols) {
|
|
|
6404
6430
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
6405
6431
|
const runner = row.runner ?? "";
|
|
6406
6432
|
const bodyPad = parent ? " " : " ";
|
|
6407
|
-
const activity = row.approvals ? `${inRow(row.approvals)}: enter opens it` : row.awaiting ? inRow(row.awaiting) : row.receipt ? inRow(row.receipt) : "";
|
|
6433
|
+
const activity = row.approvals ? `${inRow(row.approvals)}: enter opens it` : row.awaiting ? inRow(row.awaiting) : row.receipt ? inRow(row.receipt) : row.attaching ? inRow(row.attaching) : "";
|
|
6408
6434
|
const skills = (task.skills ?? []).join(" \xB7 ");
|
|
6409
6435
|
const meta = [activity, ...row.research && row.running ? [style.cyan("research")] : [], runner, model];
|
|
6410
6436
|
const variant = task.type === "ai" ? sanitize(task.assignedModel?.thinkingEffort ?? "") : "";
|
|
@@ -6570,7 +6596,7 @@ function keySheetBody(cols) {
|
|
|
6570
6596
|
function commandSheetBody(cols) {
|
|
6571
6597
|
const body = [];
|
|
6572
6598
|
for (const [category, title] of Object.entries(CATEGORY_TITLE)) {
|
|
6573
|
-
const commands =
|
|
6599
|
+
const commands = slashCommandTable().filter((c) => c.category === category);
|
|
6574
6600
|
if (commands.length === 0) continue;
|
|
6575
6601
|
body.push(style.bold(title));
|
|
6576
6602
|
for (const command of commands) {
|
|
@@ -8087,7 +8113,8 @@ function stopPlanning(state, sessionId) {
|
|
|
8087
8113
|
function queueToEditor(state) {
|
|
8088
8114
|
const all = (0, import_core47.unsendAll)(state.queuedPrompts);
|
|
8089
8115
|
if (!all) return state;
|
|
8090
|
-
|
|
8116
|
+
const text = (0, import_core47.aheadOfDraft)(all.text, state.editor.text);
|
|
8117
|
+
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text, cursor: all.text.length } };
|
|
8091
8118
|
}
|
|
8092
8119
|
function unsendLatest(state) {
|
|
8093
8120
|
const latest = (0, import_core47.unsendLatest)(state.queuedPrompts);
|
|
@@ -8168,6 +8195,7 @@ function runTaskToView(t) {
|
|
|
8168
8195
|
status: planLabel(t.status, "pending"),
|
|
8169
8196
|
idleSince: t.idleSince ?? null,
|
|
8170
8197
|
...t.completionReceived === "done" || t.completionReceived === "failed" || t.completionReceived === "blocked" ? { completionReceived: t.completionReceived } : {},
|
|
8198
|
+
...t.attaching === 1 || t.attaching === 2 ? { attaching: t.attaching } : {},
|
|
8171
8199
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
8172
8200
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
8173
8201
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -8390,8 +8418,10 @@ function saveSettings(state, effect) {
|
|
|
8390
8418
|
|
|
8391
8419
|
// src/tui/reducers/commands.ts
|
|
8392
8420
|
var unknownCommand = (state, name) => fail3(state, `Unknown command: /${name}. Type /help to see what's available.`);
|
|
8393
|
-
function runCommand(state,
|
|
8394
|
-
|
|
8421
|
+
function runCommand(state, parsed) {
|
|
8422
|
+
const name = canonicalCommandName(parsed.name);
|
|
8423
|
+
const { args } = parsed;
|
|
8424
|
+
if (!findCommand(parsed.name)) return unknownCommand(state, parsed.name);
|
|
8395
8425
|
switch (name) {
|
|
8396
8426
|
case "help":
|
|
8397
8427
|
return step({ ...state, overlay: { kind: "help", scroll: 0 } });
|
|
@@ -8556,6 +8586,8 @@ function newSession(state) {
|
|
|
8556
8586
|
planScroll: null,
|
|
8557
8587
|
status: "idle",
|
|
8558
8588
|
planner: "idle",
|
|
8589
|
+
plannerPause: null,
|
|
8590
|
+
plannerResend: null,
|
|
8559
8591
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
8560
8592
|
// and its pending requests deny on their own timeout.
|
|
8561
8593
|
pendingApprovals: [],
|
|
@@ -9019,6 +9051,8 @@ function reduceAction(state, action) {
|
|
|
9019
9051
|
return step(withRun({ ...state, sessionId: action.sessionId, goal: action.goal, accountLimits: [], servedModels: {}, taskView: null, status: "idle" }, import_core52.EMPTY_RUN));
|
|
9020
9052
|
// A loaded session is a different one: the turn the old one had out, the
|
|
9021
9053
|
// prompts held behind it and a stop armed against it are not this one's.
|
|
9054
|
+
// Nor is an open prompt editor: task ids repeat across plans, so the
|
|
9055
|
+
// loaded plan's `task-1` would keep it, and Enter save the old draft there.
|
|
9022
9056
|
// (`sessionStarted` cannot reset them: `startConversation` dispatches it
|
|
9023
9057
|
// as its own turn begins.)
|
|
9024
9058
|
case "sessionLoaded":
|
|
@@ -9029,12 +9063,17 @@ function reduceAction(state, action) {
|
|
|
9029
9063
|
sessionId: action.sessionId,
|
|
9030
9064
|
goal: action.goal,
|
|
9031
9065
|
taskView: null,
|
|
9066
|
+
expandedTaskId: null,
|
|
9067
|
+
taskEditor: null,
|
|
9032
9068
|
status: "idle",
|
|
9033
9069
|
planner: "idle",
|
|
9070
|
+
plannerPause: null,
|
|
9071
|
+
plannerResend: null,
|
|
9034
9072
|
turnGate: import_core52.NO_TURN,
|
|
9035
9073
|
pendingApprovals: [],
|
|
9036
9074
|
queuedPrompts: import_core52.EMPTY_HOLD,
|
|
9037
9075
|
stopArmed: false,
|
|
9076
|
+
stopRequested: false,
|
|
9038
9077
|
forceSendPending: null,
|
|
9039
9078
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay,
|
|
9040
9079
|
runSettingsKnown: false
|
|
@@ -9045,21 +9084,25 @@ function reduceAction(state, action) {
|
|
|
9045
9084
|
// id is let go rather than guessed at. A reply that was merely lost is not
|
|
9046
9085
|
// this: the session is the daemon's and the turn may be running (`turnLost`).
|
|
9047
9086
|
case "sessionCleared":
|
|
9087
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
9048
9088
|
return step(withRun({
|
|
9049
|
-
...state,
|
|
9089
|
+
...queueToEditor(state),
|
|
9050
9090
|
accountLimits: [],
|
|
9051
9091
|
servedModels: {},
|
|
9052
9092
|
sessionId: null,
|
|
9053
9093
|
goal: "",
|
|
9054
9094
|
taskView: null,
|
|
9095
|
+
expandedTaskId: null,
|
|
9096
|
+
taskEditor: null,
|
|
9055
9097
|
status: "idle",
|
|
9098
|
+
plannerPause: null,
|
|
9099
|
+
plannerResend: null,
|
|
9056
9100
|
turnGate: import_core52.NO_TURN,
|
|
9057
9101
|
pendingApprovals: [],
|
|
9058
|
-
queuedPrompts: import_core52.EMPTY_HOLD,
|
|
9059
9102
|
stopArmed: false,
|
|
9060
9103
|
forceSendPending: null,
|
|
9061
9104
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay,
|
|
9062
|
-
runSettingsKnown: false
|
|
9105
|
+
runSettingsKnown: action.unreached ? state.runSettingsKnown : false
|
|
9063
9106
|
}, import_core52.EMPTY_RUN));
|
|
9064
9107
|
// Deleting the session on screen stops it in the daemon; a view left on it
|
|
9065
9108
|
// would answer every next message with a session-not-found.
|
|
@@ -9262,12 +9305,16 @@ function reduceAction(state, action) {
|
|
|
9262
9305
|
});
|
|
9263
9306
|
}
|
|
9264
9307
|
case "settingsLoaded": {
|
|
9265
|
-
|
|
9266
|
-
const forThisSession = action.sessionId === void 0 ?
|
|
9267
|
-
const
|
|
9308
|
+
const { read } = action;
|
|
9309
|
+
const forThisSession = action.sessionId === void 0 ? read === void 0 || state.sessionId === null : action.sessionId === state.sessionId;
|
|
9310
|
+
const workspaceFresh = read === void 0 || read >= state.settingsRead;
|
|
9311
|
+
const runFresh = forThisSession && (read === void 0 || read >= state.runSettingsRead);
|
|
9312
|
+
if (!workspaceFresh && !runFresh) return step(state);
|
|
9313
|
+
const next = workspaceFresh ? applySettings(state, action.settings, runFresh) : applySettings(state, { run: action.settings.run }, true);
|
|
9268
9314
|
return step(refreshStepper({
|
|
9269
9315
|
...next,
|
|
9270
|
-
settingsRead:
|
|
9316
|
+
settingsRead: read !== void 0 && workspaceFresh ? Math.max(state.settingsRead, read) : state.settingsRead,
|
|
9317
|
+
runSettingsRead: read !== void 0 && runFresh ? Math.max(state.runSettingsRead, read) : state.runSettingsRead
|
|
9271
9318
|
}));
|
|
9272
9319
|
}
|
|
9273
9320
|
case "modelsLoaded":
|
|
@@ -9293,13 +9340,17 @@ function reduceAction(state, action) {
|
|
|
9293
9340
|
return step(refillPicker({ ...state, rewindTargets: action.targets }, ["rewind"]));
|
|
9294
9341
|
// A fork holds no run and no turn, whatever the session it came from was
|
|
9295
9342
|
// doing — the original keeps both, and its events are stale from here on.
|
|
9296
|
-
// Its plan arrives next, onto an empty run view.
|
|
9343
|
+
// Its plan arrives next, onto an empty run view. Nor does it keep an open
|
|
9344
|
+
// prompt editor: the fork copies the task ids, so Enter would save the
|
|
9345
|
+
// original's draft into the fork.
|
|
9297
9346
|
case "sessionForked":
|
|
9298
9347
|
return step(withRun({
|
|
9299
9348
|
...state,
|
|
9300
9349
|
sessionId: action.sessionId,
|
|
9301
9350
|
goal: action.goal,
|
|
9302
9351
|
taskView: null,
|
|
9352
|
+
expandedTaskId: null,
|
|
9353
|
+
taskEditor: null,
|
|
9303
9354
|
status: "idle",
|
|
9304
9355
|
planner: "idle",
|
|
9305
9356
|
turnGate: import_core52.NO_TURN,
|
|
@@ -9307,6 +9358,7 @@ function reduceAction(state, action) {
|
|
|
9307
9358
|
pendingApprovals: [],
|
|
9308
9359
|
queuedPrompts: import_core52.EMPTY_HOLD,
|
|
9309
9360
|
stopArmed: false,
|
|
9361
|
+
stopRequested: false,
|
|
9310
9362
|
forceSendPending: null,
|
|
9311
9363
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay,
|
|
9312
9364
|
runSettingsKnown: false
|
|
@@ -9347,6 +9399,7 @@ function reduceAction(state, action) {
|
|
|
9347
9399
|
case "daemonReconnected":
|
|
9348
9400
|
return step(state, [{ type: "loadIsolationPreview" }]);
|
|
9349
9401
|
case "failed": {
|
|
9402
|
+
if (state.sessionId !== null && stale(state, action.sessionId)) return step(state);
|
|
9350
9403
|
if (!action.turn) {
|
|
9351
9404
|
const abandoned = reportError(taskLogAbandoned(state), action.message);
|
|
9352
9405
|
return step(state.status === "starting" ? { ...abandoned, status: "idle" } : abandoned);
|
|
@@ -9367,7 +9420,10 @@ function reduceAction(state, action) {
|
|
|
9367
9420
|
if (!plannerInFlight(state)) return step(reported);
|
|
9368
9421
|
return drainQueue(state, reported);
|
|
9369
9422
|
}
|
|
9423
|
+
// As `failed`: an opening that let itself go asks on the welcome it left,
|
|
9424
|
+
// never over a session loaded meanwhile.
|
|
9370
9425
|
case "workspaceNeedsInit":
|
|
9426
|
+
if (state.sessionId !== null && stale(state, action.sessionId)) return step(state);
|
|
9371
9427
|
return step({
|
|
9372
9428
|
...state,
|
|
9373
9429
|
planner: "idle",
|
|
@@ -9378,7 +9434,23 @@ function reduceAction(state, action) {
|
|
|
9378
9434
|
action: { kind: "init-workspace", goal: action.goal, workspace: action.workspace }
|
|
9379
9435
|
}
|
|
9380
9436
|
});
|
|
9437
|
+
// A refused opening stays on screen while the daemon is revived; the
|
|
9438
|
+
// user may have moved to another session or a fresh welcome since, and
|
|
9439
|
+
// that view, its queue and its planner are not the opening's to take.
|
|
9440
|
+
case "openingRevived": {
|
|
9441
|
+
const restarted = "The server had stopped; started a new one";
|
|
9442
|
+
if (stale(state, action.sessionId)) {
|
|
9443
|
+
return step(say(state, "system", `${restarted}. "${action.goal}" was not sent again, as it is no longer the session on screen.`));
|
|
9444
|
+
}
|
|
9445
|
+
if (!plannerInFlight(state) || state.stopRequested) {
|
|
9446
|
+
const cleared = reduceAction(state, { type: "sessionCleared", sessionId: action.sessionId, unreached: true }).state;
|
|
9447
|
+
return step(say({ ...cleared, planner: "idle", stopRequested: false }, "system", `${restarted}. The goal was not sent again, as planning was stopped.`));
|
|
9448
|
+
}
|
|
9449
|
+
const retry = { type: "startConversation", goal: action.goal, sessionId: action.sessionId, ...action.allowInit ? { allowInit: true } : {} };
|
|
9450
|
+
return step(say(state, "system", `${restarted} and retried.`), [retry]);
|
|
9451
|
+
}
|
|
9381
9452
|
case "notice":
|
|
9453
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
9382
9454
|
return step(say(state, action.level === "error" ? "error" : "system", action.message, !action.holdPlace));
|
|
9383
9455
|
case "resize": {
|
|
9384
9456
|
const resized = { ...state, rows: action.rows, cols: action.cols, selection: null };
|
|
@@ -10024,23 +10096,40 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
10024
10096
|
|
|
10025
10097
|
// src/tui/effects.ts
|
|
10026
10098
|
async function runEffect(effect, deps) {
|
|
10099
|
+
const performed = effect.type === "startConversation" ? { ...effect, sessionId: effect.sessionId ?? deps.newSessionId() } : effect;
|
|
10100
|
+
const opening = performed.type === "startConversation" ? performed : null;
|
|
10027
10101
|
const turn = isPlannerTurn(effect);
|
|
10028
|
-
const
|
|
10102
|
+
const turnSession = turn && "sessionId" in performed ? { sessionId: performed.sessionId } : {};
|
|
10103
|
+
const fail4 = (action) => deps.dispatch(turn ? { ...action, turn, ...turnSession } : action);
|
|
10029
10104
|
try {
|
|
10030
|
-
await perform(
|
|
10105
|
+
await perform(performed, deps);
|
|
10031
10106
|
} catch (err) {
|
|
10032
10107
|
if (!isConnectionRefused(err)) {
|
|
10033
10108
|
fail4(failedFrom(err));
|
|
10034
10109
|
return;
|
|
10035
10110
|
}
|
|
10036
10111
|
let revived = false;
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10112
|
+
if (effect.type !== "startConversation" || effect.sessionId === void 0) {
|
|
10113
|
+
try {
|
|
10114
|
+
revived = await deps.reviveDaemon();
|
|
10115
|
+
} catch {
|
|
10116
|
+
revived = false;
|
|
10117
|
+
}
|
|
10041
10118
|
}
|
|
10042
10119
|
if (!revived) {
|
|
10120
|
+
if (opening) deps.dispatch({ type: "sessionCleared", sessionId: opening.sessionId, unreached: true });
|
|
10043
10121
|
fail4({ type: "failed", message: describeConnectionRefused(deps.port) });
|
|
10122
|
+
if (opening) await reloadSettings(deps);
|
|
10123
|
+
return;
|
|
10124
|
+
}
|
|
10125
|
+
if (opening) {
|
|
10126
|
+
deps.dispatch({ type: "daemonReconnected" });
|
|
10127
|
+
deps.dispatch({
|
|
10128
|
+
type: "openingRevived",
|
|
10129
|
+
sessionId: opening.sessionId,
|
|
10130
|
+
goal: opening.goal,
|
|
10131
|
+
...opening.allowInit ? { allowInit: opening.allowInit } : {}
|
|
10132
|
+
});
|
|
10044
10133
|
return;
|
|
10045
10134
|
}
|
|
10046
10135
|
deps.dispatch({ type: "notice", message: "The server had stopped; started a new one and retried." });
|
|
@@ -10048,7 +10137,7 @@ async function runEffect(effect, deps) {
|
|
|
10048
10137
|
deps.dispatch({ type: "daemonReconnected" });
|
|
10049
10138
|
}
|
|
10050
10139
|
try {
|
|
10051
|
-
await perform(
|
|
10140
|
+
await perform(performed, deps);
|
|
10052
10141
|
} catch (retryErr) {
|
|
10053
10142
|
fail4(isConnectionRefused(retryErr) ? { type: "failed", message: describeConnectionRefused(deps.port) } : failedFrom(retryErr));
|
|
10054
10143
|
}
|
|
@@ -10084,7 +10173,7 @@ async function perform(effect, deps) {
|
|
|
10084
10173
|
const { api, dispatch, workspace } = deps;
|
|
10085
10174
|
switch (effect.type) {
|
|
10086
10175
|
case "startConversation": {
|
|
10087
|
-
const sessionId =
|
|
10176
|
+
const { sessionId } = effect;
|
|
10088
10177
|
dispatch({ type: "sessionStarted", sessionId, goal: effect.goal });
|
|
10089
10178
|
try {
|
|
10090
10179
|
await converse(deps, sessionId, () => api.startConversation(sessionId, effect.goal, void 0, workspace, effect.allowInit), true);
|
|
@@ -10093,11 +10182,15 @@ async function perform(effect, deps) {
|
|
|
10093
10182
|
await reloadSettings(deps, sessionId);
|
|
10094
10183
|
throw err;
|
|
10095
10184
|
}
|
|
10185
|
+
if (isConnectionRefused(err)) {
|
|
10186
|
+
unfollow(deps, sessionId);
|
|
10187
|
+
throw err;
|
|
10188
|
+
}
|
|
10096
10189
|
unfollow(deps, sessionId);
|
|
10097
|
-
dispatch({ type: "sessionCleared" });
|
|
10190
|
+
dispatch({ type: "sessionCleared", sessionId });
|
|
10098
10191
|
await reloadSettings(deps);
|
|
10099
10192
|
if (err instanceof WorkspaceInitNeededError && !effect.allowInit) {
|
|
10100
|
-
dispatch({ type: "workspaceNeedsInit", goal: effect.goal, workspace: err.workspace });
|
|
10193
|
+
dispatch({ type: "workspaceNeedsInit", goal: effect.goal, workspace: err.workspace, sessionId });
|
|
10101
10194
|
return;
|
|
10102
10195
|
}
|
|
10103
10196
|
throw err;
|
|
@@ -10167,7 +10260,7 @@ async function perform(effect, deps) {
|
|
|
10167
10260
|
try {
|
|
10168
10261
|
await api.respondToApproval(effect.sessionId, effect.approvalId, effect.granted);
|
|
10169
10262
|
} catch {
|
|
10170
|
-
dispatch({ type: "failed", message: "Could not deliver the approval answer: the planner will treat it as denied." });
|
|
10263
|
+
dispatch({ type: "failed", message: "Could not deliver the approval answer: the planner will treat it as denied.", sessionId: effect.sessionId });
|
|
10171
10264
|
}
|
|
10172
10265
|
return;
|
|
10173
10266
|
}
|
|
@@ -10176,7 +10269,7 @@ async function perform(effect, deps) {
|
|
|
10176
10269
|
try {
|
|
10177
10270
|
await api.respondToApproval(effect.sessionId, effect.approvalId, effect.answer);
|
|
10178
10271
|
} catch {
|
|
10179
|
-
dispatch({ type: "failed", message: "Could not deliver the answer: the task is still waiting for approval." });
|
|
10272
|
+
dispatch({ type: "failed", message: "Could not deliver the answer: the task is still waiting for approval.", sessionId: effect.sessionId });
|
|
10180
10273
|
}
|
|
10181
10274
|
return;
|
|
10182
10275
|
}
|
|
@@ -10693,7 +10786,7 @@ async function converse(deps, sessionId, call, opening = false) {
|
|
|
10693
10786
|
}
|
|
10694
10787
|
if (unusable) {
|
|
10695
10788
|
const { err, shown } = unusable;
|
|
10696
|
-
deps.dispatch({ ...failedFrom(shown ? new Error(`The planner's reply arrived but could not be shown: ${explain(err)}`) : err), turn: true });
|
|
10789
|
+
deps.dispatch({ ...failedFrom(shown ? new Error(`The planner's reply arrived but could not be shown: ${explain(err)}`) : err), turn: true, sessionId });
|
|
10697
10790
|
} else if (lost !== null) {
|
|
10698
10791
|
await settleLostReply(deps, sessionId, { opening, reason: lost });
|
|
10699
10792
|
}
|
|
@@ -10737,15 +10830,15 @@ async function readBackSession(deps, sessionId, how) {
|
|
|
10737
10830
|
} catch (err) {
|
|
10738
10831
|
if (err instanceof DaemonError && err.code === "session_not_found" && how.opening) {
|
|
10739
10832
|
unfollow(deps, sessionId);
|
|
10740
|
-
deps.dispatch({ type: "sessionCleared" });
|
|
10741
|
-
deps.dispatch({ type: "failed", message: "The reply was lost and the server holds no session for it. Send your goal again to start over.", code: err.code, turn: true });
|
|
10833
|
+
deps.dispatch({ type: "sessionCleared", sessionId });
|
|
10834
|
+
deps.dispatch({ type: "failed", message: "The reply was lost and the server holds no session for it. Send your goal again to start over.", code: err.code, turn: true, sessionId });
|
|
10742
10835
|
return;
|
|
10743
10836
|
}
|
|
10744
10837
|
if (how.turnEnded || err instanceof DaemonError && err.code === "session_not_found") {
|
|
10745
|
-
deps.dispatch({ ...failedFrom(err), turn: true });
|
|
10838
|
+
deps.dispatch({ ...failedFrom(err), turn: true, sessionId });
|
|
10746
10839
|
return;
|
|
10747
10840
|
}
|
|
10748
|
-
deps.dispatch({ type: "notice", level: "error", message: `Could not read the session back from the server: ${explain(err)}
|
|
10841
|
+
deps.dispatch({ type: "notice", level: "error", message: `Could not read the session back from the server: ${explain(err)}`, sessionId });
|
|
10749
10842
|
}
|
|
10750
10843
|
}
|
|
10751
10844
|
function restoredChat(plan, sessionId) {
|
|
@@ -11208,6 +11301,7 @@ var writeLine = (line) => console.error(line);
|
|
|
11208
11301
|
var toStderr = { onNotice: writeLine, onUpdate: writeLine };
|
|
11209
11302
|
|
|
11210
11303
|
// src/tui/index.ts
|
|
11304
|
+
var DAEMON_STOP_GRACE_MS = 3e3;
|
|
11211
11305
|
async function runLogin(start, command, cwd, env) {
|
|
11212
11306
|
const plan = await (0, import_core55.planDirectLaunch)(command[0], command.slice(1));
|
|
11213
11307
|
return new Promise((resolve6, reject) => {
|
|
@@ -11353,12 +11447,19 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
11353
11447
|
const { rows, cols } = terminal.size();
|
|
11354
11448
|
app.dispatch({ type: "resize", rows, cols });
|
|
11355
11449
|
app.start();
|
|
11450
|
+
let exiting = false;
|
|
11356
11451
|
function shutdown(code) {
|
|
11452
|
+
if (exiting) return process.exit(code);
|
|
11453
|
+
exiting = true;
|
|
11357
11454
|
watchingSettings.dispose();
|
|
11358
11455
|
terminal?.close();
|
|
11359
|
-
if (owned)
|
|
11456
|
+
if (!owned) return process.exit(code);
|
|
11457
|
+
const grace = setTimeout(() => process.exit(code), DAEMON_STOP_GRACE_MS);
|
|
11458
|
+
void stopDaemon(port).catch(() => {
|
|
11459
|
+
}).finally(() => {
|
|
11460
|
+
clearTimeout(grace);
|
|
11461
|
+
process.exit(code);
|
|
11360
11462
|
});
|
|
11361
|
-
process.exit(code);
|
|
11362
11463
|
}
|
|
11363
11464
|
process.on("SIGINT", () => {
|
|
11364
11465
|
if (loginRunning) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ordewell/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Ordewell CLI and TUI — plan, run and verify coding-agent tasks from the terminal.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Alessandro Costanzo Ciano",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"typecheck": "tsc --noEmit"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@ordewell/core": "^0.11.
|
|
58
|
-
"@ordewell/web": "^0.11.
|
|
57
|
+
"@ordewell/core": "^0.11.3",
|
|
58
|
+
"@ordewell/web": "^0.11.3",
|
|
59
59
|
"ws": "^8.18.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|