@vincemakes/kiso-code 0.45.3 → 0.46.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 +2 -1
- package/dist/builtin.d.ts +5 -4
- package/dist/builtin.js +12 -7
- package/dist/chat.js +121 -19
- package/dist/child-result.d.ts +23 -0
- package/dist/child-result.js +59 -0
- package/dist/config.d.ts +13 -6
- package/dist/config.js +30 -16
- package/dist/create-coding-agent.js +13 -1
- package/dist/dispatch.d.ts +2 -0
- package/dist/dispatch.js +170 -33
- package/dist/empty-sessions.d.ts +4 -2
- package/dist/empty-sessions.js +6 -2
- package/dist/external-editor.js +4 -1
- package/dist/floor.js +71 -26
- package/dist/index.js +89 -29
- package/dist/launch.d.ts +29 -0
- package/dist/launch.js +43 -0
- package/dist/mode.d.ts +90 -17
- package/dist/mode.js +135 -32
- package/dist/preferences.d.ts +14 -0
- package/dist/preferences.js +34 -3
- package/dist/protected-shell.js +60 -19
- package/dist/protected-writes.d.ts +2 -2
- package/dist/protected-writes.js +2 -2
- package/dist/readonly-shell.js +16 -14
- package/dist/replay.js +3 -0
- package/dist/resume.js +2 -2
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +41 -11
- package/dist/shell-words.d.ts +34 -1
- package/dist/shell-words.js +84 -4
- package/dist/state.d.ts +31 -0
- package/dist/state.js +88 -2
- package/dist/task-notice.d.ts +22 -0
- package/dist/task-notice.js +86 -0
- package/dist/trust-ui.js +6 -6
- package/package.json +14 -14
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Moved verbatim from index.ts's makeAgent (R1, 2026-09-23) and renamed;
|
|
8
8
|
* the body is not edited beyond the rename and activeStoreDir's setter.
|
|
9
9
|
*/
|
|
10
|
-
import { activeStoreDir, setActiveStoreDir, body, bodyLog, codingToolOptions, extensionsDir, floorOn, kisoHome, loadedExtensions, ownSessionsDir, projectRoot, protectedFiles, secretEnvNamesOf, sessionsDir, setAgentModel, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, setFloorOn, setMergedConfig, setModelChoice, setNeverInherited, setRetryShown, setSessionStore, setUserProtectedPaths, settingsLayers, workspaceRoot } from "./state.js";
|
|
10
|
+
import { activeStoreDir, childTurnBudget, setActiveStoreDir, body, bodyLog, codingToolOptions, extensionsDir, floorOn, kisoHome, loadedExtensions, ownSessionsDir, projectRoot, protectedFiles, secretEnvNamesOf, sessionsDir, setAgentModel, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setCurrentProfileName, setExtensionLists, setFloorOn, setMergedConfig, setModelChoice, setNeverInherited, setRetryShown, setSessionStore, setUserProtectedPaths, settingsLayers, workspaceRoot } from "./state.js";
|
|
11
11
|
import { claimProjectDir, projectLayoutActive } from "./projects.js";
|
|
12
12
|
import { migrationNotice, pendingLegacyIds, planMigration, runMigration } from "./session-migration.js";
|
|
13
13
|
import { askUi, resolveProjectTrust } from "./trust-ui.js";
|
|
@@ -34,6 +34,7 @@ import { createFauxProvider } from "@vincemakes/kiso-evals";
|
|
|
34
34
|
import { sweepStaleMergeDirs } from "./temp-sweep.js";
|
|
35
35
|
import { CODING_TOOL_RULES, composeSystemPrompt } from "./coding-prompt.js";
|
|
36
36
|
import { createAgent, loadExtensions, loadProjectExtensions, SessionStore } from "@vincemakes/kiso-runtime";
|
|
37
|
+
import { preferences, startingEffort } from "./preferences.js";
|
|
37
38
|
/** LT-1: the stream watchdog's bound from the environment — a non-negative
|
|
38
39
|
* number of milliseconds (0 disables it); anything else is ignored. The
|
|
39
40
|
* PTY rigs use it to trip the watchdog in seconds against a stub that
|
|
@@ -265,6 +266,15 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
|
|
|
265
266
|
// config profile — a direct provider/model or an env key names none.
|
|
266
267
|
workspace: workspaceRoot(),
|
|
267
268
|
...(resolved !== null && merged.models?.[resolved.name] === resolved.profile ? { profileName: resolved.name } : {}),
|
|
269
|
+
// B1: a NEW session of a config profile starts at the effort last
|
|
270
|
+
// picked for it (the picker's fallback applied); the runtime reads it
|
|
271
|
+
// for a new session only — a resumed one keeps its own
|
|
272
|
+
...(() => {
|
|
273
|
+
if (resolved === null || merged.models?.[resolved.name] !== resolved.profile)
|
|
274
|
+
return {};
|
|
275
|
+
const effort = startingEffort(resolved.profile, preferences().effort?.[resolved.name]);
|
|
276
|
+
return effort === null ? {} : { reasoning: { thinking: "default", effort } };
|
|
277
|
+
})(),
|
|
268
278
|
// Area 5: the coding tools are bound to the workspace — every path
|
|
269
279
|
// they touch is canonicalized inside cwd, escapes are refused.
|
|
270
280
|
tools: [...createCodingTools(codingToolOptions())], // DC-49 — the options live in state.ts, shared with the `!` command's runner
|
|
@@ -314,6 +324,8 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
|
|
|
314
324
|
// Modes: the five tiers join at the CHAIN HEAD, before the user/
|
|
315
325
|
// project extensions (the deny>allow>ask composition keeps a user
|
|
316
326
|
// deny winning over any mode tier — bypass included).
|
|
327
|
+
// ADR-0058 3d (D6): a background child's budget, its only bound.
|
|
328
|
+
...(childTurnBudget !== undefined ? { maxTurns: childTurnBudget } : {}),
|
|
317
329
|
extensions,
|
|
318
330
|
...(resolved !== null
|
|
319
331
|
? {
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface DispatchCtx {
|
|
|
39
39
|
readonly requestSwitch: (id: string) => void;
|
|
40
40
|
/** DC-57: true once this entry has been asked to leave — the guard that keeps a departing session from answering a batch's later lines. */
|
|
41
41
|
readonly leaving: () => boolean;
|
|
42
|
+
/** ADR-0058 (3e): with live tasks, ask before exiting; true = exit. */
|
|
43
|
+
readonly confirmExit: () => Promise<boolean>;
|
|
42
44
|
/** §2.5: end this chat() with a REBUILD of the agent on the SAME
|
|
43
45
|
* session — extensions, skills and config are read again. */
|
|
44
46
|
readonly requestReload: () => void;
|
package/dist/dispatch.js
CHANGED
|
@@ -3,21 +3,22 @@
|
|
|
3
3
|
* turns. The bodies moved verbatim from chat()'s closure; chat provides
|
|
4
4
|
* the context (the chain, the run state, the prompt arming).
|
|
5
5
|
*/
|
|
6
|
-
import { STATUS_GLYPHS, contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpRows, kUnit, modePickView, modelPickView, compactingStatus, palette, renderEvent, settledLabel, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
6
|
+
import { STATUS_GLYPHS, contextRows, contextUnavailableRows, displayVerb, escapeTerminal, helpRows, kUnit, modePickView, modelPickView, namedPickView, compactingStatus, palette, renderEvent, settledLabel, slashCommandNames } from "@vincemakes/kiso-tui";
|
|
7
7
|
import { newSessionId } from "./session-id.js";
|
|
8
8
|
import { buildAdapter, lookupModelMetadata, resolveContinuationScope, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
|
|
9
|
-
import {
|
|
9
|
+
import { MODE_LABEL, MODE_NOTE, OFFERED_MODES, applyModeSetting, getDontAsk, getMode, modeDisplay, parseMode, setDontAsk } from "./mode.js";
|
|
10
10
|
import { clipboardWrite, lastAnswer } from "./clipboard.js";
|
|
11
11
|
import { protectedBangReason, protectedShellVerdict } from "./protected-shell.js";
|
|
12
12
|
import { agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, VERSION, setLastBinding } from "./state.js";
|
|
13
13
|
import { adapterOptionsFor } from "./auth/adapter-options.js";
|
|
14
14
|
import { profileProviderLabel, providerLabel } from "./provider-label.js";
|
|
15
15
|
import { installedVersion, versionStatusLine } from "./stale-version.js";
|
|
16
|
-
import { preferences, setPreference } from "./preferences.js";
|
|
16
|
+
import { preferences, rememberEffort, setPreference } from "./preferences.js";
|
|
17
17
|
import { settingsRows } from "./settings.js";
|
|
18
18
|
import { floorOn, settingsLayers } from "./state.js";
|
|
19
19
|
import { currentGround } from "@vincemakes/kiso-tui-cells/render";
|
|
20
|
-
import { queuedSwitchLines } from "./state.js";
|
|
20
|
+
import { queuedSwitchLines, seenUnknownTasks, tasksFor } from "./state.js";
|
|
21
|
+
import { taskOutput, taskRow, taskStateLabel } from "./task-notice.js";
|
|
21
22
|
import { contextWindowTokens, microcompactThresholdFor, startStatusSpinner, statedContextWindow, windowSourceNote } from "./chat.js";
|
|
22
23
|
import { authForProfile, directWriteProfile, profileAvailable, resolveContextWindow, unavailableReason } from "./config.js";
|
|
23
24
|
import { shellTool } from "@vincemakes/kiso-tools-node";
|
|
@@ -97,7 +98,7 @@ function effortAxis(p) {
|
|
|
97
98
|
// the sentence the coordination note asked for: said only when the
|
|
98
99
|
// cursor could NOT land where the previous selection asked.
|
|
99
100
|
...(carried !== undefined && carried !== "default" && carriedIdx < 0 && defaultIdx >= 0
|
|
100
|
-
? { levelNote: `effort ${carried} \u2192 ${levels[defaultIdx]}:
|
|
101
|
+
? { levelNote: `effort ${carried} \u2192 ${levels[defaultIdx]}: this model's default (it has no ${carried})` }
|
|
101
102
|
: {}),
|
|
102
103
|
};
|
|
103
104
|
}
|
|
@@ -396,6 +397,16 @@ export function dispatch(line, ctx) {
|
|
|
396
397
|
});
|
|
397
398
|
return;
|
|
398
399
|
}
|
|
400
|
+
if (trimmed === "\x02background") {
|
|
401
|
+
// ADR-0058 (3e) — ctrl+b: the running foreground command(s) to the
|
|
402
|
+
// background, through the session's TaskManager (§6), at once — a
|
|
403
|
+
// steer waits for a command to be 2 s old (D1), the key never does.
|
|
404
|
+
// Off the chain on purpose: it is pressed DURING a run.
|
|
405
|
+
const moved = tasksFor(ctx.session.id)?.detachAll("person") ?? [];
|
|
406
|
+
if (moved.length === 0)
|
|
407
|
+
body.notice("nothing to move to the background");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
399
410
|
if (trimmed === "\x14think") {
|
|
400
411
|
// §2.3 / 0.40.6 — ctrl+t hides thinking (one italic line per block,
|
|
401
412
|
// the open one included) and shows it again. DC-50's mechanism: one
|
|
@@ -510,7 +521,9 @@ export function dispatch(line, ctx) {
|
|
|
510
521
|
env: process.env,
|
|
511
522
|
...(settingsLayers.modeFlag !== undefined ? { modeFlag: settingsLayers.modeFlag } : {}),
|
|
512
523
|
...(settingsLayers.modelFlag !== undefined ? { modelFlag: settingsLayers.modelFlag } : {}),
|
|
524
|
+
dontAskFlag: settingsLayers.dontAskFlag,
|
|
513
525
|
mode: getMode(),
|
|
526
|
+
dontAsk: getDontAsk(),
|
|
514
527
|
model: { label: `${agentModel}${providerLabel(agentBaseUrl, upstreamOf(agentBaseUrl))}${profile === null ? "" : ` · profile ${profile}`}`, profile, switched: settingsLayers.modelSwitched },
|
|
515
528
|
ground: currentGround(),
|
|
516
529
|
floorOn,
|
|
@@ -563,59 +576,89 @@ export function dispatch(line, ctx) {
|
|
|
563
576
|
// /mode <name> switches — the notice cell leaves the audit
|
|
564
577
|
// line in the body, the status bar repaints at once.
|
|
565
578
|
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
566
|
-
const
|
|
567
|
-
|
|
579
|
+
const arg = trimmed.slice(5).trim();
|
|
580
|
+
// a tier switch, old names included — and when the switch came
|
|
581
|
+
// with the old dontAsk tier and leaves with it, a line says so
|
|
582
|
+
const switchTier = (s) => {
|
|
583
|
+
const wasOn = getDontAsk();
|
|
584
|
+
applyModeSetting(s);
|
|
585
|
+
body.notice(`mode → ${MODE_LABEL[getMode()]}`);
|
|
586
|
+
if (getDontAsk() !== wasOn)
|
|
587
|
+
body.notice(`don't ask → ${getDontAsk() ? "on" : "off"}`);
|
|
588
|
+
ctx.paintIdle();
|
|
589
|
+
};
|
|
590
|
+
if (arg === "") {
|
|
568
591
|
// DC-36 — BARE /mode PICKS, the way bare /model has since
|
|
569
|
-
// TUI2-R2 ④. The
|
|
570
|
-
//
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
// missing.
|
|
592
|
+
// TUI2-R2 ④. The tiers are a CLOSED set, which makes this the
|
|
593
|
+
// least defensible place in the product to have asked a human
|
|
594
|
+
// to type the answer: everything needed to make it a choice
|
|
595
|
+
// was on screen and only the choosing was missing.
|
|
574
596
|
//
|
|
575
|
-
// The printed form survives
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
// on one. `/mode <name>` is untouched.
|
|
597
|
+
// The printed form survives wherever there is no panel to
|
|
598
|
+
// draw — a pipe, a dock-less TTY — because that is a
|
|
599
|
+
// machine-readable surface. `/mode <name>` is untouched.
|
|
579
600
|
if (dock.active && ctx.input.panelAsk !== undefined) {
|
|
580
601
|
const current = getMode();
|
|
581
602
|
const picked = await new Promise((resolve) => {
|
|
582
603
|
ctx.input.panelAsk(modePickView({
|
|
583
|
-
header
|
|
584
|
-
|
|
604
|
+
// the header names the switch when it is on; the
|
|
605
|
+
// rows are the tiers alone — don't ask is not a tier
|
|
606
|
+
// and is not offered beside them (owner, 2026-09-30)
|
|
607
|
+
header: `mode — current: ${modeDisplay()}`,
|
|
608
|
+
options: OFFERED_MODES.map((name) => ({ label: MODE_LABEL[name], note: [MODE_NOTE[name], ...(name === current ? ["current"] : [])].join(" · ") })),
|
|
585
609
|
// MP-1 (0.40.7): the cursor opens on the tier in force
|
|
586
610
|
...(OFFERED_MODES.indexOf(current) >= 0 ? { initial: OFFERED_MODES.indexOf(current) } : {}),
|
|
587
611
|
},
|
|
588
612
|
// DC-12 (design §4): a panel WAITING ON A HUMAN says ❯.
|
|
589
|
-
ctx.isRunning() ? "❯ run paused" : `▸ ${
|
|
613
|
+
ctx.isRunning() ? "❯ run paused" : `▸ ${modeDisplay()}`), (v) => resolve(v.action === "picked" ? v.result : null));
|
|
590
614
|
});
|
|
591
615
|
ctx.paintIdle();
|
|
592
616
|
if (picked === null) {
|
|
593
617
|
ctx.input.prompt();
|
|
594
618
|
return; // esc — nothing switched, nothing said
|
|
595
619
|
}
|
|
596
|
-
const chosen = "index" in picked ? OFFERED_MODES[picked.index] :
|
|
620
|
+
const chosen = "index" in picked ? (OFFERED_MODES[picked.index] === undefined ? undefined : { mode: OFFERED_MODES[picked.index] }) : parseMode(picked.custom);
|
|
597
621
|
if (chosen === undefined) {
|
|
598
622
|
bodyLog(`no such mode: ${"index" in picked ? String(picked.index) : picked.custom.trim()}`);
|
|
599
623
|
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
600
624
|
}
|
|
601
625
|
else {
|
|
602
|
-
|
|
603
|
-
body.notice(`mode → ${chosen}`);
|
|
604
|
-
ctx.paintIdle();
|
|
626
|
+
switchTier(chosen);
|
|
605
627
|
}
|
|
606
628
|
ctx.input.prompt();
|
|
607
629
|
return;
|
|
608
630
|
}
|
|
609
631
|
bodyLog(`mode ${getMode()}`);
|
|
632
|
+
if (getDontAsk())
|
|
633
|
+
bodyLog("don't ask on");
|
|
610
634
|
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
611
635
|
}
|
|
612
|
-
else
|
|
613
|
-
|
|
614
|
-
|
|
636
|
+
else {
|
|
637
|
+
const typed = parseMode(arg);
|
|
638
|
+
if (typed === undefined) {
|
|
639
|
+
bodyLog(`no such mode: ${arg}`);
|
|
640
|
+
bodyLog(`tiers: ${OFFERED_MODES.join(" ")}`);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
switchTier(typed);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
ctx.input.prompt();
|
|
647
|
+
});
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (trimmed === "/dont-ask" || trimmed.startsWith("/dont-ask ")) {
|
|
651
|
+
// The don't-ask switch on its own — the tier stays. Bare flips it;
|
|
652
|
+
// `on` / `off` set it. A switch set here outlives tier changes.
|
|
653
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
654
|
+
const arg = trimmed.slice(9).trim();
|
|
655
|
+
const want = arg === "" ? !getDontAsk() : arg === "on" ? true : arg === "off" ? false : undefined;
|
|
656
|
+
if (want === undefined) {
|
|
657
|
+
bodyLog("usage: /dont-ask [on|off]");
|
|
615
658
|
}
|
|
616
659
|
else {
|
|
617
|
-
|
|
618
|
-
body.notice(`
|
|
660
|
+
setDontAsk(want);
|
|
661
|
+
body.notice(`don't ask → ${want ? "on" : "off"}`);
|
|
619
662
|
ctx.paintIdle();
|
|
620
663
|
}
|
|
621
664
|
ctx.input.prompt();
|
|
@@ -684,7 +727,7 @@ export function dispatch(line, ctx) {
|
|
|
684
727
|
// DC-12 (design §4): a panel WAITING ON A HUMAN says ❯. The
|
|
685
728
|
// else-branch keeps ▸ — it names the current tier, which is
|
|
686
729
|
// what ▸ means everywhere else.
|
|
687
|
-
ctx.isRunning() ? "❯ run paused" : `▸ ${
|
|
730
|
+
ctx.isRunning() ? "❯ run paused" : `▸ ${modeDisplay()}`), (v) => {
|
|
688
731
|
// OR-7: the level rides beside the result, so a pick
|
|
689
732
|
// with no second axis is byte-for-byte the old one.
|
|
690
733
|
if (v.action === "picked")
|
|
@@ -824,6 +867,11 @@ export function dispatch(line, ctx) {
|
|
|
824
867
|
// §2.5: the ONE source a reload reads for the model — a
|
|
825
868
|
// switch made here must survive the rebuild.
|
|
826
869
|
setModelChoice(arg);
|
|
870
|
+
// B1: an explicit effort (typed, or the panel's level) is
|
|
871
|
+
// remembered for the profile — where its next NEW session
|
|
872
|
+
// starts (preferences.json)
|
|
873
|
+
if (direct === null && effortTok !== undefined && reasoning !== undefined)
|
|
874
|
+
rememberEffort(profName, effortTok);
|
|
827
875
|
// The window a PROFILE states travels with the switch,
|
|
828
876
|
// like the model id and the endpoint. Leaving it behind
|
|
829
877
|
// is CTX-1 one field over: the row would show a
|
|
@@ -1034,15 +1082,31 @@ export function dispatch(line, ctx) {
|
|
|
1034
1082
|
// streams), a pipe's "exit" always arrives mid-run, and the
|
|
1035
1083
|
// approval panel the fix protects only exists on the dock. An idle
|
|
1036
1084
|
// exit is immediate, byte-for-byte as before, on every surface.
|
|
1085
|
+
// 3e: with live tasks the dock asks first; staying re-prompts
|
|
1086
|
+
const exitAfterAsking = async () => {
|
|
1087
|
+
if (await ctx.confirmExit())
|
|
1088
|
+
ctx.input.close();
|
|
1089
|
+
else
|
|
1090
|
+
ctx.input.prompt();
|
|
1091
|
+
};
|
|
1037
1092
|
if (ctx.isRunning() && dock.active) {
|
|
1038
1093
|
if (trimmed === "exit")
|
|
1039
1094
|
body.notice("[exit queued — closing after the current run completes]");
|
|
1040
|
-
ctx.chainRef.current = ctx.chainRef.current.then(
|
|
1041
|
-
ctx.input.close();
|
|
1042
|
-
});
|
|
1095
|
+
ctx.chainRef.current = ctx.chainRef.current.then(exitAfterAsking);
|
|
1043
1096
|
return;
|
|
1044
1097
|
}
|
|
1045
|
-
|
|
1098
|
+
void exitAfterAsking();
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (trimmed === "/tasks" || trimmed.startsWith("/tasks ")) {
|
|
1102
|
+
// ADR-0058 (3e): the session's tasks, for the PERSON. Everything here
|
|
1103
|
+
// is drawn on the screen and nowhere else — no user_input, no event
|
|
1104
|
+
// in the session log, nothing the model sees, no wake.
|
|
1105
|
+
ctx.chainRef.current = ctx.chainRef.current.then(async () => {
|
|
1106
|
+
await tasksCommand(trimmed.slice("/tasks".length).trim(), ctx);
|
|
1107
|
+
ctx.paintIdle();
|
|
1108
|
+
ctx.input.prompt();
|
|
1109
|
+
});
|
|
1046
1110
|
return;
|
|
1047
1111
|
}
|
|
1048
1112
|
if (trimmed === "/clear") {
|
|
@@ -1188,3 +1252,76 @@ export function dispatch(line, ctx) {
|
|
|
1188
1252
|
// live count rides the status bar (+N queued).
|
|
1189
1253
|
ctx.submitTurn(line);
|
|
1190
1254
|
}
|
|
1255
|
+
/** ADR-0058 (3e) — `/tasks`, `/tasks show <id>`, `/tasks stop <id>`. On a
|
|
1256
|
+
* dock the bare command is a pick: a task, then what to do with it. A
|
|
1257
|
+
* stop is REQUESTED here; the row says `stopping` until the journal says
|
|
1258
|
+
* how it ended. Looking at the list counts as having seen its unknown
|
|
1259
|
+
* tasks (the status row stops counting them). */
|
|
1260
|
+
async function tasksCommand(arg, ctx) {
|
|
1261
|
+
const manager = tasksFor(ctx.session.id);
|
|
1262
|
+
if (manager === undefined) {
|
|
1263
|
+
bodyLog("no tasks here — this session has no task manager");
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
let list;
|
|
1267
|
+
try {
|
|
1268
|
+
list = manager.list();
|
|
1269
|
+
}
|
|
1270
|
+
catch (err) {
|
|
1271
|
+
bodyLog(`tasks: ${err.message}`);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const show = (t) => {
|
|
1275
|
+
const lines = taskOutput(t);
|
|
1276
|
+
bodyLog(`${t.id} — ${t.agent !== undefined ? "its answer" : "its last output"}${lines.length === 0 ? ": nothing yet" : ""}`);
|
|
1277
|
+
for (const line of lines)
|
|
1278
|
+
bodyLog(` ${line}`);
|
|
1279
|
+
};
|
|
1280
|
+
const stop = (t) => {
|
|
1281
|
+
bodyLog(manager.stop(t.id, "person") ? `${t.id}: stop requested` : `${t.id}: nothing to stop (${taskStateLabel(t)})`);
|
|
1282
|
+
};
|
|
1283
|
+
const [verb, id] = arg.split(/\s+/);
|
|
1284
|
+
if (verb === "show" || verb === "stop") {
|
|
1285
|
+
const t = list.find((x) => x.id === id);
|
|
1286
|
+
if (t === undefined)
|
|
1287
|
+
bodyLog(`no task ${id ?? ""} in this session`);
|
|
1288
|
+
else if (verb === "show")
|
|
1289
|
+
show(t);
|
|
1290
|
+
else
|
|
1291
|
+
stop(t);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (arg !== "") {
|
|
1295
|
+
bodyLog("usage: /tasks · /tasks show <id> · /tasks stop <id>");
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (list.length === 0) {
|
|
1299
|
+
bodyLog("no tasks in this session");
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
for (const t of list)
|
|
1303
|
+
if (t.state.kind === "unknown")
|
|
1304
|
+
seenUnknownTasks.add(t.id);
|
|
1305
|
+
if (!dock.active) {
|
|
1306
|
+
for (const t of list) {
|
|
1307
|
+
const r = taskRow(t);
|
|
1308
|
+
bodyLog(`${r.label} ${r.note}`);
|
|
1309
|
+
}
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const ask = (header, options) => new Promise((resolve) => {
|
|
1313
|
+
ctx.input.panelAsk(namedPickView("tasks", { header, options }, ctx.isRunning() ? "❯ run paused" : "▸ tasks", "which task? (id) "), (v) => resolve(v.action === "picked" && "index" in v.result ? v.result.index : null));
|
|
1314
|
+
});
|
|
1315
|
+
const shown = list.slice(-20); // the newest twenty; /tasks show <id> reaches any
|
|
1316
|
+
const which = await ask(`tasks — ${list.length} in this session`, shown.map((t) => taskRow(t)));
|
|
1317
|
+
const task = which === null ? undefined : shown[which];
|
|
1318
|
+
if (task === undefined)
|
|
1319
|
+
return;
|
|
1320
|
+
const live = task.state.kind === "running" || task.state.kind === "starting";
|
|
1321
|
+
const actions = [task.agent !== undefined ? "show its answer" : "show its output", ...(live ? ["stop it"] : [])];
|
|
1322
|
+
const action = await ask(`${task.id} · ${taskStateLabel(task)} · ${taskRow(task).note}`, actions.map((label) => ({ label })));
|
|
1323
|
+
if (action === 0)
|
|
1324
|
+
show(task);
|
|
1325
|
+
else if (action === 1)
|
|
1326
|
+
stop(task);
|
|
1327
|
+
}
|
package/dist/empty-sessions.d.ts
CHANGED
|
@@ -14,8 +14,10 @@ export interface EmptyScan {
|
|
|
14
14
|
export declare function pidAlive(pid: number): boolean;
|
|
15
15
|
/** Every session in `dirs` with a sidecar and no log. Reads only. */
|
|
16
16
|
export declare function findEmptySessions(dirs: readonly string[], alive?: (pid: number) => boolean): EmptyScan;
|
|
17
|
-
/** The platform's Trash: ~/.Trash on macOS,
|
|
18
|
-
|
|
17
|
+
/** The platform's Trash: ~/.Trash on macOS, kiso's own folder on Windows
|
|
18
|
+
* (its Recycle Bin is not a folder a program moves files into), the
|
|
19
|
+
* freedesktop one elsewhere. */
|
|
20
|
+
export declare function defaultTrashRoot(kisoHome: string): string;
|
|
19
21
|
/** Move the sessions into one new folder under `trashRoot`, keeping each
|
|
20
22
|
* session folder's name; returns that folder. Never deletes: a file that
|
|
21
23
|
* cannot be renamed across devices is copied, then unlinked. */
|
package/dist/empty-sessions.js
CHANGED
|
@@ -72,10 +72,14 @@ export function findEmptySessions(dirs, alive = pidAlive) {
|
|
|
72
72
|
}
|
|
73
73
|
return { empty, inUse };
|
|
74
74
|
}
|
|
75
|
-
/** The platform's Trash: ~/.Trash on macOS,
|
|
76
|
-
|
|
75
|
+
/** The platform's Trash: ~/.Trash on macOS, kiso's own folder on Windows
|
|
76
|
+
* (its Recycle Bin is not a folder a program moves files into), the
|
|
77
|
+
* freedesktop one elsewhere. */
|
|
78
|
+
export function defaultTrashRoot(kisoHome) {
|
|
77
79
|
if (process.platform === "darwin")
|
|
78
80
|
return join(homedir(), ".Trash");
|
|
81
|
+
if (process.platform === "win32")
|
|
82
|
+
return join(kisoHome, "trash");
|
|
79
83
|
return join(process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share"), "Trash", "files");
|
|
80
84
|
}
|
|
81
85
|
/** Move the sessions into one new folder under `trashRoot`, keeping each
|
package/dist/external-editor.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
24
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
25
25
|
import { join } from "node:path";
|
|
26
|
+
import { launchCommand } from "./launch.js";
|
|
26
27
|
/**
|
|
27
28
|
* Run `bin` on `text` and return what came back, or `null` when the edit
|
|
28
29
|
* failed — in which case `onFailure` has already been told, in one line,
|
|
@@ -43,7 +44,9 @@ export function runExternalEditor(bin, root, text, onFailure) {
|
|
|
43
44
|
const file = join(dir, "message.md"); // .md so the editor lights it up
|
|
44
45
|
try {
|
|
45
46
|
writeFileSync(file, text, { encoding: "utf8", mode: 0o600 });
|
|
46
|
-
|
|
47
|
+
// Windows: an editor is often a .cmd shim (code.cmd) — through cmd.exe
|
|
48
|
+
const l = launchCommand(bin, [file]);
|
|
49
|
+
const r = spawnSync(l.file, [...l.args], { stdio: "inherit", shell: false, ...(l.windowsVerbatimArguments === true ? { windowsVerbatimArguments: true } : {}) });
|
|
47
50
|
if (r.error !== undefined || (r.status !== null && r.status !== 0)) {
|
|
48
51
|
onFailure(`[ctrl+g] ${bin} exited ${r.status ?? "abnormally"} — the composer is unchanged`);
|
|
49
52
|
return null;
|