@namzu/cli 29.1.0 → 32.0.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 +296 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +20 -2
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +42 -5
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +2 -1
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/exec-json.d.ts.map +1 -1
- package/dist/commands/exec-json.js +24 -1
- package/dist/commands/exec-json.js.map +1 -1
- package/dist/commands/exec.d.ts.map +1 -1
- package/dist/commands/exec.js +5 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/prior-messages.d.ts.map +1 -1
- package/dist/commands/prior-messages.js +42 -2
- package/dist/commands/prior-messages.js.map +1 -1
- package/dist/commands/schedule.d.ts.map +1 -1
- package/dist/commands/schedule.js +7 -5
- package/dist/commands/schedule.js.map +1 -1
- package/dist/config/composer-triggers.d.ts +41 -0
- package/dist/config/composer-triggers.d.ts.map +1 -0
- package/dist/config/composer-triggers.js +80 -0
- package/dist/config/composer-triggers.js.map +1 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +58 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/context/doctrine.d.ts +3 -3
- package/dist/context/doctrine.d.ts.map +1 -1
- package/dist/context/doctrine.js +3 -3
- package/dist/context/doctrine.js.map +1 -1
- package/dist/context/environment.d.ts +26 -3
- package/dist/context/environment.d.ts.map +1 -1
- package/dist/context/environment.js +68 -9
- package/dist/context/environment.js.map +1 -1
- package/dist/context/runtime-message.d.ts.map +1 -1
- package/dist/context/runtime-message.js +4 -0
- package/dist/context/runtime-message.js.map +1 -1
- package/dist/integrations/mcp/servers.d.ts +72 -5
- package/dist/integrations/mcp/servers.d.ts.map +1 -1
- package/dist/integrations/mcp/servers.js +144 -23
- package/dist/integrations/mcp/servers.js.map +1 -1
- package/dist/integrations/notifications/desktop.d.ts +2 -0
- package/dist/integrations/notifications/desktop.d.ts.map +1 -1
- package/dist/integrations/notifications/desktop.js +6 -5
- package/dist/integrations/notifications/desktop.js.map +1 -1
- package/dist/integrations/plugins/runtime.d.ts +10 -2
- package/dist/integrations/plugins/runtime.d.ts.map +1 -1
- package/dist/integrations/plugins/runtime.js +16 -29
- package/dist/integrations/plugins/runtime.js.map +1 -1
- package/dist/integrations/providers/harness-credentials.d.ts +9 -4
- package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
- package/dist/integrations/providers/harness-credentials.js +16 -8
- package/dist/integrations/providers/harness-credentials.js.map +1 -1
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +2 -1
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +20 -2
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/subagents/NamzuCliAgent.d.ts +8 -0
- package/dist/integrations/subagents/NamzuCliAgent.d.ts.map +1 -0
- package/dist/integrations/subagents/NamzuCliAgent.js +9 -0
- package/dist/integrations/subagents/NamzuCliAgent.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +5 -4
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +46 -11
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/web/open-url.d.ts +24 -0
- package/dist/integrations/web/open-url.d.ts.map +1 -0
- package/dist/integrations/web/open-url.js +73 -0
- package/dist/integrations/web/open-url.js.map +1 -0
- package/dist/integrations/web/search.d.ts.map +1 -1
- package/dist/integrations/web/search.js +8 -6
- package/dist/integrations/web/search.js.map +1 -1
- package/dist/permissions/rules.d.ts +5 -1
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +85 -3
- package/dist/permissions/rules.js.map +1 -1
- package/dist/schedule/build.d.ts +26 -2
- package/dist/schedule/build.d.ts.map +1 -1
- package/dist/schedule/build.js +139 -20
- package/dist/schedule/build.js.map +1 -1
- package/dist/schedule/changes.d.ts +6 -0
- package/dist/schedule/changes.d.ts.map +1 -1
- package/dist/schedule/changes.js +23 -0
- package/dist/schedule/changes.js.map +1 -1
- package/dist/schedule/commands/add.d.ts +1 -1
- package/dist/schedule/commands/add.d.ts.map +1 -1
- package/dist/schedule/commands/add.js +188 -54
- package/dist/schedule/commands/add.js.map +1 -1
- package/dist/schedule/commands/lifecycle.d.ts.map +1 -1
- package/dist/schedule/commands/lifecycle.js +2 -1
- package/dist/schedule/commands/lifecycle.js.map +1 -1
- package/dist/schedule/commands/list.d.ts +2 -0
- package/dist/schedule/commands/list.d.ts.map +1 -1
- package/dist/schedule/commands/list.js +69 -20
- package/dist/schedule/commands/list.js.map +1 -1
- package/dist/schedule/daemon/daemon.d.ts +1 -1
- package/dist/schedule/daemon/daemon.d.ts.map +1 -1
- package/dist/schedule/daemon/daemon.js +22 -5
- package/dist/schedule/daemon/daemon.js.map +1 -1
- package/dist/schedule/daemon/notify.d.ts +29 -2
- package/dist/schedule/daemon/notify.d.ts.map +1 -1
- package/dist/schedule/daemon/notify.js +71 -2
- package/dist/schedule/daemon/notify.js.map +1 -1
- package/dist/schedule/env.d.ts +8 -4
- package/dist/schedule/env.d.ts.map +1 -1
- package/dist/schedule/env.js +13 -6
- package/dist/schedule/env.js.map +1 -1
- package/dist/schedule/fire/calls.d.ts +49 -0
- package/dist/schedule/fire/calls.d.ts.map +1 -0
- package/dist/schedule/fire/calls.js +114 -0
- package/dist/schedule/fire/calls.js.map +1 -0
- package/dist/schedule/fire/fire.d.ts +1 -1
- package/dist/schedule/fire/fire.d.ts.map +1 -1
- package/dist/schedule/fire/fire.js +209 -15
- package/dist/schedule/fire/fire.js.map +1 -1
- package/dist/schedule/fire/result.d.ts +1 -1
- package/dist/schedule/fire/result.d.ts.map +1 -1
- package/dist/schedule/fire/result.js +2 -1
- package/dist/schedule/fire/result.js.map +1 -1
- package/dist/schedule/fire/run-script.d.ts +46 -0
- package/dist/schedule/fire/run-script.d.ts.map +1 -0
- package/dist/schedule/fire/run-script.js +81 -0
- package/dist/schedule/fire/run-script.js.map +1 -0
- package/dist/schedule/fire/unattended-note.d.ts +8 -0
- package/dist/schedule/fire/unattended-note.d.ts.map +1 -1
- package/dist/schedule/fire/unattended-note.js +5 -0
- package/dist/schedule/fire/unattended-note.js.map +1 -1
- package/dist/schedule/fire/wake-gate.d.ts +39 -0
- package/dist/schedule/fire/wake-gate.d.ts.map +1 -0
- package/dist/schedule/fire/wake-gate.js +90 -0
- package/dist/schedule/fire/wake-gate.js.map +1 -0
- package/dist/schedule/floor.d.ts +28 -8
- package/dist/schedule/floor.d.ts.map +1 -1
- package/dist/schedule/floor.js +624 -114
- package/dist/schedule/floor.js.map +1 -1
- package/dist/schedule/policy.d.ts +40 -0
- package/dist/schedule/policy.d.ts.map +1 -1
- package/dist/schedule/policy.js +36 -0
- package/dist/schedule/policy.js.map +1 -1
- package/dist/schedule/script-check.d.ts +53 -0
- package/dist/schedule/script-check.d.ts.map +1 -0
- package/dist/schedule/script-check.js +145 -0
- package/dist/schedule/script-check.js.map +1 -0
- package/dist/schedule/script-shell.d.ts +3 -0
- package/dist/schedule/script-shell.d.ts.map +1 -0
- package/dist/schedule/script-shell.js +5 -0
- package/dist/schedule/script-shell.js.map +1 -0
- package/dist/schedule/service/index.d.ts +1 -1
- package/dist/schedule/service/index.d.ts.map +1 -1
- package/dist/schedule/service/index.js +20 -11
- package/dist/schedule/service/index.js.map +1 -1
- package/dist/schedule/service/manifest.js +1 -1
- package/dist/schedule/service/manifest.js.map +1 -1
- package/dist/schedule/service/windows-task.d.ts +7 -0
- package/dist/schedule/service/windows-task.d.ts.map +1 -1
- package/dist/schedule/service/windows-task.js +65 -0
- package/dist/schedule/service/windows-task.js.map +1 -1
- package/dist/schedule/service/wsl.d.ts +13 -1
- package/dist/schedule/service/wsl.d.ts.map +1 -1
- package/dist/schedule/service/wsl.js +16 -6
- package/dist/schedule/service/wsl.js.map +1 -1
- package/dist/schedule/store/atomic.d.ts +5 -2
- package/dist/schedule/store/atomic.d.ts.map +1 -1
- package/dist/schedule/store/atomic.js +7 -4
- package/dist/schedule/store/atomic.js.map +1 -1
- package/dist/schedule/store/claims.js +1 -1
- package/dist/schedule/store/claims.js.map +1 -1
- package/dist/schedule/store/history.d.ts +1 -1
- package/dist/schedule/store/history.d.ts.map +1 -1
- package/dist/schedule/store/history.js +4 -1
- package/dist/schedule/store/history.js.map +1 -1
- package/dist/schedule/store/jobs.d.ts +19 -3
- package/dist/schedule/store/jobs.d.ts.map +1 -1
- package/dist/schedule/store/jobs.js +83 -16
- package/dist/schedule/store/jobs.js.map +1 -1
- package/dist/schedule/store/state.js +1 -1
- package/dist/schedule/store/state.js.map +1 -1
- package/dist/schedule/types.d.ts +98 -6
- package/dist/schedule/types.d.ts.map +1 -1
- package/dist/schedule/types.js +25 -1
- package/dist/schedule/types.js.map +1 -1
- package/dist/schedule/visible-source.d.ts +18 -0
- package/dist/schedule/visible-source.d.ts.map +1 -0
- package/dist/schedule/visible-source.js +59 -0
- package/dist/schedule/visible-source.js.map +1 -0
- package/dist/skills/directory.d.ts +38 -0
- package/dist/skills/directory.d.ts.map +1 -0
- package/dist/skills/directory.js +51 -0
- package/dist/skills/directory.js.map +1 -0
- package/dist/test-support/toolset.d.ts +23 -0
- package/dist/test-support/toolset.d.ts.map +1 -0
- package/dist/test-support/toolset.js +27 -0
- package/dist/test-support/toolset.js.map +1 -0
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +343 -64
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/ChoicePicker.d.ts +2 -0
- package/dist/tui/ChoicePicker.d.ts.map +1 -1
- package/dist/tui/ChoicePicker.js +10 -2
- package/dist/tui/ChoicePicker.js.map +1 -1
- package/dist/tui/Composer.d.ts +62 -3
- package/dist/tui/Composer.d.ts.map +1 -1
- package/dist/tui/Composer.js +162 -25
- package/dist/tui/Composer.js.map +1 -1
- package/dist/tui/ComposerFrame.d.ts +1 -1
- package/dist/tui/ComposerFrame.js +3 -3
- package/dist/tui/ComposerFrame.js.map +1 -1
- package/dist/tui/EffortSlider.d.ts +8 -9
- package/dist/tui/EffortSlider.d.ts.map +1 -1
- package/dist/tui/EffortSlider.js +15 -12
- package/dist/tui/EffortSlider.js.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +13 -1
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +26 -4
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/StatusBar.d.ts +12 -12
- package/dist/tui/StatusBar.d.ts.map +1 -1
- package/dist/tui/StatusBar.js +15 -15
- package/dist/tui/StatusBar.js.map +1 -1
- package/dist/tui/agent.d.ts +73 -12
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +430 -180
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/hypermode.d.ts +37 -0
- package/dist/tui/hypermode.d.ts.map +1 -0
- package/dist/tui/hypermode.js +58 -0
- package/dist/tui/hypermode.js.map +1 -0
- package/dist/tui/open-browser.d.ts +29 -1
- package/dist/tui/open-browser.d.ts.map +1 -1
- package/dist/tui/open-browser.js +83 -11
- package/dist/tui/open-browser.js.map +1 -1
- package/dist/tui/permission-review.d.ts +11 -2
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +176 -19
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/schedule/host-commands.d.ts.map +1 -1
- package/dist/tui/schedule/host-commands.js +95 -33
- package/dist/tui/schedule/host-commands.js.map +1 -1
- package/dist/tui/schedule/integration.d.ts +6 -2
- package/dist/tui/schedule/integration.d.ts.map +1 -1
- package/dist/tui/schedule/integration.js +3 -1
- package/dist/tui/schedule/integration.js.map +1 -1
- package/dist/tui/schedule/resume.d.ts.map +1 -1
- package/dist/tui/schedule/resume.js +10 -1
- package/dist/tui/schedule/resume.js.map +1 -1
- package/dist/tui/schedule/tool-host.d.ts +18 -1
- package/dist/tui/schedule/tool-host.d.ts.map +1 -1
- package/dist/tui/schedule/tool-host.js +384 -84
- package/dist/tui/schedule/tool-host.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +23 -5
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +80 -31
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/theme.d.ts +10 -4
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +4 -3
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/triggers/analyze.d.ts +57 -0
- package/dist/tui/triggers/analyze.d.ts.map +1 -0
- package/dist/tui/triggers/analyze.js +221 -0
- package/dist/tui/triggers/analyze.js.map +1 -0
- package/dist/tui/triggers/context-text.d.ts +21 -0
- package/dist/tui/triggers/context-text.d.ts.map +1 -0
- package/dist/tui/triggers/context-text.js +44 -0
- package/dist/tui/triggers/context-text.js.map +1 -0
- package/dist/tui/triggers/copy.d.ts +38 -0
- package/dist/tui/triggers/copy.d.ts.map +1 -0
- package/dist/tui/triggers/copy.js +163 -0
- package/dist/tui/triggers/copy.js.map +1 -0
- package/dist/tui/triggers/detect.d.ts +83 -0
- package/dist/tui/triggers/detect.d.ts.map +1 -0
- package/dist/tui/triggers/detect.js +295 -0
- package/dist/tui/triggers/detect.js.map +1 -0
- package/dist/tui/triggers/fold.d.ts +34 -0
- package/dist/tui/triggers/fold.d.ts.map +1 -0
- package/dist/tui/triggers/fold.js +44 -0
- package/dist/tui/triggers/fold.js.map +1 -0
- package/dist/tui/triggers/pattern.d.ts +66 -0
- package/dist/tui/triggers/pattern.d.ts.map +1 -0
- package/dist/tui/triggers/pattern.js +243 -0
- package/dist/tui/triggers/pattern.js.map +1 -0
- package/dist/tui/triggers/provenance.d.ts +58 -0
- package/dist/tui/triggers/provenance.d.ts.map +1 -0
- package/dist/tui/triggers/provenance.js +98 -0
- package/dist/tui/triggers/provenance.js.map +1 -0
- package/dist/tui/triggers/registry.d.ts +113 -0
- package/dist/tui/triggers/registry.d.ts.map +1 -0
- package/dist/tui/triggers/registry.js +164 -0
- package/dist/tui/triggers/registry.js.map +1 -0
- package/dist/tui/triggers/setting.d.ts +18 -0
- package/dist/tui/triggers/setting.d.ts.map +1 -0
- package/dist/tui/triggers/setting.js +61 -0
- package/dist/tui/triggers/setting.js.map +1 -0
- package/dist/tui/triggers/verbs.d.ts +29 -0
- package/dist/tui/triggers/verbs.d.ts.map +1 -0
- package/dist/tui/triggers/verbs.js +140 -0
- package/dist/tui/triggers/verbs.js.map +1 -0
- package/dist/tui/types.d.ts +3 -1
- package/dist/tui/types.d.ts.map +1 -1
- package/package.json +12 -12
- package/skills/README.md +5 -0
- package/skills/schedule-task/SKILL.md +123 -5
|
@@ -8,22 +8,38 @@
|
|
|
8
8
|
* "Create paused" creates a job. A job created here records
|
|
9
9
|
* `confirmation.surface: tool-confirmed`.
|
|
10
10
|
*/
|
|
11
|
+
import { realpathSync } from 'node:fs';
|
|
11
12
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
12
|
-
import { describeSchedule, hostTimeZone,
|
|
13
|
+
import { describeSchedule, hostTimeZone, installedCommandShellForDialect, upcomingFireTimes, validateTimeZone, } from '@namzu/sdk';
|
|
13
14
|
import { readPermissionLayers } from '../../config/load.js';
|
|
14
15
|
import { discoverProviders } from '../../integrations/providers/discover.js';
|
|
15
|
-
import { DEFAULT_MAX_ITERATIONS, DEFAULT_TIMEOUT_MS, DEFAULT_TOKEN_BUDGET, buildJob, confirmJob, previewLines, runsPerDay, } from '../../schedule/build.js';
|
|
16
|
+
import { DEFAULT_MAX_ITERATIONS, DEFAULT_TIMEOUT_MS, DEFAULT_TOKEN_BUDGET, JobRequestError, buildJob, confirmJob, editedJob, previewLines, runsPerDay, } from '../../schedule/build.js';
|
|
17
|
+
import { changesBlock, changesSinceConfirmed, confirmationView, describeChanges, permissionsChanged, } from '../../schedule/changes.js';
|
|
18
|
+
import { callsCount } from '../../schedule/fire/calls.js';
|
|
16
19
|
import { schedulePaths } from '../../schedule/paths.js';
|
|
17
|
-
import { compileJobPolicy } from '../../schedule/policy.js';
|
|
20
|
+
import { allowsCommands, compileJobPolicy, compileScriptCheckPolicy, } from '../../schedule/policy.js';
|
|
21
|
+
import { verifyScheduledScript } from '../../schedule/script-check.js';
|
|
22
|
+
import { scriptShellUnavailableReason } from '../../schedule/script-shell.js';
|
|
18
23
|
import { readManifest } from '../../schedule/service/manifest.js';
|
|
19
|
-
import { appendHistory } from '../../schedule/store/history.js';
|
|
20
|
-
import { createJob, deleteJob, findJob, listJobs, updateJob } from '../../schedule/store/jobs.js';
|
|
24
|
+
import { appendHistory, readHistory } from '../../schedule/store/history.js';
|
|
25
|
+
import { ScheduleConflictError, createJob, deleteJob, findJob, listJobs, updateJob, } from '../../schedule/store/jobs.js';
|
|
21
26
|
import { nextFireOf, readState } from '../../schedule/store/state.js';
|
|
27
|
+
import { visibleScheduleMessage } from '../../schedule/visible-source.js';
|
|
22
28
|
function within(root, path) {
|
|
23
29
|
const rel = relative(root, path);
|
|
24
30
|
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
25
31
|
}
|
|
26
|
-
|
|
32
|
+
/** The session's folder as a job records its own: canonical, symbolic links resolved. */
|
|
33
|
+
function canonicalCwd(cwd) {
|
|
34
|
+
try {
|
|
35
|
+
return realpathSync(cwd);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return resolve(cwd);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** A job as the tool reports it; `here` when it runs in the session's folder, which alone shows its prompt. */
|
|
42
|
+
function summary(job, here, home) {
|
|
27
43
|
const tz = job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone();
|
|
28
44
|
const state = readState(schedulePaths(home), job.id);
|
|
29
45
|
return {
|
|
@@ -32,8 +48,14 @@ function summary(job, withPrompt, home) {
|
|
|
32
48
|
state: job.state,
|
|
33
49
|
schedule: describeSchedule(job.schedule, { tz }),
|
|
34
50
|
...(nextFireOf(job, state) ? { nextFireAt: nextFireOf(job, state) } : {}),
|
|
35
|
-
...(state.lastRun
|
|
36
|
-
|
|
51
|
+
...(state.lastRun
|
|
52
|
+
? {
|
|
53
|
+
lastStatus: callsCount(state.lastRun)
|
|
54
|
+
? `${state.lastRun.status} (${callsCount(state.lastRun)})`
|
|
55
|
+
: state.lastRun.status,
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
...(here ? { prompt: job.prompt, inSessionFolder: true } : {}),
|
|
37
59
|
};
|
|
38
60
|
}
|
|
39
61
|
async function credentialText(provider) {
|
|
@@ -50,20 +72,145 @@ async function credentialText(provider) {
|
|
|
50
72
|
return 'unknown';
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
|
-
/** The preview lines, the full prompt with hidden characters shown
|
|
54
|
-
|
|
55
|
-
const
|
|
75
|
+
/** The preview lines, the credential, the warnings, and the full prompt with hidden characters shown. */
|
|
76
|
+
function confirmationBody(p, findings, lines) {
|
|
77
|
+
const scriptSection = p.runKind && p.runKind !== 'agent' && p.script
|
|
78
|
+
? [
|
|
79
|
+
`${p.runKind === 'script' ? 'Script' : 'Wake-gate script'} (exactly as it will run, ${p.script.shell}; verified against the scheduled-run floor and any deny rules)`,
|
|
80
|
+
...p.script.body.split('\n').map((l) => ` │ ${l}`),
|
|
81
|
+
p.runKind === 'script'
|
|
82
|
+
? 'Runs exactly as shown; the scheduled-run floor and every deny rule apply to this script'
|
|
83
|
+
: 'Runs exactly as shown; the scheduled-run floor and every deny rule apply to the gate, and the permission set governs the agent phase',
|
|
84
|
+
]
|
|
85
|
+
: [];
|
|
86
|
+
const promptSection = p.prompt.trim()
|
|
87
|
+
? [
|
|
88
|
+
p.runKind === 'script+agent'
|
|
89
|
+
? 'Prompt (used only when the wake-gate says wake: true)'
|
|
90
|
+
: 'Prompt (exactly as the run will read it)',
|
|
91
|
+
...p.prompt.split('\n').map((l) => ` │ ${l}`),
|
|
92
|
+
]
|
|
93
|
+
: [];
|
|
56
94
|
return [
|
|
57
|
-
'⏲ PROPOSED BY THE MODEL, NOT BY YOU — a scheduled job that runs later with nobody watching.',
|
|
58
95
|
...lines,
|
|
59
|
-
`Credential ${p.credentialSource ?? 'unknown'}
|
|
96
|
+
...(p.runKind === 'script' ? [] : [`Credential ${p.credentialSource ?? 'unknown'}`]),
|
|
60
97
|
...p.warnings.map((w) => `Warning ${w}`),
|
|
61
|
-
...
|
|
62
|
-
|
|
63
|
-
...
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
98
|
+
...findings.map((f) => `Warning ${f}`),
|
|
99
|
+
...scriptSection,
|
|
100
|
+
...promptSection,
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
/** The preview lines, the full prompt with hidden characters shown, and the tripwire's findings. */
|
|
104
|
+
export function renderConfirmation(request, lines) {
|
|
105
|
+
return visibleScheduleMessage([
|
|
106
|
+
'⏲ PROPOSED BY THE MODEL, NOT BY YOU — a scheduled job that runs later with nobody watching.',
|
|
107
|
+
...confirmationBody(request.preview, request.promptFindings, lines),
|
|
108
|
+
].join('\n'));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A change the model proposed: what changes first (`-`/`+` lines, as
|
|
112
|
+
* `schedule edit` shows them), a warning when the permissions change, then
|
|
113
|
+
* the job as it would run, whole.
|
|
114
|
+
*/
|
|
115
|
+
export function renderUpdateConfirmation(request, lines) {
|
|
116
|
+
return visibleScheduleMessage([
|
|
117
|
+
`⏲ PROPOSED BY THE MODEL, NOT BY YOU — a change to the scheduled job ${request.preview.name}, which runs later with nobody watching.`,
|
|
118
|
+
...changesBlock(request.changes),
|
|
119
|
+
...(request.permissionsChange
|
|
120
|
+
? [
|
|
121
|
+
'Warning THE PERMISSIONS CHANGE: from its next run the job may do what the rules below allow.',
|
|
122
|
+
]
|
|
123
|
+
: []),
|
|
124
|
+
...confirmationBody(request.preview, request.promptFindings, lines),
|
|
125
|
+
].join('\n'));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The request `buildJob` rebuilds a job from, for a change the model
|
|
129
|
+
* proposed: every field it left out as the job has it. A new permission set
|
|
130
|
+
* keeps the job's `execution` unless it names one, and the job's additional
|
|
131
|
+
* directories (which only the operator sets).
|
|
132
|
+
*/
|
|
133
|
+
export function updateRequest(current, changes, cwd, fallbackModel) {
|
|
134
|
+
const cron = current.schedule.kind === 'cron' ? current.schedule : undefined;
|
|
135
|
+
let spec;
|
|
136
|
+
if (changes.when === undefined) {
|
|
137
|
+
if (changes.tz === undefined)
|
|
138
|
+
spec = current.schedule;
|
|
139
|
+
else if (cron)
|
|
140
|
+
spec = { ...cron, tz: validateTimeZone(changes.tz) };
|
|
141
|
+
else
|
|
142
|
+
throw new JobRequestError(`a time zone changes only a cron schedule, and "${current.name}" is not one; give when as well.`);
|
|
143
|
+
}
|
|
144
|
+
const tz = changes.tz ?? cron?.tz;
|
|
145
|
+
const dirs = current.permissions.additionalDirectories;
|
|
146
|
+
const kept = dirs && dirs.length > 0 ? { additionalDirectories: [...dirs] } : {};
|
|
147
|
+
const proposed = changes.permissions;
|
|
148
|
+
const permissions = proposed
|
|
149
|
+
? {
|
|
150
|
+
...(proposed.preset ? { preset: proposed.preset } : {}),
|
|
151
|
+
...(proposed.rules ? { rules: proposed.rules } : {}),
|
|
152
|
+
unmatched: proposed.unmatched,
|
|
153
|
+
execution: proposed.execution ?? current.permissions.execution,
|
|
154
|
+
...kept,
|
|
155
|
+
...(proposed.browser ? { browser: proposed.browser } : {}),
|
|
156
|
+
}
|
|
157
|
+
: {
|
|
158
|
+
rules: current.permissions.rules,
|
|
159
|
+
unmatched: current.permissions.unmatched,
|
|
160
|
+
execution: current.permissions.execution,
|
|
161
|
+
...kept,
|
|
162
|
+
...(current.permissions.browser ? { browser: current.permissions.browser } : {}),
|
|
163
|
+
};
|
|
164
|
+
// A kind or script the tool did not touch carries the job's own forward
|
|
165
|
+
// unchanged, exactly as `editedJob` does for a terminal edit. Moving TO
|
|
166
|
+
// `'agent'` always drops the script — whether or not one was given here,
|
|
167
|
+
// since an agent job cannot carry one (`buildJob` refuses that
|
|
168
|
+
// combination outright) — and the wake-gate cap only ever applies to a
|
|
169
|
+
// `runKind` that stays (or becomes) `'script+agent'`.
|
|
170
|
+
const runKind = changes.runKind ?? current.runKind;
|
|
171
|
+
if (runKind === 'script' && changes.prompt !== undefined)
|
|
172
|
+
throw new JobRequestError('a pure script job has no prompt; remove prompt');
|
|
173
|
+
if (runKind === 'script' && changes.budget !== undefined)
|
|
174
|
+
throw new JobRequestError('a pure script job has no agent budget; remove budget and use script.timeoutMs for its timeout');
|
|
175
|
+
if (runKind === 'script' &&
|
|
176
|
+
permissions.browser &&
|
|
177
|
+
(current.runKind !== 'script' || changes.permissions !== undefined))
|
|
178
|
+
throw new JobRequestError('a pure script job cannot use a browser grant; remove permissions.browser');
|
|
179
|
+
const script = runKind === 'agent' ? undefined : (changes.script ?? current.script);
|
|
180
|
+
return {
|
|
181
|
+
name: current.name,
|
|
182
|
+
prompt: runKind === 'script' ? '' : (changes.prompt ?? current.prompt),
|
|
183
|
+
when: changes.when ?? '',
|
|
184
|
+
...(spec ? { spec } : {}),
|
|
185
|
+
folder: changes.folder !== undefined ? resolve(cwd, changes.folder) : current.folder.path,
|
|
186
|
+
...(tz ? { tz } : {}),
|
|
187
|
+
...(runKind ? { runKind } : {}),
|
|
188
|
+
...(script ? { script } : {}),
|
|
189
|
+
...(runKind === 'script+agent' && current.wakeGate ? { wakeGate: current.wakeGate } : {}),
|
|
190
|
+
permissions,
|
|
191
|
+
budget: { ...current.budget, ...(changes.budget ?? {}) },
|
|
192
|
+
...(runKind === 'script'
|
|
193
|
+
? {}
|
|
194
|
+
: current.model
|
|
195
|
+
? {
|
|
196
|
+
model: `${current.model.provider}${current.model.model ? `/${current.model.model}` : ''}`,
|
|
197
|
+
...(current.model.effort ? { effort: current.model.effort } : {}),
|
|
198
|
+
}
|
|
199
|
+
: fallbackModel
|
|
200
|
+
? {
|
|
201
|
+
model: `${fallbackModel.provider}${fallbackModel.model ? `/${fallbackModel.model}` : ''}`,
|
|
202
|
+
}
|
|
203
|
+
: {}),
|
|
204
|
+
includeSummary: current.notify.includeSummary,
|
|
205
|
+
keepSessions: current.retention.keepSessions,
|
|
206
|
+
pauseAfterFailures: current.failurePolicy.pauseAfterFailures,
|
|
207
|
+
approvalTtlMs: current.approvalTtlMs,
|
|
208
|
+
createdBy: current.createdBy,
|
|
209
|
+
// `unmatched: allow` on the host is the operator's choice
|
|
210
|
+
// (`--allow-unattended-host`); a change that keeps the permissions
|
|
211
|
+
// keeps it, and the tool can never propose it.
|
|
212
|
+
allowUnattendedHost: proposed === undefined && current.permissions.unmatched === 'allow',
|
|
213
|
+
};
|
|
67
214
|
}
|
|
68
215
|
/**
|
|
69
216
|
* Each optional value the model set to something other than what the
|
|
@@ -97,6 +244,96 @@ export function chosenByTheModel(draft, job, cwd, config) {
|
|
|
97
244
|
}
|
|
98
245
|
export function createScheduleToolHost(ui) {
|
|
99
246
|
const built = new WeakMap();
|
|
247
|
+
const proposedUpdates = new WeakMap();
|
|
248
|
+
/** What the person is shown for a job, every field computed here; `extra` warnings last. */
|
|
249
|
+
const previewOf = async (job, extra, now) => {
|
|
250
|
+
const layers = readPermissionLayers({ cwd: job.folder.canonical });
|
|
251
|
+
const policy = compileJobPolicy(job.permissions, {
|
|
252
|
+
layers,
|
|
253
|
+
namzuHome: ui.home(),
|
|
254
|
+
folder: job.folder,
|
|
255
|
+
});
|
|
256
|
+
if (policy.diagnostics.length > 0)
|
|
257
|
+
throw new Error(`The rules do not compile: ${policy.diagnostics.join('; ')}`);
|
|
258
|
+
if (job.runKind && job.runKind !== 'agent' && !job.script)
|
|
259
|
+
throw new Error(`The ${job.runKind} job has no script to preview.`);
|
|
260
|
+
// A model-proposed script/wake-gate gets the same static check
|
|
261
|
+
// `schedule add` runs before anything is shown, so a proposal the
|
|
262
|
+
// floor or a `deny` rule refuses never reaches this confirmation
|
|
263
|
+
// screen at all.
|
|
264
|
+
if (job.runKind && job.runKind !== 'agent' && job.script) {
|
|
265
|
+
const selectedShell = installedCommandShellForDialect(job.script.shell);
|
|
266
|
+
if (!selectedShell)
|
|
267
|
+
throw new Error(scriptShellUnavailableReason(job.script.shell));
|
|
268
|
+
const scriptPolicy = compileScriptCheckPolicy(job.permissions, {
|
|
269
|
+
layers,
|
|
270
|
+
namzuHome: ui.home(),
|
|
271
|
+
folder: job.folder,
|
|
272
|
+
});
|
|
273
|
+
const checked = verifyScheduledScript(job.script.body, selectedShell.dialect, scriptPolicy);
|
|
274
|
+
if (!checked.ok) {
|
|
275
|
+
throw new Error(`The ${job.runKind === 'script' ? 'script' : 'wake-gate script'} was refused: ${checked.reason}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const roots = [ui.cwd(), ...ui.extraRoots()];
|
|
279
|
+
const outside = !roots.some((root) => within(root, job.folder.canonical));
|
|
280
|
+
const perDay = runsPerDay(job.schedule, now);
|
|
281
|
+
const hasScriptPhase = job.runKind === 'script' || job.runKind === 'script+agent';
|
|
282
|
+
const networkCapable = policy.network ||
|
|
283
|
+
(job.permissions.execution === 'host' && (hasScriptPhase || allowsCommands(job.permissions)));
|
|
284
|
+
const warnings = [
|
|
285
|
+
...(outside
|
|
286
|
+
? ['The folder is outside this session’s working directory and added directories.']
|
|
287
|
+
: []),
|
|
288
|
+
...(networkCapable ? ['This run can reach the network.'] : []),
|
|
289
|
+
...(job.permissions.browser && job.runKind !== 'script'
|
|
290
|
+
? [
|
|
291
|
+
`This run drives the browser signed in as you (profile ${job.permissions.browser.profile}) on ${Object.keys(job.permissions.browser.sites).join(', ')}.`,
|
|
292
|
+
]
|
|
293
|
+
: []),
|
|
294
|
+
...extra,
|
|
295
|
+
];
|
|
296
|
+
const preview = {
|
|
297
|
+
name: job.name,
|
|
298
|
+
folder: job.folder.canonical,
|
|
299
|
+
outsideSessionRoots: outside,
|
|
300
|
+
prompt: job.prompt,
|
|
301
|
+
...(job.runKind && job.runKind !== 'agent' ? { runKind: job.runKind } : {}),
|
|
302
|
+
...(job.script ? { script: job.script } : {}),
|
|
303
|
+
schedule: describeSchedule(job.schedule, {
|
|
304
|
+
tz: job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone(),
|
|
305
|
+
}),
|
|
306
|
+
nextFireTimes: upcomingFireTimes(job.schedule, now, 3).map((t) => t.toISOString()),
|
|
307
|
+
rules: policy.lines,
|
|
308
|
+
unmatched: job.permissions.unmatched,
|
|
309
|
+
execution: job.permissions.execution,
|
|
310
|
+
networkAccess: networkCapable,
|
|
311
|
+
networkGrantAccess: policy.network,
|
|
312
|
+
budget: job.runKind === 'script' && job.script
|
|
313
|
+
? {
|
|
314
|
+
maxIterations: 0,
|
|
315
|
+
tokenBudget: 0,
|
|
316
|
+
timeoutMs: job.script.timeoutMs,
|
|
317
|
+
}
|
|
318
|
+
: {
|
|
319
|
+
maxIterations: job.budget.maxIterations,
|
|
320
|
+
tokenBudget: job.budget.tokenBudget,
|
|
321
|
+
timeoutMs: job.budget.timeoutMs,
|
|
322
|
+
},
|
|
323
|
+
...(job.runKind === 'script' || job.schedule.kind === 'at'
|
|
324
|
+
? {}
|
|
325
|
+
: { dailyTokenCeiling: perDay * job.budget.tokenBudget }),
|
|
326
|
+
...(job.runKind === 'script' || !job.model
|
|
327
|
+
? {}
|
|
328
|
+
: {
|
|
329
|
+
model: `${job.model.provider}${job.model.model ? `/${job.model.model}` : ''}`,
|
|
330
|
+
credentialSource: await credentialText(job.model.provider),
|
|
331
|
+
}),
|
|
332
|
+
warnings,
|
|
333
|
+
};
|
|
334
|
+
return { preview, policy };
|
|
335
|
+
};
|
|
336
|
+
const NOT_INSTALLED_NOTE = 'No scheduler is installed on this machine, so the job does not run until the operator runs `namzu schedule install`. Tell them; do not say it will run.';
|
|
100
337
|
const paths = () => schedulePaths(ui.home());
|
|
101
338
|
const history = (job, action) => appendHistory(paths(), job.id, {
|
|
102
339
|
v: 1,
|
|
@@ -110,16 +347,22 @@ export function createScheduleToolHost(ui) {
|
|
|
110
347
|
// confirmation shows it line by line, so the model may propose one.
|
|
111
348
|
browserGrants: true,
|
|
112
349
|
async preview(draft) {
|
|
350
|
+
if (draft.runKind === 'script' && draft.budget !== undefined)
|
|
351
|
+
throw new JobRequestError('a pure script job has no agent budget; remove budget and use script.timeoutMs for its timeout');
|
|
352
|
+
if (draft.runKind === 'script' && draft.permissions.browser !== undefined)
|
|
353
|
+
throw new JobRequestError('a pure script job cannot use a browser grant; remove permissions.browser');
|
|
113
354
|
const model = ui.model();
|
|
114
|
-
if (!model)
|
|
355
|
+
if (!model && draft.runKind !== 'script')
|
|
115
356
|
throw new Error('This session has no model; a scheduled job runs on the session’s model.');
|
|
116
357
|
const now = new Date();
|
|
117
358
|
const folder = resolve(ui.cwd(), draft.folder ?? '.');
|
|
118
359
|
const job = buildJob({
|
|
119
360
|
name: draft.name,
|
|
120
|
-
prompt: draft.prompt,
|
|
361
|
+
prompt: draft.prompt ?? '',
|
|
121
362
|
when: draft.when,
|
|
122
363
|
folder,
|
|
364
|
+
...(draft.runKind ? { runKind: draft.runKind } : {}),
|
|
365
|
+
...(draft.script ? { script: draft.script } : {}),
|
|
123
366
|
...(draft.tz ? { tz: draft.tz } : {}),
|
|
124
367
|
permissions: {
|
|
125
368
|
...(draft.permissions.preset ? { preset: draft.permissions.preset } : {}),
|
|
@@ -129,59 +372,18 @@ export function createScheduleToolHost(ui) {
|
|
|
129
372
|
...(draft.permissions.browser ? { browser: draft.permissions.browser } : {}),
|
|
130
373
|
},
|
|
131
374
|
...(draft.budget ? { budget: draft.budget } : {}),
|
|
132
|
-
model
|
|
375
|
+
...(model && draft.runKind !== 'script'
|
|
376
|
+
? {
|
|
377
|
+
model: `${model.provider}${model.model ? `/${model.model}` : ''}`,
|
|
378
|
+
}
|
|
379
|
+
: {}),
|
|
133
380
|
createdBy: {
|
|
134
381
|
surface: 'tool',
|
|
135
382
|
...(ui.sessionId() ? { sessionId: ui.sessionId() } : {}),
|
|
136
383
|
},
|
|
137
384
|
}, { paths: paths(), config: ui.config(), now });
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
namzuHome: ui.home(),
|
|
141
|
-
folder: job.folder,
|
|
142
|
-
});
|
|
143
|
-
if (policy.diagnostics.length > 0)
|
|
144
|
-
throw new Error(`The rules do not compile: ${policy.diagnostics.join('; ')}`);
|
|
145
|
-
const roots = [ui.cwd(), ...ui.extraRoots()];
|
|
146
|
-
const outside = !roots.some((root) => within(root, job.folder.canonical));
|
|
147
|
-
const perDay = runsPerDay(job.schedule, now);
|
|
148
|
-
const warnings = [
|
|
149
|
-
...(outside
|
|
150
|
-
? ['The folder is outside this session’s working directory and added directories.']
|
|
151
|
-
: []),
|
|
152
|
-
...(policy.network ? ['This run can reach the network.'] : []),
|
|
153
|
-
...(job.permissions.browser
|
|
154
|
-
? [
|
|
155
|
-
`This run drives the browser signed in as you (profile ${job.permissions.browser.profile}) on ${Object.keys(job.permissions.browser.sites).join(', ')}.`,
|
|
156
|
-
]
|
|
157
|
-
: []),
|
|
158
|
-
...chosenByTheModel(draft, job, ui.cwd(), ui.config()).map((line) => `Chosen by the model, not the default: ${line}`),
|
|
159
|
-
];
|
|
160
|
-
const preview = {
|
|
161
|
-
name: job.name,
|
|
162
|
-
folder: job.folder.canonical,
|
|
163
|
-
outsideSessionRoots: outside,
|
|
164
|
-
prompt: job.prompt,
|
|
165
|
-
schedule: describeSchedule(job.schedule, {
|
|
166
|
-
tz: job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone(),
|
|
167
|
-
}),
|
|
168
|
-
nextFireTimes: upcomingFireTimes(job.schedule, now, 3).map((t) => t.toISOString()),
|
|
169
|
-
rules: policy.lines,
|
|
170
|
-
unmatched: job.permissions.unmatched,
|
|
171
|
-
execution: job.permissions.execution,
|
|
172
|
-
networkAccess: policy.network,
|
|
173
|
-
budget: {
|
|
174
|
-
maxIterations: job.budget.maxIterations,
|
|
175
|
-
tokenBudget: job.budget.tokenBudget,
|
|
176
|
-
timeoutMs: job.budget.timeoutMs,
|
|
177
|
-
},
|
|
178
|
-
...(job.schedule.kind === 'at'
|
|
179
|
-
? {}
|
|
180
|
-
: { dailyTokenCeiling: perDay * job.budget.tokenBudget }),
|
|
181
|
-
model: `${job.model.provider}${job.model.model ? `/${job.model.model}` : ''}`,
|
|
182
|
-
credentialSource: await credentialText(job.model.provider),
|
|
183
|
-
warnings,
|
|
184
|
-
};
|
|
385
|
+
const chosen = chosenByTheModel(draft, job, ui.cwd(), ui.config()).map((line) => `Chosen by the model, not the default: ${line}`);
|
|
386
|
+
const { preview, policy } = await previewOf(job, chosen, now);
|
|
185
387
|
built.set(preview, { job, lines: previewLines(job, policy, now) });
|
|
186
388
|
return preview;
|
|
187
389
|
},
|
|
@@ -192,7 +394,7 @@ export function createScheduleToolHost(ui) {
|
|
|
192
394
|
ui.say(renderConfirmation(request, entry.lines));
|
|
193
395
|
const answer = await ui.ask({
|
|
194
396
|
questionId: `schedule:${entry.job.id}`,
|
|
195
|
-
question: `Create the scheduled job "${entry.job.name}" the model proposed? (details above)
|
|
397
|
+
question: visibleScheduleMessage(`Create the scheduled job "${entry.job.name}" the model proposed? (details above)`),
|
|
196
398
|
header: 'Proposed by the model',
|
|
197
399
|
options: [
|
|
198
400
|
{ id: 'cancel', label: 'Cancel', description: 'Create nothing' },
|
|
@@ -219,7 +421,9 @@ export function createScheduleToolHost(ui) {
|
|
|
219
421
|
const entry = built.get(preview);
|
|
220
422
|
if (!entry)
|
|
221
423
|
throw new Error('That proposal is no longer current; propose it again.');
|
|
222
|
-
const job = createJob(paths(), confirmJob(entry.job, 'tool-confirmed', new Date(), {
|
|
424
|
+
const job = createJob(paths(), confirmJob(entry.job, 'tool-confirmed', new Date(), {
|
|
425
|
+
paused: options.paused,
|
|
426
|
+
}));
|
|
223
427
|
history(job, 'created');
|
|
224
428
|
// A job with no scheduler to run it does nothing, and the model's
|
|
225
429
|
// "done" said nothing about that.
|
|
@@ -227,23 +431,115 @@ export function createScheduleToolHost(ui) {
|
|
|
227
431
|
ui.say(`⏲ Scheduled job ${job.name} created${options.paused ? ' (paused)' : ''}. /schedule lists it.${installed ? '' : ' The scheduler is not installed, so it does not run until you install it: namzu schedule install.'}`);
|
|
228
432
|
return {
|
|
229
433
|
name: job.name,
|
|
230
|
-
...(installed
|
|
231
|
-
? {}
|
|
232
|
-
: {
|
|
233
|
-
note: 'No scheduler is installed on this machine, so the job does not run until the operator runs `namzu schedule install`. Tell them; do not say it will run.',
|
|
234
|
-
}),
|
|
434
|
+
...(installed ? {} : { note: NOT_INSTALLED_NOTE }),
|
|
235
435
|
};
|
|
236
436
|
},
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
437
|
+
// A change is confirmed like a new job, whole, with what changes
|
|
438
|
+
// above it, and saved to the same job: its id, name and history stay.
|
|
439
|
+
// Every field it can touch is in the confirmation's digest, so the
|
|
440
|
+
// saved job carries the operator's new confirmation, as an edit on a
|
|
441
|
+
// terminal does.
|
|
442
|
+
async previewUpdate(ref, changes) {
|
|
443
|
+
const current = findJob(paths(), ref);
|
|
444
|
+
const now = new Date();
|
|
445
|
+
const job = editedJob(current, buildJob(updateRequest(current, changes, ui.cwd(), ui.model()), {
|
|
446
|
+
paths: paths(),
|
|
447
|
+
config: ui.config(),
|
|
448
|
+
now,
|
|
449
|
+
}));
|
|
450
|
+
const { preview, policy } = await previewOf(job, [], now);
|
|
451
|
+
const view = (j) => confirmationView(j, compileJobPolicy(j.permissions, {
|
|
452
|
+
layers: readPermissionLayers({ cwd: j.folder.canonical }),
|
|
453
|
+
namzuHome: ui.home(),
|
|
454
|
+
folder: j.folder,
|
|
455
|
+
}), now);
|
|
456
|
+
const differs = describeChanges(view(current), view(job));
|
|
457
|
+
if (differs.length === 0)
|
|
458
|
+
throw new Error(`That changes nothing in "${current.name}".`);
|
|
459
|
+
const lines = [...changesSinceConfirmed(readHistory(paths(), current.id)), ...differs];
|
|
460
|
+
proposedUpdates.set(preview, {
|
|
461
|
+
current,
|
|
462
|
+
job,
|
|
463
|
+
lines: previewLines(job, policy, now),
|
|
464
|
+
changes: lines,
|
|
465
|
+
});
|
|
466
|
+
return {
|
|
467
|
+
preview,
|
|
468
|
+
changes: lines,
|
|
469
|
+
permissionsChange: permissionsChanged(current, job),
|
|
470
|
+
};
|
|
471
|
+
},
|
|
472
|
+
async confirmUpdate(request, signal) {
|
|
473
|
+
const entry = proposedUpdates.get(request.preview);
|
|
474
|
+
if (!entry)
|
|
475
|
+
return false;
|
|
476
|
+
ui.say(renderUpdateConfirmation(request, entry.lines));
|
|
477
|
+
const answer = await ui.ask({
|
|
478
|
+
questionId: `schedule-update:${entry.current.id}`,
|
|
479
|
+
question: visibleScheduleMessage(`Save the change the model proposed to the scheduled job "${entry.current.name}"? (details above)`),
|
|
480
|
+
header: 'Proposed by the model',
|
|
481
|
+
options: [
|
|
482
|
+
{ id: 'cancel', label: 'Cancel', description: 'Change nothing' },
|
|
483
|
+
{
|
|
484
|
+
id: 'save',
|
|
485
|
+
label: 'Save',
|
|
486
|
+
description: request.permissionsChange
|
|
487
|
+
? 'It keeps its history and runs under the new permissions above'
|
|
488
|
+
: 'It keeps its history and runs as shown above',
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
multiSelect: false,
|
|
492
|
+
allowFreeText: false,
|
|
493
|
+
}, signal);
|
|
494
|
+
return answer.kind === 'answer' && answer.selectedOptionIds[0] === 'save';
|
|
495
|
+
},
|
|
496
|
+
async update(preview) {
|
|
497
|
+
const entry = proposedUpdates.get(preview);
|
|
498
|
+
if (!entry)
|
|
499
|
+
throw new Error('That proposal is no longer current; propose it again.');
|
|
500
|
+
const now = new Date();
|
|
501
|
+
let next;
|
|
502
|
+
try {
|
|
503
|
+
next = updateJob(paths(), entry.current.id, entry.current.revision, () => confirmJob(entry.job, 'tool-confirmed', now, {
|
|
504
|
+
paused: entry.current.state === 'paused',
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
if (error instanceof ScheduleConflictError)
|
|
509
|
+
throw new Error(`"${entry.current.name}" changed while the operator was being asked; propose the change again.`);
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
proposedUpdates.delete(preview);
|
|
513
|
+
appendHistory(paths(), next.id, {
|
|
514
|
+
v: 1,
|
|
515
|
+
kind: 'job',
|
|
516
|
+
at: now.toISOString(),
|
|
517
|
+
action: 'edited',
|
|
518
|
+
by: 'tool',
|
|
519
|
+
changes: entry.changes,
|
|
520
|
+
});
|
|
521
|
+
const installed = readManifest(paths()) !== undefined;
|
|
522
|
+
ui.say(`⏲ Scheduled job ${next.name} changed (${next.state}); it keeps its history. /schedule shows it.${installed ? '' : ' The scheduler is not installed, so it does not run until you install it: namzu schedule install.'}`);
|
|
523
|
+
return {
|
|
524
|
+
name: next.name,
|
|
525
|
+
...(installed ? {} : { note: NOT_INSTALLED_NOTE }),
|
|
526
|
+
};
|
|
527
|
+
},
|
|
528
|
+
// Every job, whatever `allFolders` says: a model that has just created
|
|
529
|
+
// a job in another folder, and does not pass `allFolders`, was told
|
|
530
|
+
// "No scheduled jobs." while `namzu schedule list` showed it. The
|
|
531
|
+
// session folder's are marked, and only theirs carry the prompt.
|
|
532
|
+
async list() {
|
|
533
|
+
const cwd = canonicalCwd(ui.cwd());
|
|
534
|
+
const { jobs, errors } = listJobs(paths());
|
|
535
|
+
if (errors.length > 0)
|
|
536
|
+
throw new Error(`${errors.length} job file${errors.length === 1 ? '' : 's'} could not be read; inspect or repair the files under NAMZU_HOME before listing scheduled jobs.`);
|
|
537
|
+
return jobs.map((job) => summary(job, job.folder.canonical === cwd, ui.home()));
|
|
242
538
|
},
|
|
243
539
|
async find(ref) {
|
|
244
540
|
try {
|
|
245
541
|
const job = findJob(paths(), ref);
|
|
246
|
-
return summary(job, job.folder.canonical === ui.cwd(), ui.home());
|
|
542
|
+
return summary(job, job.folder.canonical === canonicalCwd(ui.cwd()), ui.home());
|
|
247
543
|
}
|
|
248
544
|
catch {
|
|
249
545
|
return undefined;
|
|
@@ -252,11 +548,15 @@ export function createScheduleToolHost(ui) {
|
|
|
252
548
|
async confirmAction(job, action, signal) {
|
|
253
549
|
const answer = await ui.ask({
|
|
254
550
|
questionId: `schedule-${action}:${job.name}`,
|
|
255
|
-
question: `${action === 'delete' ? 'Delete' : 'Resume'} the scheduled job "${job.name}" (${job.schedule}, in ${job.folder})? The model asked
|
|
551
|
+
question: visibleScheduleMessage(`${action === 'delete' ? 'Delete' : 'Resume'} the scheduled job "${job.name}" (${job.schedule}, in ${job.folder})? The model asked.`),
|
|
256
552
|
header: 'Proposed by the model',
|
|
257
553
|
options: [
|
|
258
554
|
{ id: 'no', label: 'No', description: 'Leave it as it is' },
|
|
259
|
-
{
|
|
555
|
+
{
|
|
556
|
+
id: 'yes',
|
|
557
|
+
label: action === 'delete' ? 'Delete it' : 'Resume it',
|
|
558
|
+
description: '',
|
|
559
|
+
},
|
|
260
560
|
],
|
|
261
561
|
multiSelect: false,
|
|
262
562
|
allowFreeText: false,
|