@hwip/cli 0.0.1 → 0.1.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/LICENSE +190 -0
- package/NOTICE +11 -0
- package/README.md +1979 -0
- package/dist/agent-list.d.ts +76 -0
- package/dist/agent-list.js +102 -0
- package/dist/agent-read.d.ts +105 -0
- package/dist/agent-read.js +309 -0
- package/dist/agent-rules-fetch.d.ts +51 -0
- package/dist/agent-rules-fetch.js +275 -0
- package/dist/agent-rules.default.d.ts +30 -0
- package/dist/agent-rules.default.js +292 -0
- package/dist/agent-session.d.ts +23 -0
- package/dist/agent-session.js +44 -0
- package/dist/agent-state.d.ts +84 -0
- package/dist/agent-state.js +188 -0
- package/dist/agent-target.d.ts +60 -0
- package/dist/agent-target.js +81 -0
- package/dist/agents.d.ts +31 -0
- package/dist/agents.js +75 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +76 -0
- package/dist/args.d.ts +74 -0
- package/dist/args.js +254 -0
- package/dist/ask-context-attach.d.ts +73 -0
- package/dist/ask-context-attach.js +151 -0
- package/dist/ask-context-screen.d.ts +21 -0
- package/dist/ask-context-screen.js +49 -0
- package/dist/ask-context-switch.d.ts +31 -0
- package/dist/ask-context-switch.js +74 -0
- package/dist/ask-context.d.ts +84 -0
- package/dist/ask-context.js +172 -0
- package/dist/ask-pending.d.ts +47 -0
- package/dist/ask-pending.js +221 -0
- package/dist/ask-seal.d.ts +120 -0
- package/dist/ask-seal.js +179 -0
- package/dist/ask.d.ts +107 -0
- package/dist/ask.js +551 -0
- package/dist/blocked-prompt-seal.d.ts +35 -0
- package/dist/blocked-prompt-seal.js +89 -0
- package/dist/blocked-prompt.d.ts +57 -0
- package/dist/blocked-prompt.js +181 -0
- package/dist/channel.d.ts +67 -0
- package/dist/channel.js +136 -0
- package/dist/check-update.d.ts +28 -0
- package/dist/check-update.js +153 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +813 -0
- package/dist/codex-config.d.ts +24 -0
- package/dist/codex-config.js +169 -0
- package/dist/codex-transcript.d.ts +59 -0
- package/dist/codex-transcript.js +373 -0
- package/dist/command-refusal.d.ts +56 -0
- package/dist/command-refusal.js +127 -0
- package/dist/command-scan.d.ts +46 -0
- package/dist/command-scan.js +154 -0
- package/dist/command-seal.d.ts +57 -0
- package/dist/command-seal.js +88 -0
- package/dist/command-seq.d.ts +30 -0
- package/dist/command-seq.js +150 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.js +392 -0
- package/dist/core-sections.d.ts +18 -0
- package/dist/core-sections.js +28 -0
- package/dist/crypto.d.ts +278 -0
- package/dist/crypto.js +698 -0
- package/dist/desktop-notify.d.ts +33 -0
- package/dist/desktop-notify.js +49 -0
- package/dist/device-directory.d.ts +36 -0
- package/dist/device-directory.js +97 -0
- package/dist/device-presence.d.ts +17 -0
- package/dist/device-presence.js +62 -0
- package/dist/downloads.d.ts +29 -0
- package/dist/downloads.js +106 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +28 -0
- package/dist/file-crypto.d.ts +94 -0
- package/dist/file-crypto.js +272 -0
- package/dist/file-view-budget.d.ts +15 -0
- package/dist/file-view-budget.js +34 -0
- package/dist/file-view-switch.d.ts +28 -0
- package/dist/file-view-switch.js +122 -0
- package/dist/file-view.d.ts +108 -0
- package/dist/file-view.js +327 -0
- package/dist/gate.d.ts +141 -0
- package/dist/gate.js +336 -0
- package/dist/gemini-transcript.d.ts +32 -0
- package/dist/gemini-transcript.js +189 -0
- package/dist/http-failure.d.ts +98 -0
- package/dist/http-failure.js +349 -0
- package/dist/hwip-core.generated.d.ts +7 -0
- package/dist/hwip-core.generated.js +14 -0
- package/dist/hwip-hook.d.ts +77 -0
- package/dist/hwip-hook.js +340 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/input-sequencer.d.ts +65 -0
- package/dist/input-sequencer.js +142 -0
- package/dist/insecure-url.d.ts +16 -0
- package/dist/insecure-url.js +76 -0
- package/dist/installer.d.ts +96 -0
- package/dist/installer.js +850 -0
- package/dist/inventory-offload.d.ts +49 -0
- package/dist/inventory-offload.js +209 -0
- package/dist/inventory-worker.d.ts +2 -0
- package/dist/inventory-worker.js +27 -0
- package/dist/jsonc-doc.d.ts +106 -0
- package/dist/jsonc-doc.js +354 -0
- package/dist/keep-awake.d.ts +32 -0
- package/dist/keep-awake.js +46 -0
- package/dist/key-changes.d.ts +48 -0
- package/dist/key-changes.js +140 -0
- package/dist/lan-auth.d.ts +140 -0
- package/dist/lan-auth.js +217 -0
- package/dist/lan-endpoint.d.ts +41 -0
- package/dist/lan-endpoint.js +115 -0
- package/dist/lan-receiver.d.ts +122 -0
- package/dist/lan-receiver.js +510 -0
- package/dist/lan-sender.d.ts +76 -0
- package/dist/lan-sender.js +184 -0
- package/dist/lan-transfer.d.ts +36 -0
- package/dist/lan-transfer.js +54 -0
- package/dist/listener-device-id.d.ts +20 -0
- package/dist/listener-device-id.js +133 -0
- package/dist/listener-log.d.ts +4 -0
- package/dist/listener-log.js +23 -0
- package/dist/listener-process.d.ts +200 -0
- package/dist/listener-process.js +470 -0
- package/dist/listener-service.d.ts +225 -0
- package/dist/listener-service.js +682 -0
- package/dist/listener-worktree-cleanup.d.ts +27 -0
- package/dist/listener-worktree-cleanup.js +62 -0
- package/dist/listener.d.ts +1325 -0
- package/dist/listener.js +5680 -0
- package/dist/login-address.d.ts +25 -0
- package/dist/login-address.js +97 -0
- package/dist/login-page.d.ts +29 -0
- package/dist/login-page.js +93 -0
- package/dist/login.d.ts +98 -0
- package/dist/login.js +388 -0
- package/dist/mcp-command.d.ts +45 -0
- package/dist/mcp-command.js +50 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.js +29 -0
- package/dist/node-support.d.ts +19 -0
- package/dist/node-support.js +38 -0
- package/dist/notify-failure.d.ts +57 -0
- package/dist/notify-failure.js +150 -0
- package/dist/opencode-transcript.d.ts +139 -0
- package/dist/opencode-transcript.js +390 -0
- package/dist/optional-features.d.ts +6 -0
- package/dist/optional-features.js +19 -0
- package/dist/pane-fit.d.ts +40 -0
- package/dist/pane-fit.js +119 -0
- package/dist/peer-key.d.ts +33 -0
- package/dist/peer-key.js +34 -0
- package/dist/pi-transcript.d.ts +26 -0
- package/dist/pi-transcript.js +255 -0
- package/dist/presence.d.ts +14 -0
- package/dist/presence.js +124 -0
- package/dist/private-files.d.ts +51 -0
- package/dist/private-files.js +218 -0
- package/dist/project-switch.d.ts +19 -0
- package/dist/project-switch.js +60 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +21 -0
- package/dist/remote-agents.d.ts +410 -0
- package/dist/remote-agents.js +1218 -0
- package/dist/remote-hook.d.ts +12 -0
- package/dist/remote-hook.js +196 -0
- package/dist/replace-file.d.ts +50 -0
- package/dist/replace-file.js +328 -0
- package/dist/schedule-command.d.ts +35 -0
- package/dist/schedule-command.js +260 -0
- package/dist/schedule-launch.d.ts +15 -0
- package/dist/schedule-launch.js +24 -0
- package/dist/schedule-ready.d.ts +44 -0
- package/dist/schedule-ready.js +71 -0
- package/dist/schedule-remote.d.ts +77 -0
- package/dist/schedule-remote.js +167 -0
- package/dist/schedule-runner.d.ts +89 -0
- package/dist/schedule-runner.js +452 -0
- package/dist/schedule.d.ts +149 -0
- package/dist/schedule.js +428 -0
- package/dist/secret-mask.d.ts +29 -0
- package/dist/secret-mask.js +245 -0
- package/dist/self-register.d.ts +45 -0
- package/dist/self-register.js +67 -0
- package/dist/send.d.ts +56 -0
- package/dist/send.js +104 -0
- package/dist/session-diff.d.ts +161 -0
- package/dist/session-diff.js +445 -0
- package/dist/session-registry.d.ts +143 -0
- package/dist/session-registry.js +216 -0
- package/dist/sessions-report.d.ts +59 -0
- package/dist/sessions-report.js +99 -0
- package/dist/shell-audit.d.ts +34 -0
- package/dist/shell-audit.js +47 -0
- package/dist/subagent-transcripts.d.ts +167 -0
- package/dist/subagent-transcripts.js +437 -0
- package/dist/subscriber-trust.d.ts +128 -0
- package/dist/subscriber-trust.js +397 -0
- package/dist/templates.d.ts +105 -0
- package/dist/templates.js +871 -0
- package/dist/tmux-install.d.ts +44 -0
- package/dist/tmux-install.js +65 -0
- package/dist/tmux-liveness.d.ts +45 -0
- package/dist/tmux-liveness.js +71 -0
- package/dist/tmux-pane.d.ts +55 -0
- package/dist/tmux-pane.js +126 -0
- package/dist/tmux-socket.d.ts +20 -0
- package/dist/tmux-socket.js +292 -0
- package/dist/todo-fold.d.ts +31 -0
- package/dist/todo-fold.js +102 -0
- package/dist/transcript-tail.d.ts +312 -0
- package/dist/transcript-tail.js +718 -0
- package/dist/trust-command.d.ts +49 -0
- package/dist/trust-command.js +200 -0
- package/dist/trust-lock.d.ts +26 -0
- package/dist/trust-lock.js +94 -0
- package/dist/trust-pins.d.ts +37 -0
- package/dist/trust-pins.js +82 -0
- package/dist/turn-recorder-wire.d.ts +70 -0
- package/dist/turn-recorder-wire.js +165 -0
- package/dist/turn-recorder.d.ts +190 -0
- package/dist/turn-recorder.js +695 -0
- package/dist/turn-ring.d.ts +93 -0
- package/dist/turn-ring.js +418 -0
- package/dist/turn-watch.d.ts +177 -0
- package/dist/turn-watch.js +630 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +2 -0
- package/dist/uninstall.d.ts +55 -0
- package/dist/uninstall.js +507 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +421 -0
- package/dist/verify.d.ts +47 -0
- package/dist/verify.js +396 -0
- package/dist/worktree.d.ts +144 -0
- package/dist/worktree.js +310 -0
- package/dist/wrapper.d.ts +254 -0
- package/dist/wrapper.js +704 -0
- package/dist/ws-url.d.ts +109 -0
- package/dist/ws-url.js +202 -0
- package/package.json +78 -10
- package/bin/hwip.js +0 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What the phone sees and says about schedules (F8 slices 06 and 07). The decisions live here so
|
|
4
|
+
* `listener.ts` only wires them: this file reads the schedule files and the runner's state, and
|
|
5
|
+
* takes the phone's words at face value for nothing.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.applyPhoneSchedule = exports.livePhoneScheduleDeps = exports.scheduleAnswer = exports.phoneScheduleList = exports.LIST_MAX_BYTES = exports.LIST_PROMPT_CHARS = void 0;
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const schedule_js_1 = require("./schedule.js");
|
|
11
|
+
const schedule_ready_js_1 = require("./schedule-ready.js");
|
|
12
|
+
const session_registry_js_1 = require("./session-registry.js");
|
|
13
|
+
const wrapper_js_1 = require("./wrapper.js");
|
|
14
|
+
/** The longest prompt the list carries; the full text is in the file on this machine. */
|
|
15
|
+
exports.LIST_PROMPT_CHARS = 500;
|
|
16
|
+
/** The sealed list must fit one relay frame (cf. `DIFF_SEALED_MAX_BYTES`), counted in UTF-8 bytes. */
|
|
17
|
+
exports.LIST_MAX_BYTES = 24 * 1024;
|
|
18
|
+
/** Prompt lengths tried in turn until the list fits; the last drops prompts altogether. */
|
|
19
|
+
const PROMPT_CHARS_STEPS = [exports.LIST_PROMPT_CHARS, 200, 80, 0];
|
|
20
|
+
/** `text` cut to `max` UTF-16 units with an ellipsis, minus a surrogate pair the cut would split. */
|
|
21
|
+
const clip = (text, max) => {
|
|
22
|
+
if (text.length <= max)
|
|
23
|
+
return text;
|
|
24
|
+
const cut = text.slice(0, max - 1);
|
|
25
|
+
const last = cut.charCodeAt(cut.length - 1);
|
|
26
|
+
return `${last >= 0xd800 && last <= 0xdbff ? cut.slice(0, -1) : cut}…`;
|
|
27
|
+
};
|
|
28
|
+
const isoOrNull = (ms) => {
|
|
29
|
+
if (ms === null)
|
|
30
|
+
return null;
|
|
31
|
+
const date = new Date(ms);
|
|
32
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
33
|
+
};
|
|
34
|
+
/** The schedules on this machine for the app, oldest first, prompts cut to `promptChars`. Null when the directory could not be listed or holds more than a machine may: an empty list would then be a lie. */
|
|
35
|
+
const phoneScheduleList = (now, promptChars = exports.LIST_PROMPT_CHARS) => {
|
|
36
|
+
const read = (0, schedule_js_1.listSchedules)();
|
|
37
|
+
if (read.incomplete)
|
|
38
|
+
return null;
|
|
39
|
+
const state = (0, schedule_js_1.readScheduleState)().schedules;
|
|
40
|
+
const schedules = read.schedules.map((schedule) => {
|
|
41
|
+
const last = state[schedule.id]?.last;
|
|
42
|
+
const lastAt = last ? isoOrNull(last.at) : null;
|
|
43
|
+
return {
|
|
44
|
+
id: schedule.id,
|
|
45
|
+
source: schedule.source,
|
|
46
|
+
agentKind: schedule.agentKind,
|
|
47
|
+
project: (0, node_path_1.basename)(schedule.cwd),
|
|
48
|
+
session: schedule.session,
|
|
49
|
+
when: schedule.when,
|
|
50
|
+
...(schedule.prompt !== undefined && promptChars > 0 ? { prompt: clip(schedule.prompt, promptChars) } : {}),
|
|
51
|
+
maxRuntimeMin: schedule.maxRuntimeMin,
|
|
52
|
+
nextAt: isoOrNull((0, schedule_js_1.nextOccurrence)(schedule.when, now, Date.parse(schedule.createdAt))),
|
|
53
|
+
...(last && lastAt ? { last: { outcome: last.outcome, at: lastAt } } : {}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
return { schedules, unreadable: read.unreadable };
|
|
57
|
+
};
|
|
58
|
+
exports.phoneScheduleList = phoneScheduleList;
|
|
59
|
+
/**
|
|
60
|
+
* The list as one answer. The text that gets sealed names the request and the time it answers, so a relay
|
|
61
|
+
* cannot hand an older sealed list back as the answer to a newer request. Prompts are cut shorter, step by
|
|
62
|
+
* step, until the answer fits a frame; it is an error only when even the list without prompts does not.
|
|
63
|
+
*/
|
|
64
|
+
const scheduleAnswer = (now, requestId, at) => {
|
|
65
|
+
for (const promptChars of PROMPT_CHARS_STEPS) {
|
|
66
|
+
const listing = (0, exports.phoneScheduleList)(now, promptChars);
|
|
67
|
+
if (!listing)
|
|
68
|
+
return { ok: false, error: 'read_failed' };
|
|
69
|
+
const { schedules, unreadable } = listing;
|
|
70
|
+
const sealed = JSON.stringify({ v: 1, requestId, at, schedules, ...(unreadable > 0 ? { unreadable } : {}) });
|
|
71
|
+
if (Buffer.byteLength(sealed) <= exports.LIST_MAX_BYTES)
|
|
72
|
+
return { ok: true, listing, sealed };
|
|
73
|
+
}
|
|
74
|
+
return { ok: false, error: 'too_large' };
|
|
75
|
+
};
|
|
76
|
+
exports.scheduleAnswer = scheduleAnswer;
|
|
77
|
+
exports.livePhoneScheduleDeps = {
|
|
78
|
+
now: Date.now,
|
|
79
|
+
recall: (session, agentKind) => (agentKind === undefined ? (0, session_registry_js_1.recallSession)(session) : (0, session_registry_js_1.recallRun)(session, agentKind)),
|
|
80
|
+
directoryExists: session_registry_js_1.sessionDirectoryExists,
|
|
81
|
+
canPrompt: schedule_ready_js_1.canPromptKind,
|
|
82
|
+
};
|
|
83
|
+
const refuse = (reason, why) => ({ ok: false, reason, why });
|
|
84
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
85
|
+
const CREATE_KEYS = new Set(['op', 'when', 'prompt', 'agentKind', 'maxRuntimeMin']);
|
|
86
|
+
/** Stands in for the run time while the rest of a request is checked; the shortest `validateSchedule` takes. */
|
|
87
|
+
const SHORTEST_RUN_MIN = 5;
|
|
88
|
+
/** The fields every schedule made from a phone request has, whatever the request says: all but `id` come from this machine's record. */
|
|
89
|
+
const fromRecord = (entry, id, now) => ({
|
|
90
|
+
v: 1,
|
|
91
|
+
id,
|
|
92
|
+
createdAt: new Date(now).toISOString(),
|
|
93
|
+
source: 'phone',
|
|
94
|
+
agentKind: entry.agentKind,
|
|
95
|
+
cwd: entry.cwd,
|
|
96
|
+
label: `sched-${id}`,
|
|
97
|
+
session: `${(0, wrapper_js_1.tmuxSessionName)((0, wrapper_js_1.safeBasename)(entry.cwd))}-sched-${id}`,
|
|
98
|
+
});
|
|
99
|
+
const create = (request, session, deps) => {
|
|
100
|
+
// Whatever else the request carries — a directory, arguments, a label — is refused, not ignored: it was meant to be used.
|
|
101
|
+
const extra = Object.keys(request).find((k) => !CREATE_KEYS.has(k));
|
|
102
|
+
if (extra !== undefined)
|
|
103
|
+
return refuse('schedule_invalid', `a create carries ${extra}`);
|
|
104
|
+
const { when, prompt, agentKind, maxRuntimeMin } = request;
|
|
105
|
+
if (agentKind !== undefined && typeof agentKind !== 'string')
|
|
106
|
+
return refuse('schedule_invalid', 'agentKind is not text');
|
|
107
|
+
// Directory and agent are what this machine recorded for the session, never what the phone says.
|
|
108
|
+
const entry = deps.recall(session, agentKind);
|
|
109
|
+
// The session name is in the clear, so not knowing it can be told as it is. A session known under other agents only
|
|
110
|
+
// is read off sealed text (the agent the request named), so it goes out as a bad request.
|
|
111
|
+
if (!entry) {
|
|
112
|
+
const knownUnderAnother = agentKind !== undefined && deps.recall(session) !== null;
|
|
113
|
+
return knownUnderAnother ? refuse('schedule_invalid', 'that agent never ran under that session name') : refuse('unknown_session', 'no recorded session by that name');
|
|
114
|
+
}
|
|
115
|
+
if (!deps.directoryExists(entry))
|
|
116
|
+
return refuse('unknown_session', 'the recorded directory is gone');
|
|
117
|
+
if (!deps.canPrompt(entry.agentKind))
|
|
118
|
+
return refuse('schedule_invalid', `${entry.agentKind} cannot take a scheduled prompt`);
|
|
119
|
+
const now = deps.now();
|
|
120
|
+
const checked = (0, schedule_js_1.validateSchedule)({ ...fromRecord(entry, 'aaaaaa', now), when, prompt, maxRuntimeMin: maxRuntimeMin ?? SHORTEST_RUN_MIN });
|
|
121
|
+
if (!checked.ok)
|
|
122
|
+
return refuse('schedule_invalid', checked.error);
|
|
123
|
+
const runtime = typeof maxRuntimeMin === 'number' ? maxRuntimeMin : (0, schedule_js_1.defaultMaxRuntimeMin)(checked.value.when);
|
|
124
|
+
const added = (0, schedule_js_1.addSchedule)((id) => ({ ...checked.value, ...fromRecord(entry, id, now), maxRuntimeMin: runtime }));
|
|
125
|
+
if (added.ok)
|
|
126
|
+
return { ok: true };
|
|
127
|
+
return refuse(added.failure === 'limit' ? 'schedule_limit' : added.failure === 'io' ? 'schedule_failed' : 'schedule_invalid', added.error);
|
|
128
|
+
};
|
|
129
|
+
const remove = (request, session) => {
|
|
130
|
+
if (Object.keys(request).some((k) => k !== 'op' && k !== 'id'))
|
|
131
|
+
return refuse('schedule_invalid', 'a remove carries only an id');
|
|
132
|
+
const { id } = request;
|
|
133
|
+
if (typeof id !== 'string')
|
|
134
|
+
return refuse('schedule_invalid', 'a remove needs an id');
|
|
135
|
+
// The push names the schedule's own session, so a request for one schedule cannot remove another.
|
|
136
|
+
const read = (0, schedule_js_1.listSchedules)();
|
|
137
|
+
// A list that is not all of them cannot say a schedule is not there: that is this machine's trouble, not the request's.
|
|
138
|
+
if (read.incomplete)
|
|
139
|
+
return refuse('schedule_failed', 'the schedules could not be listed');
|
|
140
|
+
const target = read.schedules.find((schedule) => schedule.id === id);
|
|
141
|
+
if (!target || target.session !== session)
|
|
142
|
+
return refuse('schedule_invalid', 'no such schedule for that session');
|
|
143
|
+
// Gone by the time it is removed (the person, or `hwip schedule rm`, was first) is the state asked for.
|
|
144
|
+
const removed = (0, schedule_js_1.removeSchedule)(id);
|
|
145
|
+
return removed === 'invalid' ? refuse('schedule_invalid', 'not a schedule id') : { ok: true };
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Carry out one change the phone asked for: `{ op: 'create', when, prompt, agentKind?, maxRuntimeMin? }` for the
|
|
149
|
+
* recorded session `session`, or `{ op: 'remove', id }` for the schedule whose session that is. Nothing here
|
|
150
|
+
* types into a session. `request` is whatever the phone sealed, so every field is checked as it is used.
|
|
151
|
+
*/
|
|
152
|
+
const applyPhoneSchedule = (request, session, deps = exports.livePhoneScheduleDeps) => {
|
|
153
|
+
if (!isRecord(request))
|
|
154
|
+
return refuse('schedule_invalid', 'the request is not an object');
|
|
155
|
+
try {
|
|
156
|
+
if (request.op === 'create')
|
|
157
|
+
return create(request, session, deps);
|
|
158
|
+
if (request.op === 'remove')
|
|
159
|
+
return remove(request, session);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
// A file that would not read or write is this machine's trouble, not a mistake in the request.
|
|
163
|
+
return refuse('schedule_failed', err instanceof Error ? err.message : String(err));
|
|
164
|
+
}
|
|
165
|
+
return refuse('schedule_invalid', 'op is neither create nor remove');
|
|
166
|
+
};
|
|
167
|
+
exports.applyPhoneSchedule = applyPhoneSchedule;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts scheduled sessions on time (F8). The listener calls `tick(now)` on a timer; everything the tick touches
|
|
3
|
+
* outside this file (the clock, tmux, `hwip <agent> --detach`, the state file) comes in through `deps`, so a
|
|
4
|
+
* fake clock and fakes drive every case in the tests.
|
|
5
|
+
*
|
|
6
|
+
* What a tick decides, per schedule: how many due times passed since its cursor, whether the newest is still
|
|
7
|
+
* worth running (`GRACE_MS`; older ones are `missed` and never run late), and whether the session may start.
|
|
8
|
+
* The cursor is written before anything is launched, so a listener killed in between skips that run instead of
|
|
9
|
+
* starting it twice.
|
|
10
|
+
*/
|
|
11
|
+
import { type Schedule, type ScheduleList, type ScheduleStateFile } from './schedule.js';
|
|
12
|
+
/** How often the listener ticks. */
|
|
13
|
+
export declare const TICK_MS = 30000;
|
|
14
|
+
/** A due time older than this when the tick sees it (the machine slept, the listener was down) is missed, not run. */
|
|
15
|
+
export declare const GRACE_MS = 120000;
|
|
16
|
+
/** After a launch exits 0, how long before the session has to be there. */
|
|
17
|
+
export declare const START_CHECK_MS = 2000;
|
|
18
|
+
/** A launch that has not exited by then is given up on. */
|
|
19
|
+
export declare const LAUNCH_TIMEOUT_MS = 30000;
|
|
20
|
+
/** The same for `--wt`: the launch first makes the worktree (`git worktree add`, and whatever its hooks do), which a big repo does not finish in 30 s. */
|
|
21
|
+
export declare const LAUNCH_WT_TIMEOUT_MS = 120000;
|
|
22
|
+
/** After a push that did not go out, how long until the next try; doubled on every failure up to the cap. */
|
|
23
|
+
export declare const PUSH_RETRY_MS = 30000;
|
|
24
|
+
export declare const PUSH_RETRY_MAX_MS: number;
|
|
25
|
+
/** How often the listener polls sessions that are waiting for their prompt (only while one is). */
|
|
26
|
+
export declare const PROMPT_POLL_MS = 5000;
|
|
27
|
+
/**
|
|
28
|
+
* How long after the start a session may take to become ready for its prompt; later, the prompt is given up on. Ready
|
|
29
|
+
* takes two polls in a row on the same idle screen (`schedule-ready.ts`), so the earliest prompt goes one poll after
|
|
30
|
+
* the session is idle, and a retry after a failed inject two polls later.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PROMPT_WAIT_MS = 90000;
|
|
33
|
+
/** Scheduled sessions running at once. */
|
|
34
|
+
export declare const MAX_RUNNING = 3;
|
|
35
|
+
export type LaunchResult = {
|
|
36
|
+
ok: true;
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
error: string;
|
|
40
|
+
};
|
|
41
|
+
/** What became of a push: `retry` is worth another try later; `rejected` is the server's no (4xx, a quota among them), which trying again cannot change. */
|
|
42
|
+
export type NotifyOutcome = 'sent' | 'retry' | 'rejected';
|
|
43
|
+
/**
|
|
44
|
+
* What a failed push means for trying again. Only the server saying no to this very push is final: a 4xx (a daily
|
|
45
|
+
* limit among them). The client's own timeout (reported as 408), a proxy asking for credentials (407) and a page that
|
|
46
|
+
* is not the API's (a captive portal) are the network failing, and the push is worth sending again when it is back.
|
|
47
|
+
*/
|
|
48
|
+
export declare const notifyFailureOutcome: (e: unknown) => Exclude<NotifyOutcome, "sent">;
|
|
49
|
+
export interface ScheduleRunnerDeps {
|
|
50
|
+
listSchedules: () => ScheduleList;
|
|
51
|
+
readState: () => ScheduleStateFile;
|
|
52
|
+
/** Replaces the state file atomically; false when it could not. */
|
|
53
|
+
writeState: (state: ScheduleStateFile) => boolean;
|
|
54
|
+
sessionExists: (session: string) => boolean;
|
|
55
|
+
/** The session is known to be over. Not the same as `!sessionExists`: when tmux cannot be asked, nobody knows. */
|
|
56
|
+
sessionEnded: (session: string) => boolean;
|
|
57
|
+
directoryExists: (cwd: string) => boolean;
|
|
58
|
+
/** The `hwip` subcommand that starts an agent kind; undefined for a kind the table does not have. */
|
|
59
|
+
subcommandFor: (kind: string) => string | undefined;
|
|
60
|
+
/** Runs `hwip <args>` in `cwd` and resolves when it has exited, or gives up after `timeoutMs`. */
|
|
61
|
+
launch: (args: string[], cwd: string, timeoutMs: number) => Promise<LaunchResult>;
|
|
62
|
+
sleep: (ms: number) => Promise<void>;
|
|
63
|
+
log: (line: string) => void;
|
|
64
|
+
/** Whether a prompt can ever be typed into this kind of agent: its detection rules can say `idle` (see `schedule-ready.ts`). */
|
|
65
|
+
canPrompt: (agentKind: string) => boolean;
|
|
66
|
+
/** The agent in `session` is up and its rules say it is idle: the screen is not a dialog or a task in progress. */
|
|
67
|
+
ready: (session: string, agentKind: string) => boolean;
|
|
68
|
+
/** Types `prompt` into the session and submits it; false when it was not delivered (rate limit, no pane, …). */
|
|
69
|
+
inject: (session: string, prompt: string) => boolean;
|
|
70
|
+
/** Asks the session to end the way a phone's exit request does (the agent's quit command, then signals). It never kills the tmux session. */
|
|
71
|
+
endSession: (session: string) => void;
|
|
72
|
+
/** Tells the person (title, body). */
|
|
73
|
+
notify: (title: string, body: string) => Promise<NotifyOutcome>;
|
|
74
|
+
/** The wall clock (default `Date.now`); a launch takes seconds, and the schedules after it are judged by the time it ends. */
|
|
75
|
+
clock?: () => number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The arguments of `hwip` that start `schedule`'s session. One array element per argument, never a string for a shell:
|
|
79
|
+
* `agentArgs` are passed on as typed, whatever they contain.
|
|
80
|
+
*/
|
|
81
|
+
export declare const launchArgs: (schedule: Schedule, subcommand: string) => string[];
|
|
82
|
+
export interface ScheduleRunner {
|
|
83
|
+
/** Starts what is due, then looks after the sessions already running. Every `TICK_MS`. */
|
|
84
|
+
tick: (now: number) => Promise<void>;
|
|
85
|
+
/** Looks after the running sessions only (their prompt, their runtime cap). Every `PROMPT_POLL_MS`; does nothing while no prompt is waiting. */
|
|
86
|
+
poll: (now: number) => Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
export declare const createScheduleRunner: (deps: ScheduleRunnerDeps) => ScheduleRunner;
|
|
89
|
+
//# sourceMappingURL=schedule-runner.d.ts.map
|