@wirecat/cli-core 0.566.0 → 0.568.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/dist/background/schedules.d.ts +35 -1
- package/dist/background/schedules.js +74 -8
- package/dist/cli/bot/command.js +5 -2
- package/dist/cli/bot/context.d.ts +2 -2
- package/dist/cli/bot/context.js +2 -2
- package/dist/cli/effects.d.ts +10 -0
- package/dist/cli/effects.js +19 -0
- package/dist/cli/index.d.ts +4 -3
- package/dist/cli/index.js +3 -2
- package/dist/cli/messenger/account-command.js +8 -2
- package/dist/cli/messenger/mcp-command.js +4 -1
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/program.js +2 -2
- package/dist/cli/schedule/schedule-command.d.ts +23 -4
- package/dist/cli/schedule/schedule-command.js +107 -22
- package/dist/cli/settings.d.ts +9 -1
- package/dist/cli/settings.js +31 -5
- package/dist/entities/account-profile.d.ts +2 -0
- package/dist/entities/account-profile.js +2 -0
- package/dist/entities/index.d.ts +1 -0
- package/dist/errors/reasons.js +4 -1
- package/dist/mail/cli/marks.d.ts +2 -1
- package/dist/mail/cli/marks.js +13 -2
- package/dist/mail/cli/review.js +7 -5
- package/dist/mail/index.d.ts +1 -1
- package/dist/mail/index.js +1 -1
- package/dist/mail/marks.d.ts +21 -5
- package/dist/mail/marks.js +81 -26
- package/dist/meetings/findings-rules.d.ts +2 -2
- package/dist/meetings/findings-rules.js +15 -17
- package/dist/ports/app.d.ts +2 -0
- package/dist/ports/app.js +2 -0
- package/dist/services/proposals/kinds/mail-findings.d.ts +3 -2
- package/dist/services/proposals/kinds/mail-findings.js +3 -2
- package/dist/store/sqlite/mail-findings.js +10 -7
- package/dist/store/sqlite/work-tasks.js +1 -1
- package/package.json +1 -1
- package/parity.json +10 -0
- /package/dist/cli/{meeting-findings-input.d.ts → findings-input.d.ts} +0 -0
- /package/dist/cli/{meeting-findings-input.js → findings-input.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,80 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.568.0 — 05.11.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **`holderHasMail` is gone: `mailWaiting` checks and `advancePastSkipped` moves.** `holderHasMail` read like a
|
|
15
|
+
check but moved the holder's mark on every schedule tick. `mailWaiting(store, holder)` only reads: how many emails
|
|
16
|
+
past the holder's marks would reach it and when the oldest was stored, or `unmarked`. `advancePastSkipped(store,
|
|
17
|
+
holder)` moves the mark over the mail at its head the holder would only skip, and leaves it for the next check when
|
|
18
|
+
a sync holds the store. `ScheduleHost.newMail` now returns `MailWaiting`. A host calls `advancePastSkipped`, then
|
|
19
|
+
`mailWaiting`.
|
|
20
|
+
- **A gated agent schedule waits for a batch of mail.** On its timer, `schedule agent --on-new-mail <holder>` starts
|
|
21
|
+
the agent once 10 emails wait for it or the oldest has waited 30 minutes; `--mail-wait <duration>` and
|
|
22
|
+
`--mail-batch <n>` change that per schedule, and a host's `ScheduleHost.limits` gives its config file's values
|
|
23
|
+
(`SCHEDULE_SETTINGS`, read with `scheduleLimitsOf`): flag, then config file, then default. `schedule run` by hand
|
|
24
|
+
still starts it for any mail. Its skipped answer names `why` instead of `holder`.
|
|
25
|
+
- **An agent schedule stops for the day at a cost cap.** A claude run is asked for its JSON answer, so its cost
|
|
26
|
+
(`total_cost_usd`) is kept in `last.json` (`costUsd`) and summed per local day in `spend.json`; the report kept is
|
|
27
|
+
the answer's text. Once a day's runs cost `--max-cost-per-day` (default 5 US dollars, or the config file's; 0 for
|
|
28
|
+
none), no run starts until the next day; `last.json` says why under `skipped`, and `schedule list` shows the day's
|
|
29
|
+
spend and the skip.
|
|
30
|
+
- **`messages acknowledge` moves only a holder the caller may move.** A run that `schedule agent --on-new-mail` started
|
|
31
|
+
gets `<PREFIX>_MAIL_HOLDER` and moves that holder's mark only; any other agent, or a run under the read-only lock,
|
|
32
|
+
is refused with `mail.holder-not-yours`; the owner moves any. A cursor past the newest stored email, or read in the
|
|
33
|
+
future, is refused with `mail.cursor-ahead`. A scheduled triage agent needs `--on-new-mail`.
|
|
34
|
+
- **`review --holder` leaves out suspicious senders, and now keeps them on the mark.** Their
|
|
35
|
+
emails stay on the holder's mark and come in a later review once the sender's score is cleared, instead of being
|
|
36
|
+
passed for good. This was listed under Added in the release that brought it; it changes what an existing caller
|
|
37
|
+
reads.
|
|
38
|
+
- **A mail findings citation of an email the store does not hold is `not_found`** with reason
|
|
39
|
+
`mail-findings.not-stored`, no longer `mail-findings.stale`, which told the agent to read the mailbox again. An email
|
|
40
|
+
deleted after the batch was staged still makes accepting it stale.
|
|
41
|
+
- **`parseMailFindingsJson`, `parseMeetingFindingsJson` and `parseEvidenceFindingsJson` live in
|
|
42
|
+
`src/cli/findings-input.ts`;** the exports from `@wirecat/cli-core/cli` are unchanged. `findingText` and
|
|
43
|
+
`findingFields` are the checks themselves, not wrappers.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **An accepted `new_contact` mail finding names the person.** Sync already names a sender's person from the From
|
|
48
|
+
display name, so accepting set nothing while the receipt said accepted; the owner's chosen name now replaces it.
|
|
49
|
+
- **`mail review` offers at most two next steps.** It gave one `conversations show` step per cut thread, up to 50;
|
|
50
|
+
it now gives one, and names the other cut threads under `missing`.
|
|
51
|
+
|
|
52
|
+
## 0.567.0 — 05.11.2026
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **`<tool> bot list` runs without a profile word**, as `account list` does; `<tool> <profile> bot list` still
|
|
57
|
+
works.
|
|
58
|
+
- **A scheduled agent may run other CLIs the host declares** (`ScheduleHost.agentRuns`, type `AgentCli`): memo's
|
|
59
|
+
mail triage runs `mail`. Claude gets `Bash(<cli>:*)` for each; codex may write each one's state, config and cache
|
|
60
|
+
folders; every run sets `<PREFIX>_READ_ONLY=1` and `<PREFIX>_SCHEDULED` for each, whatever the schedule file says.
|
|
61
|
+
`runSchedule` takes the same list as `agentRuns`.
|
|
62
|
+
- **`<PREFIX>_READ_ONLY` lets through one authority change, `rules add`, when the command is marked
|
|
63
|
+
`narrowsUnderLock`** (`LOCKED_AUTHORITY_COMMANDS`, `admittedUnderLock`). The command narrows itself under the lock;
|
|
64
|
+
every other command marked `grantsAuthority` is still refused, and MCP still needs `mcp.authority`.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **`account list` no longer lists a bot as an account that is not signed in.** A name that only the bot side
|
|
69
|
+
uses (the file's `bot` section, a bot's own files) and that has no personal section, recorded login or session is
|
|
70
|
+
left out; `bot list` has it. A profile that is an account and a bot too says `bot: true`, a new optional field of
|
|
71
|
+
`account_profile`. `mcp serve --profiles all` serves the same account profiles. `Settings` gains the optional
|
|
72
|
+
`accountProfiles` and `botProfiles`.
|
|
73
|
+
- **`config set` keeps a long id as the digits typed.** `config set --bot phone_number_id 100000000000001` was
|
|
74
|
+
parsed as a number and refused by a setting that takes text; when the parsed value is refused and the text as
|
|
75
|
+
typed passes, the text is kept, never through a JS number.
|
|
76
|
+
|
|
77
|
+
### Changed — may break callers
|
|
78
|
+
|
|
79
|
+
- **A write on a stale task revision fails with `conflict`, exit 15, not `configuration_error`, exit 3.** The reason
|
|
80
|
+
stays `task.stale` with the task's current `revision` in `details`; only the code and the exit number change
|
|
81
|
+
(cli-base's new `conflict`). A caller that branched on `configuration_error` for it should branch on the reason or
|
|
82
|
+
the new code. `vocabulary.stale`, `tag-review.stale` and `meeting-project.stale` keep `configuration_error` for now.
|
|
83
|
+
|
|
10
84
|
## 0.566.0 — 05.11.2026
|
|
11
85
|
|
|
12
86
|
### Changed — may break callers
|
|
@@ -19,7 +19,29 @@ export interface Schedule {
|
|
|
19
19
|
createdAt: string;
|
|
20
20
|
/** A holder whose mark gates the run: it starts only when mail is stored past that mark. */
|
|
21
21
|
onNewMail?: string;
|
|
22
|
+
/** Gated on mail: start once the oldest waiting email has waited this many minutes, or `mailBatch` emails wait. */
|
|
23
|
+
mailWaitMinutes?: number;
|
|
24
|
+
mailBatch?: number;
|
|
25
|
+
/** An agent's: no run starts for the rest of the local day once its runs cost this much, in US dollars. */
|
|
26
|
+
maxCostPerDay?: number;
|
|
22
27
|
}
|
|
28
|
+
/** What a gated agent waits for, and what a day of its runs may cost, when neither its flags nor the config say. */
|
|
29
|
+
export declare const SCHEDULE_DEFAULTS: {
|
|
30
|
+
readonly mailWaitMinutes: 30;
|
|
31
|
+
readonly mailBatch: 10;
|
|
32
|
+
readonly maxCostPerDay: 5;
|
|
33
|
+
};
|
|
34
|
+
/** One local day's cost of a schedule's runs, as the harness reported it (`spend.json` beside the schedule). */
|
|
35
|
+
export interface DaySpend {
|
|
36
|
+
/** The local date, YYYY-MM-DD. */
|
|
37
|
+
day: string;
|
|
38
|
+
usd: number;
|
|
39
|
+
}
|
|
40
|
+
export declare const localDay: (at: Date) => string;
|
|
41
|
+
/** What the schedule's runs cost today; nothing before its first priced run of the day. */
|
|
42
|
+
export declare const spentToday: (file: string, now?: Date) => number;
|
|
43
|
+
/** Another CLI a scheduled agent may run by name, as the host declares it: `memo`'s agent runs `mail`. */
|
|
44
|
+
export type AgentCli = Pick<AppIdentity, "command" | "appName" | "envPrefix">;
|
|
23
45
|
export interface LastRun {
|
|
24
46
|
at: string;
|
|
25
47
|
exitCode: number;
|
|
@@ -35,6 +57,13 @@ export interface LastRun {
|
|
|
35
57
|
failingSince?: string;
|
|
36
58
|
/** Failed runs in a row; absent once a run succeeds. */
|
|
37
59
|
failures?: number;
|
|
60
|
+
/** What the run cost, as the harness reported it; absent when it reports none. */
|
|
61
|
+
costUsd?: number;
|
|
62
|
+
/** The last time the timer did not start it, and why; a run after it clears this. */
|
|
63
|
+
skipped?: {
|
|
64
|
+
at: string;
|
|
65
|
+
why: string;
|
|
66
|
+
};
|
|
38
67
|
}
|
|
39
68
|
/**
|
|
40
69
|
* Why a failed run failed, from the error object the CLI printed last: its code and reason id. Never the
|
|
@@ -58,7 +87,10 @@ export declare const recordRun: (path: string, run: {
|
|
|
58
87
|
traceId?: string;
|
|
59
88
|
reason?: string;
|
|
60
89
|
errorCode?: string;
|
|
90
|
+
costUsd?: number;
|
|
61
91
|
}) => LastRun;
|
|
92
|
+
/** Notes on the last run that the timer left this one out, keeping that run's result. */
|
|
93
|
+
export declare const recordSkip: (file: string, why: string, at?: Date) => void;
|
|
62
94
|
/**
|
|
63
95
|
* One stderr line per failing background job, or nothing: `tg: scheduled digest failing since … (3 runs):
|
|
64
96
|
* <reason> — tg schedule list`. A status the owner has to ask for is how a broken timer goes unnoticed.
|
|
@@ -113,11 +145,13 @@ export declare const CREDENTIALS_UNREACHABLE = "credentials.unreachable";
|
|
|
113
145
|
* keyring, so a run that fails for that reason is tried again, a minute then up to 15 minutes apart, for up to
|
|
114
146
|
* 12 hours. A schedule that repeats every few minutes is left to its next tick; a run by hand fails at once.
|
|
115
147
|
*/
|
|
116
|
-
export declare const runSchedule: (app: AppIdentity, system: ServerSystem, env: NodeJS.ProcessEnv, file: string, { now, timer, signal, killAfterMs, untilUnlocked, wait, }?: {
|
|
148
|
+
export declare const runSchedule: (app: AppIdentity, system: ServerSystem, env: NodeJS.ProcessEnv, file: string, { now, timer, signal, killAfterMs, untilUnlocked, wait, agentRuns, }?: {
|
|
117
149
|
now?: () => Date;
|
|
118
150
|
timer?: Timer;
|
|
119
151
|
signal?: AbortSignal;
|
|
120
152
|
killAfterMs?: number;
|
|
121
153
|
untilUnlocked?: boolean;
|
|
122
154
|
wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
155
|
+
/** The other CLIs an agent may run; each runs under its own read-only lock and knows it runs from a schedule. */
|
|
156
|
+
agentRuns?: readonly AgentCli[];
|
|
123
157
|
}) => Promise<LastRun>;
|
|
@@ -1,10 +1,54 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
4
|
import { CliError, errorCodes, writeSecurely } from "@wirecat/cli-base";
|
|
5
|
-
import { readOnlyLockOf } from "../ports/app.js";
|
|
5
|
+
import { mailHolderVariable, readOnlyLockOf } from "../ports/app.js";
|
|
6
6
|
import { recordLogFor } from "../runs/record-log.js";
|
|
7
7
|
import { childTrace } from "../runs/scope.js";
|
|
8
|
+
/** What a gated agent waits for, and what a day of its runs may cost, when neither its flags nor the config say. */
|
|
9
|
+
export const SCHEDULE_DEFAULTS = { mailWaitMinutes: 30, mailBatch: 10, maxCostPerDay: 5 };
|
|
10
|
+
export const localDay = (at) => `${at.getFullYear()}-${String(at.getMonth() + 1).padStart(2, "0")}-${String(at.getDate()).padStart(2, "0")}`;
|
|
11
|
+
const spendFile = (file) => join(file, "..", "spend.json");
|
|
12
|
+
/** What the schedule's runs cost today; nothing before its first priced run of the day. */
|
|
13
|
+
export const spentToday = (file, now = new Date()) => {
|
|
14
|
+
try {
|
|
15
|
+
const spend = JSON.parse(readFileSync(spendFile(file), "utf8"));
|
|
16
|
+
return spend.day === localDay(now) && typeof spend.usd === "number" ? spend.usd : 0;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const addSpend = (folder, usd, at) => {
|
|
23
|
+
const file = join(folder, "schedule.json");
|
|
24
|
+
const spend = { day: localDay(at), usd: spentToday(file, at) + usd };
|
|
25
|
+
writeFileSync(spendFile(file), `${JSON.stringify(spend)}\n`, { mode: 0o600 });
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Claude reports a run's cost only in its JSON answer, so a scheduled claude run asks for it; the report kept is the
|
|
29
|
+
* answer's text. Codex reports none.
|
|
30
|
+
*/
|
|
31
|
+
const priced = (argv) => {
|
|
32
|
+
const [program = ""] = argv;
|
|
33
|
+
const claude = /^claude(\.exe|\.cmd)?$/i.test(basename(program));
|
|
34
|
+
return claude && argv.includes("-p") && !argv.includes("--output-format")
|
|
35
|
+
? [...argv, "--output-format", "json"]
|
|
36
|
+
: argv;
|
|
37
|
+
};
|
|
38
|
+
const reportOf = (stdout) => {
|
|
39
|
+
try {
|
|
40
|
+
const answer = JSON.parse(stdout.toString("utf8"));
|
|
41
|
+
if (answer?.type !== "result")
|
|
42
|
+
return { text: stdout };
|
|
43
|
+
return {
|
|
44
|
+
text: typeof answer.result === "string" ? Buffer.from(answer.result) : stdout,
|
|
45
|
+
...(typeof answer.total_cost_usd === "number" ? { costUsd: answer.total_cost_usd } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { text: stdout };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
8
52
|
const REASON_ID = /^[a-z0-9][a-z0-9.-]{0,79}$/;
|
|
9
53
|
/**
|
|
10
54
|
* Why a failed run failed, from the error object the CLI printed last: its code and reason id. Never the
|
|
@@ -52,6 +96,14 @@ export const recordRun = (path, run) => {
|
|
|
52
96
|
writeSecurely(path, `${JSON.stringify(last, null, 2)}\n`, 0o600);
|
|
53
97
|
return last;
|
|
54
98
|
};
|
|
99
|
+
/** Notes on the last run that the timer left this one out, keeping that run's result. */
|
|
100
|
+
export const recordSkip = (file, why, at = new Date()) => {
|
|
101
|
+
const path = join(file, "..", "last.json");
|
|
102
|
+
const before = readRun(path);
|
|
103
|
+
if (!before)
|
|
104
|
+
return;
|
|
105
|
+
writeSecurely(path, `${JSON.stringify({ ...before, skipped: { at: at.toISOString(), why } }, null, 2)}\n`, 0o600);
|
|
106
|
+
};
|
|
55
107
|
const shortTime = (iso) => {
|
|
56
108
|
const at = new Date(iso);
|
|
57
109
|
const two = (n) => String(n).padStart(2, "0");
|
|
@@ -358,7 +410,7 @@ const pause = (ms, signal) => new Promise((resolve) => {
|
|
|
358
410
|
* keyring, so a run that fails for that reason is tried again, a minute then up to 15 minutes apart, for up to
|
|
359
411
|
* 12 hours. A schedule that repeats every few minutes is left to its next tick; a run by hand fails at once.
|
|
360
412
|
*/
|
|
361
|
-
export const runSchedule = async (app, system, env, file, { now = () => new Date(), timer, signal, killAfterMs = KILL_AFTER_MS, untilUnlocked = false, wait = pause, } = {}) => {
|
|
413
|
+
export const runSchedule = async (app, system, env, file, { now = () => new Date(), timer, signal, killAfterMs = KILL_AFTER_MS, untilUnlocked = false, wait = pause, agentRuns = [], } = {}) => {
|
|
362
414
|
const schedule = readSchedule(file);
|
|
363
415
|
const folder = join(file, "..");
|
|
364
416
|
const runs = join(folder, "runs");
|
|
@@ -366,12 +418,12 @@ export const runSchedule = async (app, system, env, file, { now = () => new Date
|
|
|
366
418
|
const agent = schedule.kind === "agent";
|
|
367
419
|
const retries = untilUnlocked && !("every" in schedule.when);
|
|
368
420
|
const firstAt = now().getTime();
|
|
369
|
-
let last = await attemptSchedule(app, system, env, schedule, folder, { now, signal, killAfterMs });
|
|
421
|
+
let last = await attemptSchedule(app, system, env, schedule, folder, { now, signal, killAfterMs, agentRuns });
|
|
370
422
|
for (let delay = LOCKED_FIRST_WAIT_MS; retries && lockedOut(last) && !signal?.aborted && now().getTime() - firstAt + delay <= LOCKED_GIVE_UP_MS; delay = Math.min(delay * 2, LOCKED_LONGEST_WAIT_MS)) {
|
|
371
423
|
await wait(delay, signal);
|
|
372
424
|
if (signal?.aborted)
|
|
373
425
|
break;
|
|
374
|
-
last = await attemptSchedule(app, system, env, schedule, folder, { now, signal, killAfterMs });
|
|
426
|
+
last = await attemptSchedule(app, system, env, schedule, folder, { now, signal, killAfterMs, agentRuns });
|
|
375
427
|
}
|
|
376
428
|
const { exitCode, output } = last;
|
|
377
429
|
const errors = agent ? output.replace(/\.md$/, ".log") : output;
|
|
@@ -399,7 +451,7 @@ export const runSchedule = async (app, system, env, file, { now = () => new Date
|
|
|
399
451
|
return last;
|
|
400
452
|
};
|
|
401
453
|
/** One run of the schedule's command or agent: its output in `runs/`, its result in `last.json`. */
|
|
402
|
-
const attemptSchedule = async (app, system, env, schedule, folder, { now, signal, killAfterMs }) => {
|
|
454
|
+
const attemptSchedule = async (app, system, env, schedule, folder, { now, signal, killAfterMs, agentRuns, }) => {
|
|
403
455
|
const runs = join(folder, "runs");
|
|
404
456
|
const stamp = now()
|
|
405
457
|
.toISOString()
|
|
@@ -408,15 +460,22 @@ const attemptSchedule = async (app, system, env, schedule, folder, { now, signal
|
|
|
408
460
|
const agent = schedule.kind === "agent";
|
|
409
461
|
const output = join(runs, `${stamp}.${agent ? "md" : "log"}`);
|
|
410
462
|
const errors = agent ? join(runs, `${stamp}.log`) : output;
|
|
411
|
-
const [program = "", ...args] = agent ? schedule.argv : [...system.entry, ...schedule.argv];
|
|
463
|
+
const [program = "", ...args] = agent ? priced(schedule.argv) : [...system.entry, ...schedule.argv];
|
|
412
464
|
const trace = childTrace();
|
|
413
465
|
const childEnv = {
|
|
414
466
|
...env,
|
|
415
467
|
...schedule.environment,
|
|
416
468
|
TRACEPARENT: trace.traceparent,
|
|
417
469
|
[`${app.envPrefix}_SCHEDULED`]: schedule.name,
|
|
470
|
+
...(schedule.onNewMail ? { [mailHolderVariable(app)]: schedule.onNewMail } : {}),
|
|
418
471
|
// A run started under the lock stays under it, whatever the schedule file says.
|
|
419
472
|
...(agent || readOnlyLockOf(app, env) ? { [`${app.envPrefix}_READ_ONLY`]: "1" } : {}),
|
|
473
|
+
...(agent
|
|
474
|
+
? Object.fromEntries(agentRuns.flatMap((other) => [
|
|
475
|
+
[`${other.envPrefix}_READ_ONLY`, "1"],
|
|
476
|
+
[`${other.envPrefix}_SCHEDULED`, schedule.name],
|
|
477
|
+
]))
|
|
478
|
+
: {}),
|
|
420
479
|
};
|
|
421
480
|
// The child's output is data, and an agent's is another program's: it is kept as it came. These lines say
|
|
422
481
|
// only when it ran and how it ended.
|
|
@@ -424,6 +483,7 @@ const attemptSchedule = async (app, system, env, schedule, folder, { now, signal
|
|
|
424
483
|
const facts = { schedule: schedule.name, kind: schedule.kind };
|
|
425
484
|
const began = now().getTime();
|
|
426
485
|
log.info({ event: "schedule.start", ...facts });
|
|
486
|
+
let costUsd;
|
|
427
487
|
const exitCode = await new Promise((resolve) => {
|
|
428
488
|
const child = spawn(program, args, { env: childEnv, cwd: folder, stdio: ["ignore", "pipe", "pipe"] });
|
|
429
489
|
const out = [];
|
|
@@ -448,24 +508,30 @@ const attemptSchedule = async (app, system, env, schedule, folder, { now, signal
|
|
|
448
508
|
signal?.removeEventListener("abort", stop);
|
|
449
509
|
if (kill)
|
|
450
510
|
clearTimeout(kill);
|
|
451
|
-
|
|
511
|
+
const report = agent ? reportOf(Buffer.concat(out)) : { text: Buffer.concat([...out, ...err]) };
|
|
512
|
+
costUsd = report.costUsd;
|
|
513
|
+
writeFileSync(output, report.text, { mode: 0o600 });
|
|
452
514
|
if (agent)
|
|
453
515
|
writeFileSync(errors, Buffer.concat(err), { mode: 0o600 });
|
|
454
516
|
resolve(code ?? (killedBy === "SIGKILL" ? 137 : killedBy === "SIGTERM" ? 143 : 1));
|
|
455
517
|
});
|
|
456
518
|
});
|
|
457
519
|
const ended = now();
|
|
520
|
+
if (costUsd !== undefined)
|
|
521
|
+
addSpend(folder, costUsd, ended);
|
|
458
522
|
log[exitCode === 0 ? "info" : "error"]({
|
|
459
523
|
event: "schedule.exit",
|
|
460
524
|
...facts,
|
|
461
525
|
exitCode,
|
|
462
526
|
durationMs: ended.getTime() - began,
|
|
527
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
463
528
|
});
|
|
464
529
|
return recordRun(join(folder, "last.json"), {
|
|
465
530
|
at: ended.toISOString(),
|
|
466
531
|
exitCode,
|
|
467
532
|
output,
|
|
468
533
|
traceId: trace.traceId,
|
|
534
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
469
535
|
...(exitCode === 0 ? {} : failureOf(readFileSync(errors, "utf8"), exitCode)),
|
|
470
536
|
});
|
|
471
537
|
};
|
package/dist/cli/bot/command.js
CHANGED
|
@@ -119,13 +119,16 @@ const meCommand = (bot) => annotate(new Command("me"), { effects: REMOTE_READ, e
|
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
-
const listCommand = (bot, tokenVariable) => withQuery(annotate(new Command("list"), {
|
|
122
|
+
const listCommand = (bot, tokenVariable) => withQuery(annotate(new Command("list"), {
|
|
123
|
+
effects: LOCAL_READ,
|
|
124
|
+
examples: [`${bot.app.command} bot list`, `${bot.app.command} bot list --check`],
|
|
125
|
+
}), BOT_PROFILES_QUERY)
|
|
123
126
|
.description(`every name on this machine that has a bot token; --check asks ${bot.name ?? "the messenger"} which bot each is`)
|
|
124
127
|
.summary("every bot profile on this machine; --check asks which bot each is")
|
|
125
128
|
.option("--check", "ask the messenger who each bot is, with its token")
|
|
126
129
|
.action(async function (words) {
|
|
127
130
|
const wanted = botProfileName(words);
|
|
128
|
-
const context = botContext(this, bot);
|
|
131
|
+
const context = botContext(this, bot, { needsProfile: false });
|
|
129
132
|
const check = this.opts().check === true;
|
|
130
133
|
if (check)
|
|
131
134
|
online(context, this);
|
|
@@ -2,14 +2,14 @@ import type { Command } from "commander";
|
|
|
2
2
|
import { SendJournal } from "../../sends/journal.js";
|
|
3
3
|
import { type Level, type PermissionKey } from "../../sends/permissions.js";
|
|
4
4
|
import { RecipientList } from "../../sends/recipients.js";
|
|
5
|
-
import type { Settings } from "../settings.js";
|
|
5
|
+
import type { ResolveOptions, Settings } from "../settings.js";
|
|
6
6
|
import type { BotAdapter, BotChatRef, BotConnectOptions, BotMessenger } from "./port.js";
|
|
7
7
|
import { ChatRegistry } from "./registry.js";
|
|
8
8
|
import { BotTokenStore } from "./token.js";
|
|
9
9
|
/** How the owner types this bot — `max sales bot` — so a refusal names the command that fixes it. */
|
|
10
10
|
export declare const botWords: (bot: BotMessenger, profile: string) => string;
|
|
11
11
|
/** What a bot command needs, resolved once: the bot's settings, its token, its lists. */
|
|
12
|
-
export declare const botContext: (command: Command, bot: BotMessenger) => {
|
|
12
|
+
export declare const botContext: (command: Command, bot: BotMessenger, resolving?: Pick<ResolveOptions, "needsProfile">) => {
|
|
13
13
|
settings: Settings;
|
|
14
14
|
renderer: import("@wirecat/cli-base").Renderer;
|
|
15
15
|
format: import("@wirecat/cli-base").RenderFormat;
|
package/dist/cli/bot/context.js
CHANGED
|
@@ -28,8 +28,8 @@ const refuseBotPermissions = (command, bot, settings) => {
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
/** What a bot command needs, resolved once: the bot's settings, its token, its lists. */
|
|
31
|
-
export const botContext = (command, bot) => {
|
|
32
|
-
const base = baseContext(command, (flags, options) => bot.resolveSettings(flags, { ...options, kind: "bot" }));
|
|
31
|
+
export const botContext = (command, bot, resolving = {}) => {
|
|
32
|
+
const base = baseContext(command, (flags, options) => bot.resolveSettings(flags, { ...options, ...resolving, kind: "bot" }));
|
|
33
33
|
const { settings, env, keyring } = base;
|
|
34
34
|
const profile = settings.profile;
|
|
35
35
|
const files = botFiles(bot.app, profile, env);
|
package/dist/cli/effects.d.ts
CHANGED
|
@@ -49,6 +49,16 @@ export declare const PROPOSE_COMMANDS: readonly ["messages acknowledge", "mail f
|
|
|
49
49
|
/** Marks a command of the `propose` tier; the lock lets it through only when its path is in `PROPOSE_COMMANDS`. */
|
|
50
50
|
export declare const proposesOnly: <T extends Command>(command: T) => T;
|
|
51
51
|
export declare const isProposal: (command: Command) => boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The authority changes `<PREFIX>_READ_ONLY` lets through, a closed list: `mail rules add`, which the owner lets the
|
|
54
|
+
* scheduled mail triage run. Such a command is marked `narrowsUnderLock` **and** has one of these paths, and under the
|
|
55
|
+
* lock it narrows itself: it only adds what the owner can list and remove, and replaces nothing. Adding a path widens
|
|
56
|
+
* what a read-only agent decides for later runs, so it needs its own review.
|
|
57
|
+
*/
|
|
58
|
+
export declare const LOCKED_AUTHORITY_COMMANDS: readonly ["rules add"];
|
|
59
|
+
/** Marks an authority change that checks the lock itself; the lock lets it through only when its path is listed. */
|
|
60
|
+
export declare const narrowsUnderLock: <T extends Command>(command: T) => T;
|
|
61
|
+
export declare const admittedUnderLock: (command: Command) => boolean;
|
|
52
62
|
/** A local edit remains an edit even when it first fetches metadata from the messenger. */
|
|
53
63
|
export declare const changesStore: <T extends Command>(command: T) => T;
|
|
54
64
|
/**
|
package/dist/cli/effects.js
CHANGED
|
@@ -79,6 +79,25 @@ export const isProposal = (command) => {
|
|
|
79
79
|
const path = pathOf(command).join(" ");
|
|
80
80
|
return PROPOSE_COMMANDS.some((listed) => path === listed || path.endsWith(` ${listed}`));
|
|
81
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The authority changes `<PREFIX>_READ_ONLY` lets through, a closed list: `mail rules add`, which the owner lets the
|
|
84
|
+
* scheduled mail triage run. Such a command is marked `narrowsUnderLock` **and** has one of these paths, and under the
|
|
85
|
+
* lock it narrows itself: it only adds what the owner can list and remove, and replaces nothing. Adding a path widens
|
|
86
|
+
* what a read-only agent decides for later runs, so it needs its own review.
|
|
87
|
+
*/
|
|
88
|
+
export const LOCKED_AUTHORITY_COMMANDS = ["rules add"];
|
|
89
|
+
const NARROWS = new WeakSet();
|
|
90
|
+
/** Marks an authority change that checks the lock itself; the lock lets it through only when its path is listed. */
|
|
91
|
+
export const narrowsUnderLock = (command) => {
|
|
92
|
+
NARROWS.add(command);
|
|
93
|
+
return command;
|
|
94
|
+
};
|
|
95
|
+
export const admittedUnderLock = (command) => {
|
|
96
|
+
if (!NARROWS.has(command))
|
|
97
|
+
return false;
|
|
98
|
+
const path = pathOf(command).join(" ");
|
|
99
|
+
return LOCKED_AUTHORITY_COMMANDS.some((listed) => path === listed || path.endsWith(` ${listed}`));
|
|
100
|
+
};
|
|
82
101
|
const STORE_WRITES = new WeakSet();
|
|
83
102
|
/** A local edit remains an edit even when it first fetches metadata from the messenger. */
|
|
84
103
|
export const changesStore = (command) => {
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AI_ENTRIES, AI_SETTING_KEYS, type AISettings, resolveAISettings } from "../analysis/settings.js";
|
|
2
|
+
export type { AgentCli } from "../background/schedules.js";
|
|
2
3
|
export { parseDuration } from "../domain/duration.js";
|
|
3
4
|
export { openOf, PROFILE_PLACEHOLDER, type ProfilesFor, type RefOpen, type RefSummary, refLabel, } from "../domain/ref-summaries.js";
|
|
4
5
|
export { botTemplate } from "../entities/list-results.js";
|
|
@@ -70,10 +71,11 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
70
71
|
export { type Closeable, withDeadline } from "./deadline.js";
|
|
71
72
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
72
73
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
73
|
-
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isProposal, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, PROPOSE_COMMANDS, previewFlag, proposesOnly, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
74
|
+
export { admittedUnderLock, changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isProposal, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, LOCKED_AUTHORITY_COMMANDS, logsInOrOut, NO_EFFECTS, narrowsUnderLock, ownerOnlyOption, PROPOSE_COMMANDS, previewFlag, proposesOnly, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
74
75
|
export { type CliEntry, type EntryContext, type EntryIo, openBrowser, startCli } from "./entry.js";
|
|
75
76
|
export { type AnswerOutput, answerSchema, type Body, type ItemBody, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
76
77
|
export { withAbort } from "./execution.js";
|
|
78
|
+
export { parseEvidenceFindingsJson, parseMailFindingsJson, parseMeetingFindingsJson, } from "./findings-input.js";
|
|
77
79
|
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
78
80
|
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, loadScenarios, MEMO_SCENARIOS, type Scenario, shellProblems, } from "./guide.js";
|
|
79
81
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
@@ -86,7 +88,6 @@ export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTermLists,
|
|
|
86
88
|
export { MCP_HTTP_DEFAULT_PORT, type McpServerOptions, type McpServerTarget, mcpServerCommand, } from "./mcp-server-command.js";
|
|
87
89
|
export { formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
88
90
|
export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
|
|
89
|
-
export { parseEvidenceFindingsJson, parseMailFindingsJson, parseMeetingFindingsJson, } from "./meeting-findings-input.js";
|
|
90
91
|
export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
|
|
91
92
|
export { type MeetingPreparationReader, meetingPreparationInput, readMeetingPreparation, } from "./meeting-preparation-read.js";
|
|
92
93
|
export { readMeetingProjectCandidates } from "./meeting-project-candidates-read.js";
|
|
@@ -119,7 +120,7 @@ export { type ProjectTextHints, projectAnswerParts, projectCutoffTime, projectEv
|
|
|
119
120
|
export { type ProjectStatusHints, type ProjectStatusItem, projectStatusAnswer, projectStatusRow, projectStatusSchema, projectStatusText, } from "./project-status.js";
|
|
120
121
|
export { STATUS_ROWS, STATUS_SIGNAL_ROWS } from "./project-status-rows.js";
|
|
121
122
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
122
|
-
export type
|
|
123
|
+
export { SCHEDULE_SETTINGS, type ScheduleHost, type ScheduleLimits, scheduleLimitsOf, } from "./schedule/schedule-command.js";
|
|
123
124
|
export { type DeclaredSetting, type SettingRow, settingRows, withSettingDefaults } from "./setting-defaults.js";
|
|
124
125
|
export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
|
|
125
126
|
export { type PermissionAnswer, readSetupAnswers, type SetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -62,10 +62,11 @@ export { sharedCommandTemplate } from "./contract-templates.js";
|
|
|
62
62
|
export { withDeadline } from "./deadline.js";
|
|
63
63
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
64
64
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
65
|
-
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isProposal, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, PROPOSE_COMMANDS, previewFlag, proposesOnly, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
65
|
+
export { admittedUnderLock, changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isProposal, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, LOCKED_AUTHORITY_COMMANDS, logsInOrOut, NO_EFFECTS, narrowsUnderLock, ownerOnlyOption, PROPOSE_COMMANDS, previewFlag, proposesOnly, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
66
66
|
export { openBrowser, startCli } from "./entry.js";
|
|
67
67
|
export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
68
68
|
export { withAbort } from "./execution.js";
|
|
69
|
+
export { parseEvidenceFindingsJson, parseMailFindingsJson, parseMeetingFindingsJson, } from "./findings-input.js";
|
|
69
70
|
export { askSetupQuestions, asksAtStart, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
70
71
|
export { checkGuide, checkStep, loadScenarios, MEMO_SCENARIOS, shellProblems, } from "./guide.js";
|
|
71
72
|
export { commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, nextUsually, } from "./help/command-help.js";
|
|
@@ -78,7 +79,6 @@ export { commandPath, LIST_QUERY_GUIDE, listQuery, nativeField, nativeTermLists,
|
|
|
78
79
|
export { MCP_HTTP_DEFAULT_PORT, mcpServerCommand, } from "./mcp-server-command.js";
|
|
79
80
|
export { formatMeetingBatch, prepareMeetingBatch } from "./meeting-batch.js";
|
|
80
81
|
export { meetingEvidenceItem, meetingPersonContextItem } from "./meeting-evidence-answer.js";
|
|
81
|
-
export { parseEvidenceFindingsJson, parseMailFindingsJson, parseMeetingFindingsJson, } from "./meeting-findings-input.js";
|
|
82
82
|
export { meetingPreparationAnswer } from "./meeting-preparation-answer.js";
|
|
83
83
|
export { meetingPreparationInput, readMeetingPreparation, } from "./meeting-preparation-read.js";
|
|
84
84
|
export { readMeetingProjectCandidates } from "./meeting-project-candidates-read.js";
|
|
@@ -109,6 +109,7 @@ export { projectAnswerParts, projectCutoffTime, projectEvidenceText, projectWind
|
|
|
109
109
|
export { projectStatusAnswer, projectStatusRow, projectStatusSchema, projectStatusText, } from "./project-status.js";
|
|
110
110
|
export { STATUS_ROWS, STATUS_SIGNAL_ROWS } from "./project-status-rows.js";
|
|
111
111
|
export { fieldsOf, projectFields } from "./result-fields.js";
|
|
112
|
+
export { SCHEDULE_SETTINGS, scheduleLimitsOf, } from "./schedule/schedule-command.js";
|
|
112
113
|
export { settingRows, withSettingDefaults } from "./setting-defaults.js";
|
|
113
114
|
export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
|
|
114
115
|
export { readSetupAnswers, setupPermissionCeiling } from "./setup-answers.js";
|
|
@@ -22,13 +22,18 @@ export const accountCommand = (messenger) => {
|
|
|
22
22
|
.description("the logged-in account")
|
|
23
23
|
.addCommand(withQuery(annotate(new Command("list"), { effects: REMOTE_READ, examples: [`${tool} account list`] }), ACCOUNT_PROFILES_QUERY)
|
|
24
24
|
.summary("profiles on this computer and their signed-in accounts")
|
|
25
|
-
.description("every profile on this computer, and the account each is logged in as;
|
|
25
|
+
.description("every account profile on this computer, and the account each is logged in as; a name that is only a bot " +
|
|
26
|
+
"is left out (`bot list` has it), one that is a bot too says `bot: true`; asks the messenger nothing")
|
|
26
27
|
.action(async function (words) {
|
|
27
28
|
const terms = queryTerms(words, ACCOUNT_PROFILES_QUERY, "account list");
|
|
28
29
|
const context = messengerContext(this, messenger, { needsProfile: false });
|
|
29
30
|
const { app, provider } = messenger;
|
|
30
31
|
const current = context.settings.sources.profile === NONE_NAMED ? undefined : context.settings.profile;
|
|
31
|
-
const
|
|
32
|
+
const { profilesHere, accountProfiles = profilesHere, botProfiles = [] } = context.settings;
|
|
33
|
+
const isAccount = (profile) => accountProfiles.includes(profile) || messenger.profileState?.(profile, context.env) === "signed-in";
|
|
34
|
+
const profiles = [
|
|
35
|
+
...new Set([...(current === undefined ? [] : [current]), ...profilesHere.filter(isAccount)]),
|
|
36
|
+
]
|
|
32
37
|
.filter((profile) => terms.get("profile") === undefined || profile === terms.get("profile"))
|
|
33
38
|
.filter((profile) => terms.get("current") === undefined ||
|
|
34
39
|
(profile === current) === (terms.get("current")?.toLowerCase() === "true"))
|
|
@@ -48,6 +53,7 @@ export const accountCommand = (messenger) => {
|
|
|
48
53
|
...(messenger.profileState
|
|
49
54
|
? { signed_in: messenger.profileState(profile, context.env) !== "signed-out" }
|
|
50
55
|
: {}),
|
|
56
|
+
...(botProfiles.includes(profile) ? { bot: true } : {}),
|
|
51
57
|
});
|
|
52
58
|
}
|
|
53
59
|
renderAnswer(this, context, wholeList("account list", items));
|
|
@@ -56,7 +56,10 @@ export const servedProfiles = (self, messenger, { settings, env }, given, words,
|
|
|
56
56
|
if (lock)
|
|
57
57
|
return { set: { option: lock, profiles: () => [lock], key: `_${lock}` } };
|
|
58
58
|
if (names.length === 1 && names[0] === EVERY_PROFILE) {
|
|
59
|
-
const every = () =>
|
|
59
|
+
const every = () => {
|
|
60
|
+
const here = messenger.resolveSettings(self.optsWithGlobals(), { env, needsProfile: false, readOnly: true });
|
|
61
|
+
return here.accountProfiles ?? here.profilesHere;
|
|
62
|
+
};
|
|
60
63
|
return { set: { option: EVERY_PROFILE, profiles: every, key: ALL_PROFILES } };
|
|
61
64
|
}
|
|
62
65
|
if (names.length === 0 || names.includes(EVERY_PROFILE))
|
|
@@ -3,9 +3,9 @@ import type { CacheDatabase } from "../../store/driver.js";
|
|
|
3
3
|
import type { Messenger } from "./context.js";
|
|
4
4
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
5
5
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
6
|
-
error?: undefined;
|
|
7
6
|
path: string;
|
|
8
7
|
exists: boolean;
|
|
8
|
+
error?: undefined;
|
|
9
9
|
} | {
|
|
10
10
|
path: string;
|
|
11
11
|
exists: boolean;
|
package/dist/cli/program.js
CHANGED
|
@@ -21,7 +21,7 @@ import { commandPathOf } from "./command-contract.js";
|
|
|
21
21
|
import { entityRows } from "./compact.js";
|
|
22
22
|
import { outputFor, provide } from "./context.js";
|
|
23
23
|
import { refuseByCommandPermissions, refuseWriteBeforeConnecting } from "./early-refusal.js";
|
|
24
|
-
import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
|
|
24
|
+
import { admittedUnderLock, changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
|
|
25
25
|
import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
|
|
26
26
|
import { byteCount, DEFAULT_AGENT_COOPERATIVE_MS, DEFAULT_COMMAND_MS, DEFAULT_COOPERATIVE_MS, DEFAULT_OUTPUT_BYTES, DEFAULT_SYNC_MS, execution, } from "./execution.js";
|
|
27
27
|
import { setupAtStart } from "./first-run.js";
|
|
@@ -334,7 +334,7 @@ const runOnce = async (argv, definition, options) => {
|
|
|
334
334
|
if (options.relayed && isLogin(action))
|
|
335
335
|
throw new CliError("permission_error", `\`${command} ${path.join(" ")}\` logs the account in or out, which serve never runs for a client — stop ` +
|
|
336
336
|
`serve first (\`${command} ${options.relayed.profile} server stop\`), or update ${command}, which then runs it here`, { sent: false });
|
|
337
|
-
if (lock && changesAuthorityNow(action) && !flags.dryRun)
|
|
337
|
+
if (lock && changesAuthorityNow(action) && !admittedUnderLock(action) && !flags.dryRun)
|
|
338
338
|
throw new CliError("permission_error", `this process is read-only (${lock}) — \`${command} ${path.join(" ")}\` changes what a later run may do; ` +
|
|
339
339
|
`run it without ${lock}`);
|
|
340
340
|
if (options.agent && options.agent.authority !== true && changesAuthorityNow(action))
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { type AgentCli, type Schedule } from "../../background/schedules.js";
|
|
3
|
+
import { type MailWaiting } from "../../mail/marks.js";
|
|
2
4
|
import { type AppIdentity } from "../../ports/app.js";
|
|
3
5
|
import { type AnswerOutput } from "../envelope.js";
|
|
6
|
+
import type { DeclaredSetting } from "../setting-defaults.js";
|
|
4
7
|
export interface ScheduleHost {
|
|
5
8
|
app: AppIdentity;
|
|
6
9
|
context(command: Command, resolving?: {
|
|
@@ -10,14 +13,30 @@ export interface ScheduleHost {
|
|
|
10
13
|
profile: string;
|
|
11
14
|
};
|
|
12
15
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
16
|
+
* What mail waits past the holder's mark (`mailWaiting`). Given, `schedule agent` takes `--on-new-mail`: the agent
|
|
17
|
+
* then runs only once enough of it waits, or the oldest has waited long enough.
|
|
15
18
|
*/
|
|
16
|
-
newMail?: (command: Command, holder: string) => Promise<
|
|
19
|
+
newMail?: (command: Command, holder: string) => Promise<MailWaiting>;
|
|
20
|
+
/** The config file's limits for agent schedules; a schedule's own flags win, `SCHEDULE_DEFAULTS` fill the rest. */
|
|
21
|
+
limits?: (command: Command) => ScheduleLimits;
|
|
22
|
+
/**
|
|
23
|
+
* Other CLIs the scheduled agent may run besides this one, each under its own `<PREFIX>_READ_ONLY`: memo's mail
|
|
24
|
+
* triage runs `mail`. Claude may run them; codex may write their state, config and cache folders.
|
|
25
|
+
*/
|
|
26
|
+
agentRuns?: readonly AgentCli[];
|
|
17
27
|
}
|
|
28
|
+
export type ScheduleLimits = Partial<Pick<Schedule, "mailWaitMinutes" | "mailBatch" | "maxCostPerDay">>;
|
|
29
|
+
/** The agent-schedule limits a host with `newMail` writes into its config file's top level (WC-CMD-30). */
|
|
30
|
+
export declare const SCHEDULE_SETTINGS: readonly DeclaredSetting[];
|
|
31
|
+
/** `SCHEDULE_SETTINGS` as the config file gives them; a missing or unusable value is left to the default. */
|
|
32
|
+
export declare const scheduleLimitsOf: (config: {
|
|
33
|
+
schedule_mail_wait?: unknown;
|
|
34
|
+
schedule_mail_batch?: unknown;
|
|
35
|
+
schedule_max_cost_per_day?: unknown;
|
|
36
|
+
}) => ScheduleLimits;
|
|
18
37
|
/**
|
|
19
38
|
* Runs the CLI's own commands, or an agent, later or on a timer — through systemd (Linux), launchd
|
|
20
39
|
* (macOS) or Task Scheduler (Windows). Every timer runs `schedule exec <file>`; the schedule file holds
|
|
21
40
|
* the rest, so what runs and with which variables is the same on all three.
|
|
22
41
|
*/
|
|
23
|
-
export declare const scheduleCommandFor: ({ app, context: contextOf, newMail }: ScheduleHost) => Command;
|
|
42
|
+
export declare const scheduleCommandFor: ({ app, context: contextOf, newMail, limits: configured, agentRuns, }: ScheduleHost) => Command;
|