@codizelabs/agent-rewake 0.2.0 → 0.3.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.
|
@@ -144,7 +144,7 @@ const startOfDay = (ms) => {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
* "3:05 PM today", "9:00 AM tomorrow
|
|
147
|
+
* "3:05 PM today", "9:00 AM tomorrow, Thursday", "Friday at 9:00 AM", "Monday 12 October at
|
|
148
148
|
* 9:00 AM": Rewake's own wording for times (src/core/time.ts formatWhen), in the host's time zone.
|
|
149
149
|
*/
|
|
150
150
|
export function when(ms, now, clock = "12h") {
|
|
@@ -152,7 +152,7 @@ export function when(ms, now, clock = "12h") {
|
|
|
152
152
|
const dayDiff = Math.round((startOfDay(ms) - startOfDay(now)) / 86_400_000);
|
|
153
153
|
const weekday = new Intl.DateTimeFormat("en-US", { weekday: "long" }).format(ms);
|
|
154
154
|
if (dayDiff === 0) return `${time} today`;
|
|
155
|
-
if (dayDiff === 1) return `${time} tomorrow
|
|
155
|
+
if (dayDiff === 1) return `${time} tomorrow, ${weekday}`;
|
|
156
156
|
if (dayDiff > 1 && dayDiff < 7) return `${weekday} at ${time}`;
|
|
157
157
|
const date = new Intl.DateTimeFormat("en-US", { day: "numeric", month: "long" }).format(ms);
|
|
158
158
|
return `${weekday} ${date} at ${time}`;
|
|
@@ -56,6 +56,13 @@ const QUESTION =
|
|
|
56
56
|
|
|
57
57
|
let isInteractive = false;
|
|
58
58
|
let surface = null;
|
|
59
|
+
/**
|
|
60
|
+
* Claude Code's own "Continue automatically at usage limit" as this session started with it: the
|
|
61
|
+
* running Claude Code keeps that value, so a change in the settings file mid-session doesn't count.
|
|
62
|
+
*/
|
|
63
|
+
let nativeSetting;
|
|
64
|
+
/** How long after the reset Claude Code's own continue may take before Rewake steps in. */
|
|
65
|
+
const NATIVE_GRACE_MS = 2 * 60_000;
|
|
59
66
|
/** How Claude Code was started (`CLAUDE_CODE_ENTRYPOINT`): "cli", "claude-vscode", "sdk-ts"… */
|
|
60
67
|
let entrypoint;
|
|
61
68
|
/** Sessions whose limit is being handled right now (a burst of StopFailures is one limit). */
|
|
@@ -67,6 +74,10 @@ let clock = "12h";
|
|
|
67
74
|
let armed;
|
|
68
75
|
/** `<stateDir>/hosts/claude-code/sessions`, when the installer said where the state folder is. */
|
|
69
76
|
let mirrorDir;
|
|
77
|
+
/** Rewake's shared "automatic resume" (settings.json newThreads): "on" counts as "always" here. */
|
|
78
|
+
let sharedAuto;
|
|
79
|
+
/** The session's folder, for where Rewake's CLI names it ("in the shop folder"). */
|
|
80
|
+
let sessionCwd;
|
|
70
81
|
/** This process, in a send's claim: two Claude Code processes can have one session open. */
|
|
71
82
|
const ME = Math.random().toString(36).slice(2);
|
|
72
83
|
/** A claim older than this was left by a process that stopped mid-send. */
|
|
@@ -95,6 +106,7 @@ async function loadConfig($) {
|
|
|
95
106
|
try {
|
|
96
107
|
const settings = JSON.parse(await $.fs.read(`${cfg.stateDir}/settings.json`));
|
|
97
108
|
if (settings.clock === "24h" || settings.clock === "12h") clock = settings.clock;
|
|
109
|
+
if (["on", "off", "ask"].includes(settings.newThreads)) sharedAuto = settings.newThreads;
|
|
98
110
|
if (["plugged-in", "always", "never"].includes(settings.keepAwake))
|
|
99
111
|
keepAwake = settings.keepAwake;
|
|
100
112
|
} catch {
|
|
@@ -115,6 +127,7 @@ async function mirror($, id, ep) {
|
|
|
115
127
|
schemaVersion: 1,
|
|
116
128
|
host: "claude-code",
|
|
117
129
|
sessionId: id,
|
|
130
|
+
...(typeof sessionCwd === "string" && { cwd: sessionCwd }),
|
|
118
131
|
state,
|
|
119
132
|
kind,
|
|
120
133
|
resetAt,
|
|
@@ -221,7 +234,10 @@ async function refreshStatus($, id, now) {
|
|
|
221
234
|
|
|
222
235
|
/** A limit whose reset time is known: leave it to Claude Code, or ask. */
|
|
223
236
|
async function classify($, id, ep, now) {
|
|
224
|
-
const
|
|
237
|
+
const autoContinueAtUsageLimit =
|
|
238
|
+
nativeSetting === undefined
|
|
239
|
+
? (await $.settings.read()).autoContinueAtUsageLimit
|
|
240
|
+
: nativeSetting.value;
|
|
225
241
|
if (
|
|
226
242
|
nativeLikely({
|
|
227
243
|
isInteractive,
|
|
@@ -238,11 +254,26 @@ async function classify($, id, ep, now) {
|
|
|
238
254
|
await ask($, id, ep, now);
|
|
239
255
|
}
|
|
240
256
|
|
|
257
|
+
/**
|
|
258
|
+
* The person's choice for this plugin: what they said here ("always" or "ask"), else Rewake's
|
|
259
|
+
* shared setting (turned on with `agent-rewake continue --always` or in Zed's Settings).
|
|
260
|
+
*/
|
|
261
|
+
function autoChoice(prefs) {
|
|
262
|
+
return prefs.autoContinue ?? (sharedAuto === "on" ? "always" : undefined);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Why Rewake continues without asking here: the person's choice in Claude Code, or the shared one. */
|
|
266
|
+
function autoText(prefs) {
|
|
267
|
+
return prefs.autoContinue === "always"
|
|
268
|
+
? "After a usage limit, Rewake continues without asking when the reset is within a day. To be asked each time: /rewake auto off"
|
|
269
|
+
: "Rewake's setting for every agent is on (agent-rewake continue --always, or Zed's Settings), so after a usage limit it continues without asking when the reset is within a day. To be asked in Claude Code: /rewake auto off";
|
|
270
|
+
}
|
|
271
|
+
|
|
241
272
|
async function ask($, id, ep, now) {
|
|
242
273
|
const prefs = (await $.store.get("prefs")) ?? {};
|
|
243
274
|
let state = "armed";
|
|
244
275
|
// A reset more than a day away is always asked about, even with "always".
|
|
245
|
-
if (mustAsk({ autoContinue: prefs
|
|
276
|
+
if (mustAsk({ autoContinue: autoChoice(prefs), fireAt: ep.fireAt, now, surface })) {
|
|
246
277
|
const yes = `Continue at ${at(ep.fireAt, now)}`;
|
|
247
278
|
const always = `${yes}, and from now on in every session when the reset is within a day`;
|
|
248
279
|
try {
|
|
@@ -438,6 +469,10 @@ async function tick($) {
|
|
|
438
469
|
await refreshStatus($, id, now);
|
|
439
470
|
}
|
|
440
471
|
if (ep?.state === "armed" && ep.fireAt !== undefined && now >= ep.fireAt) return fire($, id);
|
|
472
|
+
// Claude Code was expected to continue by itself, but hasn't said so since the reset: Rewake
|
|
473
|
+
// asks (or, with "always", continues), so a session never waits with nobody to continue it.
|
|
474
|
+
if (ep?.state === "native" && ep.resetAt !== undefined && now >= ep.resetAt + NATIVE_GRACE_MS)
|
|
475
|
+
return ask($, id, { ...ep, fireAt: now }, now);
|
|
441
476
|
await updateWake($, ep, now);
|
|
442
477
|
// Scheduled messages wait while a limit is pending; one per tick.
|
|
443
478
|
if (busy || (ep && ep.state !== "sent")) return;
|
|
@@ -492,6 +527,12 @@ async function addMessage($, id, when, text) {
|
|
|
492
527
|
* `/rewake` in a session that isn't waiting at a limit: one question per step, when (presets with
|
|
493
528
|
* their times, or Custom…), then what. Dismissing the first question shows what's scheduled.
|
|
494
529
|
*/
|
|
530
|
+
/** Messages to pick in `/rewake`'s second question; "Other" types any message. */
|
|
531
|
+
const MESSAGE_PRESETS = [
|
|
532
|
+
"Continue from where you left off.",
|
|
533
|
+
"Check where things stand and report back.",
|
|
534
|
+
];
|
|
535
|
+
|
|
495
536
|
async function scheduleFlow($, id, now) {
|
|
496
537
|
const presets = [
|
|
497
538
|
["In 30 minutes", 30 * MINUTE],
|
|
@@ -506,11 +547,27 @@ async function scheduleFlow($, id, now) {
|
|
|
506
547
|
header: "Rewake",
|
|
507
548
|
});
|
|
508
549
|
when = presets.find((p) => p.label === answer)?.at;
|
|
550
|
+
// A time typed in the question's own free-text choice ("Other", "Type something").
|
|
551
|
+
if (when === undefined && answer !== custom && String(answer).trim() !== "") {
|
|
552
|
+
const p = parseWhen(String(answer).replace(/^\s*at\s+/i, ""), now);
|
|
553
|
+
if (!p.ok) return `Rewake: ${p.error}`;
|
|
554
|
+
when = p.at;
|
|
555
|
+
}
|
|
509
556
|
if (when === undefined && answer === custom) {
|
|
510
|
-
|
|
511
|
-
|
|
557
|
+
// Real choices, not a bare question: Claude Code's VS Code and Cursor panels answer a
|
|
558
|
+
// question without options with Yes/No. "Other" (the panels' own) types any time.
|
|
559
|
+
const examples = ["in 45m", clock === "24h" ? "18:00" : "6pm", "tomorrow 9:00"]
|
|
560
|
+
.map((t) => ({ t, p: parseWhen(t, now) }))
|
|
561
|
+
.filter((e) => e.p.ok)
|
|
562
|
+
.map((e) => ({ label: `${e.t} (${at(e.p.at, now)})`, t: e.t }));
|
|
563
|
+
const typed = String(
|
|
564
|
+
await $.ui.ask("When? Pick one, or type a time.", {
|
|
565
|
+
options: examples.map((e) => e.label),
|
|
566
|
+
header: "Rewake",
|
|
567
|
+
}),
|
|
512
568
|
);
|
|
513
|
-
const
|
|
569
|
+
const picked = examples.find((e) => e.label === typed)?.t ?? typed;
|
|
570
|
+
const p = parseWhen(picked.replace(/^\s*at\s+/i, ""), now);
|
|
514
571
|
if (!p.ok) return `Rewake: ${p.error}`;
|
|
515
572
|
when = p.at;
|
|
516
573
|
}
|
|
@@ -521,7 +578,10 @@ async function scheduleFlow($, id, now) {
|
|
|
521
578
|
let text;
|
|
522
579
|
try {
|
|
523
580
|
text = String(
|
|
524
|
-
await $.ui.ask(
|
|
581
|
+
await $.ui.ask(
|
|
582
|
+
`What should Rewake send into this session ${atWhen(when, now)}? Pick one, or type your own.`,
|
|
583
|
+
{ options: MESSAGE_PRESETS, header: "Rewake" },
|
|
584
|
+
),
|
|
525
585
|
).trim();
|
|
526
586
|
} catch {
|
|
527
587
|
return "Nothing was scheduled.";
|
|
@@ -542,10 +602,7 @@ async function listText($, id, now) {
|
|
|
542
602
|
for (const [i, s] of sched.entries()) lines.push(`${i + 1}. ${at(s.at, now)}: ${s.text}`);
|
|
543
603
|
if (lines.length === 0) lines.push("Nothing scheduled in this session.");
|
|
544
604
|
const prefs = (await $.store.get("prefs")) ?? {};
|
|
545
|
-
if (prefs
|
|
546
|
-
lines.push(
|
|
547
|
-
"After a usage limit, Rewake continues without asking when the reset is within a day. To be asked each time: /rewake auto off",
|
|
548
|
-
);
|
|
605
|
+
if (autoChoice(prefs) === "always") lines.push(autoText(prefs));
|
|
549
606
|
return lines.join("\n");
|
|
550
607
|
}
|
|
551
608
|
|
|
@@ -650,12 +707,13 @@ async function runCommand($, args) {
|
|
|
650
707
|
text =
|
|
651
708
|
"From now on, after a usage limit Rewake continues every session without asking when the reset is within a day. To be asked each time: /rewake auto off";
|
|
652
709
|
} else if (c.on === false) {
|
|
653
|
-
|
|
710
|
+
// "ask" stays even when Rewake's shared setting is on: the person asked for it here.
|
|
711
|
+
await $.store.set("prefs", { ...rest, autoContinue: "ask" });
|
|
654
712
|
text = "Rewake will ask before continuing after a usage limit.";
|
|
655
713
|
} else {
|
|
656
714
|
text =
|
|
657
|
-
prefs
|
|
658
|
-
?
|
|
715
|
+
autoChoice(prefs) === "always"
|
|
716
|
+
? autoText(prefs)
|
|
659
717
|
: "After a usage limit, Rewake asks before continuing. To continue without asking: /rewake auto on";
|
|
660
718
|
}
|
|
661
719
|
} else {
|
|
@@ -671,8 +729,14 @@ const passOn = (_$, e, next) => (next.called ? undefined : next(e));
|
|
|
671
729
|
export function register(on) {
|
|
672
730
|
on("session.start", async ($, e, next) => {
|
|
673
731
|
isInteractive = e.isInteractive;
|
|
732
|
+
sessionCwd = e.cwd;
|
|
674
733
|
entrypoint = await $.env.get("CLAUDE_CODE_ENTRYPOINT").catch(() => undefined);
|
|
675
734
|
surface = surfaceOf({ surface: e.surface, entrypoint });
|
|
735
|
+
try {
|
|
736
|
+
nativeSetting = { value: (await $.settings.read()).autoContinueAtUsageLimit };
|
|
737
|
+
} catch {
|
|
738
|
+
nativeSetting = undefined;
|
|
739
|
+
}
|
|
676
740
|
await loadConfig($);
|
|
677
741
|
$.clock.every(TICK_MS, () => void tick($));
|
|
678
742
|
void reopen($);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codizelabs/agent-rewake",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|