@crouton-kit/crouter 0.3.18 → 0.3.20
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/dist/build-root.js +6 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
- package/dist/builtin-memory/crouter-development/personas.md +117 -0
- package/dist/builtin-memory/crouter-development/plugins.md +163 -0
- package/dist/builtin-memory/design.md +60 -0
- package/dist/builtin-memory/development.md +116 -0
- package/dist/builtin-memory/planning.md +66 -0
- package/dist/builtin-memory/spec.md +99 -0
- package/dist/builtin-personas/design/PERSONA.md +1 -0
- package/dist/builtin-personas/design/orchestrator.md +2 -1
- package/dist/builtin-personas/developer/PERSONA.md +1 -0
- package/dist/builtin-personas/developer/orchestrator.md +2 -1
- package/dist/builtin-personas/explore/PERSONA.md +1 -0
- package/dist/builtin-personas/explore/orchestrator.md +4 -0
- package/dist/builtin-personas/general/PERSONA.md +1 -0
- package/dist/builtin-personas/general/orchestrator.md +4 -0
- package/dist/builtin-personas/lifecycle/resident.md +1 -1
- package/dist/builtin-personas/lifecycle/terminal.md +5 -2
- package/dist/builtin-personas/orchestration-kernel.md +15 -24
- package/dist/builtin-personas/plan/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/orchestrator.md +2 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -0
- package/dist/builtin-personas/review/orchestrator.md +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/PERSONA.md +1 -0
- package/dist/builtin-personas/spec/orchestrator.md +2 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
- package/dist/builtin-views/_lib/states.mjs +161 -0
- package/dist/builtin-views/canvas/client.mjs +303 -0
- package/dist/builtin-views/canvas/view.mjs +576 -0
- package/dist/builtin-views/git-pr/client.mjs +440 -0
- package/dist/builtin-views/git-pr/view.mjs +675 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
- package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
- package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
- package/dist/builtin-views/inbox/view.mjs +889 -0
- package/dist/builtin-views/linkedin/client.mjs +610 -0
- package/dist/builtin-views/linkedin/view.mjs +1171 -0
- package/dist/clients/attach/attach-cmd.d.ts +2 -0
- package/dist/clients/attach/attach-cmd.js +354 -0
- package/dist/clients/attach/chat-view.d.ts +77 -0
- package/dist/clients/attach/chat-view.js +450 -0
- package/dist/clients/attach/clipboard-image.d.ts +16 -0
- package/dist/clients/attach/clipboard-image.js +113 -0
- package/dist/clients/attach/config-load.d.ts +31 -0
- package/dist/clients/attach/config-load.js +113 -0
- package/dist/clients/attach/extension-dialogs.d.ts +29 -0
- package/dist/clients/attach/extension-dialogs.js +101 -0
- package/dist/clients/attach/input-controller.d.ts +54 -0
- package/dist/clients/attach/input-controller.js +204 -0
- package/dist/clients/attach/slash-commands.d.ts +36 -0
- package/dist/clients/attach/slash-commands.js +200 -0
- package/dist/clients/attach/view-socket.d.ts +48 -0
- package/dist/clients/attach/view-socket.js +126 -0
- package/dist/commands/attention.js +3 -3
- package/dist/commands/canvas-prune.js +1 -1
- package/dist/commands/daemon.js +3 -2
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/human/queue.js +54 -8
- package/dist/commands/human/shared.d.ts +2 -0
- package/dist/commands/memory/find.d.ts +1 -0
- package/dist/commands/memory/find.js +180 -0
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +140 -0
- package/dist/commands/memory/list.d.ts +1 -0
- package/dist/commands/memory/list.js +79 -0
- package/dist/commands/memory/read.js +103 -0
- package/dist/commands/memory/shared.d.ts +30 -0
- package/dist/commands/memory/shared.js +122 -0
- package/dist/commands/memory/write.d.ts +1 -0
- package/dist/commands/memory/write.js +85 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +27 -0
- package/dist/commands/node.d.ts +3 -2
- package/dist/commands/node.js +610 -61
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/push.js +6 -6
- package/dist/commands/revive.js +1 -1
- package/dist/commands/skill/author.js +1 -1
- package/dist/commands/skill/shared.d.ts +1 -8
- package/dist/commands/skill/shared.js +2 -55
- package/dist/commands/skill.js +9 -14
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/update.js +1 -1
- package/dist/commands/view-cycle.d.ts +2 -0
- package/dist/commands/view-cycle.js +125 -0
- package/dist/commands/view-list.d.ts +2 -0
- package/dist/commands/view-list.js +66 -0
- package/dist/commands/view-new.d.ts +2 -0
- package/dist/commands/view-new.js +70 -0
- package/dist/commands/view-pick.d.ts +2 -0
- package/dist/commands/view-pick.js +119 -0
- package/dist/commands/view-run.d.ts +2 -0
- package/dist/commands/view-run.js +191 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +29 -0
- package/dist/core/__tests__/broker-attach-limits.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
- package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
- package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
- package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dialogs.test.js +126 -0
- package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +87 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
- package/dist/core/__tests__/cascade-close.test.js +12 -2
- package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
- package/dist/core/__tests__/child-death-wake.test.js +245 -0
- package/dist/core/__tests__/context-intro.test.js +76 -62
- package/dist/core/__tests__/daemon-boot.test.js +14 -5
- package/dist/core/__tests__/daemon-liveness.test.js +34 -9
- package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
- package/dist/core/__tests__/detach-focus.test.js +206 -0
- package/dist/core/__tests__/draw-style.test.d.ts +1 -0
- package/dist/core/__tests__/draw-style.test.js +258 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
- package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
- package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
- package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
- package/dist/core/__tests__/helpers/broker-clients.d.ts +43 -0
- package/dist/core/__tests__/helpers/broker-clients.js +178 -0
- package/dist/core/__tests__/helpers/harness.d.ts +9 -0
- package/dist/core/__tests__/helpers/harness.js +111 -1
- package/dist/core/__tests__/home-session.test.js +41 -8
- package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
- package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation-verbs.test.d.ts +1 -0
- package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
- package/dist/core/__tests__/live-mutation.test.js +6 -132
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/placement-focus.test.js +5 -0
- package/dist/core/__tests__/placement-teardown.test.js +54 -11
- package/dist/core/__tests__/relaunch.test.js +4 -3
- package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
- package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
- package/dist/core/__tests__/spawn-root.test.js +10 -0
- package/dist/core/__tests__/spike-harness.test.js +1 -0
- package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
- package/dist/core/__tests__/wake-bearings.test.js +156 -0
- package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
- package/dist/core/__tests__/wake-origin.test.js +110 -0
- package/dist/core/bootstrap.js +1 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +24 -2
- package/dist/core/canvas/browse/model.d.ts +38 -2
- package/dist/core/canvas/browse/model.js +134 -10
- package/dist/core/canvas/browse/render.d.ts +6 -12
- package/dist/core/canvas/browse/render.js +72 -104
- package/dist/core/canvas/canvas.js +16 -23
- package/dist/core/canvas/db.js +46 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/pid.d.ts +4 -0
- package/dist/core/canvas/pid.js +23 -0
- package/dist/core/canvas/render.d.ts +11 -2
- package/dist/core/canvas/render.js +69 -0
- package/dist/core/canvas/types.d.ts +91 -3
- package/dist/core/canvas/wakeups.d.ts +76 -0
- package/dist/core/canvas/wakeups.js +185 -0
- package/dist/core/config.js +4 -1
- package/dist/core/frontmatter.js +37 -124
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/loader.d.ts +7 -0
- package/dist/core/personas/loader.js +36 -5
- package/dist/core/personas/resolve.js +13 -3
- package/dist/core/predicate.d.ts +63 -0
- package/dist/core/predicate.js +189 -0
- package/dist/core/resolver.js +26 -5
- package/dist/core/runtime/bearings.d.ts +53 -12
- package/dist/core/runtime/bearings.js +132 -59
- package/dist/core/runtime/broker-cli.d.ts +1 -0
- package/dist/core/runtime/broker-cli.js +46 -0
- package/dist/core/runtime/broker-protocol.d.ts +332 -0
- package/dist/core/runtime/broker-protocol.js +153 -0
- package/dist/core/runtime/broker-sdk.d.ts +48 -0
- package/dist/core/runtime/broker-sdk.js +72 -0
- package/dist/core/runtime/broker.d.ts +55 -0
- package/dist/core/runtime/broker.js +1128 -0
- package/dist/core/runtime/close.js +35 -6
- package/dist/core/runtime/host.d.ts +53 -0
- package/dist/core/runtime/host.js +186 -0
- package/dist/core/runtime/kickoff.d.ts +2 -1
- package/dist/core/runtime/kickoff.js +91 -5
- package/dist/core/runtime/launch.d.ts +54 -4
- package/dist/core/runtime/launch.js +85 -5
- package/dist/core/runtime/lifecycle.js +23 -6
- package/dist/core/runtime/memory.d.ts +2 -42
- package/dist/core/runtime/memory.js +11 -162
- package/dist/core/runtime/nodes.d.ts +37 -0
- package/dist/core/runtime/nodes.js +60 -1
- package/dist/core/runtime/persona.js +21 -11
- package/dist/core/runtime/pi-vendored.d.ts +18 -0
- package/dist/core/runtime/pi-vendored.js +49 -0
- package/dist/core/runtime/placement.d.ts +32 -14
- package/dist/core/runtime/placement.js +206 -52
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +4 -12
- package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
- package/dist/core/runtime/{demote.js → recycle.js} +27 -11
- package/dist/core/runtime/reset.js +11 -8
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +23 -0
- package/dist/core/runtime/spawn.js +77 -23
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +28 -0
- package/dist/core/runtime/tmux.js +80 -6
- package/dist/core/scope.d.ts +11 -0
- package/dist/core/scope.js +39 -0
- package/dist/core/spawn.d.ts +5 -0
- package/dist/core/spawn.js +20 -2
- package/dist/core/substrate/gate.d.ts +13 -0
- package/dist/core/substrate/gate.js +21 -0
- package/dist/core/substrate/index.d.ts +7 -0
- package/dist/core/substrate/index.js +18 -0
- package/dist/core/substrate/on-read.d.ts +14 -0
- package/dist/core/substrate/on-read.js +292 -0
- package/dist/core/substrate/render.d.ts +25 -0
- package/dist/core/substrate/render.js +256 -0
- package/dist/core/substrate/schema.d.ts +76 -0
- package/dist/core/substrate/schema.js +124 -0
- package/dist/core/substrate/session-cache.d.ts +30 -0
- package/dist/core/substrate/session-cache.js +77 -0
- package/dist/core/substrate/subject.d.ts +41 -0
- package/dist/core/substrate/subject.js +54 -0
- package/dist/core/tui/contract.d.ts +83 -0
- package/dist/core/tui/contract.js +8 -0
- package/dist/core/tui/draw.d.ts +96 -0
- package/dist/core/tui/draw.js +339 -0
- package/dist/core/tui/host.d.ts +29 -0
- package/dist/core/tui/host.js +379 -0
- package/dist/core/tui/loader.d.ts +16 -0
- package/dist/core/tui/loader.js +94 -0
- package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
- package/dist/core/wake.d.ts +86 -0
- package/dist/core/wake.js +308 -0
- package/dist/daemon/crtrd.d.ts +29 -4
- package/dist/daemon/crtrd.js +662 -46
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
- package/dist/pi-extensions/canvas-commands.d.ts +3 -0
- package/dist/pi-extensions/canvas-commands.js +10 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
- package/dist/pi-extensions/canvas-context-intro.js +55 -15
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
- package/dist/pi-extensions/canvas-nav.d.ts +3 -0
- package/dist/pi-extensions/canvas-nav.js +87 -23
- package/dist/pi-extensions/canvas-stophook.js +17 -8
- package/dist/pi-extensions/canvas-view.d.ts +21 -0
- package/dist/pi-extensions/canvas-view.js +75 -0
- package/dist/prompts/skill.js +19 -26
- package/dist/prompts/view.d.ts +7 -0
- package/dist/prompts/view.js +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/dist/commands/skill/find.d.ts +0 -4
- package/dist/commands/skill/find.js +0 -257
- package/dist/commands/skill/read.js +0 -91
- /package/dist/commands/{skill → memory}/read.d.ts +0 -0
- /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Time grammar + recurrence evaluation for scheduled wakeups.
|
|
2
|
+
//
|
|
3
|
+
// This is the ONE place that owns wakeup time logic. Both the CLI surface
|
|
4
|
+
// (`node wake at|until|spawn`) and the daemon's firing pass import
|
|
5
|
+
// from here, so there is no duplicated cron usage and no surface<->daemon
|
|
6
|
+
// inverted dependency:
|
|
7
|
+
// - parseWhen / parseCadence — arm-time grammar (surface, T7)
|
|
8
|
+
// - nextSlotAfter — per-tick advance / coalescing (daemon, T4)
|
|
9
|
+
//
|
|
10
|
+
// Design refs: surface-design §4 (grammar), §5 (timezone/DST ruling),
|
|
11
|
+
// design §5.3 + D7 (engine evaluates UTC; the surface bakes the IANA zone
|
|
12
|
+
// into a calendar `recur`). Tick-scale only — no sub-second precision.
|
|
13
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/** Cadence floor: reject any interval / cron min-spacing below this (AC-N4). */
|
|
18
|
+
export const CADENCE_FLOOR_MS = 60_000;
|
|
19
|
+
const DURATION_UNIT_MS = {
|
|
20
|
+
s: 1_000,
|
|
21
|
+
m: 60_000,
|
|
22
|
+
h: 3_600_000,
|
|
23
|
+
d: 86_400_000,
|
|
24
|
+
};
|
|
25
|
+
// `<int><unit>` repeated; units s/m/h/d (surface-design §4.1).
|
|
26
|
+
const DURATION_RE = /^\+?(?:\d+[smhd])+$/;
|
|
27
|
+
const DURATION_PART_RE = /(\d+)([smhd])/g;
|
|
28
|
+
// ISO-8601 date-time core (no zone). Captures Y M D h m [s] [ms].
|
|
29
|
+
const ISO_CORE_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d{1,3}))?/;
|
|
30
|
+
// Trailing zone designator: Z | ±HH[:MM] | ±HHMM.
|
|
31
|
+
const ZONE_SUFFIX_RE = /^(Z|[+-]\d{2}(?::?\d{2})?)$/;
|
|
32
|
+
// Cron `@alias` -> 5-field expansion (the engine never stores a bare alias).
|
|
33
|
+
const CRON_ALIASES = {
|
|
34
|
+
'@yearly': '0 0 1 1 *',
|
|
35
|
+
'@annually': '0 0 1 1 *',
|
|
36
|
+
'@monthly': '0 0 1 * *',
|
|
37
|
+
'@weekly': '0 0 * * 0',
|
|
38
|
+
'@daily': '0 0 * * *',
|
|
39
|
+
'@midnight': '0 0 * * *',
|
|
40
|
+
'@hourly': '0 * * * *',
|
|
41
|
+
};
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Internal helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function whenError(code, message, received) {
|
|
46
|
+
return { error: { code, message, received } };
|
|
47
|
+
}
|
|
48
|
+
function cadenceError(code, message, received) {
|
|
49
|
+
return { error: { code, message, received } };
|
|
50
|
+
}
|
|
51
|
+
/** Total ms for a relative-duration string, or null if it is not one. */
|
|
52
|
+
function parseDurationMs(s) {
|
|
53
|
+
if (!DURATION_RE.test(s))
|
|
54
|
+
return null;
|
|
55
|
+
let total = 0;
|
|
56
|
+
DURATION_PART_RE.lastIndex = 0;
|
|
57
|
+
let m;
|
|
58
|
+
while ((m = DURATION_PART_RE.exec(s)) !== null) {
|
|
59
|
+
total += Number(m[1]) * DURATION_UNIT_MS[m[2]];
|
|
60
|
+
}
|
|
61
|
+
return total;
|
|
62
|
+
}
|
|
63
|
+
/** Host-configured IANA zone (crtr is a single-machine runtime). */
|
|
64
|
+
function hostZone() {
|
|
65
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
66
|
+
}
|
|
67
|
+
/** True iff `tz` is a resolvable IANA zone name. */
|
|
68
|
+
function isValidZone(tz) {
|
|
69
|
+
try {
|
|
70
|
+
new Intl.DateTimeFormat('en-US', { timeZone: tz });
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Offset (ms) of `tz` at the given UTC instant: wall-clock-as-UTC minus instant. */
|
|
78
|
+
function zoneOffsetMs(tz, utcMs) {
|
|
79
|
+
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
80
|
+
timeZone: tz,
|
|
81
|
+
hourCycle: 'h23',
|
|
82
|
+
year: 'numeric',
|
|
83
|
+
month: '2-digit',
|
|
84
|
+
day: '2-digit',
|
|
85
|
+
hour: '2-digit',
|
|
86
|
+
minute: '2-digit',
|
|
87
|
+
second: '2-digit',
|
|
88
|
+
});
|
|
89
|
+
const parts = {};
|
|
90
|
+
for (const p of dtf.formatToParts(new Date(utcMs))) {
|
|
91
|
+
if (p.type !== 'literal')
|
|
92
|
+
parts[p.type] = p.value;
|
|
93
|
+
}
|
|
94
|
+
const asUTC = Date.UTC(Number(parts['year']), Number(parts['month']) - 1, Number(parts['day']), Number(parts['hour']), Number(parts['minute']), Number(parts['second']));
|
|
95
|
+
return asUTC - utcMs;
|
|
96
|
+
}
|
|
97
|
+
/** Interpret a wall-clock instant in `tz` and return the UTC ms instant. */
|
|
98
|
+
function wallClockToUtc(y, mo, d, h, mi, s, ms, tz) {
|
|
99
|
+
const guess = Date.UTC(y, mo - 1, d, h, mi, s, ms);
|
|
100
|
+
const off1 = zoneOffsetMs(tz, guess);
|
|
101
|
+
let utc = guess - off1;
|
|
102
|
+
// Second pass settles DST-boundary cases where the offset at the candidate
|
|
103
|
+
// instant differs from the offset at the naive guess.
|
|
104
|
+
const off2 = zoneOffsetMs(tz, utc);
|
|
105
|
+
if (off2 !== off1)
|
|
106
|
+
utc = guess - off2;
|
|
107
|
+
return utc;
|
|
108
|
+
}
|
|
109
|
+
/** Classify a string as a zoned/bare ISO date-time, or null if it is neither. */
|
|
110
|
+
function classifyIso(s) {
|
|
111
|
+
const m = ISO_CORE_RE.exec(s);
|
|
112
|
+
if (!m || m.index !== 0)
|
|
113
|
+
return null;
|
|
114
|
+
const rest = s.slice(m[0].length);
|
|
115
|
+
if (rest === '') {
|
|
116
|
+
return {
|
|
117
|
+
zoned: false,
|
|
118
|
+
y: Number(m[1]),
|
|
119
|
+
mo: Number(m[2]),
|
|
120
|
+
d: Number(m[3]),
|
|
121
|
+
h: Number(m[4]),
|
|
122
|
+
mi: Number(m[5]),
|
|
123
|
+
s: m[6] ? Number(m[6]) : 0,
|
|
124
|
+
ms: m[7] ? Number(m[7].padEnd(3, '0')) : 0,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (ZONE_SUFFIX_RE.test(rest))
|
|
128
|
+
return { zoned: true };
|
|
129
|
+
return null; // trailing garbage -> not a valid ISO instant
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// parseWhen — resolve a one-shot <when> to an absolute UTC instant
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
/**
|
|
135
|
+
* Resolve a `<when>` to a single UTC `fireAt`:
|
|
136
|
+
* - relative duration (`90s`, `1h30m`) -> now + Σ
|
|
137
|
+
* - absolute zoned ISO (`…Z` / `±HH:MM`) -> that exact instant
|
|
138
|
+
* - absolute bare ISO (`2026-06-07T09:00`) -> wall clock in `tz`
|
|
139
|
+
* (default host-local) -> UTC
|
|
140
|
+
*
|
|
141
|
+
* A bare wall-clock time is interpreted in `opts.tz` when given, else the host
|
|
142
|
+
* zone, and frozen to a UTC instant (DST never re-enters a one-shot). A
|
|
143
|
+
* resolved instant not strictly in the future is rejected (`wake_in_past`).
|
|
144
|
+
*/
|
|
145
|
+
export function parseWhen(when, opts) {
|
|
146
|
+
const received = when;
|
|
147
|
+
const raw = when.trim();
|
|
148
|
+
const nowMs = opts.now.getTime();
|
|
149
|
+
// 1. relative duration
|
|
150
|
+
const durMs = parseDurationMs(raw);
|
|
151
|
+
if (durMs !== null) {
|
|
152
|
+
const fireMs = nowMs + durMs;
|
|
153
|
+
if (fireMs <= nowMs) {
|
|
154
|
+
return whenError('wake_in_past', `That resolves to now or the past. Use a positive future duration (e.g. "5m", "2h").`, received);
|
|
155
|
+
}
|
|
156
|
+
return { fireAt: new Date(fireMs).toISOString() };
|
|
157
|
+
}
|
|
158
|
+
// 2/3. absolute ISO (zoned or bare)
|
|
159
|
+
const iso = classifyIso(raw);
|
|
160
|
+
if (iso) {
|
|
161
|
+
let fireMs;
|
|
162
|
+
if (iso.zoned) {
|
|
163
|
+
const d = new Date(raw);
|
|
164
|
+
if (Number.isNaN(d.getTime())) {
|
|
165
|
+
return whenError('bad_when', `Not a valid ISO-8601 instant: "${received}".`, received);
|
|
166
|
+
}
|
|
167
|
+
fireMs = d.getTime();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const tz = opts.tz ?? hostZone();
|
|
171
|
+
if (!isValidZone(tz)) {
|
|
172
|
+
return whenError('unknown_zone', `Unknown time zone: "${tz}". Use an IANA name (e.g. "America/New_York").`, opts.tz ?? tz);
|
|
173
|
+
}
|
|
174
|
+
fireMs = wallClockToUtc(iso.y, iso.mo, iso.d, iso.h, iso.mi, iso.s, iso.ms, tz);
|
|
175
|
+
if (Number.isNaN(fireMs)) {
|
|
176
|
+
return whenError('bad_when', `Not a valid date-time: "${received}".`, received);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (fireMs <= nowMs) {
|
|
180
|
+
return whenError('wake_in_past', `That time is now or in the past. Pick a future instant.`, received);
|
|
181
|
+
}
|
|
182
|
+
return { fireAt: new Date(fireMs).toISOString() };
|
|
183
|
+
}
|
|
184
|
+
return whenError('bad_when', `Could not parse "${received}". Use a duration ("5m", "1h30m"), a zoned ISO ("2026-06-07T09:00:00Z"), or a bare ISO ("2026-06-07T09:00").`, received);
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// parseCadence — resolve --every to a stored recur + first fire
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
/**
|
|
190
|
+
* Resolve a `<cadence>` (the `--every` value) to a stored `recur` JSON string
|
|
191
|
+
* plus the first `fireAt`:
|
|
192
|
+
* - fixed interval (a duration) -> {"every":"<dur>"}, first = now + interval
|
|
193
|
+
* - calendar cron / @alias -> {"cron":"<5-field>","tz":"<iana>"},
|
|
194
|
+
* first = next match strictly after now
|
|
195
|
+
*
|
|
196
|
+
* Aliases (`@daily`) are expanded to a 5-field cron before baking, and the
|
|
197
|
+
* IANA zone (from `opts.tz`, else host-local) is baked in so the engine stays a
|
|
198
|
+
* pure UTC evaluator (design D7). Rejects sub-floor spacing (`cadence_too_fast`,
|
|
199
|
+
* < 60s — for both intervals and seconds-granular crons), ungrammatical input
|
|
200
|
+
* (`bad_cadence`), and unknown zones (`unknown_zone`).
|
|
201
|
+
*/
|
|
202
|
+
export function parseCadence(every, opts) {
|
|
203
|
+
const received = every;
|
|
204
|
+
const raw = every.trim();
|
|
205
|
+
const nowMs = opts.now.getTime();
|
|
206
|
+
// 1. fixed interval
|
|
207
|
+
const durMs = parseDurationMs(raw);
|
|
208
|
+
if (durMs !== null) {
|
|
209
|
+
if (durMs < CADENCE_FLOOR_MS) {
|
|
210
|
+
return cadenceError('cadence_too_fast', `Interval too fast: minimum cadence is 60s. Use a longer --every (e.g. "1m", "5m", "1h").`, received);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
recur: JSON.stringify({ every: raw }),
|
|
214
|
+
firstFireAt: new Date(nowMs + durMs).toISOString(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// 2. calendar cron / alias
|
|
218
|
+
const expanded = CRON_ALIASES[raw] ?? raw;
|
|
219
|
+
const tz = opts.tz ?? hostZone();
|
|
220
|
+
if (opts.tz !== undefined && !isValidZone(opts.tz)) {
|
|
221
|
+
return cadenceError('unknown_zone', `Unknown time zone: "${opts.tz}". Use an IANA name (e.g. "America/New_York").`, opts.tz);
|
|
222
|
+
}
|
|
223
|
+
let interval;
|
|
224
|
+
let firstFireAt;
|
|
225
|
+
try {
|
|
226
|
+
interval = CronExpressionParser.parse(expanded, { currentDate: opts.now, tz });
|
|
227
|
+
firstFireAt = interval.next().toDate().toISOString();
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return cadenceError('bad_cadence', `Could not parse cadence "${received}". Use a duration ("6h"), a 5-field cron ("0 9 * * *"), or an @alias ("@daily").`, received);
|
|
231
|
+
}
|
|
232
|
+
// Cadence floor: a 5-field cron is always >=60s; only a seconds-granular cron
|
|
233
|
+
// (multiple values in the seconds field) can resolve below the floor.
|
|
234
|
+
const secField = interval.fields?.second;
|
|
235
|
+
const secValues = secField && Array.isArray(secField.values) ? secField.values : [0];
|
|
236
|
+
if (secValues.length > 1) {
|
|
237
|
+
return cadenceError('cadence_too_fast', `Cron resolves below the 60s floor (sub-minute spacing). Use a cadence of at least one minute.`, received);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
recur: JSON.stringify({ cron: expanded, tz }),
|
|
241
|
+
firstFireAt,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// cadenceDisplay — a recur JSON rendered for humans + agents
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
/**
|
|
248
|
+
* Render a stored `recur` JSON as a compact, human-readable cadence:
|
|
249
|
+
* - {"every":"6h"} -> `every 6h`
|
|
250
|
+
* - {"cron":"0 9 * * *","tz":"America/…"} -> ``cron `0 9 * * *` (America/…)``
|
|
251
|
+
* - null / undefined / unparseable -> `none`
|
|
252
|
+
*
|
|
253
|
+
* The ONE cadence-display helper, shared by the `node wake` CLI surface
|
|
254
|
+
* (list/guidance) and the <crtr-wake> wake-provenance block (bearings.ts), so
|
|
255
|
+
* the cadence an agent reads in its wake block matches `crtr node wake list`
|
|
256
|
+
* exactly. Cadence only — never an instance count.
|
|
257
|
+
*/
|
|
258
|
+
export function cadenceDisplay(recur) {
|
|
259
|
+
if (recur === null || recur === undefined)
|
|
260
|
+
return 'none';
|
|
261
|
+
try {
|
|
262
|
+
const r = JSON.parse(recur);
|
|
263
|
+
if (typeof r.every === 'string')
|
|
264
|
+
return `every ${r.every}`;
|
|
265
|
+
if (typeof r.cron === 'string') {
|
|
266
|
+
return `cron \`${r.cron}\`${typeof r.tz === 'string' ? ` (${r.tz})` : ''}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
/* fall through */
|
|
271
|
+
}
|
|
272
|
+
return 'none';
|
|
273
|
+
}
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// nextSlotAfter — the coalescing primitive (daemon advance)
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
/**
|
|
278
|
+
* Earliest occurrence of `recur` strictly greater than `now`, as a UTC ISO
|
|
279
|
+
* string. Anchors on `now` (not the stored `fire_at`), so it structurally
|
|
280
|
+
* coalesces every slot missed while the daemon was down (design §5.3, AC-E2).
|
|
281
|
+
* DST-correct because the IANA zone is baked into a calendar `recur`.
|
|
282
|
+
*
|
|
283
|
+
* parseCadence validates the cadence at arm time, so a throw here is a rare
|
|
284
|
+
* backstop (a corrupted/foreign row) — T4 must quarantine such a row, never
|
|
285
|
+
* re-query it each tick.
|
|
286
|
+
*/
|
|
287
|
+
export function nextSlotAfter(recur, now) {
|
|
288
|
+
let parsed;
|
|
289
|
+
try {
|
|
290
|
+
parsed = JSON.parse(recur);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
throw new Error(`nextSlotAfter: malformed recur JSON: ${recur}`);
|
|
294
|
+
}
|
|
295
|
+
if (typeof parsed.every === 'string') {
|
|
296
|
+
const ms = parseDurationMs(parsed.every);
|
|
297
|
+
if (ms === null || ms <= 0) {
|
|
298
|
+
throw new Error(`nextSlotAfter: invalid interval recur: ${recur}`);
|
|
299
|
+
}
|
|
300
|
+
return new Date(now.getTime() + ms).toISOString();
|
|
301
|
+
}
|
|
302
|
+
if (typeof parsed.cron === 'string') {
|
|
303
|
+
const tz = typeof parsed.tz === 'string' ? parsed.tz : 'UTC';
|
|
304
|
+
const interval = CronExpressionParser.parse(parsed.cron, { currentDate: now, tz });
|
|
305
|
+
return interval.next().toDate().toISOString(); // strictly after `now`
|
|
306
|
+
}
|
|
307
|
+
throw new Error(`nextSlotAfter: recur has neither 'every' nor 'cron': ${recur}`);
|
|
308
|
+
}
|
package/dist/daemon/crtrd.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
import { type NodeRow } from '../core/canvas/index.js';
|
|
2
|
+
import { isPidAlive } from '../core/canvas/pid.js';
|
|
3
|
+
export declare const YIELD_STALL_GRACE_MS: number;
|
|
4
|
+
export declare const NEVER_LAUNCHED_GRACE_MS: number;
|
|
5
|
+
/** Never-launched decision (audit 2026-06-09, Bug 4 — node mq45b6ch-9ecc2f03):
|
|
6
|
+
* a spawn that opened its window but whose pi never came up leaves a row with
|
|
7
|
+
* ZERO cycles, NO session, NO pid — and, when the window lingers (e.g. a
|
|
8
|
+
* remain-on-exit corpse), the live-pane pass read "no pid to judge" and left it
|
|
9
|
+
* forever; the parent waited on a node that was never going to run. Pure core:
|
|
10
|
+
* a node that has ever booted (session recorded) or ever been revived (cycles
|
|
11
|
+
* bumped) is NOT never-launched — the cycles gate also bounds this to ONE
|
|
12
|
+
* automatic retry (the relaunch bumps cycles), so a persistently failing
|
|
13
|
+
* launch can't loop. Age below the grace is a normal in-flight boot. */
|
|
14
|
+
export declare function neverLaunchedVerdict(piSessionId: string | null, cycles: number, ageMs: number): 'leave' | 'relaunch';
|
|
15
|
+
/** Yield-stall decision (audit 2026-06-09, Bug 1 — node mq5v9hfa-74493d45 and
|
|
16
|
+
* three sibling orchestrators): a `node yield` records intent='refresh' and
|
|
17
|
+
* agent_end dispatches the in-place respawn, but at very large contexts the pi
|
|
18
|
+
* process can HANG instead of exiting — leaving pi alive, the turn over, and
|
|
19
|
+
* the intent pending forever. The daemon only revives DEAD pis, so without
|
|
20
|
+
* this verdict the node is permanently stuck (no revive, no wake). Pure core,
|
|
21
|
+
* mirroring livenessVerdict; the kill side effects live in handleYieldStall.
|
|
22
|
+
*
|
|
23
|
+
* 'kill' ONLY when ALL of: pi alive, intent='refresh', the turn is over (busy
|
|
24
|
+
* marker absent — a node may legitimately keep working for many minutes after
|
|
25
|
+
* running `node yield` mid-turn, and a working pi must never be killed), and
|
|
26
|
+
* the state has persisted past YIELD_STALL_GRACE_MS. */
|
|
27
|
+
export declare function yieldStallVerdict(piPidAlive: boolean | null, intent: NodeRow['intent'], busy: boolean, stalledFor: number | null): 'leave' | 'pending' | 'kill';
|
|
1
28
|
export type LivenessVerdict = 'leave' | 'pending' | 'revive';
|
|
2
29
|
/** Decide what to do with a node whose tmux pane is alive, from its pi
|
|
3
30
|
* liveness and how long it's been dead. Pure — the time-and-tmux side effects
|
|
@@ -6,12 +33,10 @@ export type LivenessVerdict = 'leave' | 'pending' | 'revive';
|
|
|
6
33
|
* relaunch in flight) — leave those to the pane-gone pass.
|
|
7
34
|
* deadFor: ms since first observed dead, or null on the first observation. */
|
|
8
35
|
export declare function livenessVerdict(piPidAlive: boolean | null, deadFor: number | null): LivenessVerdict;
|
|
36
|
+
export declare const DEAD_REAP_GRACE_MS: number;
|
|
9
37
|
/** Read the pid stored in the pidfile, or null if absent / malformed. */
|
|
10
38
|
export declare function readPidfile(): number | null;
|
|
11
|
-
|
|
12
|
-
* 0)` throws ESRCH when the process is gone; EPERM means it exists but isn't
|
|
13
|
-
* ours — still alive. */
|
|
14
|
-
export declare function isPidAlive(pid: number): boolean;
|
|
39
|
+
export { isPidAlive };
|
|
15
40
|
/** True when a crtrd process is already running (pidfile exists + pid alive). */
|
|
16
41
|
export declare function isDaemonRunning(): boolean;
|
|
17
42
|
export declare function superviseTick(now?: number): Promise<void>;
|