@codizelabs/agent-rewake 0.4.0 → 0.6.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.
@@ -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 createHash2 } from "node:crypto";
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 `${createHash2("sha256").update(sessionId).digest("hex").slice(0, 32)}.json`;
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: (signal) => {
4562
+ kill: (signal2) => {
4487
4563
  try {
4488
- return process.kill(pid, signal);
4564
+ return process.kill(pid, signal2);
4489
4565
  } catch {
4490
4566
  return false;
4491
4567
  }
4492
4568
  }
4493
4569
  },
4494
4570
  p,
4495
- "SIGTERM",
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.4.0" : "0.0.0");
4971
+ VERSION2 = pkg.version ?? (true ? "0.6.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"];
@@ -6848,6 +6930,31 @@ function chooseSnapshot(snapshots, last, nowSec) {
6848
6930
  const fullest = (s) => Math.max(...[s.primary, s.secondary].map((w) => w ? w.used_percent ?? 0 : 0));
6849
6931
  return withWindows.reduce((a, b) => fullest(b) > fullest(a) ? b : a);
6850
6932
  }
6933
+ function lastWindows(tail) {
6934
+ let found;
6935
+ for (const line2 of tail.split("\n")) {
6936
+ if (!line2.startsWith("{") || !line2.includes('"token_count"')) continue;
6937
+ try {
6938
+ const p = JSON.parse(line2).payload;
6939
+ const rl = p?.type === "token_count" ? p.rate_limits : void 0;
6940
+ if (!rl || typeof rl !== "object") continue;
6941
+ if (rl.limit_id && rl.limit_id !== "codex") continue;
6942
+ if (rl.primary || rl.secondary) found = rl;
6943
+ } catch {
6944
+ }
6945
+ }
6946
+ const out = [];
6947
+ for (const w of [found?.primary, found?.secondary]) {
6948
+ const m = w?.window_minutes;
6949
+ if (!w || typeof m !== "number" || !(m > 0) || typeof w.used_percent !== "number") continue;
6950
+ out.push({
6951
+ kind: m >= 270 && m <= 330 ? "five_hour" : m >= 9e3 && m <= 11e3 ? "seven_day" : `${Math.max(1, Math.round(m / 60))}_hour`,
6952
+ percentUsed: w.used_percent,
6953
+ ...typeof w.resets_at === "number" && { resetsAt: w.resets_at * 1e3 }
6954
+ });
6955
+ }
6956
+ return out;
6957
+ }
6851
6958
  function spentWindowReset(rl, nowSec) {
6852
6959
  if (!rl || !/^workspace_/.test(rl.rate_limit_reached_type ?? "") || rl.spend_control_reached)
6853
6960
  return {};
@@ -8205,7 +8312,7 @@ var init_install6 = __esm({
8205
8312
 
8206
8313
  // src/wrap.ts
8207
8314
  import { spawnSync as spawnSync8 } from "node:child_process";
8208
- import { createHash as createHash3 } from "node:crypto";
8315
+ import { createHash as createHash4 } from "node:crypto";
8209
8316
  import {
8210
8317
  chmodSync as chmodSync2,
8211
8318
  existsSync as existsSync14,
@@ -8359,7 +8466,7 @@ function ensureNpxAgent(id, npx, stateDir2, env, logFile) {
8359
8466
  );
8360
8467
  }
8361
8468
  function zedBinaryDir(id, version, target, env) {
8362
- const sha = (s) => createHash3("sha256").update(s).digest("hex").slice(0, 16);
8469
+ const sha = (s) => createHash4("sha256").update(s).digest("hex").slice(0, 16);
8363
8470
  const archiveKey = target.sha256 ? `${target.archive}\0sha256:${target.sha256.toLowerCase()}` : target.archive;
8364
8471
  return join24(
8365
8472
  zedDataDir(env),
@@ -8430,7 +8537,7 @@ async function downloadBinaryAgent(id, version, target, stateDir2, fetchImpl = f
8430
8537
  if (!res.ok) throw new Error(`download failed (HTTP ${res.status})`);
8431
8538
  const bytes = Buffer.from(await res.arrayBuffer());
8432
8539
  if (target.sha256) {
8433
- const got = createHash3("sha256").update(bytes).digest("hex");
8540
+ const got = createHash4("sha256").update(bytes).digest("hex");
8434
8541
  if (got !== target.sha256.toLowerCase())
8435
8542
  throw new Error("the download doesn't match the registry's checksum");
8436
8543
  }
@@ -9803,6 +9910,9 @@ function quitZed(p = platform3()) {
9803
9910
  if (p === "win32") return "close every Zed window so Zed exits";
9804
9911
  return "quit Zed completely (Ctrl+Q)";
9805
9912
  }
9913
+ function reloadAgent(p = platform3()) {
9914
+ 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`;
9915
+ }
9806
9916
  function nextSteps(reachShown = false) {
9807
9917
  return [
9808
9918
  "",
@@ -10011,7 +10121,8 @@ function explainResume(r, now, locale) {
10011
10121
  lines.push(
10012
10122
  ` When: ${when2}`,
10013
10123
  ` 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}`
10124
+ ` Nothing is sent now: this view only reads. ${r.cancel}`,
10125
+ ...r.now ? [` ${r.now}`] : []
10015
10126
  );
10016
10127
  return lines;
10017
10128
  }
@@ -10029,6 +10140,10 @@ function explainResume(r, now, locale) {
10029
10140
  lines.push(
10030
10141
  " Its time has already passed by too long, so Rewake won't send it by itself. It will tell you."
10031
10142
  );
10143
+ if (r.workspaceChanged === "tell" || r.workspaceChanged === "note")
10144
+ lines.push(
10145
+ ` 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"}.`
10146
+ );
10032
10147
  lines.push(" What could stop it:");
10033
10148
  lines.push(
10034
10149
  ` - You type in the ${r.noun} before then: Rewake sends nothing.`,
@@ -10038,12 +10153,17 @@ function explainResume(r, now, locale) {
10038
10153
  lines.push(
10039
10154
  ` - The ${r.noun} is open in a window or terminal then: Rewake sends nothing and tells you.`
10040
10155
  );
10156
+ if (r.workspaceChanged === "hold")
10157
+ lines.push(
10158
+ " - 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."
10159
+ );
10041
10160
  lines.push(
10042
10161
  " - 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
10162
  " - The computer is off or asleep then: Rewake sends nothing if it runs much later, and tells you.",
10044
10163
  " - A credit or billing limit: Rewake never resumes it, because waiting doesn't fix it.",
10045
10164
  ` Nothing is sent now: this view only reads. ${r.cancel}`
10046
10165
  );
10166
+ if (r.now) lines.push(` ${r.now}`);
10047
10167
  return lines;
10048
10168
  }
10049
10169
  var PENDING, oneLine;
@@ -10268,6 +10388,7 @@ function explainSchedule(stateDir2, idPrefix, now, hostOf, locale) {
10268
10388
  const folder = s.cwd ? basename7(s.cwd) : "";
10269
10389
  const title = new ThreadStore(stateDir2).get(s.sessionId)?.title;
10270
10390
  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)}`;
10391
+ const workspaceSetting = loadSettings(stateDir2).workspaceChanged;
10271
10392
  const lines = explainResume(
10272
10393
  {
10273
10394
  dueAt: s.dueAt,
@@ -10279,7 +10400,11 @@ function explainSchedule(stateDir2, idPrefix, now, hostOf, locale) {
10279
10400
  noun: host?.noun ?? "thread",
10280
10401
  outsideZed: host !== void 0,
10281
10402
  ...host === void 0 && { lateMs: ZED_LATE_MS },
10282
- cancel: host ? `To cancel it: ${rewake(`continue --cancel ${s.scheduleId.slice(0, 8)}`)}` : `To cancel it, use the schedules page: ${rewake("ui")}`
10403
+ ...host !== void 0 && s.workspace && workspaceSetting !== "off" && { workspaceChanged: workspaceSetting },
10404
+ cancel: host ? `To cancel it: ${rewake(`continue --cancel ${s.scheduleId.slice(0, 8)}`)}` : `To cancel it, use the schedules page: ${rewake("ui")}`,
10405
+ ...host && {
10406
+ now: `To continue it now instead of waiting for the reset: ${rewake(`continue --now ${s.scheduleId.slice(0, 8)}`)}`
10407
+ }
10283
10408
  },
10284
10409
  now,
10285
10410
  locale
@@ -10321,6 +10446,7 @@ var init_overview = __esm({
10321
10446
  "src/ui/overview.ts"() {
10322
10447
  "use strict";
10323
10448
  init_explain();
10449
+ init_settings();
10324
10450
  init_store();
10325
10451
  init_threads();
10326
10452
  init_time();
@@ -13681,6 +13807,7 @@ ${pending.map((s, i) => ` ${i + 1}. ${about(o, s)} [${s.scheduleId.slice(0, 8)
13681
13807
  for (const s of chosen3) await cancel(o, store, s);
13682
13808
  return 0;
13683
13809
  }
13810
+ if (o.mode === "now") return runNow(o);
13684
13811
  const list2 = waiting(o);
13685
13812
  if (list2.length === 0) {
13686
13813
  o.out("Nothing to continue: no closed session is stopped at a usage limit.\n");
@@ -13835,7 +13962,7 @@ function about(o, s) {
13835
13962
  const host = hostFor(o, s.host);
13836
13963
  return `${host ? `${placeOf(host, s.cwd)} ` : s.host === void 0 ? "Zed thread " : ""}${formatAt(s.dueAt, o.deps.now)}`;
13837
13964
  }
13838
- async function cancelOne(o, store, id) {
13965
+ function findOne(o, store, id) {
13839
13966
  const wanted = id.trim().toLowerCase();
13840
13967
  const found = wanted ? store.list().filter((s2) => s2.scheduleId.toLowerCase().startsWith(wanted)) : [];
13841
13968
  const fail2 = (text2) => {
@@ -13857,6 +13984,11 @@ ${found.map((s2) => ` ${s2.scheduleId.slice(0, 8)} ${about(o, s2)}`).join("\n"
13857
13984
  return fail2(
13858
13985
  `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
13986
  );
13987
+ return s;
13988
+ }
13989
+ async function cancelOne(o, store, id) {
13990
+ const s = findOne(o, store, id);
13991
+ if (typeof s === "number") return s;
13860
13992
  if (TERMINAL_STATUSES.has(s.status)) {
13861
13993
  const host = hostFor(o, s.host);
13862
13994
  o.out(
@@ -13867,6 +13999,150 @@ ${found.map((s2) => ` ${s2.scheduleId.slice(0, 8)} ${about(o, s2)}`).join("\n"
13867
13999
  }
13868
14000
  return await cancel(o, store, s) ? 0 : 1;
13869
14001
  }
14002
+ async function runNow(o) {
14003
+ const now = o.deps.now;
14004
+ const store = new ScheduleStore(o.deps.stateDir);
14005
+ if (!o.interactive && (o.nowId === void 0 || o.yes !== true)) {
14006
+ (o.err ?? o.out)(
14007
+ `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.
14008
+ `
14009
+ );
14010
+ return 1;
14011
+ }
14012
+ let s;
14013
+ if (o.nowId !== void 0) {
14014
+ const found = findOne(o, store, o.nowId);
14015
+ if (typeof found === "number") return found;
14016
+ s = found;
14017
+ } else {
14018
+ const windowOf = (x) => o.hosts.find((h) => h.id === x.host && h.maxWaitAfterLimitMs !== void 0);
14019
+ const waitingHere = store.list().filter((x) => x.host !== void 0 && x.host !== "acp" && WAITING.has(x.status));
14020
+ const pending = waitingHere.filter((x) => !windowOf(x));
14021
+ if (pending.length === 0) {
14022
+ const blocked = waitingHere.map(windowOf).find((h) => h !== void 0);
14023
+ o.out(
14024
+ blocked ? `Nothing here can be continued now: ${windowReason(blocked)}.
14025
+ ` : "Nothing to resume now: Rewake isn't set to continue any session.\n"
14026
+ );
14027
+ return 0;
14028
+ }
14029
+ s = pending[0];
14030
+ if (pending.length > 1) {
14031
+ const placeIn = (x) => {
14032
+ const h = hostFor(o, x.host);
14033
+ return h ? placeOf(h, x.cwd) : "";
14034
+ };
14035
+ const twin = (x) => pending.filter((y) => placeIn(y) === placeIn(x)).length > 1;
14036
+ o.out(
14037
+ `Planned resumes:
14038
+ ${pending.map((x, i) => ` ${i + 1}. ${about(o, x)}${twin(x) ? ` (${printable(x.cwd)})` : ""} [${x.scheduleId.slice(0, 8)}]`).join("\n")}
14039
+ `
14040
+ );
14041
+ const answer = await o.ask(
14042
+ `Which one should Rewake continue now? (1-${pending.length}, or Enter to keep waiting) `
14043
+ );
14044
+ const one = pending[Number(answer.trim()) - 1];
14045
+ if (!one) {
14046
+ o.out("Nothing was changed.\n");
14047
+ return 1;
14048
+ }
14049
+ s = one;
14050
+ }
14051
+ }
14052
+ const host = hostFor(o, s.host);
14053
+ const noun = host?.noun ?? "session";
14054
+ const place = host ? placeOf(host, s.cwd) : "that session";
14055
+ const reopen = host?.reopen ?? `open the ${noun}`;
14056
+ if (TERMINAL_STATUSES.has(s.status)) {
14057
+ o.out(
14058
+ `Nothing to continue: the resume of ${place} ${s.status === "sent" ? "was already sent" : s.status === "cancelled" ? "was cancelled" : "has already ended"}.
14059
+ `
14060
+ );
14061
+ return 0;
14062
+ }
14063
+ if (s.status === "sending") {
14064
+ o.out(`Rewake is already continuing ${place}. It finishes on its own.
14065
+ `);
14066
+ return 1;
14067
+ }
14068
+ if (!WAITING.has(s.status)) {
14069
+ o.out(
14070
+ `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.
14071
+ `
14072
+ );
14073
+ return 1;
14074
+ }
14075
+ const window = o.hosts.find((h) => h.id === s.host && h.maxWaitAfterLimitMs !== void 0);
14076
+ if (window) {
14077
+ o.out(
14078
+ `Rewake can't continue ${place} from here: ${windowReason(window)}. Type /rewake in the ${noun}, or ${reopen} to continue it yourself.
14079
+ `
14080
+ );
14081
+ return 1;
14082
+ }
14083
+ o.out(
14084
+ `Rewake will continue ${place} now, without waiting for the reset (planned ${formatAt(s.dueAt, now)}).
14085
+ Message, sent once as if you had typed it: "${oneLine2(s.text)}"
14086
+ 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).
14087
+ 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.
14088
+ `
14089
+ );
14090
+ if (o.yes !== true && !/^y(es)?$/i.test((await o.ask("Continue it now? [y/N] ")).trim())) {
14091
+ o.out("Nothing was changed.\n");
14092
+ return 1;
14093
+ }
14094
+ const outcome2 = await o.fireNow?.(s.scheduleId) ?? "not-ours";
14095
+ const after = store.get(s.scheduleId);
14096
+ switch (outcome2) {
14097
+ case "sent":
14098
+ o.out(`Rewake continued ${place}. ${capital2(reopen)} to see what it did.
14099
+ `);
14100
+ return 0;
14101
+ case "limited":
14102
+ o.out(
14103
+ `The usage limit is still on, so nothing was sent. Your resume is still planned for ${formatAt(s.dueAt, now)}.
14104
+ `
14105
+ );
14106
+ return 0;
14107
+ case "open":
14108
+ o.out(
14109
+ `The ${noun} is open, so nothing was sent. Close it and run this again, or it still resumes at ${formatAt(s.dueAt, now)}.
14110
+ `
14111
+ );
14112
+ return 1;
14113
+ case "changed":
14114
+ o.out(
14115
+ `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)}`)}
14116
+ `
14117
+ );
14118
+ return 1;
14119
+ case "typed":
14120
+ case "native":
14121
+ o.out(
14122
+ `${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)}`)}
14123
+ `
14124
+ );
14125
+ return 1;
14126
+ case "skipped":
14127
+ if (after?.failureReason !== "you") o.out("Rewake sent nothing.\n");
14128
+ return 1;
14129
+ case "busy":
14130
+ o.out(`Rewake is already working on ${place}. Try again in a minute.
14131
+ `);
14132
+ return 1;
14133
+ case "notified":
14134
+ case "failed":
14135
+ if (after && WAITING.has(after.status))
14136
+ o.out(`Your resume is still planned for ${formatAt(after.dueAt, now)}.
14137
+ `);
14138
+ return 1;
14139
+ default:
14140
+ o.out(`Nothing was sent: the resume of ${place} had changed or ended.
14141
+ `);
14142
+ return 1;
14143
+ }
14144
+ }
14145
+ var windowReason, WAITING, capital2;
13870
14146
  var init_continue = __esm({
13871
14147
  "src/continue.ts"() {
13872
14148
  "use strict";
@@ -13876,9 +14152,13 @@ var init_continue = __esm({
13876
14152
  init_time();
13877
14153
  init_closed();
13878
14154
  init_sessions();
14155
+ init_overview();
13879
14156
  init_command2();
13880
14157
  init_printable();
13881
14158
  init_sleep_settings();
14159
+ windowReason = (h) => `${h.name} can only be continued from the window that is waiting for it, within 4 hours of its usage limit`;
14160
+ WAITING = /* @__PURE__ */ new Set(["scheduled", "waiting_for_limit"]);
14161
+ capital2 = (text2) => `${text2.charAt(0).toUpperCase()}${text2.slice(1)}`;
13882
14162
  }
13883
14163
  });
13884
14164
 
@@ -14364,7 +14644,7 @@ function diagnose(ctx) {
14364
14644
  area: "Rewake",
14365
14645
  level: "todo",
14366
14646
  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, then quit Zed completely and open it again: ${update}`
14647
+ fix: `Run the install command again: ${update} Then ${reloadAgent(platform6)}.`
14368
14648
  });
14369
14649
  const pins = /* @__PURE__ */ new Set();
14370
14650
  for (const entry of wrappedEntries) {
@@ -14377,7 +14657,7 @@ function diagnose(ctx) {
14377
14657
  area: "Rewake",
14378
14658
  level: "todo",
14379
14659
  text: `Zed starts Rewake ${pin}; this is Rewake ${ctx.version}.`,
14380
- fix: `To update, run: ${update} Then quit Zed completely and open it again.`
14660
+ fix: `To update, run: ${update} Then ${reloadAgent(platform6)}.`
14381
14661
  });
14382
14662
  else if (compareVersions(pin, ctx.version) > 0)
14383
14663
  add({
@@ -14846,7 +15126,7 @@ var init_doctor = __esm({
14846
15126
  });
14847
15127
 
14848
15128
  // src/doctor-report.ts
14849
- import { createHash as createHash4 } from "node:crypto";
15129
+ import { createHash as createHash5 } from "node:crypto";
14850
15130
  import { join as join37 } from "node:path";
14851
15131
  function redactor(home, ids) {
14852
15132
  const homes = [.../* @__PURE__ */ new Set([home, home.replace(/\\/g, "/")])].filter((h) => h.length > 1);
@@ -14949,7 +15229,7 @@ var init_doctor_report = __esm({
14949
15229
  LEFT_OUT = /* @__PURE__ */ new Set(["pid", "text", "prompt", "body", "content", "title", "cwd", "folder"]);
14950
15230
  FIRE_EVENTS = /^(fire\.|hook$|hook\.|limit\.|schedule\.settled|schedule\.delivering)/;
14951
15231
  short2 = (s) => s.slice(0, 8);
14952
- hash = (id) => createHash4("sha256").update(id).digest("hex");
15232
+ hash = (id) => createHash5("sha256").update(id).digest("hex");
14953
15233
  iso = (t) => new Date(t).toISOString();
14954
15234
  }
14955
15235
  });
@@ -15411,7 +15691,9 @@ var init_settings_command = __esm({
15411
15691
  "auto-when-prompts-skipped",
15412
15692
  "clock",
15413
15693
  "keep-awake",
15414
- "resume-prompt"
15694
+ "resume-prompt",
15695
+ "workspace-changed",
15696
+ "usage-warnings"
15415
15697
  ];
15416
15698
  SETTINGS = [
15417
15699
  {
@@ -15478,6 +15760,40 @@ var init_settings_command = __esm({
15478
15760
  return v && v !== DEFAULT_RESUME_PROMPT ? { ...rest, resumePrompt: v } : rest;
15479
15761
  },
15480
15762
  saved: (v) => v && v !== DEFAULT_RESUME_PROMPT ? "Resumes send your message from now on." : "Resumes send Rewake's own message again."
15763
+ },
15764
+ {
15765
+ name: "workspace-changed",
15766
+ title: "Project changed while waiting",
15767
+ short: "What Rewake does when the project changed while a resume waited, outside Zed.",
15768
+ more: "It compares the project's latest git commit and its tracked files (their names and status, never their contents, only on this computer).",
15769
+ choices: () => [
15770
+ ["tell", "Only tell you, and send the message as it is"],
15771
+ ["note", "Also add a line asking the agent to re-read files before editing"],
15772
+ ["hold", "Send nothing and ask you first"],
15773
+ ["off", "Don't check"]
15774
+ ],
15775
+ get: (s) => s.workspaceChanged,
15776
+ set: (s, v) => ({
15777
+ ...s,
15778
+ workspaceChanged: WORKSPACE_CHANGED_VALUES.find((x) => x === v) ?? s.workspaceChanged
15779
+ }),
15780
+ 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."
15781
+ },
15782
+ {
15783
+ name: "usage-warnings",
15784
+ title: "Warnings before a usage limit",
15785
+ short: "Warn me when a usage limit is nearly used up (Claude Code, at the moment it happens; Codex, with your next prompt).",
15786
+ more: "Claude Code and Codex only. It uses their own usage figures, with no network and no sign-in, and never types anything for you.",
15787
+ choices: () => [
15788
+ ["on", "Warn at 80% and 95% used, and when the pace would reach the limit within 45 minutes"],
15789
+ ["off", "Don't warn"]
15790
+ ],
15791
+ get: (s) => s.usageWarnings,
15792
+ set: (s, v) => ({
15793
+ ...s,
15794
+ usageWarnings: USAGE_WARNINGS_VALUES.find((x) => x === v) ?? s.usageWarnings
15795
+ }),
15796
+ saved: (v) => v === "off" ? "Claude Code and Codex won't warn you before a usage limit." : "Claude Code and Codex warn you at 80% and 95% of a usage limit, and when your pace would reach it within 45 minutes."
15481
15797
  }
15482
15798
  ];
15483
15799
  orList2 = (v) => v.length < 2 ? v[0] ?? "" : `${v.slice(0, -1).join(", ")} or ${v.at(-1)}`;
@@ -15781,10 +16097,12 @@ var init_help = __esm({
15781
16097
  {
15782
16098
  name: "continue",
15783
16099
  summary: "Continue a closed agent session after its usage limit",
15784
- usage: ["continue [--always | --ask | --cancel [<id>]]"],
16100
+ usage: ["continue [--always | --ask | --cancel [<id>] | --now [<id>] [--yes]]"],
15785
16101
  about: [
15786
16102
  "Continues a session you closed at its usage limit, when the limit resets. Run alone, it asks",
15787
- "which session and when."
16103
+ "which session and when.",
16104
+ "--now is for a planned resume outside Zed that shouldn't wait for the reset (you upgraded",
16105
+ "your plan, or the limit lifted early). In Zed, use Rewake \u2192 Change a scheduled message\u2026."
15788
16106
  ],
15789
16107
  options: [
15790
16108
  { flag: "--always", text: "Continue sessions by itself from now on" },
@@ -15792,11 +16110,27 @@ var init_help = __esm({
15792
16110
  {
15793
16111
  flag: "--cancel",
15794
16112
  text: "Cancel a planned resume: pick it, or give its id"
16113
+ },
16114
+ {
16115
+ flag: "--now",
16116
+ arg: "[<id>]",
16117
+ text: "Continue a planned resume now, without waiting for the reset (outside Zed)"
16118
+ },
16119
+ {
16120
+ flag: "--yes",
16121
+ alias: "-y",
16122
+ text: "With --now: don't ask first (needed without a terminal)"
15795
16123
  }
15796
16124
  ],
15797
16125
  footer: [
15798
- "With several planned and a terminal, --cancel lists them to pick one, or all. The ids are",
15799
- "in square brackets in `agent-rewake schedules`."
16126
+ "With several planned and a terminal, --cancel and --now list them to pick one (--cancel also",
16127
+ "all). --now shows what it will send and asks first; without a terminal it needs the id and",
16128
+ "--yes. It stays busy until the agent finishes; Ctrl+C stops it and cancels that resume. If the",
16129
+ "limit is still on, the session is open or changed, you typed after the limit, the agent",
16130
+ "continued, or the message can't reach the agent (signed out, session deleted or archived,",
16131
+ "folder gone), nothing is sent and the resume stays planned. Other errors end it as failed.",
16132
+ "The ids are in square",
16133
+ "brackets in `agent-rewake schedules`."
15800
16134
  ]
15801
16135
  },
15802
16136
  {
@@ -16916,7 +17250,10 @@ function codexAdapter(deps) {
16916
17250
  try {
16917
17251
  const started = s.attempts.length > 0 ? Math.min(...s.attempts.map((a) => a.startedAt)) : void 0;
16918
17252
  if (started === void 0) return void 0;
16919
- return userMessages(readTail2(transcript, 512 * 1024), started).includes(s.text) || void 0;
17253
+ const sent = [s.text, `${s.text}
17254
+
17255
+ ${WORKSPACE_NOTE}`];
17256
+ return userMessages(readTail2(transcript, 512 * 1024), started).some((m) => sent.includes(m)) || void 0;
16920
17257
  } catch {
16921
17258
  return void 0;
16922
17259
  }
@@ -16943,11 +17280,138 @@ var init_adapter = __esm({
16943
17280
  "src/hosts/codex/adapter.ts"() {
16944
17281
  "use strict";
16945
17282
  init_resume();
17283
+ init_workspace();
16946
17284
  init_cli();
16947
17285
  init_rollout();
16948
17286
  }
16949
17287
  });
16950
17288
 
17289
+ // src/core/usage-warn.ts
17290
+ function timeToLimit(samples, now) {
17291
+ const recent = samples.filter((s) => s.t >= now - RECENT_MS);
17292
+ const first = recent[0];
17293
+ const last = recent.at(-1);
17294
+ if (!first || !last || last.t !== now || last.t - first.t < MIN_SPAN_MS) return void 0;
17295
+ const rate = (last.p - first.p) / (last.t - first.t);
17296
+ return rate > 0 ? (100 - last.p) / rate : void 0;
17297
+ }
17298
+ function paceRange(ms) {
17299
+ const five = (x) => Math.max(5, Math.round(x / (5 * MINUTE)) * 5);
17300
+ const lo = five(ms * 0.7);
17301
+ return [lo, Math.max(lo + 5, five(ms * 1.4))];
17302
+ }
17303
+ function usageStep(prev, w, now) {
17304
+ const reset = w.resetsAt;
17305
+ const p = w.percentUsed;
17306
+ if (reset === void 0 || !Number.isFinite(reset) || reset <= now) return void 0;
17307
+ if (!(p >= 0 && p < 100)) return void 0;
17308
+ const base = prev && Math.abs(prev.resetsAt - reset) < SAME_WINDOW_MS ? prev : { resetsAt: reset, samples: [], high: 0, pace: false };
17309
+ const last = base.samples.at(-1);
17310
+ const samples = last?.p === p ? base.samples : [...last && p < last.p ? [] : base.samples, { t: now, p }];
17311
+ const kept = samples.slice(-SAMPLE_CAP);
17312
+ const level = WARN_AT.filter((l) => p >= l && l > base.high).pop();
17313
+ const ms = level === void 0 && !base.pace ? timeToLimit(kept, now) : void 0;
17314
+ const pace = ms !== void 0 && ms <= PACE_HORIZON_MS && now + ms < reset ? paceRange(ms) : void 0;
17315
+ return {
17316
+ next: {
17317
+ resetsAt: reset,
17318
+ samples: kept,
17319
+ high: level ?? base.high,
17320
+ pace: base.pace || pace !== void 0
17321
+ },
17322
+ ...level !== void 0 && { level },
17323
+ ...pace && { pace }
17324
+ };
17325
+ }
17326
+ function limitName(kind) {
17327
+ const hours = /^(\d+)_hour$/.exec(kind)?.[1];
17328
+ const name = kind === "five_hour" ? "5-hour" : kind === "seven_day" ? "7-day" : hours ? `${hours}-hour` : kind.replace(/_/g, " ");
17329
+ return `${name} limit`;
17330
+ }
17331
+ function levelText(kind, percent, reset) {
17332
+ const head = `${limitName(kind)}: ${Math.floor(percent)}% used, as of Codex's last reply. It resets ${reset}.`;
17333
+ return percent >= 95 ? `${head} A good moment to wrap up or save what you are doing.` : head;
17334
+ }
17335
+ function paceText(kind, [lo, hi], reset) {
17336
+ return `At this pace you reach the ${limitName(kind)} in about ${lo} to ${hi} minutes, before it resets ${reset}.`;
17337
+ }
17338
+ var MINUTE, WARN_AT, SAMPLE_CAP, MIN_SPAN_MS, RECENT_MS, PACE_HORIZON_MS, SAME_WINDOW_MS;
17339
+ var init_usage_warn = __esm({
17340
+ "src/core/usage-warn.ts"() {
17341
+ "use strict";
17342
+ MINUTE = 6e4;
17343
+ WARN_AT = [80, 95];
17344
+ SAMPLE_CAP = 12;
17345
+ MIN_SPAN_MS = 10 * MINUTE;
17346
+ RECENT_MS = 60 * MINUTE;
17347
+ PACE_HORIZON_MS = 45 * MINUTE;
17348
+ SAME_WINDOW_MS = 30 * MINUTE;
17349
+ }
17350
+ });
17351
+
17352
+ // src/hosts/codex/usage.ts
17353
+ import { readFileSync as readFileSync33 } from "node:fs";
17354
+ import { dirname as dirname7, join as join44 } from "node:path";
17355
+ function validStored(v) {
17356
+ const out = {};
17357
+ for (const [home, kinds] of Object.entries(v)) {
17358
+ if (typeof kinds !== "object" || kinds === null || Array.isArray(kinds)) continue;
17359
+ const ok = {};
17360
+ for (const [kind, st] of Object.entries(kinds)) {
17361
+ const s = st;
17362
+ if (s && typeof s.resetsAt === "number" && Array.isArray(s.samples)) ok[kind] = st;
17363
+ }
17364
+ out[home] = ok;
17365
+ }
17366
+ return out;
17367
+ }
17368
+ function load(file) {
17369
+ try {
17370
+ const v = JSON.parse(readFileSync33(file, "utf8"));
17371
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? validStored(v) : {};
17372
+ } catch {
17373
+ return {};
17374
+ }
17375
+ }
17376
+ function usageWarning(stateDir2, rollout, now) {
17377
+ try {
17378
+ const windows = lastWindows(readTail2(rollout));
17379
+ if (windows.length === 0) return void 0;
17380
+ const file = join44(stateDir2, FILE);
17381
+ const all = load(file);
17382
+ const home = homeOf(rollout);
17383
+ const kept = { ...all[home] };
17384
+ const texts = [];
17385
+ for (const w of windows) {
17386
+ const step = usageStep(kept[w.kind], w, now);
17387
+ if (!step) continue;
17388
+ kept[w.kind] = step.next;
17389
+ const reset = formatAt(step.next.resetsAt, now);
17390
+ if (step.level !== void 0) texts.push(levelText(w.kind, w.percentUsed, reset));
17391
+ else if (step.pace) texts.push(paceText(w.kind, step.pace, reset));
17392
+ }
17393
+ ensurePrivateDir(stateDir2);
17394
+ replaceFileExclusive(file, `${JSON.stringify({ ...all, [home]: kept })}
17395
+ `, 384);
17396
+ return texts.length > 0 ? JSON.stringify({ systemMessage: `Rewake: ${texts.join(" ")}` }) : void 0;
17397
+ } catch {
17398
+ return void 0;
17399
+ }
17400
+ }
17401
+ var FILE, homeOf;
17402
+ var init_usage = __esm({
17403
+ "src/hosts/codex/usage.ts"() {
17404
+ "use strict";
17405
+ init_time();
17406
+ init_usage_warn();
17407
+ init_fs();
17408
+ init_paths();
17409
+ init_rollout();
17410
+ FILE = "codex-usage.json";
17411
+ homeOf = (rollout) => dirname7(dirname7(dirname7(dirname7(dirname7(rollout)))));
17412
+ }
17413
+ });
17414
+
16951
17415
  // src/hosts/codex/hooks.ts
16952
17416
  import { basename as basename9 } from "node:path";
16953
17417
  function threadOf(input) {
@@ -16994,7 +17458,10 @@ function codexHooks(deps) {
16994
17458
  }
16995
17459
  };
16996
17460
  store.put(resume);
16997
- if (deps.arm(resume.scheduleId, at) !== false) return true;
17461
+ if (deps.arm(resume.scheduleId, at) !== false) {
17462
+ withWorkspace(store, resume, ctx.now);
17463
+ return true;
17464
+ }
16998
17465
  store.cancel(resume.scheduleId, ctx.now);
16999
17466
  return false;
17000
17467
  };
@@ -17016,7 +17483,7 @@ function codexHooks(deps) {
17016
17483
  const m = REWAKE2.exec(prompt2);
17017
17484
  if (!m) {
17018
17485
  cancelPending(ctx, thread.id);
17019
- return void 0;
17486
+ return applySettings(ctx.stateDir).usageWarnings === "off" ? void 0 : usageWarning(ctx.stateDir, thread.path, ctx.now);
17020
17487
  }
17021
17488
  const c = continueOnly(CODEX_PLACE, m[1] ?? "", ctx.now);
17022
17489
  if (c.kind === "reply") return block2(c.text);
@@ -17118,6 +17585,7 @@ var init_hooks = __esm({
17118
17585
  init_time();
17119
17586
  init_closed();
17120
17587
  init_rollout();
17588
+ init_usage();
17121
17589
  CODEX_PLACE = { name: "Codex", typed: "rewake", features: /* @__PURE__ */ new Set() };
17122
17590
  REWAKE2 = /^\s*\/?rewake(?:\s+([\s\S]*?))?\s*$/i;
17123
17591
  block2 = (reason) => JSON.stringify({ decision: "block", reason });
@@ -17276,14 +17744,14 @@ var init_host6 = __esm({
17276
17744
  });
17277
17745
 
17278
17746
  // src/hosts/cursor/host.ts
17279
- import { readFileSync as readFileSync33 } from "node:fs";
17747
+ import { readFileSync as readFileSync34 } from "node:fs";
17280
17748
  function block3(userMessage) {
17281
17749
  return JSON.stringify({ continue: false, user_message: userMessage });
17282
17750
  }
17283
17751
  function transcriptError(path) {
17284
17752
  if (typeof path !== "string" || path === "") return void 0;
17285
17753
  try {
17286
- const lines = readFileSync33(path, "utf8").trimEnd().split("\n");
17754
+ const lines = readFileSync34(path, "utf8").trimEnd().split("\n");
17287
17755
  const last = JSON.parse(lines.at(-1) ?? "{}");
17288
17756
  if (last.type === "turn_ended" && last.status === "error" && typeof last.error === "string")
17289
17757
  return last.error;
@@ -17861,7 +18329,7 @@ var init_hook = __esm({
17861
18329
 
17862
18330
  // src/install/cleanup.ts
17863
18331
  import { existsSync as existsSync27, readdirSync as readdirSync19, rmdirSync, rmSync as rmSync20 } from "node:fs";
17864
- import { dirname as dirname7, join as join44 } from "node:path";
18332
+ import { dirname as dirname8, join as join45 } from "node:path";
17865
18333
  function removeHelpers(stateDir2, timers) {
17866
18334
  for (const id of timerIds(timers)) {
17867
18335
  try {
@@ -17869,16 +18337,16 @@ function removeHelpers(stateDir2, timers) {
17869
18337
  } catch {
17870
18338
  }
17871
18339
  }
17872
- rmSync20(join44(stateDir2, "timers"), { recursive: true, force: true });
18340
+ rmSync20(join45(stateDir2, "timers"), { recursive: true, force: true });
17873
18341
  for (const file of [launcherPath(stateDir2), nodeShimPath(stateDir2)])
17874
18342
  rmSync20(file, { force: true });
17875
- rmSync20(join44(stateDir2, "bin", "agent-rewake.version"), { force: true });
18343
+ rmSync20(join45(stateDir2, "bin", "agent-rewake.version"), { force: true });
17876
18344
  try {
17877
- rmdirSync(join44(stateDir2, "bin"));
18345
+ rmdirSync(join45(stateDir2, "bin"));
17878
18346
  } catch {
17879
18347
  }
17880
- rmSync20(join44(stateDir2, "locks"), { recursive: true, force: true });
17881
- rmSync20(join44(stateDir2, ".pruned"), { force: true });
18348
+ rmSync20(join45(stateDir2, "locks"), { recursive: true, force: true });
18349
+ rmSync20(join45(stateDir2, ".pruned"), { force: true });
17882
18350
  }
17883
18351
  function findBackups(dirs) {
17884
18352
  const found = /* @__PURE__ */ new Set();
@@ -17889,7 +18357,7 @@ function findBackups(dirs) {
17889
18357
  } catch {
17890
18358
  continue;
17891
18359
  }
17892
- for (const n of names) if (n.includes(".agent-rewake-backup-")) found.add(join44(dir, n));
18360
+ for (const n of names) if (n.includes(".agent-rewake-backup-")) found.add(join45(dir, n));
17893
18361
  }
17894
18362
  return [...found].sort();
17895
18363
  }
@@ -17900,11 +18368,11 @@ Rewake is still set up in ${list(f.stillIn)}, so its folder and the files those
17900
18368
  `;
17901
18369
  const kept = [];
17902
18370
  const mine = [
17903
- hasFiles(join44(f.stateDir, "schedules")) && "your scheduled messages",
17904
- hasFiles(join44(f.stateDir, "threads")) && "your thread settings",
17905
- existsSync27(join44(f.stateDir, "settings.json")) && "your settings",
17906
- hasFiles(join44(f.stateDir, "logs")) && "logs (no message text)",
17907
- hasFiles(join44(f.stateDir, "agents")) && "agents Rewake downloaded"
18371
+ hasFiles(join45(f.stateDir, "schedules")) && "your scheduled messages",
18372
+ hasFiles(join45(f.stateDir, "threads")) && "your thread settings",
18373
+ existsSync27(join45(f.stateDir, "settings.json")) && "your settings",
18374
+ hasFiles(join45(f.stateDir, "logs")) && "logs (no message text)",
18375
+ hasFiles(join45(f.stateDir, "agents")) && "agents Rewake downloaded"
17908
18376
  ].filter((x) => typeof x === "string");
17909
18377
  if (mine.length > 0) kept.push(`Rewake's folder, with ${list(mine)}: ${f.stateDir}`);
17910
18378
  for (const b of f.backups) kept.push(`A copy Rewake made of a file before changing it: ${b}`);
@@ -17930,9 +18398,9 @@ function finishUninstall(o) {
17930
18398
  removeHelpers(state, defaultTimerHost(state, "node", launcherPath(state)));
17931
18399
  const backups = findBackups([
17932
18400
  zedDir,
17933
- join44(o.home, ".cursor"),
17934
- dirname7(jetbrainsFile(o.env, o.home)),
17935
- dirname7(devinFile(o.env, o.home, o.platform))
18401
+ join45(o.home, ".cursor"),
18402
+ dirname8(jetbrainsFile(o.env, o.home)),
18403
+ dirname8(devinFile(o.env, o.home, o.platform))
17936
18404
  ]);
17937
18405
  const text2 = existsSync27(state) ? leftoverText({ stateDir: state, stillIn, backups, codex: o.chosen.includes("codex") }) : void 0;
17938
18406
  return { text: text2, remains: stillIn.length > 0 };
@@ -18747,11 +19215,11 @@ var init_report = __esm({
18747
19215
  });
18748
19216
 
18749
19217
  // src/timers/slots.ts
18750
- import { readFileSync as readFileSync34 } from "node:fs";
18751
- import { join as join45 } from "node:path";
19218
+ import { readFileSync as readFileSync35 } from "node:fs";
19219
+ import { join as join46 } from "node:path";
18752
19220
  function lastStart2(stateDir2) {
18753
19221
  try {
18754
- const n = Number(readFileSync34(join45(stateDir2, "locks", START_FILE), "utf8").trim());
19222
+ const n = Number(readFileSync35(join46(stateDir2, "locks", START_FILE), "utf8").trim());
18755
19223
  return Number.isFinite(n) ? n : 0;
18756
19224
  } catch {
18757
19225
  return 0;
@@ -18781,7 +19249,7 @@ async function takeSlot(o) {
18781
19249
  try {
18782
19250
  const wait2 = Math.min(lastStart2(o.stateDir) + gap - clock2(), deadline - clock2());
18783
19251
  if (wait2 > 0) await sleep(wait2);
18784
- writeFileAtomic(ensurePrivateDir(join45(o.stateDir, "locks")), START_FILE, `${clock2()}
19252
+ writeFileAtomic(ensurePrivateDir(join46(o.stateDir, "locks")), START_FILE, `${clock2()}
18785
19253
  `);
18786
19254
  } finally {
18787
19255
  if (mine) lock.release(START_KEY);
@@ -18816,9 +19284,17 @@ import { basename as basename10 } from "node:path";
18816
19284
  function runningText(at, noun = "session") {
18817
19285
  return `${at}: Rewake is continuing the ${noun} now. You'll get a notification when it's done. To stop it: "${rewake("continue --cancel")}".`;
18818
19286
  }
19287
+ function foregroundText(at, noun = "session") {
19288
+ return `${at}: Rewake is continuing the ${noun} now. Press Ctrl+C to stop it.`;
19289
+ }
18819
19290
  function doneText(at, noun = "session", now = Date.now()) {
18820
19291
  return `${at}: Rewake's run ended normally ${formatAt(now, now)}. Open the ${noun} to see what it did.`;
18821
19292
  }
19293
+ function workspaceNoteText(noun, added) {
19294
+ const what = `The project's tracked files or latest commit changed while the ${noun} waited`;
19295
+ 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.";
19296
+ return `${what}${added ? "," : "."} ${how} Change this with: ${rewake("settings workspace-changed")} (tell, note, hold or off).`;
19297
+ }
18822
19298
  function where(host, s) {
18823
19299
  const folder = s.cwd ? basename10(s.cwd) : "";
18824
19300
  return folder ? `${host.name} in the "${folder}" folder` : host.name;
@@ -18832,6 +19308,13 @@ function notice(why, agent, now, f) {
18832
19308
  return `${agent}: the usage limit has reset. The ${n} is open, so Rewake didn't send anything. Continue it there.`;
18833
19309
  case "changed":
18834
19310
  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.`;
19311
+ case "workspace": {
19312
+ const what = [
19313
+ ...f.workspace?.commit ? ["its latest commit moved"] : [],
19314
+ ...f.workspace?.changes ? ["its tracked files differ"] : []
19315
+ ].join(" and ");
19316
+ 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).`;
19317
+ }
18835
19318
  case "late":
18836
19319
  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
19320
  case "far-reset":
@@ -18880,6 +19363,14 @@ function rearmEarly(id, store, deps, now) {
18880
19363
  deps.log?.("fire.early", { armed: armed.ok });
18881
19364
  return "early";
18882
19365
  }
19366
+ function keptOutcome(d) {
19367
+ if (d.action === "notify") return d.why === "open" || d.why === "changed" ? d.why : "limited";
19368
+ if (d.action === "skip") {
19369
+ if (d.why === "typed" || d.why === "native") return d.why;
19370
+ return d.why === "expired" ? "limited" : "gone";
19371
+ }
19372
+ return "limited";
19373
+ }
18883
19374
  async function fire(id, deps) {
18884
19375
  const store = new ScheduleStore(deps.stateDir);
18885
19376
  const log = deps.log ?? (() => {
@@ -18905,7 +19396,7 @@ async function fire(id, deps) {
18905
19396
  }
18906
19397
  const lock = new SessionLock(deps.stateDir);
18907
19398
  const lockKey = `fire:${id}`;
18908
- if (first.status !== "cancelled" && now < first.dueAt - EARLY_MS) {
19399
+ if (!deps.immediate && first.status !== "cancelled" && now < first.dueAt - EARLY_MS) {
18909
19400
  if (!lock.acquire(lockKey)) return "early";
18910
19401
  try {
18911
19402
  return rearmEarly(id, store, deps, now);
@@ -18931,7 +19422,7 @@ async function fire(id, deps) {
18931
19422
  removeTimer();
18932
19423
  return "gone";
18933
19424
  }
18934
- if (s.status !== "cancelled" && now < s.dueAt - EARLY_MS)
19425
+ if (!deps.immediate && s.status !== "cancelled" && now < s.dueAt - EARLY_MS)
18935
19426
  return rearmEarly(id, store, deps, now);
18936
19427
  const wait2 = (until, why, count = true) => {
18937
19428
  const next = Math.max(until, now + MIN_ARM_MS);
@@ -18964,7 +19455,10 @@ async function fire(id, deps) {
18964
19455
  const place = await takeSlot({ stateDir: deps.stateDir, ...deps.slots }).catch(
18965
19456
  () => "error"
18966
19457
  );
18967
- if (place === void 0) return wait2(now + MIN_ARM_MS, "no-free-place", false);
19458
+ if (place === void 0) {
19459
+ if (deps.immediate) return "busy";
19460
+ return wait2(now + MIN_ARM_MS, "no-free-place", false);
19461
+ }
18968
19462
  if (place !== "error") slot.release = place;
18969
19463
  }
18970
19464
  const settings = loadSettings(deps.stateDir);
@@ -18977,7 +19471,7 @@ async function fire(id, deps) {
18977
19471
  }
18978
19472
  const decision = decideFire({
18979
19473
  resume: {
18980
- dueAt: s.dueAt,
19474
+ dueAt: deps.immediate ? now : s.dueAt,
18981
19475
  status: s.status,
18982
19476
  ...s.rearms !== void 0 && { rearms: s.rearms }
18983
19477
  },
@@ -18997,7 +19491,7 @@ async function fire(id, deps) {
18997
19491
  }),
18998
19492
  now
18999
19493
  );
19000
- const tell = (why, status, cause, message) => {
19494
+ const tell = (why, status, cause, message, workspace) => {
19001
19495
  settle(status, cause ?? why, message);
19002
19496
  if (facts.newResetsAt !== void 0 && (why === "far-reset" || why === "expired"))
19003
19497
  store.update(id, (x) => ({ ...x, dueAt: (facts.newResetsAt ?? 0) + RESET_MARGIN_MS }), now);
@@ -19010,12 +19504,17 @@ async function fire(id, deps) {
19010
19504
  ...host.again !== void 0 && { again: host.again },
19011
19505
  dueAt: s.dueAt,
19012
19506
  ...facts.newResetsAt !== void 0 && { resetsAt: facts.newResetsAt },
19013
- ...cause && { cause }
19507
+ ...cause && { cause },
19508
+ ...workspace && { workspace }
19014
19509
  })
19015
19510
  );
19016
19511
  removeTimer();
19017
19512
  return why === "failed" || why === "expired" ? "failed" : "notified";
19018
19513
  };
19514
+ if (deps.immediate && decision.action !== "send") {
19515
+ log("fire.kept", { host: host.id, action: decision.action });
19516
+ return keptOutcome(decision);
19517
+ }
19019
19518
  log("fire.decide", {
19020
19519
  host: host.id,
19021
19520
  action: decision.action,
@@ -19023,6 +19522,19 @@ async function fire(id, deps) {
19023
19522
  });
19024
19523
  switch (decision.action) {
19025
19524
  case "send": {
19525
+ let noted = false;
19526
+ let changed = false;
19527
+ if (s.workspace && settings.workspaceChanged !== "off") {
19528
+ const here = fingerprintWorkspace(s.cwd, FIRE_BUDGET_MS);
19529
+ const differs = here && workspaceChange(s.workspace, here);
19530
+ if (differs) {
19531
+ log("fire.workspace", { action: settings.workspaceChanged });
19532
+ if (settings.workspaceChanged === "hold")
19533
+ return tell("workspace", "needs_attention", void 0, void 0, differs);
19534
+ changed = true;
19535
+ noted = settings.workspaceChanged === "note";
19536
+ }
19537
+ }
19026
19538
  const claimed = store.update(
19027
19539
  id,
19028
19540
  (x) => x.status !== s.status || x.dueAt !== s.dueAt ? void 0 : {
@@ -19050,6 +19562,7 @@ async function fire(id, deps) {
19050
19562
  const wake = deps.wake ?? new Wakefulness();
19051
19563
  wake.set(true, settings.keepAwake);
19052
19564
  let watch2;
19565
+ let told = false;
19053
19566
  onResumeRun((pid) => {
19054
19567
  store.update(
19055
19568
  id,
@@ -19059,7 +19572,12 @@ async function fire(id, deps) {
19059
19572
  }),
19060
19573
  now
19061
19574
  );
19062
- deps.notify("Agent Rewake", runningText(at, host.noun));
19575
+ told = changed;
19576
+ const running = deps.immediate ? foregroundText(at, host.noun) : runningText(at, host.noun);
19577
+ deps.notify(
19578
+ "Agent Rewake",
19579
+ changed ? `${running} ${workspaceNoteText(host.noun, noted)}` : running
19580
+ );
19063
19581
  watch2 = setInterval(() => {
19064
19582
  if (!store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped) return;
19065
19583
  clearInterval(watch2);
@@ -19067,7 +19585,12 @@ async function fire(id, deps) {
19067
19585
  }, deps.stopPollMs ?? STOP_POLL_MS);
19068
19586
  });
19069
19587
  try {
19070
- result = await host.send(s, key2);
19588
+ result = await host.send(
19589
+ noted ? { ...s, text: `${s.text}
19590
+
19591
+ ${WORKSPACE_NOTE}` } : s,
19592
+ key2
19593
+ );
19071
19594
  } catch (err) {
19072
19595
  result = { ok: false, reason: "failed", detail: err.message };
19073
19596
  } finally {
@@ -19075,7 +19598,7 @@ async function fire(id, deps) {
19075
19598
  if (watch2) clearInterval(watch2);
19076
19599
  wake.release();
19077
19600
  }
19078
- if (store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped) {
19601
+ if (store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.stopped && !(deps.immediate && result.ok)) {
19079
19602
  settle("stopped", "you");
19080
19603
  removeTimer();
19081
19604
  log("fire.stopped", {});
@@ -19093,10 +19616,25 @@ async function fire(id, deps) {
19093
19616
  if (result.ok) {
19094
19617
  settle("sent");
19095
19618
  removeTimer();
19096
- if (store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.pid !== void 0)
19619
+ if (changed && !told)
19620
+ deps.notify("Agent Rewake", `${at}: ${workspaceNoteText(host.noun, noted)}`);
19621
+ if (!deps.immediate && store.get(id)?.attempts.find((a) => a.idempotencyKey === key2)?.pid !== void 0)
19097
19622
  deps.notify("Agent Rewake", doneText(at, host.noun, deps.now()));
19098
19623
  return "sent";
19099
19624
  }
19625
+ if (deps.immediate && (result.reason === "limited" || result.reason === "busy")) {
19626
+ store.update(
19627
+ id,
19628
+ (x) => ({
19629
+ ...x,
19630
+ status: s.status,
19631
+ attempts: x.attempts.filter((a) => a.idempotencyKey !== key2)
19632
+ }),
19633
+ now
19634
+ );
19635
+ log("fire.still-limited", { host: host.id, now: true });
19636
+ return result.reason === "limited" ? "limited" : "busy";
19637
+ }
19100
19638
  if (result.reason === "limited" || result.reason === "busy") {
19101
19639
  const rearms = s.rearms ?? 0;
19102
19640
  const reset = result.reason === "limited" ? result.resetsAt : void 0;
@@ -19118,6 +19656,27 @@ async function fire(id, deps) {
19118
19656
  "needs-approval",
19119
19657
  "folder-gone"
19120
19658
  ].includes(result.detail ?? "") ? result.detail : void 0;
19659
+ if (deps.immediate && cause !== void 0 && !["timeout", "needs-approval"].includes(cause)) {
19660
+ store.update(
19661
+ id,
19662
+ (x) => ({
19663
+ ...x,
19664
+ status: s.status,
19665
+ attempts: x.attempts.filter((a) => a.idempotencyKey !== key2)
19666
+ }),
19667
+ now
19668
+ );
19669
+ deps.notify(
19670
+ "Agent Rewake",
19671
+ notice("failed", at, now, {
19672
+ noun: host.noun,
19673
+ agentName: host.name,
19674
+ ...host.reopen !== void 0 && { reopen: host.reopen },
19675
+ cause
19676
+ })
19677
+ );
19678
+ return "failed";
19679
+ }
19121
19680
  return tell("failed", "failed", cause, result.message);
19122
19681
  }
19123
19682
  case "wait":
@@ -19193,6 +19752,7 @@ var init_fire = __esm({
19193
19752
  init_command2();
19194
19753
  init_keep_awake();
19195
19754
  init_spawn2();
19755
+ init_workspace();
19196
19756
  init_slots();
19197
19757
  init_timers();
19198
19758
  EARLY_MS = 3e4;
@@ -19206,14 +19766,14 @@ var init_fire = __esm({
19206
19766
  });
19207
19767
 
19208
19768
  // src/timers/login.ts
19209
- import { existsSync as existsSync29, mkdirSync as mkdirSync3, readFileSync as readFileSync35, rmSync as rmSync21 } from "node:fs";
19210
- import { dirname as dirname8, join as join46 } from "node:path";
19769
+ import { existsSync as existsSync29, mkdirSync as mkdirSync3, readFileSync as readFileSync36, rmSync as rmSync21 } from "node:fs";
19770
+ import { dirname as dirname9, join as join47 } from "node:path";
19211
19771
  function loginItem(h) {
19212
19772
  const args = [h.node, h.cli, "sweep", "--state-dir", h.stateDir];
19213
19773
  if (h.platform === "darwin")
19214
19774
  return {
19215
19775
  kind: "launchd",
19216
- path: join46(h.home, "Library", "LaunchAgents", `${LOGIN_LABEL}.plist`),
19776
+ path: join47(h.home, "Library", "LaunchAgents", `${LOGIN_LABEL}.plist`),
19217
19777
  text: `<?xml version="1.0" encoding="UTF-8"?>
19218
19778
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
19219
19779
  <plist version="1.0">
@@ -19233,7 +19793,7 @@ function loginItem(h) {
19233
19793
  if (h.exists("/run/systemd/system"))
19234
19794
  return {
19235
19795
  kind: "systemd",
19236
- path: join46(h.home, ".config", "systemd", "user", UNIT),
19796
+ path: join47(h.home, ".config", "systemd", "user", UNIT),
19237
19797
  text: `[Unit]
19238
19798
  Description=Agent Rewake: set planned resumes again after a restart
19239
19799
 
@@ -19247,7 +19807,7 @@ WantedBy=default.target
19247
19807
  };
19248
19808
  return {
19249
19809
  kind: "autostart",
19250
- path: join46(h.home, ".config", "autostart", "agent-rewake-sweep.desktop"),
19810
+ path: join47(h.home, ".config", "autostart", "agent-rewake-sweep.desktop"),
19251
19811
  text: `[Desktop Entry]
19252
19812
  Type=Application
19253
19813
  Name=Agent Rewake
@@ -19265,9 +19825,9 @@ function syncLoginItem(h, wanted) {
19265
19825
  if (!item) return "unchanged";
19266
19826
  const had = existsSync29(item.path);
19267
19827
  if (wanted) {
19268
- const same2 = had && readFileSync35(item.path, "utf8") === item.text;
19828
+ const same2 = had && readFileSync36(item.path, "utf8") === item.text;
19269
19829
  if (same2) return "unchanged";
19270
- mkdirSync3(dirname8(item.path), { recursive: true });
19830
+ mkdirSync3(dirname9(item.path), { recursive: true });
19271
19831
  replaceFileExclusive(item.path, item.text, 420);
19272
19832
  if (item.kind === "systemd") {
19273
19833
  h.run("systemctl", ["--user", "daemon-reload"]);
@@ -19313,10 +19873,10 @@ import {
19313
19873
  utimesSync,
19314
19874
  writeFileSync as writeFileSync7
19315
19875
  } from "node:fs";
19316
- import { join as join47 } from "node:path";
19876
+ import { join as join48 } from "node:path";
19317
19877
  function pruneState(stateDir2, now, opts = {}) {
19318
19878
  if (!existsSync30(stateDir2)) return void 0;
19319
- const marker = join47(stateDir2, MARKER);
19879
+ const marker = join48(stateDir2, MARKER);
19320
19880
  if (!opts.force)
19321
19881
  try {
19322
19882
  if (now - statSync10(marker).mtimeMs < EVERY_MS) return void 0;
@@ -19347,7 +19907,7 @@ function pruneState(stateDir2, now, opts = {}) {
19347
19907
  function pruneSessions(stateDir2, cutoff, needed) {
19348
19908
  let hosts;
19349
19909
  try {
19350
- hosts = readdirSync20(join47(stateDir2, "hosts"));
19910
+ hosts = readdirSync20(join48(stateDir2, "hosts"));
19351
19911
  } catch {
19352
19912
  return 0;
19353
19913
  }
@@ -19363,7 +19923,7 @@ function pruneSessions(stateDir2, cutoff, needed) {
19363
19923
  return n;
19364
19924
  }
19365
19925
  function pruneLogs(stateDir2, cutoff) {
19366
- const dir = join47(stateDir2, "logs");
19926
+ const dir = join48(stateDir2, "logs");
19367
19927
  let names;
19368
19928
  try {
19369
19929
  names = readdirSync20(dir);
@@ -19376,7 +19936,7 @@ function pruneLogs(stateDir2, cutoff) {
19376
19936
  const m = /^rewake-(\d{4}-\d{2}-\d{2})\.jsonl$/.exec(f);
19377
19937
  if (!m || m[1] >= oldest) continue;
19378
19938
  try {
19379
- rmSync22(join47(dir, f), { force: true });
19939
+ rmSync22(join48(dir, f), { force: true });
19380
19940
  n++;
19381
19941
  } catch {
19382
19942
  }
@@ -19392,7 +19952,7 @@ function pruneTemp(dir, before, depth = 0) {
19392
19952
  }
19393
19953
  let n = 0;
19394
19954
  for (const e of entries) {
19395
- const path = join47(dir, e.name);
19955
+ const path = join48(dir, e.name);
19396
19956
  if (e.isDirectory()) {
19397
19957
  if (depth < MAX_DEPTH && !SKIP_DIRS.has(e.name)) n += pruneTemp(path, before, depth + 1);
19398
19958
  } else if (e.isFile() && TEMP_NAME.test(e.name)) {
@@ -19647,8 +20207,8 @@ var init_input = __esm({
19647
20207
  });
19648
20208
 
19649
20209
  // src/ui/page.ts
19650
- import { readFileSync as readFileSync36, writeFileSync as writeFileSync8 } from "node:fs";
19651
- import { join as join48 } from "node:path";
20210
+ import { readFileSync as readFileSync37, writeFileSync as writeFileSync8 } from "node:fs";
20211
+ import { join as join49 } from "node:path";
19652
20212
  function rowLabel(r) {
19653
20213
  if (r.host === void 0) return threadLabel(r.thread, r.schedule);
19654
20214
  const title = r.thread?.title ?? `${capital(r.noun)} ${r.schedule.sessionId.slice(0, 8)}`;
@@ -19906,11 +20466,11 @@ var init_page = __esm({
19906
20466
  return this.rows[this.selected];
19907
20467
  }
19908
20468
  prefsFile() {
19909
- return join48(this.opts.stateDir, "ui.json");
20469
+ return join49(this.opts.stateDir, "ui.json");
19910
20470
  }
19911
20471
  prefs() {
19912
20472
  try {
19913
- return JSON.parse(readFileSync36(this.prefsFile(), "utf8"));
20473
+ return JSON.parse(readFileSync37(this.prefsFile(), "utf8"));
19914
20474
  } catch {
19915
20475
  return {};
19916
20476
  }
@@ -20840,11 +21400,11 @@ var init_tui = __esm({
20840
21400
  });
20841
21401
 
20842
21402
  // src/util/installed.ts
20843
- import { readFileSync as readFileSync37 } from "node:fs";
20844
- import { join as join49 } from "node:path";
21403
+ import { readFileSync as readFileSync38 } from "node:fs";
21404
+ import { join as join50 } from "node:path";
20845
21405
  function readInstalled(stateDir2) {
20846
21406
  try {
20847
- const v = JSON.parse(readFileSync37(join49(stateDir2, FILE), "utf8"));
21407
+ const v = JSON.parse(readFileSync38(join50(stateDir2, FILE2), "utf8"));
20848
21408
  return typeof v.version === "string" && typeof v.at === "number" && Number.isFinite(v.at) ? { version: v.version, at: v.at } : void 0;
20849
21409
  } catch {
20850
21410
  return void 0;
@@ -20855,26 +21415,26 @@ function recordInstall(stateDir2, version, now) {
20855
21415
  try {
20856
21416
  writeFileAtomic(
20857
21417
  ensurePrivateDir(stateDir2),
20858
- FILE,
21418
+ FILE2,
20859
21419
  `${JSON.stringify({ version, at: now })}
20860
21420
  `
20861
21421
  );
20862
21422
  } catch {
20863
21423
  }
20864
21424
  }
20865
- var FILE;
21425
+ var FILE2;
20866
21426
  var init_installed = __esm({
20867
21427
  "src/util/installed.ts"() {
20868
21428
  "use strict";
20869
21429
  init_store();
20870
21430
  init_paths();
20871
- FILE = "installed.json";
21431
+ FILE2 = "installed.json";
20872
21432
  }
20873
21433
  });
20874
21434
 
20875
21435
  // src/util/log.ts
20876
21436
  import { appendFileSync } from "node:fs";
20877
- import { join as join50 } from "node:path";
21437
+ import { join as join51 } from "node:path";
20878
21438
  var Logger;
20879
21439
  var init_log = __esm({
20880
21440
  "src/util/log.ts"() {
@@ -20911,8 +21471,8 @@ var init_log = __esm({
20911
21471
  try {
20912
21472
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20913
21473
  if (!this.file?.endsWith(`rewake-${day}.jsonl`)) {
20914
- const dir = ensurePrivateDir(join50(stateDir(this.env), "logs"));
20915
- this.file = join50(dir, `rewake-${day}.jsonl`);
21474
+ const dir = ensurePrivateDir(join51(stateDir(this.env), "logs"));
21475
+ this.file = join51(dir, `rewake-${day}.jsonl`);
20916
21476
  }
20917
21477
  const record = { t: (/* @__PURE__ */ new Date()).toISOString(), level, event, pid: process.pid, ...fields2 };
20918
21478
  appendFileSync(this.file, `${JSON.stringify(record)}
@@ -20935,9 +21495,9 @@ __export(cli_exports, {
20935
21495
  testTiming: () => testTiming
20936
21496
  });
20937
21497
  import { spawnSync as spawnSync13 } from "node:child_process";
20938
- import { existsSync as existsSync31, readdirSync as readdirSync21, readFileSync as readFileSync38 } from "node:fs";
21498
+ import { existsSync as existsSync31, readdirSync as readdirSync21, readFileSync as readFileSync39 } from "node:fs";
20939
21499
  import { homedir as homedir23 } from "node:os";
20940
- import { join as join51 } from "node:path";
21500
+ import { join as join52 } from "node:path";
20941
21501
  import { emitKeypressEvents } from "node:readline";
20942
21502
  import { createInterface as createInterface6 } from "node:readline/promises";
20943
21503
  function agentIdentity(target, env) {
@@ -21277,14 +21837,23 @@ ${loginItemPlanText(process.platform)}`);
21277
21837
  return code3;
21278
21838
  }
21279
21839
  if (first === "continue") {
21840
+ if (argv.includes("--now")) {
21841
+ const rest = argv.slice(1).filter((a) => a !== "--now");
21842
+ const yes = rest.some((a) => a === "--yes" || a === "-y");
21843
+ const names = rest.filter((a) => a !== "--yes" && a !== "-y");
21844
+ const nowId = names[0];
21845
+ if (names.length > 1 || nowId?.startsWith("-")) {
21846
+ process.stderr.write(CONTINUE_USAGE);
21847
+ return 2;
21848
+ }
21849
+ return runContinueCommand(env, "now", void 0, nowId, yes);
21850
+ }
21280
21851
  const flag = argv[1];
21281
21852
  const mode = flag === "--always" ? "always" : flag === "--ask" ? "ask" : flag === "--cancel" ? "cancel" : void 0;
21282
21853
  const cancelId = mode === "cancel" ? argv[2] : void 0;
21283
21854
  const extra = argv.length > (cancelId === void 0 ? 2 : 3);
21284
21855
  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
- );
21856
+ process.stderr.write(CONTINUE_USAGE);
21288
21857
  return 2;
21289
21858
  }
21290
21859
  return runContinueCommand(env, mode, cancelId);
@@ -21337,7 +21906,7 @@ ${loginItemPlanText(process.platform)}`);
21337
21906
  const sep2 = argv.indexOf("--");
21338
21907
  if (wrap) {
21339
21908
  try {
21340
- const npmLog = join51(ensurePrivateDir(join51(stateDir(env), "logs")), "npm-install.log");
21909
+ const npmLog = join52(ensurePrivateDir(join52(stateDir(env), "logs")), "npm-install.log");
21341
21910
  agent = await wrappedAgentCommand(wrap.target, wrap.extra, env, stateDir(env), npmLog);
21342
21911
  } catch (err) {
21343
21912
  log.error("agent.resolve_failed", {
@@ -21543,7 +22112,7 @@ ${usageText()}
21543
22112
  return void 0;
21544
22113
  }
21545
22114
  function toolsRefused(state) {
21546
- const dir = join51(state, "logs");
22115
+ const dir = join52(state, "logs");
21547
22116
  const agents = /* @__PURE__ */ new Set();
21548
22117
  let files = [];
21549
22118
  try {
@@ -21552,7 +22121,7 @@ function toolsRefused(state) {
21552
22121
  return [];
21553
22122
  }
21554
22123
  for (const f of files) {
21555
- for (const line2 of readFileSync38(join51(dir, f), "utf8").split("\n")) {
22124
+ for (const line2 of readFileSync39(join52(dir, f), "utf8").split("\n")) {
21556
22125
  if (!line2.includes("agent_tools.refused")) continue;
21557
22126
  try {
21558
22127
  const r = JSON.parse(line2);
@@ -21579,7 +22148,7 @@ function setupZedText(env) {
21579
22148
  const task = [taskEntry(launch)];
21580
22149
  const keymap = [{ bindings: { [keyChord()]: ["task::Spawn", { task_name: TASK_LABEL }] } }];
21581
22150
  const dir = zedConfigDir();
21582
- const file = (name) => join51(dir, name);
22151
+ const file = (name) => join52(dir, name);
21583
22152
  return [
21584
22153
  `Easiest: run \`${rewake("install")}\`, which adds these for you after asking.`,
21585
22154
  "To add them by hand instead:",
@@ -22014,14 +22583,70 @@ async function prompt(question) {
22014
22583
  rl.close();
22015
22584
  }
22016
22585
  }
22017
- async function runContinueCommand(env, mode, cancelId) {
22586
+ async function runContinueCommand(env, mode, cancelId, nowId, yes = false) {
22018
22587
  const { state, timers, sweepDeps, node } = timerDeps(env);
22019
22588
  sweepQuietly(env);
22020
22589
  const adapters = hostAdapters(env, node, state);
22021
- return runContinue({
22590
+ const log = new Logger(env);
22591
+ let interrupted = false;
22592
+ let sentAnyway = false;
22593
+ const code2 = await runContinue({
22022
22594
  sleepSettings: () => readSleepSettings({ env }),
22023
22595
  ...mode && { mode },
22024
22596
  ...cancelId !== void 0 && { cancelId },
22597
+ ...nowId !== void 0 && { nowId },
22598
+ yes,
22599
+ // The resume's own timer run, at once; what it would tell by notification is said here.
22600
+ fireNow: async (id) => {
22601
+ const store = new ScheduleStore(state);
22602
+ const onInterrupt = () => {
22603
+ if (interrupted) {
22604
+ const pid = store.get(id)?.attempts.at(-1)?.pid;
22605
+ if (pid !== void 0) {
22606
+ killPidTree(pid, void 0, void 0, "SIGKILL");
22607
+ process.stdout.write("Ended the agent now.\n");
22608
+ }
22609
+ return;
22610
+ }
22611
+ interrupted = true;
22612
+ const s = store.get(id);
22613
+ if (s !== void 0 && s.status !== "sending" && !WAITING_STATES.has(s.status)) return;
22614
+ if (s?.status !== "sending") {
22615
+ process.stdout.write("Stopped. Nothing was sent, and your resume is still planned.\n");
22616
+ process.exit(130);
22617
+ }
22618
+ store.update(
22619
+ id,
22620
+ (x) => ({
22621
+ ...x,
22622
+ attempts: x.attempts.map(
22623
+ (a, i) => i === x.attempts.length - 1 ? { ...a, stopped: true } : a
22624
+ )
22625
+ }),
22626
+ Date.now()
22627
+ );
22628
+ process.stdout.write(
22629
+ "Stopping. The agent ends within a few seconds; what it has done so far stays. The planned resume is cancelled too.\n"
22630
+ );
22631
+ };
22632
+ process.on("SIGINT", onInterrupt);
22633
+ try {
22634
+ const outcome2 = await fire(id, {
22635
+ stateDir: state,
22636
+ now: Date.now,
22637
+ hosts: adapters,
22638
+ timers,
22639
+ notify: (_title, body) => process.stdout.write(`${body}
22640
+ `),
22641
+ log: (event, fields2) => log.info(event, fields2),
22642
+ immediate: true
22643
+ });
22644
+ sentAnyway = outcome2 === "sent";
22645
+ return outcome2;
22646
+ } finally {
22647
+ process.off("SIGINT", onInterrupt);
22648
+ }
22649
+ },
22025
22650
  // Codex's resumes too, which `continue` doesn't arm itself.
22026
22651
  hostOf: (id) => adapters.get(id),
22027
22652
  hosts: CLOSED_HOSTS,
@@ -22041,11 +22666,12 @@ async function runContinueCommand(env, mode, cancelId) {
22041
22666
  err: (t) => process.stderr.write(t),
22042
22667
  ask: prompt
22043
22668
  });
22669
+ return interrupted && !sentAnyway ? 130 : code2;
22044
22670
  }
22045
22671
  function previewNames(env) {
22046
22672
  return installedPreviews(env, homedir23(), stateDir(env)).map((id) => PREVIEW_NAMES[id] ?? id);
22047
22673
  }
22048
- var INSTALL_PLACES, KNOWN_OPTIONS, TIMER_PLACES;
22674
+ var INSTALL_PLACES, KNOWN_OPTIONS, WAITING_STATES, CONTINUE_USAGE, TIMER_PLACES;
22049
22675
  var init_cli2 = __esm({
22050
22676
  "src/cli.ts"() {
22051
22677
  "use strict";
@@ -22120,6 +22746,8 @@ var init_cli2 = __esm({
22120
22746
  schedules: ["--all", "--json", "--explain"],
22121
22747
  history: ["--days"]
22122
22748
  };
22749
+ WAITING_STATES = /* @__PURE__ */ new Set(["scheduled", "waiting_for_limit"]);
22750
+ CONTINUE_USAGE = "agent-rewake: usage: agent-rewake continue [--always | --ask | --cancel [<id>] | --now [<id>] [--yes]]\n";
22123
22751
  TIMER_PLACES = /* @__PURE__ */ new Set([
22124
22752
  "codex",
22125
22753
  "copilot-cli",