@codizelabs/agent-rewake 0.4.0 → 0.5.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/README.md
CHANGED
|
@@ -37,6 +37,8 @@ Coding agents stop at their usage limit: Claude's 5-hour session limit, Codex's
|
|
|
37
37
|
- **Automatic, if you want.** Choose *from now on* when Rewake asks (Claude Code), turn on auto-resume in Zed's **Rewake** menu, or run `agent-rewake continue --always` (Copilot CLI, Gemini CLI, Grok Build, Qwen Code, OpenCode, Antigravity CLI), and Rewake resumes without asking. It asks first when a limit resets more than a day away, never resumes credit or billing limits (and says so), and never approves permission prompts for you.
|
|
38
38
|
- **Sends messages later.** `/rewake in 1h Run the tests` schedules a message into the session (Zed and Claude Code; in Zed also on a repeat, such as every weekday at 9).
|
|
39
39
|
- **Keeps the computer awake, if it can.** On macOS, and on Linux (with systemd) and Windows, while a resume is due within six hours, Rewake stops the computer from idling to sleep (while plugged in, by default). Only macOS has been tried by hand. Closing the lid still puts it to sleep. `agent-rewake doctor` tells you if your computer's own sleep settings would let it sleep, and [which settings to change](https://codizelabs.github.io/agent-rewake/docs/#keep-your-computer-awake).
|
|
40
|
+
- **Warns you before a usage limit (Claude Code).** The plugin tells you when a usage window passes 80% and 95%, and when your pace over the last hour would reach the limit within about 45 minutes (as a range, "in about 20 to 40 minutes"). It uses only the usage figures Claude Code already gives it: no network, no sign-in. It only tells you; at 80% it mentions `/compact` (which uses some quota itself), at 95% it says it is a good moment to wrap up. It never types or compacts for you. Turn it off with `agent-rewake settings usage-warnings off`. Not built for Codex and the other agents.
|
|
41
|
+
- **Tells you when the project changed while it waited.** Outside Zed, if the session's folder is a git folder and its latest commit or tracked files differ by the time Rewake sends, it tells you, and sends the message as it was. You can instead have it add one fixed line asking the agent to re-read files (`agent-rewake settings workspace-changed note`), or send nothing and ask you first (`hold`). It reads only the commit id and the names-and-status summary from `git status --porcelain --untracked-files=no`, never file contents, and only on your computer.
|
|
40
42
|
- **Same agent, same session.** No new agent to pick: Rewake works inside or in front of the agents you already use, so your sessions and threads keep going where they stopped.
|
|
41
43
|
|
|
42
44
|
## Where you work
|
|
@@ -88,7 +90,7 @@ npx @codizelabs/agent-rewake doctor
|
|
|
88
90
|
|
|
89
91
|
### Updating
|
|
90
92
|
|
|
91
|
-
From any version: run `npx @codizelabs/agent-rewake@latest install`. Previews use the new version from their next session; in Zed, quit Zed completely and open it again. Your sessions, scheduled messages and settings stay. Details, and any version-specific steps (none so far), are in the [docs](https://codizelabs.github.io/agent-rewake/docs/#update). To see which version you run: `agent-rewake --version` (in Zed also the **Rewake** menu).
|
|
93
|
+
From any version: run `npx @codizelabs/agent-rewake@latest install`. Previews use the new version from their next session; in Zed, choose **Reload Agent** in the Agent Panel's menu in each open thread (Zed 1.18 or newer), or quit Zed completely and open it again. Your sessions, scheduled messages and settings stay. Details, and any version-specific steps (none so far), are in the [docs](https://codizelabs.github.io/agent-rewake/docs/#update). To see which version you run: `agent-rewake --version` (in Zed also the **Rewake** menu).
|
|
92
94
|
|
|
93
95
|
### Requirements
|
|
94
96
|
|
|
@@ -120,6 +122,7 @@ In Zed, the **Rewake** menu under the message box does everything; elsewhere, ty
|
|
|
120
122
|
| See or change messages | **Rewake → Schedules**, **Change a scheduled message…** | `/rewake list` and `/rewake cancel` (Claude Code, Gemini CLI, Grok Build, Qwen Code, Cursor; in Codex without the slash). `/rewake cancel N` deletes one message: Claude Code only |
|
|
121
123
|
| See what's planned | The Zed task **Agent Rewake: schedules** | `/rewake list` in the session; `agent-rewake schedules` lists every one, marking the resumes outside Zed; `agent-rewake doctor` shows the next planned resume of a closed session |
|
|
122
124
|
| Cancel a planned resume | **Rewake → Change a scheduled message…**, or the schedules page | `/rewake cancel` in the session where it's typed, or `agent-rewake continue --cancel` to pick one or all; `agent-rewake continue --cancel <id>` cancels one by the id in square brackets in `agent-rewake schedules` |
|
|
125
|
+
| Resume now, without waiting for the reset | **Rewake → Change a scheduled message…** | `agent-rewake continue --now` picks a planned resume outside Zed and asks first; `agent-rewake continue --now <id>` names one (`--yes` without a terminal). It makes the same checks as at the reset; if the limit is still on, the session is open or changed, you typed after the limit, the agent continued, or the message can't reach the agent (signed out, session deleted or archived, folder gone), nothing is sent and the resume stays planned for its time; Ctrl+C during a run stops it and cancels the resume |
|
|
123
126
|
| Change settings | **Rewake → Settings…** | `agent-rewake settings` lists them with what each does; `agent-rewake settings change` lets you pick one, or `agent-rewake settings <name> <value>` changes it (for example `agent-rewake settings clock 24h`) |
|
|
124
127
|
|
|
125
128
|
**One command everywhere:** `/rewake` is the same in Zed, Claude Code, Gemini CLI, Grok Build, Qwen Code and Cursor's own agent (in Codex, type `rewake` without the slash). At a usage limit it continues after the reset; `/rewake <time>` continues then, `/rewake cancel` cancels it, and `/rewake help` lists what else works where you are. Rewake answers it itself, without using the model. `npx`/`agent-rewake` on the command line is only for installing, uninstalling and the two agents with no typed command (GitHub Copilot CLI, Antigravity CLI).
|
|
@@ -135,7 +138,7 @@ In Zed, Rewake works the same way in front of every external agent: **Claude Age
|
|
|
135
138
|
The whole picture, with what Rewake never does and how to undo it, is on the [privacy and security page](https://codizelabs.github.io/agent-rewake/docs/privacy-and-security/). In short:
|
|
136
139
|
|
|
137
140
|
- Runs locally. Rewake never sees your credentials: sign-in goes through each agent's own flow. Besides fetching an agent's own program (a download, or `npm install`) when Zed hasn't already done it, its only network use is error reports, which are off unless you turn them on (`agent-rewake errors on`; see the docs' [Error reports](https://codizelabs.github.io/agent-rewake/docs/#error-reports)).
|
|
138
|
-
- Sends only what you scheduled, approved, or turned on.
|
|
141
|
+
- Sends only what you scheduled, approved, or turned on. (One fixed line, saying the project changed while it waited, is added to a resume only if you choose `note`.)
|
|
139
142
|
- Stores scheduled messages and per-thread settings in a private folder on your machine; logs hold metadata only (events, codes and program names), never message text.
|
|
140
143
|
- Keeps your message off the command line, with exceptions. When Rewake continues a closed Copilot CLI, Gemini CLI, Qwen Code or OpenCode session, it hands the message to the agent on its standard input; for Grok Build, in a file only you can read. Three cases put it on a command line, where another program on your computer can read it from the process list for as long as the command runs: **Codex** (`codex queue --message <text>`), **Antigravity CLI** (`-p <text>`), and **Gemini CLI** or **Qwen Code** when you've turned its sandbox on (they then start themselves again with the text as an argument; for Qwen Code that is read from its source, not tried). Claude Code, Cursor and Zed don't use a command line for it.
|
|
141
144
|
|
|
@@ -149,7 +152,7 @@ This takes Rewake out of every place it's set up in, after showing every change
|
|
|
149
152
|
|
|
150
153
|
## Status
|
|
151
154
|
|
|
152
|
-
Version 0.
|
|
155
|
+
Version 0.5. Rewake's automated tests run on macOS, Linux and Windows with Node.js 22 and 24, and the author uses it daily in Zed. The previews outside Zed are new: the docs say [what's been tried for each](https://codizelabs.github.io/agent-rewake/docs/#outside-zed-previews), and what hasn't. What changed in each release is in the [changelog](https://codizelabs.github.io/agent-rewake/changelog/). Feedback and bug reports are very welcome: [open an issue](https://github.com/codizelabs/agent-rewake/issues/new/choose). If Rewake missed a usage limit, [say how](https://codizelabs.github.io/agent-rewake/docs/#report-a-limit-rewake-missed).
|
|
153
156
|
|
|
154
157
|
## Contributing
|
|
155
158
|
|
package/dist/agent-rewake.js
CHANGED
|
@@ -2366,6 +2366,68 @@ var init_options_bridge = __esm({
|
|
|
2366
2366
|
}
|
|
2367
2367
|
});
|
|
2368
2368
|
|
|
2369
|
+
// src/util/workspace.ts
|
|
2370
|
+
import { execFileSync } from "node:child_process";
|
|
2371
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2372
|
+
function gitEnv() {
|
|
2373
|
+
const { GIT_DIR: _d, GIT_WORK_TREE: _w, GIT_INDEX_FILE: _i, ...rest } = process.env;
|
|
2374
|
+
return rest;
|
|
2375
|
+
}
|
|
2376
|
+
function git(cwd, args, deadline) {
|
|
2377
|
+
const left = deadline - Date.now();
|
|
2378
|
+
if (left <= 0) return void 0;
|
|
2379
|
+
try {
|
|
2380
|
+
return execFileSync(
|
|
2381
|
+
"git",
|
|
2382
|
+
["--no-optional-locks", "-c", "core.fsmonitor=false", "-C", cwd, ...args],
|
|
2383
|
+
{
|
|
2384
|
+
encoding: "utf8",
|
|
2385
|
+
timeout: left,
|
|
2386
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
2387
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2388
|
+
windowsHide: true,
|
|
2389
|
+
env: gitEnv()
|
|
2390
|
+
}
|
|
2391
|
+
);
|
|
2392
|
+
} catch {
|
|
2393
|
+
return void 0;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
function fingerprintWorkspace(cwd, budgetMs = FIRE_BUDGET_MS) {
|
|
2397
|
+
if (!cwd) return void 0;
|
|
2398
|
+
const deadline = Date.now() + budgetMs;
|
|
2399
|
+
const commit = git(cwd, ["rev-parse", "HEAD"], deadline)?.trim();
|
|
2400
|
+
if (!commit || !/^[0-9a-f]{40,64}$/.test(commit)) return void 0;
|
|
2401
|
+
const status = git(cwd, ["status", "--porcelain", "--untracked-files=no"], deadline);
|
|
2402
|
+
if (status === void 0) return void 0;
|
|
2403
|
+
const lines = status.split("\n").filter(Boolean).sort();
|
|
2404
|
+
return {
|
|
2405
|
+
commit,
|
|
2406
|
+
hash: createHash2("sha256").update(lines.join("\n")).digest("hex"),
|
|
2407
|
+
dirty: lines.length > 0
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
function workspaceChange(before, after) {
|
|
2411
|
+
const commit = before.commit !== after.commit;
|
|
2412
|
+
const changes = before.hash !== after.hash;
|
|
2413
|
+
return commit || changes ? { commit, changes } : void 0;
|
|
2414
|
+
}
|
|
2415
|
+
function validFingerprint(w) {
|
|
2416
|
+
if (typeof w !== "object" || w === null) return false;
|
|
2417
|
+
const x = w;
|
|
2418
|
+
return typeof x.commit === "string" && x.commit.length <= 64 && typeof x.hash === "string" && x.hash.length <= 64 && typeof x.dirty === "boolean";
|
|
2419
|
+
}
|
|
2420
|
+
var ARM_BUDGET_MS, FIRE_BUDGET_MS, MAX_OUTPUT_BYTES, WORKSPACE_NOTE;
|
|
2421
|
+
var init_workspace = __esm({
|
|
2422
|
+
"src/util/workspace.ts"() {
|
|
2423
|
+
"use strict";
|
|
2424
|
+
ARM_BUDGET_MS = 1e3;
|
|
2425
|
+
FIRE_BUDGET_MS = 2e3;
|
|
2426
|
+
MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
2427
|
+
WORKSPACE_NOTE = "Note from Rewake: the project changed while this session waited for the usage limit to reset. Re-read any files before editing them.";
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2369
2431
|
// src/core/store.ts
|
|
2370
2432
|
import { randomUUID } from "node:crypto";
|
|
2371
2433
|
import {
|
|
@@ -2399,7 +2461,7 @@ function validFollowUps(t) {
|
|
|
2399
2461
|
function validateSchedule(value) {
|
|
2400
2462
|
if (value === null || typeof value !== "object") return void 0;
|
|
2401
2463
|
const s = value;
|
|
2402
|
-
const ok = s.schemaVersion === 1 && typeof s.scheduleId === "string" && /^[0-9a-f-]{36}$/.test(s.scheduleId) && typeof s.sessionId === "string" && s.sessionId.length > 0 && typeof s.cwd === "string" && (s.kind === "user" || s.kind === "limit_resume" || s.kind === "auto_limit_resume") && typeof s.text === "string" && s.text.trim().length > 0 && Buffer.byteLength(s.text) <= MAX_TEXT_BYTES && typeof s.dueAt === "number" && Number.isFinite(s.dueAt) && typeof s.status === "string" && STATUSES.has(s.status) && Array.isArray(s.attempts) && typeof s.createdAt === "number" && typeof s.updatedAt === "number" && (s.repeat === void 0 || validRepeat(s.repeat)) && (s.followUps === void 0 || validFollowUps(s.followUps)) && (s.host === void 0 || typeof s.host === "string" && KNOWN_HOSTS.has(s.host)) && (s.sessionRef === void 0 || validSessionRef(s.sessionRef)) && (s.rearms === void 0 || typeof s.rearms === "number" && Number.isInteger(s.rearms) && s.rearms >= 0) && (s.earlyRearms === void 0 || typeof s.earlyRearms === "number" && Number.isInteger(s.earlyRearms) && s.earlyRearms >= 0);
|
|
2464
|
+
const ok = s.schemaVersion === 1 && typeof s.scheduleId === "string" && /^[0-9a-f-]{36}$/.test(s.scheduleId) && typeof s.sessionId === "string" && s.sessionId.length > 0 && typeof s.cwd === "string" && (s.kind === "user" || s.kind === "limit_resume" || s.kind === "auto_limit_resume") && typeof s.text === "string" && s.text.trim().length > 0 && Buffer.byteLength(s.text) <= MAX_TEXT_BYTES && typeof s.dueAt === "number" && Number.isFinite(s.dueAt) && typeof s.status === "string" && STATUSES.has(s.status) && Array.isArray(s.attempts) && typeof s.createdAt === "number" && typeof s.updatedAt === "number" && (s.repeat === void 0 || validRepeat(s.repeat)) && (s.followUps === void 0 || validFollowUps(s.followUps)) && (s.host === void 0 || typeof s.host === "string" && KNOWN_HOSTS.has(s.host)) && (s.sessionRef === void 0 || validSessionRef(s.sessionRef)) && (s.rearms === void 0 || typeof s.rearms === "number" && Number.isInteger(s.rearms) && s.rearms >= 0) && (s.earlyRearms === void 0 || typeof s.earlyRearms === "number" && Number.isInteger(s.earlyRearms) && s.earlyRearms >= 0) && (s.workspace === void 0 || validFingerprint(s.workspace));
|
|
2403
2465
|
return ok ? value : void 0;
|
|
2404
2466
|
}
|
|
2405
2467
|
function writeFileAtomic(dir, name, data) {
|
|
@@ -2420,6 +2482,7 @@ var init_store = __esm({
|
|
|
2420
2482
|
"use strict";
|
|
2421
2483
|
init_fs();
|
|
2422
2484
|
init_paths();
|
|
2485
|
+
init_workspace();
|
|
2423
2486
|
init_cron();
|
|
2424
2487
|
init_lock();
|
|
2425
2488
|
PAUSE = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -2719,6 +2782,8 @@ function loadSettings(stateDir2) {
|
|
|
2719
2782
|
const autoWhenPromptsSkipped = raw.autoWhenPromptsSkipped !== false;
|
|
2720
2783
|
const keepAwake = KEEP_AWAKE_VALUES.find((v) => v === raw.keepAwake) ?? DEFAULT_SETTINGS.keepAwake;
|
|
2721
2784
|
const errorReports = raw.errorReports === "on" ? "on" : DEFAULT_SETTINGS.errorReports;
|
|
2785
|
+
const workspaceChanged = WORKSPACE_CHANGED_VALUES.find((v) => v === raw.workspaceChanged) ?? DEFAULT_SETTINGS.workspaceChanged;
|
|
2786
|
+
const usageWarnings = raw.usageWarnings === "off" ? "off" : DEFAULT_SETTINGS.usageWarnings;
|
|
2722
2787
|
const errorReportsAsked = raw.errorReportsAsked === true;
|
|
2723
2788
|
return {
|
|
2724
2789
|
clock: clock2,
|
|
@@ -2726,6 +2791,8 @@ function loadSettings(stateDir2) {
|
|
|
2726
2791
|
autoWhenPromptsSkipped,
|
|
2727
2792
|
keepAwake,
|
|
2728
2793
|
errorReports,
|
|
2794
|
+
workspaceChanged,
|
|
2795
|
+
usageWarnings,
|
|
2729
2796
|
...resumePrompt && { resumePrompt },
|
|
2730
2797
|
...errorReportsAsked && { errorReportsAsked }
|
|
2731
2798
|
};
|
|
@@ -2746,7 +2813,7 @@ function applySettings(stateDir2) {
|
|
|
2746
2813
|
setClock(s.clock);
|
|
2747
2814
|
return s;
|
|
2748
2815
|
}
|
|
2749
|
-
var CLOCKS, NEW_THREADS_VALUES, KEEP_AWAKE_VALUES, MAX_RESUME_PROMPT, KEEP_AWAKE_SAVED, DEFAULT_SETTINGS;
|
|
2816
|
+
var CLOCKS, NEW_THREADS_VALUES, KEEP_AWAKE_VALUES, WORKSPACE_CHANGED_VALUES, USAGE_WARNINGS_VALUES, MAX_RESUME_PROMPT, KEEP_AWAKE_SAVED, DEFAULT_SETTINGS;
|
|
2750
2817
|
var init_settings = __esm({
|
|
2751
2818
|
"src/core/settings.ts"() {
|
|
2752
2819
|
"use strict";
|
|
@@ -2756,6 +2823,13 @@ var init_settings = __esm({
|
|
|
2756
2823
|
CLOCKS = ["12h", "24h"];
|
|
2757
2824
|
NEW_THREADS_VALUES = ["ask", "on", "off"];
|
|
2758
2825
|
KEEP_AWAKE_VALUES = ["plugged-in", "always", "never"];
|
|
2826
|
+
WORKSPACE_CHANGED_VALUES = [
|
|
2827
|
+
"tell",
|
|
2828
|
+
"note",
|
|
2829
|
+
"hold",
|
|
2830
|
+
"off"
|
|
2831
|
+
];
|
|
2832
|
+
USAGE_WARNINGS_VALUES = ["on", "off"];
|
|
2759
2833
|
MAX_RESUME_PROMPT = 16384;
|
|
2760
2834
|
KEEP_AWAKE_SAVED = {
|
|
2761
2835
|
"plugged-in": "Rewake keeps this computer awake for resumes and scheduled messages while it's plugged in.",
|
|
@@ -2767,13 +2841,15 @@ var init_settings = __esm({
|
|
|
2767
2841
|
newThreads: "ask",
|
|
2768
2842
|
autoWhenPromptsSkipped: true,
|
|
2769
2843
|
keepAwake: "plugged-in",
|
|
2770
|
-
errorReports: "off"
|
|
2844
|
+
errorReports: "off",
|
|
2845
|
+
workspaceChanged: "tell",
|
|
2846
|
+
usageWarnings: "on"
|
|
2771
2847
|
};
|
|
2772
2848
|
}
|
|
2773
2849
|
});
|
|
2774
2850
|
|
|
2775
2851
|
// src/core/threads.ts
|
|
2776
|
-
import { createHash as
|
|
2852
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2777
2853
|
import { readdirSync as readdirSync4, readFileSync as readFileSync7, rmSync as rmSync5 } from "node:fs";
|
|
2778
2854
|
import { join as join8 } from "node:path";
|
|
2779
2855
|
var DEFAULT_RESUME_PROMPT, ThreadStore;
|
|
@@ -2789,7 +2865,7 @@ var init_threads = __esm({
|
|
|
2789
2865
|
this.dir = join8(stateDir2, "threads");
|
|
2790
2866
|
}
|
|
2791
2867
|
file(sessionId) {
|
|
2792
|
-
return `${
|
|
2868
|
+
return `${createHash3("sha256").update(sessionId).digest("hex").slice(0, 32)}.json`;
|
|
2793
2869
|
}
|
|
2794
2870
|
get(sessionId) {
|
|
2795
2871
|
try {
|
|
@@ -4479,20 +4555,20 @@ function killTree(child, p = osPlatform2(), signal = "SIGTERM", run3 = taskkill)
|
|
|
4479
4555
|
}
|
|
4480
4556
|
child.kill(signal);
|
|
4481
4557
|
}
|
|
4482
|
-
function killPidTree(pid, p = osPlatform2(), run3 = taskkill) {
|
|
4558
|
+
function killPidTree(pid, p = osPlatform2(), run3 = taskkill, signal = "SIGTERM") {
|
|
4483
4559
|
killTree(
|
|
4484
4560
|
{
|
|
4485
4561
|
pid,
|
|
4486
|
-
kill: (
|
|
4562
|
+
kill: (signal2) => {
|
|
4487
4563
|
try {
|
|
4488
|
-
return process.kill(pid,
|
|
4564
|
+
return process.kill(pid, signal2);
|
|
4489
4565
|
} catch {
|
|
4490
4566
|
return false;
|
|
4491
4567
|
}
|
|
4492
4568
|
}
|
|
4493
4569
|
},
|
|
4494
4570
|
p,
|
|
4495
|
-
|
|
4571
|
+
signal,
|
|
4496
4572
|
run3
|
|
4497
4573
|
);
|
|
4498
4574
|
}
|
|
@@ -4892,7 +4968,7 @@ var init_version = __esm({
|
|
|
4892
4968
|
return {};
|
|
4893
4969
|
}
|
|
4894
4970
|
})();
|
|
4895
|
-
VERSION2 = pkg.version ?? (true ? "0.
|
|
4971
|
+
VERSION2 = pkg.version ?? (true ? "0.5.0" : "0.0.0");
|
|
4896
4972
|
PACKAGE_NAME = pkg.name ?? "@codizelabs/agent-rewake";
|
|
4897
4973
|
FROM_SOURCE = false;
|
|
4898
4974
|
}
|
|
@@ -5498,6 +5574,11 @@ function armedText(host, cwd, at, now) {
|
|
|
5498
5574
|
const typing = host.keepOpen ? `Typing in the ${host.noun ?? "session"} before then cancels this continue.` : `Reopening that ${host.noun ?? "session"} and typing in it before then cancels this resume.`;
|
|
5499
5575
|
return `Rewake will continue ${placeOf(host, cwd)} ${formatAt(at, now)}. Keep this computer on and awake until then.${keep} ${typing} To cancel all planned resumes: ${rewake("continue --cancel")}`;
|
|
5500
5576
|
}
|
|
5577
|
+
function withWorkspace(store, s, now) {
|
|
5578
|
+
const workspace = fingerprintWorkspace(s.cwd, ARM_BUDGET_MS);
|
|
5579
|
+
if (!workspace) return s;
|
|
5580
|
+
return store.update(s.scheduleId, (x) => ({ ...x, workspace }), now) ?? s;
|
|
5581
|
+
}
|
|
5501
5582
|
function armClosed(host, r, at, d) {
|
|
5502
5583
|
const store = new ScheduleStore(d.stateDir);
|
|
5503
5584
|
const settings = loadSettings(d.stateDir);
|
|
@@ -5517,7 +5598,7 @@ function armClosed(host, r, at, d) {
|
|
|
5517
5598
|
};
|
|
5518
5599
|
store.put(resume);
|
|
5519
5600
|
d.arm(resume.scheduleId, at + (host.fallbackDelayMs ?? 0));
|
|
5520
|
-
return resume;
|
|
5601
|
+
return withWorkspace(store, resume, d.now);
|
|
5521
5602
|
}
|
|
5522
5603
|
function onSessionStart(host, sessionId, cwd, d, program) {
|
|
5523
5604
|
if (d.env[FIRE_ENV]) return;
|
|
@@ -5692,6 +5773,7 @@ var init_closed = __esm({
|
|
|
5692
5773
|
init_command2();
|
|
5693
5774
|
init_proc();
|
|
5694
5775
|
init_secrets();
|
|
5776
|
+
init_workspace();
|
|
5695
5777
|
init_sessions();
|
|
5696
5778
|
FIRE_ENV = "AGENT_REWAKE_FIRE";
|
|
5697
5779
|
COMMON_VARS = ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"];
|
|
@@ -8205,7 +8287,7 @@ var init_install6 = __esm({
|
|
|
8205
8287
|
|
|
8206
8288
|
// src/wrap.ts
|
|
8207
8289
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
8208
|
-
import { createHash as
|
|
8290
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
8209
8291
|
import {
|
|
8210
8292
|
chmodSync as chmodSync2,
|
|
8211
8293
|
existsSync as existsSync14,
|
|
@@ -8359,7 +8441,7 @@ function ensureNpxAgent(id, npx, stateDir2, env, logFile) {
|
|
|
8359
8441
|
);
|
|
8360
8442
|
}
|
|
8361
8443
|
function zedBinaryDir(id, version, target, env) {
|
|
8362
|
-
const sha = (s) =>
|
|
8444
|
+
const sha = (s) => createHash4("sha256").update(s).digest("hex").slice(0, 16);
|
|
8363
8445
|
const archiveKey = target.sha256 ? `${target.archive}\0sha256:${target.sha256.toLowerCase()}` : target.archive;
|
|
8364
8446
|
return join24(
|
|
8365
8447
|
zedDataDir(env),
|
|
@@ -8430,7 +8512,7 @@ async function downloadBinaryAgent(id, version, target, stateDir2, fetchImpl = f
|
|
|
8430
8512
|
if (!res.ok) throw new Error(`download failed (HTTP ${res.status})`);
|
|
8431
8513
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
8432
8514
|
if (target.sha256) {
|
|
8433
|
-
const got =
|
|
8515
|
+
const got = createHash4("sha256").update(bytes).digest("hex");
|
|
8434
8516
|
if (got !== target.sha256.toLowerCase())
|
|
8435
8517
|
throw new Error("the download doesn't match the registry's checksum");
|
|
8436
8518
|
}
|
|
@@ -9803,6 +9885,9 @@ function quitZed(p = platform3()) {
|
|
|
9803
9885
|
if (p === "win32") return "close every Zed window so Zed exits";
|
|
9804
9886
|
return "quit Zed completely (Ctrl+Q)";
|
|
9805
9887
|
}
|
|
9888
|
+
function reloadAgent(p = platform3()) {
|
|
9889
|
+
return `choose "Reload Agent" in the Agent Panel's menu in each open thread (Zed 1.18 or newer), or ${quitZed(p)} and open it again`;
|
|
9890
|
+
}
|
|
9806
9891
|
function nextSteps(reachShown = false) {
|
|
9807
9892
|
return [
|
|
9808
9893
|
"",
|
|
@@ -10011,7 +10096,8 @@ function explainResume(r, now, locale) {
|
|
|
10011
10096
|
lines.push(
|
|
10012
10097
|
` When: ${when2}`,
|
|
10013
10098
|
` What Rewake will do: send your message "${oneLine(r.text)}" into the ${r.noun} once.`,
|
|
10014
|
-
` Nothing is sent now: this view only reads. ${r.cancel}
|
|
10099
|
+
` Nothing is sent now: this view only reads. ${r.cancel}`,
|
|
10100
|
+
...r.now ? [` ${r.now}`] : []
|
|
10015
10101
|
);
|
|
10016
10102
|
return lines;
|
|
10017
10103
|
}
|
|
@@ -10029,6 +10115,10 @@ function explainResume(r, now, locale) {
|
|
|
10029
10115
|
lines.push(
|
|
10030
10116
|
" Its time has already passed by too long, so Rewake won't send it by itself. It will tell you."
|
|
10031
10117
|
);
|
|
10118
|
+
if (r.workspaceChanged === "tell" || r.workspaceChanged === "note")
|
|
10119
|
+
lines.push(
|
|
10120
|
+
` Before sending, Rewake checks whether the project's latest git commit or tracked files differ from when this was planned (only the commit id and a summary of file names and status, never contents). If they do, ${r.workspaceChanged === "note" ? "it adds a line to the message asking the agent to re-read files before editing, and tells you" : "it tells you and sends the message as it is"}.`
|
|
10121
|
+
);
|
|
10032
10122
|
lines.push(" What could stop it:");
|
|
10033
10123
|
lines.push(
|
|
10034
10124
|
` - You type in the ${r.noun} before then: Rewake sends nothing.`,
|
|
@@ -10038,12 +10128,17 @@ function explainResume(r, now, locale) {
|
|
|
10038
10128
|
lines.push(
|
|
10039
10129
|
` - The ${r.noun} is open in a window or terminal then: Rewake sends nothing and tells you.`
|
|
10040
10130
|
);
|
|
10131
|
+
if (r.workspaceChanged === "hold")
|
|
10132
|
+
lines.push(
|
|
10133
|
+
" - The project's latest git commit or tracked files differ from when this was planned (Rewake reads only the commit id and a summary of file names and status, never contents): Rewake sends nothing and tells you."
|
|
10134
|
+
);
|
|
10041
10135
|
lines.push(
|
|
10042
10136
|
" - The agent is still at its usage limit: Rewake waits for the new reset and tries again, or tells you if it can't.",
|
|
10043
10137
|
" - The computer is off or asleep then: Rewake sends nothing if it runs much later, and tells you.",
|
|
10044
10138
|
" - A credit or billing limit: Rewake never resumes it, because waiting doesn't fix it.",
|
|
10045
10139
|
` Nothing is sent now: this view only reads. ${r.cancel}`
|
|
10046
10140
|
);
|
|
10141
|
+
if (r.now) lines.push(` ${r.now}`);
|
|
10047
10142
|
return lines;
|
|
10048
10143
|
}
|
|
10049
10144
|
var PENDING, oneLine;
|
|
@@ -10268,6 +10363,7 @@ function explainSchedule(stateDir2, idPrefix, now, hostOf, locale) {
|
|
|
10268
10363
|
const folder = s.cwd ? basename7(s.cwd) : "";
|
|
10269
10364
|
const title = new ThreadStore(stateDir2).get(s.sessionId)?.title;
|
|
10270
10365
|
const where2 = host ? folder ? `${host.name} in the "${folder}" folder` : host.name : title ? `The Zed thread "${title}"` : `The Zed thread ${s.sessionId.slice(0, 8)}`;
|
|
10366
|
+
const workspaceSetting = loadSettings(stateDir2).workspaceChanged;
|
|
10271
10367
|
const lines = explainResume(
|
|
10272
10368
|
{
|
|
10273
10369
|
dueAt: s.dueAt,
|
|
@@ -10279,7 +10375,11 @@ function explainSchedule(stateDir2, idPrefix, now, hostOf, locale) {
|
|
|
10279
10375
|
noun: host?.noun ?? "thread",
|
|
10280
10376
|
outsideZed: host !== void 0,
|
|
10281
10377
|
...host === void 0 && { lateMs: ZED_LATE_MS },
|
|
10282
|
-
|
|
10378
|
+
...host !== void 0 && s.workspace && workspaceSetting !== "off" && { workspaceChanged: workspaceSetting },
|
|
10379
|
+
cancel: host ? `To cancel it: ${rewake(`continue --cancel ${s.scheduleId.slice(0, 8)}`)}` : `To cancel it, use the schedules page: ${rewake("ui")}`,
|
|
10380
|
+
...host && {
|
|
10381
|
+
now: `To continue it now instead of waiting for the reset: ${rewake(`continue --now ${s.scheduleId.slice(0, 8)}`)}`
|
|
10382
|
+
}
|
|
10283
10383
|
},
|
|
10284
10384
|
now,
|
|
10285
10385
|
locale
|
|
@@ -10321,6 +10421,7 @@ var init_overview = __esm({
|
|
|
10321
10421
|
"src/ui/overview.ts"() {
|
|
10322
10422
|
"use strict";
|
|
10323
10423
|
init_explain();
|
|
10424
|
+
init_settings();
|
|
10324
10425
|
init_store();
|
|
10325
10426
|
init_threads();
|
|
10326
10427
|
init_time();
|
|
@@ -13681,6 +13782,7 @@ ${pending.map((s, i) => ` ${i + 1}. ${about(o, s)} [${s.scheduleId.slice(0, 8)
|
|
|
13681
13782
|
for (const s of chosen3) await cancel(o, store, s);
|
|
13682
13783
|
return 0;
|
|
13683
13784
|
}
|
|
13785
|
+
if (o.mode === "now") return runNow(o);
|
|
13684
13786
|
const list2 = waiting(o);
|
|
13685
13787
|
if (list2.length === 0) {
|
|
13686
13788
|
o.out("Nothing to continue: no closed session is stopped at a usage limit.\n");
|
|
@@ -13835,7 +13937,7 @@ function about(o, s) {
|
|
|
13835
13937
|
const host = hostFor(o, s.host);
|
|
13836
13938
|
return `${host ? `${placeOf(host, s.cwd)} ` : s.host === void 0 ? "Zed thread " : ""}${formatAt(s.dueAt, o.deps.now)}`;
|
|
13837
13939
|
}
|
|
13838
|
-
|
|
13940
|
+
function findOne(o, store, id) {
|
|
13839
13941
|
const wanted = id.trim().toLowerCase();
|
|
13840
13942
|
const found = wanted ? store.list().filter((s2) => s2.scheduleId.toLowerCase().startsWith(wanted)) : [];
|
|
13841
13943
|
const fail2 = (text2) => {
|
|
@@ -13857,6 +13959,11 @@ ${found.map((s2) => ` ${s2.scheduleId.slice(0, 8)} ${about(o, s2)}`).join("\n"
|
|
|
13857
13959
|
return fail2(
|
|
13858
13960
|
`That's a message in a Zed thread: change or delete it on the schedules page (${rewake("ui")}), or in the thread's Rewake menu.`
|
|
13859
13961
|
);
|
|
13962
|
+
return s;
|
|
13963
|
+
}
|
|
13964
|
+
async function cancelOne(o, store, id) {
|
|
13965
|
+
const s = findOne(o, store, id);
|
|
13966
|
+
if (typeof s === "number") return s;
|
|
13860
13967
|
if (TERMINAL_STATUSES.has(s.status)) {
|
|
13861
13968
|
const host = hostFor(o, s.host);
|
|
13862
13969
|
o.out(
|
|
@@ -13867,6 +13974,150 @@ ${found.map((s2) => ` ${s2.scheduleId.slice(0, 8)} ${about(o, s2)}`).join("\n"
|
|
|
13867
13974
|
}
|
|
13868
13975
|
return await cancel(o, store, s) ? 0 : 1;
|
|
13869
13976
|
}
|
|
13977
|
+
async function runNow(o) {
|
|
13978
|
+
const now = o.deps.now;
|
|
13979
|
+
const store = new ScheduleStore(o.deps.stateDir);
|
|
13980
|
+
if (!o.interactive && (o.nowId === void 0 || o.yes !== true)) {
|
|
13981
|
+
(o.err ?? o.out)(
|
|
13982
|
+
`agent-rewake: There is no terminal to ask in, so nothing was sent. Give the id and --yes: ${rewake("continue --now <id> --yes")}. The ids are in ${rewake("schedules")}, in square brackets.
|
|
13983
|
+
`
|
|
13984
|
+
);
|
|
13985
|
+
return 1;
|
|
13986
|
+
}
|
|
13987
|
+
let s;
|
|
13988
|
+
if (o.nowId !== void 0) {
|
|
13989
|
+
const found = findOne(o, store, o.nowId);
|
|
13990
|
+
if (typeof found === "number") return found;
|
|
13991
|
+
s = found;
|
|
13992
|
+
} else {
|
|
13993
|
+
const windowOf = (x) => o.hosts.find((h) => h.id === x.host && h.maxWaitAfterLimitMs !== void 0);
|
|
13994
|
+
const waitingHere = store.list().filter((x) => x.host !== void 0 && x.host !== "acp" && WAITING.has(x.status));
|
|
13995
|
+
const pending = waitingHere.filter((x) => !windowOf(x));
|
|
13996
|
+
if (pending.length === 0) {
|
|
13997
|
+
const blocked = waitingHere.map(windowOf).find((h) => h !== void 0);
|
|
13998
|
+
o.out(
|
|
13999
|
+
blocked ? `Nothing here can be continued now: ${windowReason(blocked)}.
|
|
14000
|
+
` : "Nothing to resume now: Rewake isn't set to continue any session.\n"
|
|
14001
|
+
);
|
|
14002
|
+
return 0;
|
|
14003
|
+
}
|
|
14004
|
+
s = pending[0];
|
|
14005
|
+
if (pending.length > 1) {
|
|
14006
|
+
const placeIn = (x) => {
|
|
14007
|
+
const h = hostFor(o, x.host);
|
|
14008
|
+
return h ? placeOf(h, x.cwd) : "";
|
|
14009
|
+
};
|
|
14010
|
+
const twin = (x) => pending.filter((y) => placeIn(y) === placeIn(x)).length > 1;
|
|
14011
|
+
o.out(
|
|
14012
|
+
`Planned resumes:
|
|
14013
|
+
${pending.map((x, i) => ` ${i + 1}. ${about(o, x)}${twin(x) ? ` (${printable(x.cwd)})` : ""} [${x.scheduleId.slice(0, 8)}]`).join("\n")}
|
|
14014
|
+
`
|
|
14015
|
+
);
|
|
14016
|
+
const answer = await o.ask(
|
|
14017
|
+
`Which one should Rewake continue now? (1-${pending.length}, or Enter to keep waiting) `
|
|
14018
|
+
);
|
|
14019
|
+
const one = pending[Number(answer.trim()) - 1];
|
|
14020
|
+
if (!one) {
|
|
14021
|
+
o.out("Nothing was changed.\n");
|
|
14022
|
+
return 1;
|
|
14023
|
+
}
|
|
14024
|
+
s = one;
|
|
14025
|
+
}
|
|
14026
|
+
}
|
|
14027
|
+
const host = hostFor(o, s.host);
|
|
14028
|
+
const noun = host?.noun ?? "session";
|
|
14029
|
+
const place = host ? placeOf(host, s.cwd) : "that session";
|
|
14030
|
+
const reopen = host?.reopen ?? `open the ${noun}`;
|
|
14031
|
+
if (TERMINAL_STATUSES.has(s.status)) {
|
|
14032
|
+
o.out(
|
|
14033
|
+
`Nothing to continue: the resume of ${place} ${s.status === "sent" ? "was already sent" : s.status === "cancelled" ? "was cancelled" : "has already ended"}.
|
|
14034
|
+
`
|
|
14035
|
+
);
|
|
14036
|
+
return 0;
|
|
14037
|
+
}
|
|
14038
|
+
if (s.status === "sending") {
|
|
14039
|
+
o.out(`Rewake is already continuing ${place}. It finishes on its own.
|
|
14040
|
+
`);
|
|
14041
|
+
return 1;
|
|
14042
|
+
}
|
|
14043
|
+
if (!WAITING.has(s.status)) {
|
|
14044
|
+
o.out(
|
|
14045
|
+
`The resume of ${place} isn't waiting for the reset any more (${s.status.replace("_", " ")}), so there is nothing to run now. ${capital2(reopen)} to continue it yourself.
|
|
14046
|
+
`
|
|
14047
|
+
);
|
|
14048
|
+
return 1;
|
|
14049
|
+
}
|
|
14050
|
+
const window = o.hosts.find((h) => h.id === s.host && h.maxWaitAfterLimitMs !== void 0);
|
|
14051
|
+
if (window) {
|
|
14052
|
+
o.out(
|
|
14053
|
+
`Rewake can't continue ${place} from here: ${windowReason(window)}. Type /rewake in the ${noun}, or ${reopen} to continue it yourself.
|
|
14054
|
+
`
|
|
14055
|
+
);
|
|
14056
|
+
return 1;
|
|
14057
|
+
}
|
|
14058
|
+
o.out(
|
|
14059
|
+
`Rewake will continue ${place} now, without waiting for the reset (planned ${formatAt(s.dueAt, now)}).
|
|
14060
|
+
Message, sent once as if you had typed it: "${oneLine2(s.text)}"
|
|
14061
|
+
Nothing is sent, and your resume stays planned for its time, if you typed in the ${noun} since the limit, if it is open or has changed, if the agent has already continued, if the usage limit is still on, or if the message can't reach the agent (signed out, session deleted or archived, folder gone).
|
|
14062
|
+
This window stays busy until the agent finishes (up to 3 hours); waiting for a free place to run can take a few minutes. Ctrl+C stops it and cancels this planned resume.
|
|
14063
|
+
`
|
|
14064
|
+
);
|
|
14065
|
+
if (o.yes !== true && !/^y(es)?$/i.test((await o.ask("Continue it now? [y/N] ")).trim())) {
|
|
14066
|
+
o.out("Nothing was changed.\n");
|
|
14067
|
+
return 1;
|
|
14068
|
+
}
|
|
14069
|
+
const outcome2 = await o.fireNow?.(s.scheduleId) ?? "not-ours";
|
|
14070
|
+
const after = store.get(s.scheduleId);
|
|
14071
|
+
switch (outcome2) {
|
|
14072
|
+
case "sent":
|
|
14073
|
+
o.out(`Rewake continued ${place}. ${capital2(reopen)} to see what it did.
|
|
14074
|
+
`);
|
|
14075
|
+
return 0;
|
|
14076
|
+
case "limited":
|
|
14077
|
+
o.out(
|
|
14078
|
+
`The usage limit is still on, so nothing was sent. Your resume is still planned for ${formatAt(s.dueAt, now)}.
|
|
14079
|
+
`
|
|
14080
|
+
);
|
|
14081
|
+
return 0;
|
|
14082
|
+
case "open":
|
|
14083
|
+
o.out(
|
|
14084
|
+
`The ${noun} is open, so nothing was sent. Close it and run this again, or it still resumes at ${formatAt(s.dueAt, now)}.
|
|
14085
|
+
`
|
|
14086
|
+
);
|
|
14087
|
+
return 1;
|
|
14088
|
+
case "changed":
|
|
14089
|
+
o.out(
|
|
14090
|
+
`The ${noun} has changed since it stopped, so nothing was sent. Your resume is still planned for ${formatAt(s.dueAt, now)}, and will send nothing then either. ${capital2(reopen)} to continue it yourself, or cancel the plan: ${rewake(`continue --cancel ${s.scheduleId.slice(0, 8)}`)}
|
|
14091
|
+
`
|
|
14092
|
+
);
|
|
14093
|
+
return 1;
|
|
14094
|
+
case "typed":
|
|
14095
|
+
case "native":
|
|
14096
|
+
o.out(
|
|
14097
|
+
`${outcome2 === "typed" ? `You typed in the ${noun} after the limit` : "The agent has already continued by itself"}, so nothing was sent. Your resume is still planned for ${formatAt(s.dueAt, now)}, and will send nothing then either. To remove it: ${rewake(`continue --cancel ${s.scheduleId.slice(0, 8)}`)}
|
|
14098
|
+
`
|
|
14099
|
+
);
|
|
14100
|
+
return 1;
|
|
14101
|
+
case "skipped":
|
|
14102
|
+
if (after?.failureReason !== "you") o.out("Rewake sent nothing.\n");
|
|
14103
|
+
return 1;
|
|
14104
|
+
case "busy":
|
|
14105
|
+
o.out(`Rewake is already working on ${place}. Try again in a minute.
|
|
14106
|
+
`);
|
|
14107
|
+
return 1;
|
|
14108
|
+
case "notified":
|
|
14109
|
+
case "failed":
|
|
14110
|
+
if (after && WAITING.has(after.status))
|
|
14111
|
+
o.out(`Your resume is still planned for ${formatAt(after.dueAt, now)}.
|
|
14112
|
+
`);
|
|
14113
|
+
return 1;
|
|
14114
|
+
default:
|
|
14115
|
+
o.out(`Nothing was sent: the resume of ${place} had changed or ended.
|
|
14116
|
+
`);
|
|
14117
|
+
return 1;
|
|
14118
|
+
}
|
|
14119
|
+
}
|
|
14120
|
+
var windowReason, WAITING, capital2;
|
|
13870
14121
|
var init_continue = __esm({
|
|
13871
14122
|
"src/continue.ts"() {
|
|
13872
14123
|
"use strict";
|
|
@@ -13876,9 +14127,13 @@ var init_continue = __esm({
|
|
|
13876
14127
|
init_time();
|
|
13877
14128
|
init_closed();
|
|
13878
14129
|
init_sessions();
|
|
14130
|
+
init_overview();
|
|
13879
14131
|
init_command2();
|
|
13880
14132
|
init_printable();
|
|
13881
14133
|
init_sleep_settings();
|
|
14134
|
+
windowReason = (h) => `${h.name} can only be continued from the window that is waiting for it, within 4 hours of its usage limit`;
|
|
14135
|
+
WAITING = /* @__PURE__ */ new Set(["scheduled", "waiting_for_limit"]);
|
|
14136
|
+
capital2 = (text2) => `${text2.charAt(0).toUpperCase()}${text2.slice(1)}`;
|
|
13882
14137
|
}
|
|
13883
14138
|
});
|
|
13884
14139
|
|
|
@@ -14364,7 +14619,7 @@ function diagnose(ctx) {
|
|
|
14364
14619
|
area: "Rewake",
|
|
14365
14620
|
level: "todo",
|
|
14366
14621
|
text: "Zed starts Rewake through npm or one Node.js folder. An upgrade of that Node.js, or no network, can stop those agents from starting.",
|
|
14367
|
-
fix: `Run the install command again
|
|
14622
|
+
fix: `Run the install command again: ${update} Then ${reloadAgent(platform6)}.`
|
|
14368
14623
|
});
|
|
14369
14624
|
const pins = /* @__PURE__ */ new Set();
|
|
14370
14625
|
for (const entry of wrappedEntries) {
|
|
@@ -14377,7 +14632,7 @@ function diagnose(ctx) {
|
|
|
14377
14632
|
area: "Rewake",
|
|
14378
14633
|
level: "todo",
|
|
14379
14634
|
text: `Zed starts Rewake ${pin}; this is Rewake ${ctx.version}.`,
|
|
14380
|
-
fix: `To update, run: ${update} Then
|
|
14635
|
+
fix: `To update, run: ${update} Then ${reloadAgent(platform6)}.`
|
|
14381
14636
|
});
|
|
14382
14637
|
else if (compareVersions(pin, ctx.version) > 0)
|
|
14383
14638
|
add({
|
|
@@ -14846,7 +15101,7 @@ var init_doctor = __esm({
|
|
|
14846
15101
|
});
|
|
14847
15102
|
|
|
14848
15103
|
// src/doctor-report.ts
|
|
14849
|
-
import { createHash as
|
|
15104
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
14850
15105
|
import { join as join37 } from "node:path";
|
|
14851
15106
|
function redactor(home, ids) {
|
|
14852
15107
|
const homes = [.../* @__PURE__ */ new Set([home, home.replace(/\\/g, "/")])].filter((h) => h.length > 1);
|
|
@@ -14949,7 +15204,7 @@ var init_doctor_report = __esm({
|
|
|
14949
15204
|
LEFT_OUT = /* @__PURE__ */ new Set(["pid", "text", "prompt", "body", "content", "title", "cwd", "folder"]);
|
|
14950
15205
|
FIRE_EVENTS = /^(fire\.|hook$|hook\.|limit\.|schedule\.settled|schedule\.delivering)/;
|
|
14951
15206
|
short2 = (s) => s.slice(0, 8);
|
|
14952
|
-
hash = (id) =>
|
|
15207
|
+
hash = (id) => createHash5("sha256").update(id).digest("hex");
|
|
14953
15208
|
iso = (t) => new Date(t).toISOString();
|
|
14954
15209
|
}
|
|
14955
15210
|
});
|
|
@@ -15411,7 +15666,9 @@ var init_settings_command = __esm({
|
|
|
15411
15666
|
"auto-when-prompts-skipped",
|
|
15412
15667
|
"clock",
|
|
15413
15668
|
"keep-awake",
|
|
15414
|
-
"resume-prompt"
|
|
15669
|
+
"resume-prompt",
|
|
15670
|
+
"workspace-changed",
|
|
15671
|
+
"usage-warnings"
|
|
15415
15672
|
];
|
|
15416
15673
|
SETTINGS = [
|
|
15417
15674
|
{
|
|
@@ -15478,6 +15735,40 @@ var init_settings_command = __esm({
|
|
|
15478
15735
|
return v && v !== DEFAULT_RESUME_PROMPT ? { ...rest, resumePrompt: v } : rest;
|
|
15479
15736
|
},
|
|
15480
15737
|
saved: (v) => v && v !== DEFAULT_RESUME_PROMPT ? "Resumes send your message from now on." : "Resumes send Rewake's own message again."
|
|
15738
|
+
},
|
|
15739
|
+
{
|
|
15740
|
+
name: "workspace-changed",
|
|
15741
|
+
title: "Project changed while waiting",
|
|
15742
|
+
short: "What Rewake does when the project changed while a resume waited, outside Zed.",
|
|
15743
|
+
more: "It compares the project's latest git commit and its tracked files (their names and status, never their contents, only on this computer).",
|
|
15744
|
+
choices: () => [
|
|
15745
|
+
["tell", "Only tell you, and send the message as it is"],
|
|
15746
|
+
["note", "Also add a line asking the agent to re-read files before editing"],
|
|
15747
|
+
["hold", "Send nothing and ask you first"],
|
|
15748
|
+
["off", "Don't check"]
|
|
15749
|
+
],
|
|
15750
|
+
get: (s) => s.workspaceChanged,
|
|
15751
|
+
set: (s, v) => ({
|
|
15752
|
+
...s,
|
|
15753
|
+
workspaceChanged: WORKSPACE_CHANGED_VALUES.find((x) => x === v) ?? s.workspaceChanged
|
|
15754
|
+
}),
|
|
15755
|
+
saved: (v) => v === "hold" ? "When the project changed while a resume waited, Rewake sends nothing and tells you." : v === "off" ? "Rewake won't check whether the project changed while a resume waited." : v === "note" ? "When the project changed while a resume waited, Rewake tells you and adds a line asking the agent to re-read files." : "When the project changed while a resume waited, Rewake tells you and sends the message as it is."
|
|
15756
|
+
},
|
|
15757
|
+
{
|
|
15758
|
+
name: "usage-warnings",
|
|
15759
|
+
title: "Warnings before a usage limit",
|
|
15760
|
+
short: "Whether Claude Code warns you when a usage window is nearly used up.",
|
|
15761
|
+
more: "Claude Code only. It uses Claude Code's own usage figures, with no network and no sign-in, and never types anything for you.",
|
|
15762
|
+
choices: () => [
|
|
15763
|
+
["on", "Warn at 80% and 95% used, and when the pace would reach the limit within 45 minutes"],
|
|
15764
|
+
["off", "Don't warn"]
|
|
15765
|
+
],
|
|
15766
|
+
get: (s) => s.usageWarnings,
|
|
15767
|
+
set: (s, v) => ({
|
|
15768
|
+
...s,
|
|
15769
|
+
usageWarnings: USAGE_WARNINGS_VALUES.find((x) => x === v) ?? s.usageWarnings
|
|
15770
|
+
}),
|
|
15771
|
+
saved: (v) => v === "off" ? "Claude Code won't warn you before a usage limit." : "Claude Code warns you at 80% and 95% of a usage limit, and when your pace would reach it within 45 minutes."
|
|
15481
15772
|
}
|
|
15482
15773
|
];
|
|
15483
15774
|
orList2 = (v) => v.length < 2 ? v[0] ?? "" : `${v.slice(0, -1).join(", ")} or ${v.at(-1)}`;
|
|
@@ -15781,10 +16072,12 @@ var init_help = __esm({
|
|
|
15781
16072
|
{
|
|
15782
16073
|
name: "continue",
|
|
15783
16074
|
summary: "Continue a closed agent session after its usage limit",
|
|
15784
|
-
usage: ["continue [--always | --ask | --cancel [<id>]]"],
|
|
16075
|
+
usage: ["continue [--always | --ask | --cancel [<id>] | --now [<id>] [--yes]]"],
|
|
15785
16076
|
about: [
|
|
15786
16077
|
"Continues a session you closed at its usage limit, when the limit resets. Run alone, it asks",
|
|
15787
|
-
"which session and when."
|
|
16078
|
+
"which session and when.",
|
|
16079
|
+
"--now is for a planned resume outside Zed that shouldn't wait for the reset (you upgraded",
|
|
16080
|
+
"your plan, or the limit lifted early). In Zed, use Rewake \u2192 Change a scheduled message\u2026."
|
|
15788
16081
|
],
|
|
15789
16082
|
options: [
|
|
15790
16083
|
{ flag: "--always", text: "Continue sessions by itself from now on" },
|
|
@@ -15792,11 +16085,27 @@ var init_help = __esm({
|
|
|
15792
16085
|
{
|
|
15793
16086
|
flag: "--cancel",
|
|
15794
16087
|
text: "Cancel a planned resume: pick it, or give its id"
|
|
16088
|
+
},
|
|
16089
|
+
{
|
|
16090
|
+
flag: "--now",
|
|
16091
|
+
arg: "[<id>]",
|
|
16092
|
+
text: "Continue a planned resume now, without waiting for the reset (outside Zed)"
|
|
16093
|
+
},
|
|
16094
|
+
{
|
|
16095
|
+
flag: "--yes",
|
|
16096
|
+
alias: "-y",
|
|
16097
|
+
text: "With --now: don't ask first (needed without a terminal)"
|
|
15795
16098
|
}
|
|
15796
16099
|
],
|
|
15797
16100
|
footer: [
|
|
15798
|
-
"With several planned and a terminal, --cancel
|
|
15799
|
-
"
|
|
16101
|
+
"With several planned and a terminal, --cancel and --now list them to pick one (--cancel also",
|
|
16102
|
+
"all). --now shows what it will send and asks first; without a terminal it needs the id and",
|
|
16103
|
+
"--yes. It stays busy until the agent finishes; Ctrl+C stops it and cancels that resume. If the",
|
|
16104
|
+
"limit is still on, the session is open or changed, you typed after the limit, the agent",
|
|
16105
|
+
"continued, or the message can't reach the agent (signed out, session deleted or archived,",
|
|
16106
|
+
"folder gone), nothing is sent and the resume stays planned. Other errors end it as failed.",
|
|
16107
|
+
"The ids are in square",
|
|
16108
|
+
"brackets in `agent-rewake schedules`."
|
|
15800
16109
|
]
|
|
15801
16110
|
},
|
|
15802
16111
|
{
|
|
@@ -16916,7 +17225,10 @@ function codexAdapter(deps) {
|
|
|
16916
17225
|
try {
|
|
16917
17226
|
const started = s.attempts.length > 0 ? Math.min(...s.attempts.map((a) => a.startedAt)) : void 0;
|
|
16918
17227
|
if (started === void 0) return void 0;
|
|
16919
|
-
|
|
17228
|
+
const sent = [s.text, `${s.text}
|
|
17229
|
+
|
|
17230
|
+
${WORKSPACE_NOTE}`];
|
|
17231
|
+
return userMessages(readTail2(transcript, 512 * 1024), started).some((m) => sent.includes(m)) || void 0;
|
|
16920
17232
|
} catch {
|
|
16921
17233
|
return void 0;
|
|
16922
17234
|
}
|
|
@@ -16943,6 +17255,7 @@ var init_adapter = __esm({
|
|
|
16943
17255
|
"src/hosts/codex/adapter.ts"() {
|
|
16944
17256
|
"use strict";
|
|
16945
17257
|
init_resume();
|
|
17258
|
+
init_workspace();
|
|
16946
17259
|
init_cli();
|
|
16947
17260
|
init_rollout();
|
|
16948
17261
|
}
|
|
@@ -16994,7 +17307,10 @@ function codexHooks(deps) {
|
|
|
16994
17307
|
}
|
|
16995
17308
|
};
|
|
16996
17309
|
store.put(resume);
|
|
16997
|
-
if (deps.arm(resume.scheduleId, at) !== false)
|
|
17310
|
+
if (deps.arm(resume.scheduleId, at) !== false) {
|
|
17311
|
+
withWorkspace(store, resume, ctx.now);
|
|
17312
|
+
return true;
|
|
17313
|
+
}
|
|
16998
17314
|
store.cancel(resume.scheduleId, ctx.now);
|
|
16999
17315
|
return false;
|
|
17000
17316
|
};
|
|
@@ -18816,9 +19132,17 @@ import { basename as basename10 } from "node:path";
|
|
|
18816
19132
|
function runningText(at, noun = "session") {
|
|
18817
19133
|
return `${at}: Rewake is continuing the ${noun} now. You'll get a notification when it's done. To stop it: "${rewake("continue --cancel")}".`;
|
|
18818
19134
|
}
|
|
19135
|
+
function foregroundText(at, noun = "session") {
|
|
19136
|
+
return `${at}: Rewake is continuing the ${noun} now. Press Ctrl+C to stop it.`;
|
|
19137
|
+
}
|
|
18819
19138
|
function doneText(at, noun = "session", now = Date.now()) {
|
|
18820
19139
|
return `${at}: Rewake's run ended normally ${formatAt(now, now)}. Open the ${noun} to see what it did.`;
|
|
18821
19140
|
}
|
|
19141
|
+
function workspaceNoteText(noun, added) {
|
|
19142
|
+
const what = `The project's tracked files or latest commit changed while the ${noun} waited`;
|
|
19143
|
+
const how = added ? "so Rewake added a line to its message asking the agent to re-read files before editing." : "Rewake sent its message as it was. To have it add a line asking the agent to re-read files, choose note; to have it ask you first, choose hold.";
|
|
19144
|
+
return `${what}${added ? "," : "."} ${how} Change this with: ${rewake("settings workspace-changed")} (tell, note, hold or off).`;
|
|
19145
|
+
}
|
|
18822
19146
|
function where(host, s) {
|
|
18823
19147
|
const folder = s.cwd ? basename10(s.cwd) : "";
|
|
18824
19148
|
return folder ? `${host.name} in the "${folder}" folder` : host.name;
|
|
@@ -18832,6 +19156,13 @@ function notice(why, agent, now, f) {
|
|
|
18832
19156
|
return `${agent}: the usage limit has reset. The ${n} is open, so Rewake didn't send anything. Continue it there.`;
|
|
18833
19157
|
case "changed":
|
|
18834
19158
|
return `${agent}: the usage limit has reset, but the ${n} has changed since it stopped, so Rewake didn't send anything. ${cap(reopen)} to see where it stands and continue.`;
|
|
19159
|
+
case "workspace": {
|
|
19160
|
+
const what = [
|
|
19161
|
+
...f.workspace?.commit ? ["its latest commit moved"] : [],
|
|
19162
|
+
...f.workspace?.changes ? ["its tracked files differ"] : []
|
|
19163
|
+
].join(" and ");
|
|
19164
|
+
return `${agent}: the usage limit has reset, but the project changed while the ${n} waited (${what}), so Rewake didn't send anything. ${f.again ? f.again(void 0) : `To continue anyway, run "${rewake("continue")}", or ${reopen}.`} Change this check with: ${rewake("settings workspace-changed")} (tell, note, hold or off).`;
|
|
19165
|
+
}
|
|
18835
19166
|
case "late":
|
|
18836
19167
|
return `${agent}: Rewake was due to continue the ${n}${f.dueAt ? ` ${formatAt(f.dueAt, now)}` : ""}, but couldn't run then (the computer may have been off or asleep). ${cap(reopen)} to continue.`;
|
|
18837
19168
|
case "far-reset":
|
|
@@ -18880,6 +19211,14 @@ function rearmEarly(id, store, deps, now) {
|
|
|
18880
19211
|
deps.log?.("fire.early", { armed: armed.ok });
|
|
18881
19212
|
return "early";
|
|
18882
19213
|
}
|
|
19214
|
+
function keptOutcome(d) {
|
|
19215
|
+
if (d.action === "notify") return d.why === "open" || d.why === "changed" ? d.why : "limited";
|
|
19216
|
+
if (d.action === "skip") {
|
|
19217
|
+
if (d.why === "typed" || d.why === "native") return d.why;
|
|
19218
|
+
return d.why === "expired" ? "limited" : "gone";
|
|
19219
|
+
}
|
|
19220
|
+
return "limited";
|
|
19221
|
+
}
|
|
18883
19222
|
async function fire(id, deps) {
|
|
18884
19223
|
const store = new ScheduleStore(deps.stateDir);
|
|
18885
19224
|
const log = deps.log ?? (() => {
|
|
@@ -18905,7 +19244,7 @@ async function fire(id, deps) {
|
|
|
18905
19244
|
}
|
|
18906
19245
|
const lock = new SessionLock(deps.stateDir);
|
|
18907
19246
|
const lockKey = `fire:${id}`;
|
|
18908
|
-
if (first.status !== "cancelled" && now < first.dueAt - EARLY_MS) {
|
|
19247
|
+
if (!deps.immediate && first.status !== "cancelled" && now < first.dueAt - EARLY_MS) {
|
|
18909
19248
|
if (!lock.acquire(lockKey)) return "early";
|
|
18910
19249
|
try {
|
|
18911
19250
|
return rearmEarly(id, store, deps, now);
|
|
@@ -18931,7 +19270,7 @@ async function fire(id, deps) {
|
|
|
18931
19270
|
removeTimer();
|
|
18932
19271
|
return "gone";
|
|
18933
19272
|
}
|
|
18934
|
-
if (s.status !== "cancelled" && now < s.dueAt - EARLY_MS)
|
|
19273
|
+
if (!deps.immediate && s.status !== "cancelled" && now < s.dueAt - EARLY_MS)
|
|
18935
19274
|
return rearmEarly(id, store, deps, now);
|
|
18936
19275
|
const wait2 = (until, why, count = true) => {
|
|
18937
19276
|
const next = Math.max(until, now + MIN_ARM_MS);
|
|
@@ -18964,7 +19303,10 @@ async function fire(id, deps) {
|
|
|
18964
19303
|
const place = await takeSlot({ stateDir: deps.stateDir, ...deps.slots }).catch(
|
|
18965
19304
|
() => "error"
|
|
18966
19305
|
);
|
|
18967
|
-
if (place === void 0)
|
|
19306
|
+
if (place === void 0) {
|
|
19307
|
+
if (deps.immediate) return "busy";
|
|
19308
|
+
return wait2(now + MIN_ARM_MS, "no-free-place", false);
|
|
19309
|
+
}
|
|
18968
19310
|
if (place !== "error") slot.release = place;
|
|
18969
19311
|
}
|
|
18970
19312
|
const settings = loadSettings(deps.stateDir);
|
|
@@ -18977,7 +19319,7 @@ async function fire(id, deps) {
|
|
|
18977
19319
|
}
|
|
18978
19320
|
const decision = decideFire({
|
|
18979
19321
|
resume: {
|
|
18980
|
-
dueAt: s.dueAt,
|
|
19322
|
+
dueAt: deps.immediate ? now : s.dueAt,
|
|
18981
19323
|
status: s.status,
|
|
18982
19324
|
...s.rearms !== void 0 && { rearms: s.rearms }
|
|
18983
19325
|
},
|
|
@@ -18997,7 +19339,7 @@ async function fire(id, deps) {
|
|
|
18997
19339
|
}),
|
|
18998
19340
|
now
|
|
18999
19341
|
);
|
|
19000
|
-
const tell = (why, status, cause, message) => {
|
|
19342
|
+
const tell = (why, status, cause, message, workspace) => {
|
|
19001
19343
|
settle(status, cause ?? why, message);
|
|
19002
19344
|
if (facts.newResetsAt !== void 0 && (why === "far-reset" || why === "expired"))
|
|
19003
19345
|
store.update(id, (x) => ({ ...x, dueAt: (facts.newResetsAt ?? 0) + RESET_MARGIN_MS }), now);
|
|
@@ -19010,12 +19352,17 @@ async function fire(id, deps) {
|
|
|
19010
19352
|
...host.again !== void 0 && { again: host.again },
|
|
19011
19353
|
dueAt: s.dueAt,
|
|
19012
19354
|
...facts.newResetsAt !== void 0 && { resetsAt: facts.newResetsAt },
|
|
19013
|
-
...cause && { cause }
|
|
19355
|
+
...cause && { cause },
|
|
19356
|
+
...workspace && { workspace }
|
|
19014
19357
|
})
|
|
19015
19358
|
);
|
|
19016
19359
|
removeTimer();
|
|
19017
19360
|
return why === "failed" || why === "expired" ? "failed" : "notified";
|
|
19018
19361
|
};
|
|
19362
|
+
if (deps.immediate && decision.action !== "send") {
|
|
19363
|
+
log("fire.kept", { host: host.id, action: decision.action });
|
|
19364
|
+
return keptOutcome(decision);
|
|
19365
|
+
}
|
|
19019
19366
|
log("fire.decide", {
|
|
19020
19367
|
host: host.id,
|
|
19021
19368
|
action: decision.action,
|
|
@@ -19023,6 +19370,19 @@ async function fire(id, deps) {
|
|
|
19023
19370
|
});
|
|
19024
19371
|
switch (decision.action) {
|
|
19025
19372
|
case "send": {
|
|
19373
|
+
let noted = false;
|
|
19374
|
+
let changed = false;
|
|
19375
|
+
if (s.workspace && settings.workspaceChanged !== "off") {
|
|
19376
|
+
const here = fingerprintWorkspace(s.cwd, FIRE_BUDGET_MS);
|
|
19377
|
+
const differs = here && workspaceChange(s.workspace, here);
|
|
19378
|
+
if (differs) {
|
|
19379
|
+
log("fire.workspace", { action: settings.workspaceChanged });
|
|
19380
|
+
if (settings.workspaceChanged === "hold")
|
|
19381
|
+
return tell("workspace", "needs_attention", void 0, void 0, differs);
|
|
19382
|
+
changed = true;
|
|
19383
|
+
noted = settings.workspaceChanged === "note";
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19026
19386
|
const claimed = store.update(
|
|
19027
19387
|
id,
|
|
19028
19388
|
(x) => x.status !== s.status || x.dueAt !== s.dueAt ? void 0 : {
|
|
@@ -19050,6 +19410,7 @@ async function fire(id, deps) {
|
|
|
19050
19410
|
const wake = deps.wake ?? new Wakefulness();
|
|
19051
19411
|
wake.set(true, settings.keepAwake);
|
|
19052
19412
|
let watch2;
|
|
19413
|
+
let told = false;
|
|
19053
19414
|
onResumeRun((pid) => {
|
|
19054
19415
|
store.update(
|
|
19055
19416
|
id,
|
|
@@ -19059,7 +19420,12 @@ async function fire(id, deps) {
|
|
|
19059
19420
|
}),
|
|
19060
19421
|
now
|
|
19061
19422
|
);
|
|
19062
|
-
|
|
19423
|
+
told = changed;
|
|
19424
|
+
const running = deps.immediate ? foregroundText(at, host.noun) : runningText(at, host.noun);
|
|
19425
|
+
deps.notify(
|
|
19426
|
+
"Agent Rewake",
|
|
19427
|
+
changed ? `${running} ${workspaceNoteText(host.noun, noted)}` : running
|
|
19428
|
+
);
|
|
19063
19429
|
watch2 = setInterval(() => {
|
|
19064
19430
|
if (!store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped) return;
|
|
19065
19431
|
clearInterval(watch2);
|
|
@@ -19067,7 +19433,12 @@ async function fire(id, deps) {
|
|
|
19067
19433
|
}, deps.stopPollMs ?? STOP_POLL_MS);
|
|
19068
19434
|
});
|
|
19069
19435
|
try {
|
|
19070
|
-
result = await host.send(
|
|
19436
|
+
result = await host.send(
|
|
19437
|
+
noted ? { ...s, text: `${s.text}
|
|
19438
|
+
|
|
19439
|
+
${WORKSPACE_NOTE}` } : s,
|
|
19440
|
+
key2
|
|
19441
|
+
);
|
|
19071
19442
|
} catch (err) {
|
|
19072
19443
|
result = { ok: false, reason: "failed", detail: err.message };
|
|
19073
19444
|
} finally {
|
|
@@ -19075,7 +19446,7 @@ async function fire(id, deps) {
|
|
|
19075
19446
|
if (watch2) clearInterval(watch2);
|
|
19076
19447
|
wake.release();
|
|
19077
19448
|
}
|
|
19078
|
-
if (store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped) {
|
|
19449
|
+
if (store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped && !(deps.immediate && result.ok)) {
|
|
19079
19450
|
settle("stopped", "you");
|
|
19080
19451
|
removeTimer();
|
|
19081
19452
|
log("fire.stopped", {});
|
|
@@ -19093,10 +19464,25 @@ async function fire(id, deps) {
|
|
|
19093
19464
|
if (result.ok) {
|
|
19094
19465
|
settle("sent");
|
|
19095
19466
|
removeTimer();
|
|
19096
|
-
if (
|
|
19467
|
+
if (changed && !told)
|
|
19468
|
+
deps.notify("Agent Rewake", `${at}: ${workspaceNoteText(host.noun, noted)}`);
|
|
19469
|
+
if (!deps.immediate && store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.pid !== void 0)
|
|
19097
19470
|
deps.notify("Agent Rewake", doneText(at, host.noun, deps.now()));
|
|
19098
19471
|
return "sent";
|
|
19099
19472
|
}
|
|
19473
|
+
if (deps.immediate && (result.reason === "limited" || result.reason === "busy")) {
|
|
19474
|
+
store.update(
|
|
19475
|
+
id,
|
|
19476
|
+
(x) => ({
|
|
19477
|
+
...x,
|
|
19478
|
+
status: s.status,
|
|
19479
|
+
attempts: x.attempts.filter((a) => a.idempotencyKey !== key2)
|
|
19480
|
+
}),
|
|
19481
|
+
now
|
|
19482
|
+
);
|
|
19483
|
+
log("fire.still-limited", { host: host.id, now: true });
|
|
19484
|
+
return result.reason === "limited" ? "limited" : "busy";
|
|
19485
|
+
}
|
|
19100
19486
|
if (result.reason === "limited" || result.reason === "busy") {
|
|
19101
19487
|
const rearms = s.rearms ?? 0;
|
|
19102
19488
|
const reset = result.reason === "limited" ? result.resetsAt : void 0;
|
|
@@ -19118,6 +19504,27 @@ async function fire(id, deps) {
|
|
|
19118
19504
|
"needs-approval",
|
|
19119
19505
|
"folder-gone"
|
|
19120
19506
|
].includes(result.detail ?? "") ? result.detail : void 0;
|
|
19507
|
+
if (deps.immediate && cause !== void 0 && !["timeout", "needs-approval"].includes(cause)) {
|
|
19508
|
+
store.update(
|
|
19509
|
+
id,
|
|
19510
|
+
(x) => ({
|
|
19511
|
+
...x,
|
|
19512
|
+
status: s.status,
|
|
19513
|
+
attempts: x.attempts.filter((a) => a.idempotencyKey !== key2)
|
|
19514
|
+
}),
|
|
19515
|
+
now
|
|
19516
|
+
);
|
|
19517
|
+
deps.notify(
|
|
19518
|
+
"Agent Rewake",
|
|
19519
|
+
notice("failed", at, now, {
|
|
19520
|
+
noun: host.noun,
|
|
19521
|
+
agentName: host.name,
|
|
19522
|
+
...host.reopen !== void 0 && { reopen: host.reopen },
|
|
19523
|
+
cause
|
|
19524
|
+
})
|
|
19525
|
+
);
|
|
19526
|
+
return "failed";
|
|
19527
|
+
}
|
|
19121
19528
|
return tell("failed", "failed", cause, result.message);
|
|
19122
19529
|
}
|
|
19123
19530
|
case "wait":
|
|
@@ -19193,6 +19600,7 @@ var init_fire = __esm({
|
|
|
19193
19600
|
init_command2();
|
|
19194
19601
|
init_keep_awake();
|
|
19195
19602
|
init_spawn2();
|
|
19603
|
+
init_workspace();
|
|
19196
19604
|
init_slots();
|
|
19197
19605
|
init_timers();
|
|
19198
19606
|
EARLY_MS = 3e4;
|
|
@@ -21277,14 +21685,23 @@ ${loginItemPlanText(process.platform)}`);
|
|
|
21277
21685
|
return code3;
|
|
21278
21686
|
}
|
|
21279
21687
|
if (first === "continue") {
|
|
21688
|
+
if (argv.includes("--now")) {
|
|
21689
|
+
const rest = argv.slice(1).filter((a) => a !== "--now");
|
|
21690
|
+
const yes = rest.some((a) => a === "--yes" || a === "-y");
|
|
21691
|
+
const names = rest.filter((a) => a !== "--yes" && a !== "-y");
|
|
21692
|
+
const nowId = names[0];
|
|
21693
|
+
if (names.length > 1 || nowId?.startsWith("-")) {
|
|
21694
|
+
process.stderr.write(CONTINUE_USAGE);
|
|
21695
|
+
return 2;
|
|
21696
|
+
}
|
|
21697
|
+
return runContinueCommand(env, "now", void 0, nowId, yes);
|
|
21698
|
+
}
|
|
21280
21699
|
const flag = argv[1];
|
|
21281
21700
|
const mode = flag === "--always" ? "always" : flag === "--ask" ? "ask" : flag === "--cancel" ? "cancel" : void 0;
|
|
21282
21701
|
const cancelId = mode === "cancel" ? argv[2] : void 0;
|
|
21283
21702
|
const extra = argv.length > (cancelId === void 0 ? 2 : 3);
|
|
21284
21703
|
if (flag !== void 0 && mode === void 0 || extra || cancelId?.startsWith("-")) {
|
|
21285
|
-
process.stderr.write(
|
|
21286
|
-
"agent-rewake: usage: agent-rewake continue [--always | --ask | --cancel [<id>]]\n"
|
|
21287
|
-
);
|
|
21704
|
+
process.stderr.write(CONTINUE_USAGE);
|
|
21288
21705
|
return 2;
|
|
21289
21706
|
}
|
|
21290
21707
|
return runContinueCommand(env, mode, cancelId);
|
|
@@ -22014,14 +22431,70 @@ async function prompt(question) {
|
|
|
22014
22431
|
rl.close();
|
|
22015
22432
|
}
|
|
22016
22433
|
}
|
|
22017
|
-
async function runContinueCommand(env, mode, cancelId) {
|
|
22434
|
+
async function runContinueCommand(env, mode, cancelId, nowId, yes = false) {
|
|
22018
22435
|
const { state, timers, sweepDeps, node } = timerDeps(env);
|
|
22019
22436
|
sweepQuietly(env);
|
|
22020
22437
|
const adapters = hostAdapters(env, node, state);
|
|
22021
|
-
|
|
22438
|
+
const log = new Logger(env);
|
|
22439
|
+
let interrupted = false;
|
|
22440
|
+
let sentAnyway = false;
|
|
22441
|
+
const code2 = await runContinue({
|
|
22022
22442
|
sleepSettings: () => readSleepSettings({ env }),
|
|
22023
22443
|
...mode && { mode },
|
|
22024
22444
|
...cancelId !== void 0 && { cancelId },
|
|
22445
|
+
...nowId !== void 0 && { nowId },
|
|
22446
|
+
yes,
|
|
22447
|
+
// The resume's own timer run, at once; what it would tell by notification is said here.
|
|
22448
|
+
fireNow: async (id) => {
|
|
22449
|
+
const store = new ScheduleStore(state);
|
|
22450
|
+
const onInterrupt = () => {
|
|
22451
|
+
if (interrupted) {
|
|
22452
|
+
const pid = store.get(id)?.attempts.at(-1)?.pid;
|
|
22453
|
+
if (pid !== void 0) {
|
|
22454
|
+
killPidTree(pid, void 0, void 0, "SIGKILL");
|
|
22455
|
+
process.stdout.write("Ended the agent now.\n");
|
|
22456
|
+
}
|
|
22457
|
+
return;
|
|
22458
|
+
}
|
|
22459
|
+
interrupted = true;
|
|
22460
|
+
const s = store.get(id);
|
|
22461
|
+
if (s !== void 0 && s.status !== "sending" && !WAITING_STATES.has(s.status)) return;
|
|
22462
|
+
if (s?.status !== "sending") {
|
|
22463
|
+
process.stdout.write("Stopped. Nothing was sent, and your resume is still planned.\n");
|
|
22464
|
+
process.exit(130);
|
|
22465
|
+
}
|
|
22466
|
+
store.update(
|
|
22467
|
+
id,
|
|
22468
|
+
(x) => ({
|
|
22469
|
+
...x,
|
|
22470
|
+
attempts: x.attempts.map(
|
|
22471
|
+
(a, i) => i === x.attempts.length - 1 ? { ...a, stopped: true } : a
|
|
22472
|
+
)
|
|
22473
|
+
}),
|
|
22474
|
+
Date.now()
|
|
22475
|
+
);
|
|
22476
|
+
process.stdout.write(
|
|
22477
|
+
"Stopping. The agent ends within a few seconds; what it has done so far stays. The planned resume is cancelled too.\n"
|
|
22478
|
+
);
|
|
22479
|
+
};
|
|
22480
|
+
process.on("SIGINT", onInterrupt);
|
|
22481
|
+
try {
|
|
22482
|
+
const outcome2 = await fire(id, {
|
|
22483
|
+
stateDir: state,
|
|
22484
|
+
now: Date.now,
|
|
22485
|
+
hosts: adapters,
|
|
22486
|
+
timers,
|
|
22487
|
+
notify: (_title, body) => process.stdout.write(`${body}
|
|
22488
|
+
`),
|
|
22489
|
+
log: (event, fields2) => log.info(event, fields2),
|
|
22490
|
+
immediate: true
|
|
22491
|
+
});
|
|
22492
|
+
sentAnyway = outcome2 === "sent";
|
|
22493
|
+
return outcome2;
|
|
22494
|
+
} finally {
|
|
22495
|
+
process.off("SIGINT", onInterrupt);
|
|
22496
|
+
}
|
|
22497
|
+
},
|
|
22025
22498
|
// Codex's resumes too, which `continue` doesn't arm itself.
|
|
22026
22499
|
hostOf: (id) => adapters.get(id),
|
|
22027
22500
|
hosts: CLOSED_HOSTS,
|
|
@@ -22041,11 +22514,12 @@ async function runContinueCommand(env, mode, cancelId) {
|
|
|
22041
22514
|
err: (t) => process.stderr.write(t),
|
|
22042
22515
|
ask: prompt
|
|
22043
22516
|
});
|
|
22517
|
+
return interrupted && !sentAnyway ? 130 : code2;
|
|
22044
22518
|
}
|
|
22045
22519
|
function previewNames(env) {
|
|
22046
22520
|
return installedPreviews(env, homedir23(), stateDir(env)).map((id) => PREVIEW_NAMES[id] ?? id);
|
|
22047
22521
|
}
|
|
22048
|
-
var INSTALL_PLACES, KNOWN_OPTIONS, TIMER_PLACES;
|
|
22522
|
+
var INSTALL_PLACES, KNOWN_OPTIONS, WAITING_STATES, CONTINUE_USAGE, TIMER_PLACES;
|
|
22049
22523
|
var init_cli2 = __esm({
|
|
22050
22524
|
"src/cli.ts"() {
|
|
22051
22525
|
"use strict";
|
|
@@ -22120,6 +22594,8 @@ var init_cli2 = __esm({
|
|
|
22120
22594
|
schedules: ["--all", "--json", "--explain"],
|
|
22121
22595
|
history: ["--days"]
|
|
22122
22596
|
};
|
|
22597
|
+
WAITING_STATES = /* @__PURE__ */ new Set(["scheduled", "waiting_for_limit"]);
|
|
22598
|
+
CONTINUE_USAGE = "agent-rewake: usage: agent-rewake continue [--always | --ask | --cancel [<id>] | --now [<id>] [--yes]]\n";
|
|
22123
22599
|
TIMER_PLACES = /* @__PURE__ */ new Set([
|
|
22124
22600
|
"codex",
|
|
22125
22601
|
"copilot-cli",
|
|
@@ -76,6 +76,104 @@ export function blockingKind(rateLimits, now) {
|
|
|
76
76
|
)?.kind;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// ---- Warnings before a limit: Claude Code's own usage figures, no network, no sign-in.
|
|
80
|
+
|
|
81
|
+
/** Percent used at which a window is warned about, lowest first. */
|
|
82
|
+
const WARN_AT = [80, 95];
|
|
83
|
+
/** Samples kept per window, and the least time they must span before a pace is trusted. */
|
|
84
|
+
const SAMPLE_CAP = 12;
|
|
85
|
+
const MIN_SPAN_MS = 10 * MINUTE;
|
|
86
|
+
/** Only readings this recent give the pace. */
|
|
87
|
+
const RECENT_MS = HOUR;
|
|
88
|
+
/** A pace warning comes when the projected limit is this near. */
|
|
89
|
+
const PACE_HORIZON_MS = 45 * MINUTE;
|
|
90
|
+
/** Two reset times closer than this are the same window (the figures can differ by a moment). */
|
|
91
|
+
const SAME_WINDOW_MS = 30 * MINUTE;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Milliseconds until the window reaches 100, from its samples ({ t, p }: time, percent used), or
|
|
95
|
+
* undefined when the pace is unknown: the figure has not moved since the newest sample (so idle
|
|
96
|
+
* time never counts as a pace), fewer than two samples from the last hour, those less than 10
|
|
97
|
+
* minutes apart, or no rise.
|
|
98
|
+
*/
|
|
99
|
+
export function timeToLimit(samples, now) {
|
|
100
|
+
const recent = samples.filter((s) => s.t >= now - RECENT_MS);
|
|
101
|
+
const first = recent[0];
|
|
102
|
+
const last = recent.at(-1);
|
|
103
|
+
if (!first || !last || last.t !== now || last.t - first.t < MIN_SPAN_MS) return undefined;
|
|
104
|
+
const rate = (last.p - first.p) / (last.t - first.t);
|
|
105
|
+
return rate > 0 ? (100 - last.p) / rate : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The estimate as a range of whole minutes in fives, about 0.7x to 1.4x: [20, 40]. */
|
|
109
|
+
export function paceRange(ms) {
|
|
110
|
+
const five = (x) => Math.max(5, Math.round(x / (5 * MINUTE)) * 5);
|
|
111
|
+
const lo = five(ms * 0.7);
|
|
112
|
+
return [lo, Math.max(lo + 5, five(ms * 1.4))];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* One window's reading, given what was kept for it (`prev`: { resetsAt, samples, high, pace },
|
|
117
|
+
* or undefined). Returns undefined when there is nothing to track (a spending cap, no reset ahead,
|
|
118
|
+
* a full window: the limit flow takes over), else { next, level?, pace? }: the state to keep, the
|
|
119
|
+
* threshold newly crossed (only the highest if it jumped past both) and the pace range to warn
|
|
120
|
+
* with. A threshold is warned once per window, the pace once per window and never together with
|
|
121
|
+
* a threshold (it waits for the next reading).
|
|
122
|
+
*/
|
|
123
|
+
export function usageStep(prev, w, now) {
|
|
124
|
+
const reset = typeof w.resetsAt === "string" ? Date.parse(w.resetsAt) : Number.NaN;
|
|
125
|
+
const p = w.percentUsed;
|
|
126
|
+
if (BILLING_KINDS.has(w.kind) || !Number.isFinite(reset) || reset <= now) return undefined;
|
|
127
|
+
if (!(p >= 0 && p < 100)) return undefined;
|
|
128
|
+
const same = prev && Math.abs(prev.resetsAt - reset) < SAME_WINDOW_MS;
|
|
129
|
+
const base = same ? prev : { resetsAt: reset, samples: [], high: 0, pace: false };
|
|
130
|
+
const last = base.samples.at(-1);
|
|
131
|
+
const samples =
|
|
132
|
+
last?.p === p ? base.samples : [...(last && p < last.p ? [] : base.samples), { t: now, p }];
|
|
133
|
+
const kept = samples.slice(-SAMPLE_CAP);
|
|
134
|
+
const level = WARN_AT.filter((l) => p >= l && l > base.high).pop();
|
|
135
|
+
const ms = level === undefined && !base.pace ? timeToLimit(kept, now) : undefined;
|
|
136
|
+
const pace =
|
|
137
|
+
ms !== undefined && ms <= PACE_HORIZON_MS && now + ms < reset ? paceRange(ms) : undefined;
|
|
138
|
+
return {
|
|
139
|
+
next: {
|
|
140
|
+
resetsAt: reset, // the latest, so a slowly drifting time can't make the window look new
|
|
141
|
+
samples: kept,
|
|
142
|
+
high: level ?? base.high,
|
|
143
|
+
pace: base.pace || pace !== undefined,
|
|
144
|
+
},
|
|
145
|
+
level,
|
|
146
|
+
pace,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** "5-hour limit", "7-day limit", "7-day opus limit", "some kind limit". */
|
|
151
|
+
export function limitName(kind) {
|
|
152
|
+
const m = /^seven_day_(.+)$/.exec(kind);
|
|
153
|
+
const name =
|
|
154
|
+
kind === "five_hour"
|
|
155
|
+
? "5-hour"
|
|
156
|
+
: kind === "seven_day"
|
|
157
|
+
? "7-day"
|
|
158
|
+
: m
|
|
159
|
+
? `7-day ${m[1].replace(/_/g, " ")}`
|
|
160
|
+
: String(kind).replace(/_/g, " ");
|
|
161
|
+
return `${name} limit`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** `reset` is the reset time as words: "at 3:00 AM today", "on Friday at 9:00 AM". */
|
|
165
|
+
export function levelText(kind, percent, reset) {
|
|
166
|
+
const head = `${limitName(kind)}: ${Math.floor(percent)}% used, as of Claude Code's last reply. It resets ${reset}.`;
|
|
167
|
+
// Compacting is a model call too, so it is only hinted at below 95%.
|
|
168
|
+
return percent >= 95
|
|
169
|
+
? `${head} A good moment to wrap up or save what you are doing.`
|
|
170
|
+
: `${head} If you are in the middle of a task, /compact now can make the rest last longer (compacting also uses some).`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function paceText(kind, [lo, hi], reset) {
|
|
174
|
+
return `At this pace you reach the ${limitName(kind)} in about ${lo} to ${hi} minutes, before it resets ${reset}.`;
|
|
175
|
+
}
|
|
176
|
+
|
|
79
177
|
/**
|
|
80
178
|
* Claude Code's own panels, by `CLAUDE_CODE_ENTRYPOINT`: the VS Code extension (also in Cursor and
|
|
81
179
|
* other VS Code builds) and the desktop app. They run Claude Code through the Agent SDK, so the
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
// offered (not answered) | armed | sent
|
|
14
14
|
// claim: { by, at }, the process sending the armed continue right now
|
|
15
15
|
// sched:<id> [{ at, text }]
|
|
16
|
+
// usage:<dir> { <kind>: { resetsAt, samples: [{ t, p }], high, pace } }, what the warnings
|
|
17
|
+
// before a limit have said. The figures are the account's, not the session's, so
|
|
18
|
+
// the key is the config dir (CLAUDE_CONFIG_DIR, which is one sign-in): a restart or
|
|
19
|
+
// another session of the same account doesn't repeat a warning.
|
|
16
20
|
// A copy of each limit record (no message text) goes to Rewake's shared state folder, so
|
|
17
21
|
// `agent-rewake doctor` and the schedules page can show it, and so Rewake can continue the session
|
|
18
22
|
// headless if Claude Code closes before the limit is answered (it adds `open`, the Claude Code
|
|
@@ -34,6 +38,7 @@ import {
|
|
|
34
38
|
helpText,
|
|
35
39
|
holdArgv,
|
|
36
40
|
LINUX_MAINS,
|
|
41
|
+
levelText,
|
|
37
42
|
limitOf,
|
|
38
43
|
MAX_REARMS,
|
|
39
44
|
MAX_REHITS,
|
|
@@ -42,6 +47,7 @@ import {
|
|
|
42
47
|
mustAsk,
|
|
43
48
|
nativeLikely,
|
|
44
49
|
notHere,
|
|
50
|
+
paceText,
|
|
45
51
|
parseCommand,
|
|
46
52
|
parseProcess,
|
|
47
53
|
parseProgram,
|
|
@@ -54,6 +60,7 @@ import {
|
|
|
54
60
|
sessionVars,
|
|
55
61
|
splitWhen,
|
|
56
62
|
surfaceOf,
|
|
63
|
+
usageStep,
|
|
57
64
|
WAITING_EXPIRES_MS,
|
|
58
65
|
WAKE_HORIZON_MS,
|
|
59
66
|
WAKE_PROBE,
|
|
@@ -106,6 +113,10 @@ const ME = Math.random().toString(36).slice(2);
|
|
|
106
113
|
const CLAIM_MS = 5 * MINUTE;
|
|
107
114
|
/** Rewake's keep-awake setting: "plugged-in" (default), "always" or "never". */
|
|
108
115
|
let keepAwake = "plugged-in";
|
|
116
|
+
/** Rewake's usage-warnings setting: "on" (default) or "off". */
|
|
117
|
+
let usageWarnings = "on";
|
|
118
|
+
/** CLAUDE_CONFIG_DIR, which names the account's warnings in $.store ("" for the default). */
|
|
119
|
+
let configDir = "";
|
|
109
120
|
/** { kind, pid, program }: Claude Code's PID and the hold program (macOS, Linux with systemd-inhibit, Windows); null where there is none. */
|
|
110
121
|
let wakeTarget;
|
|
111
122
|
/** The running hold: { key, stream }. */
|
|
@@ -131,6 +142,8 @@ async function loadConfig($) {
|
|
|
131
142
|
if (["on", "off", "ask"].includes(settings.newThreads)) sharedAuto = settings.newThreads;
|
|
132
143
|
if (["plugged-in", "always", "never"].includes(settings.keepAwake))
|
|
133
144
|
keepAwake = settings.keepAwake;
|
|
145
|
+
if (settings.usageWarnings === "on" || settings.usageWarnings === "off")
|
|
146
|
+
usageWarnings = settings.usageWarnings;
|
|
134
147
|
} catch {
|
|
135
148
|
// No settings saved yet: the default clock.
|
|
136
149
|
}
|
|
@@ -146,7 +159,7 @@ async function loadSessionInfo($) {
|
|
|
146
159
|
// SETTINGS_VARS and KEY_VARS; a test in Rewake's repository compares them with these).
|
|
147
160
|
const read = (get) => get.catch(() => undefined);
|
|
148
161
|
const values = {
|
|
149
|
-
CLAUDE_CONFIG_DIR:
|
|
162
|
+
CLAUDE_CONFIG_DIR: configDir, // read once, at session start
|
|
150
163
|
CLAUDE_CODE_PROJECT_DIR_NAME: await read($.env.get("CLAUDE_CODE_PROJECT_DIR_NAME")),
|
|
151
164
|
ANTHROPIC_BASE_URL: await read($.env.get("ANTHROPIC_BASE_URL")),
|
|
152
165
|
ANTHROPIC_MODEL: await read($.env.get("ANTHROPIC_MODEL")),
|
|
@@ -494,6 +507,26 @@ async function onMeasure($, rateLimits) {
|
|
|
494
507
|
);
|
|
495
508
|
}
|
|
496
509
|
|
|
510
|
+
/** Toasts at 80% and 95% of a usage window, and when the pace would reach the limit soon. */
|
|
511
|
+
async function warnUsage($, rateLimits) {
|
|
512
|
+
if (usageWarnings === "off" || !personAtPrompt({ isInteractive, entrypoint })) return;
|
|
513
|
+
const now = await $.clock.now();
|
|
514
|
+
const key = `usage:${configDir}`;
|
|
515
|
+
const state = (await $.store.get(key)) ?? {};
|
|
516
|
+
const toasts = [];
|
|
517
|
+
for (const w of rateLimits ?? []) {
|
|
518
|
+
const step = w && usageStep(state[w.kind], w, now);
|
|
519
|
+
if (!step) continue;
|
|
520
|
+
state[w.kind] = step.next;
|
|
521
|
+
const reset = atWhen(step.next.resetsAt, now);
|
|
522
|
+
if (step.level !== undefined) toasts.push(levelText(w.kind, w.percentUsed, reset));
|
|
523
|
+
else if (step.pace) toasts.push(paceText(w.kind, step.pace, reset));
|
|
524
|
+
}
|
|
525
|
+
// Saved before showing: a failure while showing must not make the same warning come back.
|
|
526
|
+
await $.store.set(key, state);
|
|
527
|
+
for (const text of toasts) $.ui.toast(text);
|
|
528
|
+
}
|
|
529
|
+
|
|
497
530
|
async function onNotification($, e) {
|
|
498
531
|
const id = e.session_id;
|
|
499
532
|
const ep = await $.store.get(limitKey(id));
|
|
@@ -859,6 +892,7 @@ export function register(on) {
|
|
|
859
892
|
isInteractive = e.isInteractive;
|
|
860
893
|
sessionCwd = e.cwd;
|
|
861
894
|
entrypoint = await $.env.get("CLAUDE_CODE_ENTRYPOINT").catch(() => undefined);
|
|
895
|
+
configDir = (await $.env.get("CLAUDE_CONFIG_DIR").catch(() => undefined)) ?? "";
|
|
862
896
|
inert = Boolean(await $.env.get("AGENT_REWAKE_FIRE").catch(() => undefined));
|
|
863
897
|
if (inert) return next(e);
|
|
864
898
|
surface = surfaceOf({ surface: e.surface, entrypoint });
|
|
@@ -905,7 +939,10 @@ export function register(on) {
|
|
|
905
939
|
}).catch(passOn);
|
|
906
940
|
|
|
907
941
|
on("session.measure", ($, e, next) => {
|
|
908
|
-
if (!inert && e.changed.includes("rateLimits"))
|
|
942
|
+
if (!inert && e.changed.includes("rateLimits")) {
|
|
943
|
+
void onMeasure($, e.rateLimits);
|
|
944
|
+
void warnUsage($, e.rateLimits).catch(() => undefined);
|
|
945
|
+
}
|
|
909
946
|
return next(e);
|
|
910
947
|
}).catch(passOn);
|
|
911
948
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codizelabs/agent-rewake",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Auto-resume Claude Code, Codex, Gemini CLI, GitHub Copilot, Grok or any Zed agent when its usage limit resets, in the same session, and schedule messages into your agent threads.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zed",
|