@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.
package/dist/agent-rewake.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { homedir as
|
|
7
|
-
import { join as
|
|
5
|
+
import { existsSync as existsSync23, readdirSync as readdirSync14, readFileSync as readFileSync32 } from "node:fs";
|
|
6
|
+
import { homedir as homedir19 } from "node:os";
|
|
7
|
+
import { join as join36 } from "node:path";
|
|
8
8
|
import { createInterface as createInterface6 } from "node:readline/promises";
|
|
9
9
|
|
|
10
10
|
// src/adapters/claude/spawn.ts
|
|
@@ -32,7 +32,7 @@ function claudeAdapterCommand(extraArgs, env) {
|
|
|
32
32
|
// src/addon.ts
|
|
33
33
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
34
34
|
import { watch, writeFileSync as writeFileSync3 } from "node:fs";
|
|
35
|
-
import { join as
|
|
35
|
+
import { join as join27 } from "node:path";
|
|
36
36
|
import { pathToFileURL } from "node:url";
|
|
37
37
|
|
|
38
38
|
// src/acp/ndjson.ts
|
|
@@ -1394,7 +1394,7 @@ function formatWhen(at, now, locale) {
|
|
|
1394
1394
|
const dayDiff = Math.round((startOfDay(at) - startOfDay(now)) / 864e5);
|
|
1395
1395
|
const weekday = new Intl.DateTimeFormat(locale ?? TEXT_LOCALE, { weekday: "long" }).format(at);
|
|
1396
1396
|
if (dayDiff === 0) return `${time} today`;
|
|
1397
|
-
if (dayDiff === 1) return `${time} tomorrow
|
|
1397
|
+
if (dayDiff === 1) return `${time} tomorrow, ${weekday}`;
|
|
1398
1398
|
if (dayDiff > 1 && dayDiff < 7) return `${weekday} at ${time}`;
|
|
1399
1399
|
const date = new Intl.DateTimeFormat(locale ?? TEXT_LOCALE, {
|
|
1400
1400
|
day: "numeric",
|
|
@@ -2566,16 +2566,16 @@ import {
|
|
|
2566
2566
|
chmodSync as chmodSync2,
|
|
2567
2567
|
closeSync as closeSync7,
|
|
2568
2568
|
copyFileSync,
|
|
2569
|
-
existsSync as
|
|
2569
|
+
existsSync as existsSync17,
|
|
2570
2570
|
fsyncSync as fsyncSync3,
|
|
2571
2571
|
openSync as openSync8,
|
|
2572
|
-
readFileSync as
|
|
2572
|
+
readFileSync as readFileSync22,
|
|
2573
2573
|
realpathSync as realpathSync5,
|
|
2574
2574
|
statSync as statSync4,
|
|
2575
2575
|
writeSync as writeSync3
|
|
2576
2576
|
} from "node:fs";
|
|
2577
|
-
import { homedir as
|
|
2578
|
-
import { basename as basename4, delimiter as delimiter2, dirname as dirname5, join as
|
|
2577
|
+
import { homedir as homedir14, platform as platform3 } from "node:os";
|
|
2578
|
+
import { basename as basename4, delimiter as delimiter2, dirname as dirname5, join as join25, posix as posix3, win32 as win325 } from "node:path";
|
|
2579
2579
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
2580
2580
|
|
|
2581
2581
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
@@ -3988,11 +3988,11 @@ function applyEdits(text2, edits) {
|
|
|
3988
3988
|
}
|
|
3989
3989
|
|
|
3990
3990
|
// src/hosts/previews.ts
|
|
3991
|
-
import { existsSync as
|
|
3991
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
3992
3992
|
|
|
3993
3993
|
// src/hosts/antigravity/install.ts
|
|
3994
3994
|
import { existsSync as existsSync5, rmSync as rmSync5 } from "node:fs";
|
|
3995
|
-
import { homedir as
|
|
3995
|
+
import { homedir as homedir5 } from "node:os";
|
|
3996
3996
|
import { join as join13 } from "node:path";
|
|
3997
3997
|
|
|
3998
3998
|
// src/install/detect.ts
|
|
@@ -4409,7 +4409,7 @@ var pkg = (() => {
|
|
|
4409
4409
|
return {};
|
|
4410
4410
|
}
|
|
4411
4411
|
})();
|
|
4412
|
-
var VERSION2 = pkg.version ?? (true ? "0.
|
|
4412
|
+
var VERSION2 = pkg.version ?? (true ? "0.3.0" : "0.0.0");
|
|
4413
4413
|
var PACKAGE_NAME = pkg.name ?? "@codizelabs/agent-rewake";
|
|
4414
4414
|
|
|
4415
4415
|
// src/timers/launcher.ts
|
|
@@ -4463,7 +4463,7 @@ function refreshLauncher(stateDir2, bundle, version) {
|
|
|
4463
4463
|
// src/hosts/antigravity/host.ts
|
|
4464
4464
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
4465
4465
|
import { readFileSync as readFileSync13, writeFileSync } from "node:fs";
|
|
4466
|
-
import { homedir as
|
|
4466
|
+
import { homedir as homedir4 } from "node:os";
|
|
4467
4467
|
import { join as join12, resolve, sep } from "node:path";
|
|
4468
4468
|
|
|
4469
4469
|
// src/core/limits/agents.ts
|
|
@@ -4579,6 +4579,15 @@ function classifyGrokFailure(input, billing) {
|
|
|
4579
4579
|
return { kind: "weekly", billing: false };
|
|
4580
4580
|
return { kind: "billing", billing: true };
|
|
4581
4581
|
}
|
|
4582
|
+
function classifyCursorError(text2) {
|
|
4583
|
+
if (typeof text2 !== "string" || text2 === "") return void 0;
|
|
4584
|
+
const t = text2.slice(0, 4096);
|
|
4585
|
+
if (/add a payment method|spend(ing)? limit|out of credits|on-demand usage|get cursor pro/i.test(t))
|
|
4586
|
+
return { kind: "billing", billing: true };
|
|
4587
|
+
if (/hit your (usage|rate) limit|usage limit|rate limit|upgrade your plan to continue/i.test(t))
|
|
4588
|
+
return { kind: "other", billing: false };
|
|
4589
|
+
return void 0;
|
|
4590
|
+
}
|
|
4582
4591
|
|
|
4583
4592
|
// src/core/limits/recognise.ts
|
|
4584
4593
|
function recognise(signal, now) {
|
|
@@ -4606,6 +4615,8 @@ function hostLimit(s, now) {
|
|
|
4606
4615
|
return classifyGeminiError(text2, now);
|
|
4607
4616
|
case "antigravity":
|
|
4608
4617
|
return classifyAntigravityStop({ terminationReason: s.code, error: text2 }, now);
|
|
4618
|
+
case "cursor":
|
|
4619
|
+
return classifyCursorError(text2);
|
|
4609
4620
|
case "grok":
|
|
4610
4621
|
return classifyGrokFailure(
|
|
4611
4622
|
{ error: s.code, errorDetails: text2 },
|
|
@@ -4748,6 +4759,20 @@ var SESSION_GONE = /\bsession\b(?:\s+\S+)?\s+not found|unknown session|no such s
|
|
|
4748
4759
|
|
|
4749
4760
|
// src/hosts/closed.ts
|
|
4750
4761
|
var FIRE_ENV = "AGENT_REWAKE_FIRE";
|
|
4762
|
+
var COMMON_VARS = ["XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"];
|
|
4763
|
+
var SECRET_NAME = /KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL/i;
|
|
4764
|
+
function sessionEnv(host, env) {
|
|
4765
|
+
const vars = {};
|
|
4766
|
+
for (const name of [...COMMON_VARS, ...host.settingsVars ?? []]) {
|
|
4767
|
+
const v = env[name];
|
|
4768
|
+
if (v !== void 0 && v !== "" && !SECRET_NAME.test(name)) vars[name] = v;
|
|
4769
|
+
}
|
|
4770
|
+
const keys = (host.keyVars ?? []).filter((k) => env[k] !== void 0 && env[k] !== "");
|
|
4771
|
+
return {
|
|
4772
|
+
...Object.keys(vars).length > 0 && { env: vars },
|
|
4773
|
+
...keys.length > 0 && { keysSet: keys }
|
|
4774
|
+
};
|
|
4775
|
+
}
|
|
4751
4776
|
function agentsOf(r) {
|
|
4752
4777
|
if (r.agents) return r.agents;
|
|
4753
4778
|
return r.agentPid !== void 0 && r.agentName ? [{ pid: r.agentPid, name: r.agentName }] : [];
|
|
@@ -4770,13 +4795,19 @@ function unanswered(stateDir2, r) {
|
|
|
4770
4795
|
if (!l || l.billing) return void 0;
|
|
4771
4796
|
if ((r.lastPromptAt ?? 0) > l.seenAt) return void 0;
|
|
4772
4797
|
if (pendingFor(stateDir2, r.host, r.sessionId).length > 0) return void 0;
|
|
4798
|
+
if (resumedAfter(stateDir2, r, l.seenAt)) return void 0;
|
|
4773
4799
|
return l;
|
|
4774
4800
|
}
|
|
4801
|
+
function resumedAfter(stateDir2, r, since) {
|
|
4802
|
+
return new ScheduleStore(stateDir2).listForSession(r.sessionId, r.host).some((s) => s.status === "sent" && s.kind !== "user" && s.updatedAt >= since);
|
|
4803
|
+
}
|
|
4775
4804
|
function autoFor(settings) {
|
|
4776
4805
|
return settings.newThreads === "on" ? "always" : settings.newThreads === "off" ? "never" : "ask";
|
|
4777
4806
|
}
|
|
4778
4807
|
function armedText(host, cwd, at, now) {
|
|
4779
|
-
|
|
4808
|
+
const keep = host.keepOpen ? ` ${host.keepOpen}` : "";
|
|
4809
|
+
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.`;
|
|
4810
|
+
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: agent-rewake continue --cancel`;
|
|
4780
4811
|
}
|
|
4781
4812
|
function armClosed(host, r, at, d) {
|
|
4782
4813
|
const store = new ScheduleStore(d.stateDir);
|
|
@@ -4796,7 +4827,7 @@ function armClosed(host, r, at, d) {
|
|
|
4796
4827
|
sessionRef: { sessionId: r.sessionId, cwd: r.cwd }
|
|
4797
4828
|
};
|
|
4798
4829
|
store.put(resume);
|
|
4799
|
-
d.arm(resume.scheduleId, at);
|
|
4830
|
+
d.arm(resume.scheduleId, at + (host.fallbackDelayMs ?? 0));
|
|
4800
4831
|
return resume;
|
|
4801
4832
|
}
|
|
4802
4833
|
function onSessionStart(host, sessionId, cwd, d, program) {
|
|
@@ -4818,10 +4849,18 @@ function onSessionStart(host, sessionId, cwd, d, program) {
|
|
|
4818
4849
|
}
|
|
4819
4850
|
function ensureProgram(host, sessionId, cwd, d, program) {
|
|
4820
4851
|
const records = new SessionRecords(d.stateDir, host.id);
|
|
4821
|
-
|
|
4822
|
-
const
|
|
4823
|
-
|
|
4824
|
-
|
|
4852
|
+
const had = records.get(sessionId);
|
|
4853
|
+
const seen = d.env[FIRE_ENV] ? {} : sessionEnv(host, d.env);
|
|
4854
|
+
const same2 = JSON.stringify(had?.env ?? null) === JSON.stringify(seen.env ?? null) && JSON.stringify(had?.keysSet ?? null) === JSON.stringify(seen.keysSet ?? null);
|
|
4855
|
+
if (had?.program && (same2 || d.env[FIRE_ENV])) return;
|
|
4856
|
+
const found = had?.program ? void 0 : program();
|
|
4857
|
+
records.update(sessionId, cwd, d.now, (r) => {
|
|
4858
|
+
const { env: _e, keysSet: _k, ...rest } = r;
|
|
4859
|
+
return {
|
|
4860
|
+
...d.env[FIRE_ENV] ? r : { ...rest, ...seen },
|
|
4861
|
+
...!r.program && found && { program: found }
|
|
4862
|
+
};
|
|
4863
|
+
});
|
|
4825
4864
|
}
|
|
4826
4865
|
function onPrompt(host, sessionId, cwd, d) {
|
|
4827
4866
|
if (d.env[FIRE_ENV]) return;
|
|
@@ -4896,7 +4935,7 @@ function closedAdapter(host, stateDir2, env) {
|
|
|
4896
4935
|
return {
|
|
4897
4936
|
id: host.id,
|
|
4898
4937
|
name: host.name,
|
|
4899
|
-
noun: "session",
|
|
4938
|
+
noun: host.noun ?? "session",
|
|
4900
4939
|
reopen: host.reopen ?? `open the session in ${host.name}`,
|
|
4901
4940
|
async check(s) {
|
|
4902
4941
|
const r = records.get(s.sessionRef?.sessionId ?? s.sessionId);
|
|
@@ -4909,7 +4948,9 @@ function closedAdapter(host, stateDir2, env) {
|
|
|
4909
4948
|
async send(s) {
|
|
4910
4949
|
const r = records.get(s.sessionRef?.sessionId ?? s.sessionId);
|
|
4911
4950
|
if (!r) return { ok: false, reason: "closed", detail: "deleted" };
|
|
4912
|
-
|
|
4951
|
+
const missing = (r.keysSet ?? []).filter((k) => !env[k]);
|
|
4952
|
+
if (missing.length > 0) return { ok: false, reason: "failed", detail: "missing-key" };
|
|
4953
|
+
return host.resume(r, s.text, { ...env, ...r.env, [FIRE_ENV]: s.scheduleId });
|
|
4913
4954
|
}
|
|
4914
4955
|
};
|
|
4915
4956
|
}
|
|
@@ -5106,7 +5147,23 @@ function listHooks(codex2, env, cwd, timeoutMs = 2e4) {
|
|
|
5106
5147
|
});
|
|
5107
5148
|
}
|
|
5108
5149
|
|
|
5150
|
+
// src/util/error-line.ts
|
|
5151
|
+
import { homedir as homedir3 } from "node:os";
|
|
5152
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`, "g");
|
|
5153
|
+
function errorLine(output, home = homedir3()) {
|
|
5154
|
+
const lines = output.replace(ANSI, "").split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== "" && !/^at\s|^Node\.js v\d|^\^+$/.test(l));
|
|
5155
|
+
let line = lines.at(-1);
|
|
5156
|
+
if (line === void 0) return void 0;
|
|
5157
|
+
if (home) line = line.split(home).join("~");
|
|
5158
|
+
line = line.replace(/\b(sk|ghp|gho|ghs|xai|AIza)[-_A-Za-z0-9]{8,}/g, "\u2026").replace(/\b(Bearer|token|key)[=: ]+\S+/gi, "$1 \u2026").replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "\u2026").replace(/[A-Za-z0-9_-]{32,}/g, "\u2026");
|
|
5159
|
+
return line.length > 200 ? `${line.slice(0, 199)}\u2026` : line;
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5109
5162
|
// src/hosts/host.ts
|
|
5163
|
+
function withMessage(output) {
|
|
5164
|
+
const message = errorLine(output);
|
|
5165
|
+
return message === void 0 ? {} : { message };
|
|
5166
|
+
}
|
|
5110
5167
|
var RESUME_TIMEOUT_MS = 30 * 6e4;
|
|
5111
5168
|
function resumeDeadline(child, ms = RESUME_TIMEOUT_MS) {
|
|
5112
5169
|
let stopped = false;
|
|
@@ -5121,7 +5178,7 @@ function resumeDeadline(child, ms = RESUME_TIMEOUT_MS) {
|
|
|
5121
5178
|
|
|
5122
5179
|
// src/hosts/antigravity/host.ts
|
|
5123
5180
|
var ANTIGRAVITY_ID = "antigravity";
|
|
5124
|
-
function geminiHome(env, home =
|
|
5181
|
+
function geminiHome(env, home = homedir4()) {
|
|
5125
5182
|
return env.GEMINI_HOME || join12(home, ".gemini");
|
|
5126
5183
|
}
|
|
5127
5184
|
function surfaceOf(transcriptPath, env, home) {
|
|
@@ -5279,7 +5336,7 @@ function antigravityHooksJson(node, launcher) {
|
|
|
5279
5336
|
`;
|
|
5280
5337
|
}
|
|
5281
5338
|
async function runAntigravityInstall(o) {
|
|
5282
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
5339
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir5();
|
|
5283
5340
|
const dir = pluginDir(o.env, home);
|
|
5284
5341
|
const installed = existsSync5(dir);
|
|
5285
5342
|
if (o.uninstall) {
|
|
@@ -5365,7 +5422,7 @@ import {
|
|
|
5365
5422
|
realpathSync as realpathSync4,
|
|
5366
5423
|
rmSync as rmSync6
|
|
5367
5424
|
} from "node:fs";
|
|
5368
|
-
import { homedir as
|
|
5425
|
+
import { homedir as homedir6, tmpdir } from "node:os";
|
|
5369
5426
|
import { dirname as dirname4, join as join14 } from "node:path";
|
|
5370
5427
|
|
|
5371
5428
|
// src/install/probe.ts
|
|
@@ -5551,7 +5608,7 @@ function pickClaude(programs2) {
|
|
|
5551
5608
|
return [...programs2].sort((a, b) => compareVersions(b.version ?? "0", a.version ?? "0"))[0];
|
|
5552
5609
|
}
|
|
5553
5610
|
async function runClaudeInstall(o) {
|
|
5554
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
5611
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir6();
|
|
5555
5612
|
const host = { env: o.env, home, platform: process.platform };
|
|
5556
5613
|
const programs2 = o.programs ?? [...claudePrograms(host), ...claudeDesktopPrograms(host)];
|
|
5557
5614
|
const probe = o.probe ?? (o.programs ? () => void 0 : versionProbe(o.env, o.node));
|
|
@@ -5799,7 +5856,7 @@ async function uninstallPlugin(codex2, stateDir2, env) {
|
|
|
5799
5856
|
|
|
5800
5857
|
// src/hosts/copilot/install.ts
|
|
5801
5858
|
import { existsSync as existsSync10, rmSync as rmSync9 } from "node:fs";
|
|
5802
|
-
import { homedir as
|
|
5859
|
+
import { homedir as homedir9 } from "node:os";
|
|
5803
5860
|
import { join as join19 } from "node:path";
|
|
5804
5861
|
|
|
5805
5862
|
// src/hosts/policy.ts
|
|
@@ -5809,7 +5866,7 @@ import { join as join18 } from "node:path";
|
|
|
5809
5866
|
// src/hosts/gemini/install.ts
|
|
5810
5867
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5811
5868
|
import { existsSync as existsSync8, readFileSync as readFileSync16, rmSync as rmSync8 } from "node:fs";
|
|
5812
|
-
import { homedir as
|
|
5869
|
+
import { homedir as homedir7 } from "node:os";
|
|
5813
5870
|
import { join as join16 } from "node:path";
|
|
5814
5871
|
|
|
5815
5872
|
// src/hosts/gemini/host.ts
|
|
@@ -6007,14 +6064,26 @@ function resumeGemini(r, text2, env, node = process.execPath) {
|
|
|
6007
6064
|
return resolve2({ ok: false, reason: "limited", ...resetsAt && { resetsAt } });
|
|
6008
6065
|
}
|
|
6009
6066
|
if (code === 0) return resolve2({ ok: true });
|
|
6010
|
-
resolve2({
|
|
6067
|
+
resolve2({
|
|
6068
|
+
ok: false,
|
|
6069
|
+
reason: "failed",
|
|
6070
|
+
detail: `exit ${code ?? "signal"}`,
|
|
6071
|
+
...withMessage(err || out)
|
|
6072
|
+
});
|
|
6011
6073
|
});
|
|
6012
6074
|
});
|
|
6013
6075
|
}
|
|
6014
6076
|
var geminiHost = {
|
|
6015
6077
|
id: GEMINI_ID,
|
|
6016
6078
|
name: "Gemini CLI",
|
|
6017
|
-
resume: (r, text2, env) => resumeGemini(r, text2, env)
|
|
6079
|
+
resume: (r, text2, env) => resumeGemini(r, text2, env),
|
|
6080
|
+
settingsVars: [
|
|
6081
|
+
"GEMINI_CLI_HOME",
|
|
6082
|
+
"GOOGLE_GEMINI_BASE_URL",
|
|
6083
|
+
"GOOGLE_GENAI_USE_VERTEXAI",
|
|
6084
|
+
"GOOGLE_GENAI_USE_GCA"
|
|
6085
|
+
],
|
|
6086
|
+
keyVars: ["GEMINI_API_KEY"]
|
|
6018
6087
|
};
|
|
6019
6088
|
var isGeminiTranscript = (p) => typeof p === "string" && /[\\/]\.gemini[\\/]tmp[\\/]/.test(p);
|
|
6020
6089
|
function sessionOf(input) {
|
|
@@ -6137,6 +6206,12 @@ function geminiApiKeyAuth(env, home) {
|
|
|
6137
6206
|
return (s?.security?.auth?.selectedType ?? s?.selectedAuthType) === "gemini-api-key";
|
|
6138
6207
|
}
|
|
6139
6208
|
function geminiHooksJson(node, launcher) {
|
|
6209
|
+
const NAMES = {
|
|
6210
|
+
SessionStart: "Agent Rewake (session start)",
|
|
6211
|
+
SessionEnd: "Agent Rewake (session end)",
|
|
6212
|
+
BeforeAgent: "Agent Rewake (/rewake)",
|
|
6213
|
+
AfterAgent: "Agent Rewake"
|
|
6214
|
+
};
|
|
6140
6215
|
const hooks = Object.fromEntries(
|
|
6141
6216
|
GEMINI_EVENTS.map((event) => [
|
|
6142
6217
|
event,
|
|
@@ -6145,7 +6220,7 @@ function geminiHooksJson(node, launcher) {
|
|
|
6145
6220
|
hooks: [
|
|
6146
6221
|
{
|
|
6147
6222
|
type: "command",
|
|
6148
|
-
name:
|
|
6223
|
+
name: NAMES[event] ?? "Agent Rewake",
|
|
6149
6224
|
command: `"${node}" "${launcher}" hook gemini-cli ${event}`,
|
|
6150
6225
|
timeout: 5e3
|
|
6151
6226
|
}
|
|
@@ -6180,7 +6255,7 @@ prompt = "${REWAKE_MARKER} {{args}}"
|
|
|
6180
6255
|
return dir;
|
|
6181
6256
|
}
|
|
6182
6257
|
async function runGeminiInstall(o) {
|
|
6183
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
6258
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir7();
|
|
6184
6259
|
const programs2 = o.programs ?? geminiPrograms({ env: o.env, home, platform: process.platform });
|
|
6185
6260
|
const probe = o.probe ?? (o.programs ? () => void 0 : versionProbe(o.env, o.node));
|
|
6186
6261
|
const picked = [...programs2].sort(
|
|
@@ -6290,10 +6365,10 @@ async function runGeminiInstall(o) {
|
|
|
6290
6365
|
// src/hosts/grok/host.ts
|
|
6291
6366
|
import { spawn as spawn4 } from "node:child_process";
|
|
6292
6367
|
import { existsSync as existsSync9, readFileSync as readFileSync17 } from "node:fs";
|
|
6293
|
-
import { homedir as
|
|
6368
|
+
import { homedir as homedir8 } from "node:os";
|
|
6294
6369
|
import { join as join17 } from "node:path";
|
|
6295
6370
|
var GROK_ID = "grok";
|
|
6296
|
-
function grokHome(env, home =
|
|
6371
|
+
function grokHome(env, home = homedir8()) {
|
|
6297
6372
|
return env.GROK_HOME || join17(home, ".grok");
|
|
6298
6373
|
}
|
|
6299
6374
|
function isGrok(input, env) {
|
|
@@ -6417,7 +6492,12 @@ ${err}`)) {
|
|
|
6417
6492
|
if (SESSION_GONE.test(`${out}
|
|
6418
6493
|
${err}`))
|
|
6419
6494
|
return resolve2({ ok: false, reason: "closed", detail: "deleted" });
|
|
6420
|
-
resolve2({
|
|
6495
|
+
resolve2({
|
|
6496
|
+
ok: false,
|
|
6497
|
+
reason: "failed",
|
|
6498
|
+
detail: `exit ${code ?? "signal"}`,
|
|
6499
|
+
...withMessage(err || out)
|
|
6500
|
+
});
|
|
6421
6501
|
});
|
|
6422
6502
|
});
|
|
6423
6503
|
}
|
|
@@ -6426,7 +6506,14 @@ function grokHost(env) {
|
|
|
6426
6506
|
id: GROK_ID,
|
|
6427
6507
|
name: "Grok Build",
|
|
6428
6508
|
resume: (r, text2, e) => resumeGrok(r, text2, e),
|
|
6429
|
-
isOpen: (r) => grokSessionOpen(grokHome(env), r.sessionId)
|
|
6509
|
+
isOpen: (r) => grokSessionOpen(grokHome({ ...env, ...r.env }), r.sessionId),
|
|
6510
|
+
settingsVars: [
|
|
6511
|
+
"GROK_HOME",
|
|
6512
|
+
"GROK_CLI_CHAT_PROXY_BASE_URL",
|
|
6513
|
+
"GROK_XAI_API_BASE_URL",
|
|
6514
|
+
"GROK_MODELS_BASE_URL"
|
|
6515
|
+
],
|
|
6516
|
+
keyVars: ["XAI_API_KEY"]
|
|
6430
6517
|
};
|
|
6431
6518
|
}
|
|
6432
6519
|
function grokHooks(deps) {
|
|
@@ -6531,7 +6618,7 @@ function copilotHooksJson(node, launcher) {
|
|
|
6531
6618
|
`;
|
|
6532
6619
|
}
|
|
6533
6620
|
async function runCopilotInstall(o) {
|
|
6534
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
6621
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir9();
|
|
6535
6622
|
const programs2 = o.programs ?? copilotPrograms({ env: o.env, home, platform: process.platform });
|
|
6536
6623
|
const probe = o.probe ?? (o.programs ? () => void 0 : versionProbe(o.env, o.node));
|
|
6537
6624
|
const picked = [...programs2].sort(
|
|
@@ -6619,17 +6706,143 @@ async function runCopilotInstall(o) {
|
|
|
6619
6706
|
return 0;
|
|
6620
6707
|
}
|
|
6621
6708
|
|
|
6709
|
+
// src/hosts/cursor/install.ts
|
|
6710
|
+
import { existsSync as existsSync11, readFileSync as readFileSync19 } from "node:fs";
|
|
6711
|
+
import { homedir as homedir10 } from "node:os";
|
|
6712
|
+
import { join as join20 } from "node:path";
|
|
6713
|
+
var WAIT_SECONDS = 4 * 60 * 60;
|
|
6714
|
+
var CURSOR_EVENTS = ["beforeSubmitPrompt", "stop"];
|
|
6715
|
+
function cursorHooksFile(home) {
|
|
6716
|
+
return join20(home, ".cursor", "hooks.json");
|
|
6717
|
+
}
|
|
6718
|
+
function cursorFound(env, home, platform4) {
|
|
6719
|
+
const apps2 = platform4 === "darwin" ? ["/Applications/Cursor.app", join20(home, "Applications", "Cursor.app")] : platform4 === "win32" ? env.LOCALAPPDATA ? [join20(env.LOCALAPPDATA, "Programs", "cursor", "Cursor.exe")] : [] : ["/usr/share/cursor", "/opt/Cursor", "/opt/cursor"];
|
|
6720
|
+
if (apps2.some((a) => existsSync11(a))) return true;
|
|
6721
|
+
const sep2 = platform4 === "win32" ? ";" : ":";
|
|
6722
|
+
const names = platform4 === "win32" ? ["cursor.cmd", "cursor.exe"] : ["cursor"];
|
|
6723
|
+
return (env.PATH ?? "").split(sep2).filter(Boolean).some((d) => names.some((n) => existsSync11(join20(d, n))));
|
|
6724
|
+
}
|
|
6725
|
+
var OURS = /\bhook cursor\b/;
|
|
6726
|
+
function readHooks(file) {
|
|
6727
|
+
try {
|
|
6728
|
+
const v = JSON.parse(readFileSync19(file, "utf8"));
|
|
6729
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
6730
|
+
} catch {
|
|
6731
|
+
return void 0;
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
function cursorInstalled(home) {
|
|
6735
|
+
const hooks = readHooks(cursorHooksFile(home))?.hooks;
|
|
6736
|
+
if (typeof hooks !== "object" || hooks === null) return false;
|
|
6737
|
+
return Object.values(hooks).some(
|
|
6738
|
+
(list) => Array.isArray(list) && list.some((e) => OURS.test(String(e?.command ?? "")))
|
|
6739
|
+
);
|
|
6740
|
+
}
|
|
6741
|
+
var quote = (s) => `"${s}"`;
|
|
6742
|
+
function planCursor(file, node, launcher, uninstall) {
|
|
6743
|
+
if (`${node}${launcher}`.includes('"'))
|
|
6744
|
+
return {
|
|
6745
|
+
error: `Rewake couldn't set up Cursor: the path to Node.js or to Rewake contains a double quote (${node}, ${launcher}). Rewake didn't change anything.`
|
|
6746
|
+
};
|
|
6747
|
+
const existed = existsSync11(file);
|
|
6748
|
+
const before = existed ? readFileSync19(file, "utf8") : "";
|
|
6749
|
+
const parsed = existed ? readHooks(file) : { version: 1, hooks: {} };
|
|
6750
|
+
if (!parsed)
|
|
6751
|
+
return {
|
|
6752
|
+
error: `Rewake couldn't set up Cursor: its hooks file (${file}) isn't valid JSON. Rewake didn't change it. Fix or remove the file, then run this again.`
|
|
6753
|
+
};
|
|
6754
|
+
const hooks = typeof parsed.hooks === "object" && parsed.hooks !== null ? { ...parsed.hooks } : {};
|
|
6755
|
+
for (const [event, list] of Object.entries(hooks)) {
|
|
6756
|
+
if (!Array.isArray(list)) continue;
|
|
6757
|
+
const kept = list.filter((e) => !OURS.test(String(e?.command ?? "")));
|
|
6758
|
+
if (kept.length > 0) hooks[event] = kept;
|
|
6759
|
+
else delete hooks[event];
|
|
6760
|
+
}
|
|
6761
|
+
if (!uninstall)
|
|
6762
|
+
for (const event of CURSOR_EVENTS) {
|
|
6763
|
+
const mine = {
|
|
6764
|
+
command: `${quote(node)} ${quote(launcher)} hook cursor ${event}`,
|
|
6765
|
+
timeout: event === "stop" ? WAIT_SECONDS : 10
|
|
6766
|
+
};
|
|
6767
|
+
const list = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
6768
|
+
hooks[event] = [...list, mine];
|
|
6769
|
+
}
|
|
6770
|
+
const after = `${JSON.stringify({ ...parsed, version: parsed.version ?? 1, hooks }, null, 2)}
|
|
6771
|
+
`;
|
|
6772
|
+
if (after === before) return { changes: [], notes: [] };
|
|
6773
|
+
const summary = [
|
|
6774
|
+
uninstall ? "Remove Rewake's hooks from Cursor's agent" : "Add Rewake's hooks to Cursor's agent"
|
|
6775
|
+
];
|
|
6776
|
+
const change = { file, existed, before, after, summary };
|
|
6777
|
+
return { changes: [change], notes: [] };
|
|
6778
|
+
}
|
|
6779
|
+
async function runCursorInstall(o) {
|
|
6780
|
+
const home = o.home ?? (o.env.HOME || o.env.USERPROFILE || homedir10());
|
|
6781
|
+
const file = cursorHooksFile(home);
|
|
6782
|
+
if (!o.uninstall && !(o.found ?? cursorFound(o.env, home, process.platform))) {
|
|
6783
|
+
o.out("Cursor wasn't found on this computer, so there's nothing to set up for it.\n");
|
|
6784
|
+
return 1;
|
|
6785
|
+
}
|
|
6786
|
+
const launcher = o.dryRun ? launcherPath(o.stateDir) : ensureLauncher(o.stateDir, o.bundle) ?? launcherPath(o.stateDir);
|
|
6787
|
+
const plan = planCursor(file, o.node, launcher, o.uninstall);
|
|
6788
|
+
if ("error" in plan) {
|
|
6789
|
+
o.out(`${plan.error}
|
|
6790
|
+
`);
|
|
6791
|
+
return 1;
|
|
6792
|
+
}
|
|
6793
|
+
if (plan.changes.length === 0) {
|
|
6794
|
+
o.out(
|
|
6795
|
+
o.uninstall ? "Rewake isn't set up in Cursor: nothing to remove.\n" : "Rewake is already set up in Cursor: nothing to change.\n"
|
|
6796
|
+
);
|
|
6797
|
+
return 0;
|
|
6798
|
+
}
|
|
6799
|
+
o.out(
|
|
6800
|
+
o.uninstall ? `Agent Rewake will remove its hooks from Cursor's agent:
|
|
6801
|
+
${file}
|
|
6802
|
+
Your own hooks stay. Rewake keeps a backup of the file.
|
|
6803
|
+
` : [
|
|
6804
|
+
"Agent Rewake (preview) will add its hooks to Cursor's own agent:",
|
|
6805
|
+
` ${file}`,
|
|
6806
|
+
"",
|
|
6807
|
+
"When a Cursor chat stops at its usage limit, run agent-rewake continue and pick a time within 4 hours. Rewake then continues that chat, as long as its window stays open. Typing in the chat cancels it. A limit that waiting won't lift (one that needs a paid plan or a new month) gets a notification instead. Your own hooks stay; Rewake keeps a backup of the file.",
|
|
6808
|
+
""
|
|
6809
|
+
].join("\n")
|
|
6810
|
+
);
|
|
6811
|
+
if (o.dryRun) {
|
|
6812
|
+
o.out("Dry run: nothing was changed.\n");
|
|
6813
|
+
return 0;
|
|
6814
|
+
}
|
|
6815
|
+
if (!o.yes) {
|
|
6816
|
+
if (!o.interactive) {
|
|
6817
|
+
o.out("Not a terminal, so nothing was changed. Run again with --yes to apply.\n");
|
|
6818
|
+
return 1;
|
|
6819
|
+
}
|
|
6820
|
+
if (!await o.ask("\nApply these changes? [y/N] ")) {
|
|
6821
|
+
o.out("Nothing was changed.\n");
|
|
6822
|
+
return 1;
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6825
|
+
applyPlan(plan);
|
|
6826
|
+
o.out(
|
|
6827
|
+
o.uninstall ? "Done. Rewake is out of Cursor.\n" : `
|
|
6828
|
+
Done.
|
|
6829
|
+
Next, reload Cursor's window so its agent loads the hooks: in Cursor, run "Developer: Reload Window" from the Command Palette (or restart Cursor).
|
|
6830
|
+
`
|
|
6831
|
+
);
|
|
6832
|
+
return 0;
|
|
6833
|
+
}
|
|
6834
|
+
|
|
6622
6835
|
// src/hosts/devin/install.ts
|
|
6623
|
-
import { existsSync as
|
|
6624
|
-
import { homedir as
|
|
6625
|
-
import { join as
|
|
6836
|
+
import { existsSync as existsSync13, readFileSync as readFileSync20 } from "node:fs";
|
|
6837
|
+
import { homedir as homedir11 } from "node:os";
|
|
6838
|
+
import { join as join22 } from "node:path";
|
|
6626
6839
|
|
|
6627
6840
|
// src/wrap.ts
|
|
6628
6841
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6629
6842
|
import { createHash as createHash3 } from "node:crypto";
|
|
6630
6843
|
import {
|
|
6631
6844
|
chmodSync,
|
|
6632
|
-
existsSync as
|
|
6845
|
+
existsSync as existsSync12,
|
|
6633
6846
|
mkdirSync as mkdirSync2,
|
|
6634
6847
|
openSync as openSync7,
|
|
6635
6848
|
readdirSync as readdirSync8,
|
|
@@ -6639,7 +6852,7 @@ import {
|
|
|
6639
6852
|
writeFileSync as writeFileSync2
|
|
6640
6853
|
} from "node:fs";
|
|
6641
6854
|
import { platform as platform2 } from "node:os";
|
|
6642
|
-
import { basename as basename3, join as
|
|
6855
|
+
import { basename as basename3, join as join21, win32 as win324 } from "node:path";
|
|
6643
6856
|
var WRAP_REGISTRY = "--wrap-registry";
|
|
6644
6857
|
var WRAP_COMMAND = "--wrap-command";
|
|
6645
6858
|
var CLAUDE_REGISTRY_ID = "claude-acp";
|
|
@@ -6680,7 +6893,7 @@ function platformKey(env = process.env) {
|
|
|
6680
6893
|
}
|
|
6681
6894
|
var strings = (v) => isRecord(v) ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, String(x)])) : {};
|
|
6682
6895
|
function registryAgent(id, env = process.env) {
|
|
6683
|
-
const file =
|
|
6896
|
+
const file = join21(zedDataDir(env), "external_agents", "registry", "registry.json");
|
|
6684
6897
|
let agents;
|
|
6685
6898
|
try {
|
|
6686
6899
|
agents = readJsonFile(file).agents;
|
|
@@ -6736,26 +6949,26 @@ function splitPackageSpec(spec) {
|
|
|
6736
6949
|
}
|
|
6737
6950
|
function installedVersion(dir, pkg2) {
|
|
6738
6951
|
try {
|
|
6739
|
-
const json = readJsonFile(
|
|
6952
|
+
const json = readJsonFile(join21(dir, "node_modules", pkg2, "package.json"));
|
|
6740
6953
|
return typeof json.version === "string" ? json.version : void 0;
|
|
6741
6954
|
} catch {
|
|
6742
6955
|
return void 0;
|
|
6743
6956
|
}
|
|
6744
6957
|
}
|
|
6745
6958
|
function packageExecutable(dir, pkg2) {
|
|
6746
|
-
const root =
|
|
6959
|
+
const root = join21(dir, "node_modules", pkg2);
|
|
6747
6960
|
let json;
|
|
6748
6961
|
try {
|
|
6749
|
-
json = readJsonFile(
|
|
6962
|
+
json = readJsonFile(join21(root, "package.json"));
|
|
6750
6963
|
} catch {
|
|
6751
6964
|
return void 0;
|
|
6752
6965
|
}
|
|
6753
6966
|
const bin = json.bin;
|
|
6754
|
-
if (typeof bin === "string") return
|
|
6967
|
+
if (typeof bin === "string") return join21(root, bin);
|
|
6755
6968
|
if (!isRecord(bin)) return void 0;
|
|
6756
6969
|
const short = pkg2.replace(/^@[^/]+\//, "");
|
|
6757
6970
|
const chosen = bin[short] ?? Object.values(bin)[0];
|
|
6758
|
-
return typeof chosen === "string" ?
|
|
6971
|
+
return typeof chosen === "string" ? join21(root, chosen) : void 0;
|
|
6759
6972
|
}
|
|
6760
6973
|
function npmCli(args, env) {
|
|
6761
6974
|
const script = npmScript("npm-cli.js");
|
|
@@ -6763,8 +6976,8 @@ function npmCli(args, env) {
|
|
|
6763
6976
|
}
|
|
6764
6977
|
function ensureNpxAgent(id, npx, stateDir2, env, logFile) {
|
|
6765
6978
|
const [pkg2, version] = splitPackageSpec(npx.package);
|
|
6766
|
-
const zedDir =
|
|
6767
|
-
const ownDir =
|
|
6979
|
+
const zedDir = join21(zedDataDir(env), "external_agents", "registry", "npx", safeName(id));
|
|
6980
|
+
const ownDir = join21(stateDir2, "agents", "npx", safeName(id));
|
|
6768
6981
|
for (const dir of [zedDir, ownDir]) if (installedVersion(dir, pkg2)) return dir;
|
|
6769
6982
|
ensurePrivateDir(ownDir);
|
|
6770
6983
|
const spec = version ? `${pkg2}@0.0.0 - ${version}` : pkg2;
|
|
@@ -6785,7 +6998,7 @@ function ensureNpxAgent(id, npx, stateDir2, env, logFile) {
|
|
|
6785
6998
|
function zedBinaryDir(id, version, target, env) {
|
|
6786
6999
|
const sha = (s) => createHash3("sha256").update(s).digest("hex").slice(0, 16);
|
|
6787
7000
|
const archiveKey = target.sha256 ? `${target.archive}\0sha256:${target.sha256.toLowerCase()}` : target.archive;
|
|
6788
|
-
return
|
|
7001
|
+
return join21(
|
|
6789
7002
|
zedDataDir(env),
|
|
6790
7003
|
"external_agents",
|
|
6791
7004
|
"registry",
|
|
@@ -6795,18 +7008,18 @@ function zedBinaryDir(id, version, target, env) {
|
|
|
6795
7008
|
}
|
|
6796
7009
|
function commandIn(dir, cmd) {
|
|
6797
7010
|
if (!/^\.[\\/]/.test(cmd) || cmd.includes("..")) return void 0;
|
|
6798
|
-
const path =
|
|
6799
|
-
return
|
|
7011
|
+
const path = join21(dir, cmd.slice(2));
|
|
7012
|
+
return existsSync12(path) ? path : void 0;
|
|
6800
7013
|
}
|
|
6801
7014
|
function findBinaryAgent(id, version, target, stateDir2, env) {
|
|
6802
7015
|
const exact = commandIn(zedBinaryDir(id, version, target, env), target.cmd);
|
|
6803
7016
|
if (exact) return exact;
|
|
6804
|
-
const zedAgentDir =
|
|
6805
|
-
const own =
|
|
7017
|
+
const zedAgentDir = join21(zedDataDir(env), "external_agents", "registry", zedSafeName(id));
|
|
7018
|
+
const own = join21(stateDir2, "agents", "bin", safeName(id));
|
|
6806
7019
|
for (const base of [zedAgentDir, own]) {
|
|
6807
7020
|
let dirs = [];
|
|
6808
7021
|
try {
|
|
6809
|
-
dirs = readdirSync8(base).filter((d) => d.startsWith("v_")).map((d) =>
|
|
7022
|
+
dirs = readdirSync8(base).filter((d) => d.startsWith("v_")).map((d) => join21(base, d)).sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs);
|
|
6810
7023
|
} catch {
|
|
6811
7024
|
continue;
|
|
6812
7025
|
}
|
|
@@ -6818,8 +7031,8 @@ function findBinaryAgent(id, version, target, stateDir2, env) {
|
|
|
6818
7031
|
return void 0;
|
|
6819
7032
|
}
|
|
6820
7033
|
async function downloadBinaryAgent(id, version, target, stateDir2, fetchImpl = fetch) {
|
|
6821
|
-
const base = ensurePrivateDir(
|
|
6822
|
-
const dest =
|
|
7034
|
+
const base = ensurePrivateDir(join21(stateDir2, "agents", "bin", safeName(id)));
|
|
7035
|
+
const dest = join21(base, `v_${safeName(version)}`);
|
|
6823
7036
|
const tmp = `${dest}.partial-${process.pid}`;
|
|
6824
7037
|
rmSync10(tmp, { recursive: true, force: true });
|
|
6825
7038
|
mkdirSync2(tmp, { recursive: true });
|
|
@@ -6833,7 +7046,7 @@ async function downloadBinaryAgent(id, version, target, stateDir2, fetchImpl = f
|
|
|
6833
7046
|
throw new Error("the download doesn't match the registry's checksum");
|
|
6834
7047
|
}
|
|
6835
7048
|
const name = basename3(new URL(target.archive).pathname) || "agent";
|
|
6836
|
-
const file =
|
|
7049
|
+
const file = join21(tmp, name);
|
|
6837
7050
|
writeFileSync2(file, bytes);
|
|
6838
7051
|
const lower = name.toLowerCase();
|
|
6839
7052
|
if (lower.endsWith(".zip")) {
|
|
@@ -6846,14 +7059,14 @@ async function downloadBinaryAgent(id, version, target, stateDir2, fetchImpl = f
|
|
|
6846
7059
|
unpack(unpackers("bz2", file, tmp));
|
|
6847
7060
|
rmSync10(file, { force: true });
|
|
6848
7061
|
} else if (target.cmd.replace(/^\.[\\/]/, "") !== name) {
|
|
6849
|
-
renameSync2(file,
|
|
7062
|
+
renameSync2(file, join21(tmp, target.cmd.slice(2)));
|
|
6850
7063
|
}
|
|
6851
7064
|
const cmd = commandIn(tmp, target.cmd);
|
|
6852
7065
|
if (!cmd) throw new Error(`the download has no ${target.cmd}`);
|
|
6853
7066
|
chmodSync(cmd, 493);
|
|
6854
7067
|
rmSync10(dest, { recursive: true, force: true });
|
|
6855
7068
|
renameWithRetry(tmp, dest);
|
|
6856
|
-
return
|
|
7069
|
+
return join21(dest, target.cmd.slice(2));
|
|
6857
7070
|
} catch (err) {
|
|
6858
7071
|
rmSync10(tmp, { recursive: true, force: true });
|
|
6859
7072
|
throw new Error(
|
|
@@ -6932,7 +7145,7 @@ var DEVIN_AGENTS = {
|
|
|
6932
7145
|
};
|
|
6933
7146
|
function devinFile(env, home, platform4) {
|
|
6934
7147
|
if (platform4 === "win32")
|
|
6935
|
-
return
|
|
7148
|
+
return join22(
|
|
6936
7149
|
env.USERPROFILE || home,
|
|
6937
7150
|
"AppData",
|
|
6938
7151
|
"Roaming",
|
|
@@ -6941,12 +7154,12 @@ function devinFile(env, home, platform4) {
|
|
|
6941
7154
|
"acp",
|
|
6942
7155
|
"registry.json"
|
|
6943
7156
|
);
|
|
6944
|
-
const next =
|
|
6945
|
-
const dir =
|
|
6946
|
-
return
|
|
7157
|
+
const next = join22(home, ".windsurf-next");
|
|
7158
|
+
const dir = existsSync13(join22(home, ".windsurf")) || !existsSync13(next) ? ".windsurf" : ".windsurf-next";
|
|
7159
|
+
return join22(home, dir, "acp", "registry.json");
|
|
6947
7160
|
}
|
|
6948
7161
|
function devinFound(home) {
|
|
6949
|
-
return
|
|
7162
|
+
return existsSync13(join22(home, ".windsurf")) || existsSync13(join22(home, ".windsurf-next"));
|
|
6950
7163
|
}
|
|
6951
7164
|
function platformKey2(platform4, arch) {
|
|
6952
7165
|
const os = platform4 === "win32" ? "windows" : platform4 === "darwin" ? "darwin" : "linux";
|
|
@@ -6954,7 +7167,7 @@ function platformKey2(platform4, arch) {
|
|
|
6954
7167
|
}
|
|
6955
7168
|
function readJson2(file) {
|
|
6956
7169
|
try {
|
|
6957
|
-
const v = JSON.parse(
|
|
7170
|
+
const v = JSON.parse(readFileSync20(file, "utf8"));
|
|
6958
7171
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
6959
7172
|
} catch {
|
|
6960
7173
|
return void 0;
|
|
@@ -6968,8 +7181,8 @@ function devinInstalled(file) {
|
|
|
6968
7181
|
});
|
|
6969
7182
|
}
|
|
6970
7183
|
function planDevin(file, launch, uninstall, platform4 = process.platform, arch = process.arch) {
|
|
6971
|
-
const existed =
|
|
6972
|
-
const before = existed ?
|
|
7184
|
+
const existed = existsSync13(file);
|
|
7185
|
+
const before = existed ? readFileSync20(file, "utf8") : "";
|
|
6973
7186
|
const parsed = existed ? readJson2(file) : { version: "1.0.0", agents: [], extensions: [] };
|
|
6974
7187
|
if (!parsed)
|
|
6975
7188
|
return {
|
|
@@ -7014,7 +7227,7 @@ function planDevin(file, launch, uninstall, platform4 = process.platform, arch =
|
|
|
7014
7227
|
return { changes: [change], notes: [] };
|
|
7015
7228
|
}
|
|
7016
7229
|
async function runDevinInstall(o) {
|
|
7017
|
-
const home = o.home ?? (o.env.HOME || o.env.USERPROFILE ||
|
|
7230
|
+
const home = o.home ?? (o.env.HOME || o.env.USERPROFILE || homedir11());
|
|
7018
7231
|
const file = devinFile(o.env, home, process.platform);
|
|
7019
7232
|
if (!o.uninstall && !(o.found ?? devinFound(home))) {
|
|
7020
7233
|
o.out(
|
|
@@ -7064,9 +7277,9 @@ async function runDevinInstall(o) {
|
|
|
7064
7277
|
}
|
|
7065
7278
|
|
|
7066
7279
|
// src/hosts/grok/install.ts
|
|
7067
|
-
import { existsSync as
|
|
7068
|
-
import { homedir as
|
|
7069
|
-
import { join as
|
|
7280
|
+
import { existsSync as existsSync14, rmSync as rmSync11 } from "node:fs";
|
|
7281
|
+
import { homedir as homedir12 } from "node:os";
|
|
7282
|
+
import { join as join23 } from "node:path";
|
|
7070
7283
|
var MIN_GROK = versionOf("grok").min;
|
|
7071
7284
|
var GROK_EVENTS = [
|
|
7072
7285
|
"SessionStart",
|
|
@@ -7075,7 +7288,7 @@ var GROK_EVENTS = [
|
|
|
7075
7288
|
"SessionEnd"
|
|
7076
7289
|
];
|
|
7077
7290
|
function grokHooksFile(env, home) {
|
|
7078
|
-
return
|
|
7291
|
+
return join23(grokHome(env, home), "hooks", "agent-rewake.json");
|
|
7079
7292
|
}
|
|
7080
7293
|
function grokHooksJson(node, launcher) {
|
|
7081
7294
|
const cmd = (event) => `"${node}" "${launcher}" hook grok ${event}`;
|
|
@@ -7096,7 +7309,7 @@ function grokHooksJson(node, launcher) {
|
|
|
7096
7309
|
`;
|
|
7097
7310
|
}
|
|
7098
7311
|
async function runGrokInstall(o) {
|
|
7099
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
7312
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir12();
|
|
7100
7313
|
const programs2 = o.programs ?? grokPrograms({ env: o.env, home, platform: process.platform });
|
|
7101
7314
|
const probe = o.probe ?? (o.programs ? () => void 0 : versionProbe(o.env, o.node));
|
|
7102
7315
|
const picked = [...programs2].sort(
|
|
@@ -7104,7 +7317,7 @@ async function runGrokInstall(o) {
|
|
|
7104
7317
|
)[0];
|
|
7105
7318
|
const grok = picked && !o.uninstall ? withVersion(picked, probe) : picked;
|
|
7106
7319
|
const file = grokHooksFile(o.env, home);
|
|
7107
|
-
const installed =
|
|
7320
|
+
const installed = existsSync14(file);
|
|
7108
7321
|
if (o.uninstall) {
|
|
7109
7322
|
if (!installed) {
|
|
7110
7323
|
o.out("Rewake isn't set up in Grok Build: nothing to remove.\n");
|
|
@@ -7161,7 +7374,7 @@ async function runGrokInstall(o) {
|
|
|
7161
7374
|
}
|
|
7162
7375
|
const launcher = ensureLauncher(o.stateDir, o.bundle) ?? launcherPath(o.stateDir);
|
|
7163
7376
|
writeFileAtomic(
|
|
7164
|
-
ensurePrivateDir(
|
|
7377
|
+
ensurePrivateDir(join23(grokHome(o.env, home), "hooks")),
|
|
7165
7378
|
"agent-rewake.json",
|
|
7166
7379
|
grokHooksJson(o.node, launcher)
|
|
7167
7380
|
);
|
|
@@ -7177,23 +7390,23 @@ async function runGrokInstall(o) {
|
|
|
7177
7390
|
}
|
|
7178
7391
|
|
|
7179
7392
|
// src/hosts/jetbrains/install.ts
|
|
7180
|
-
import { existsSync as
|
|
7181
|
-
import { homedir as
|
|
7182
|
-
import { join as
|
|
7393
|
+
import { existsSync as existsSync15, readFileSync as readFileSync21 } from "node:fs";
|
|
7394
|
+
import { homedir as homedir13 } from "node:os";
|
|
7395
|
+
import { join as join24 } from "node:path";
|
|
7183
7396
|
var JETBRAINS_AGENTS = {
|
|
7184
7397
|
"Claude Agent (with Rewake)": "claude-acp",
|
|
7185
7398
|
"Codex (with Rewake)": "codex-acp"
|
|
7186
7399
|
};
|
|
7187
|
-
function jetbrainsFile(env, home =
|
|
7188
|
-
return
|
|
7400
|
+
function jetbrainsFile(env, home = homedir13()) {
|
|
7401
|
+
return join24(
|
|
7189
7402
|
env.USERPROFILE && process.platform === "win32" ? env.USERPROFILE : home,
|
|
7190
7403
|
".jetbrains",
|
|
7191
7404
|
"acp.json"
|
|
7192
7405
|
);
|
|
7193
7406
|
}
|
|
7194
7407
|
function jetbrainsFound(env, home, platform4) {
|
|
7195
|
-
const dirs = platform4 === "darwin" ? [
|
|
7196
|
-
return dirs.some((d) =>
|
|
7408
|
+
const dirs = platform4 === "darwin" ? [join24(home, "Library", "Application Support", "JetBrains")] : platform4 === "win32" ? env.APPDATA ? [join24(env.APPDATA, "JetBrains")] : [] : [join24(env.XDG_CONFIG_HOME || join24(home, ".config"), "JetBrains")];
|
|
7409
|
+
return dirs.some((d) => existsSync15(d)) || existsSync15(join24(home, ".jetbrains"));
|
|
7197
7410
|
}
|
|
7198
7411
|
function jetbrainsInstalled(env, home) {
|
|
7199
7412
|
const v = readJson3(jetbrainsFile(env, home));
|
|
@@ -7202,15 +7415,15 @@ function jetbrainsInstalled(env, home) {
|
|
|
7202
7415
|
}
|
|
7203
7416
|
function readJson3(file) {
|
|
7204
7417
|
try {
|
|
7205
|
-
const v = JSON.parse(
|
|
7418
|
+
const v = JSON.parse(readFileSync21(file, "utf8"));
|
|
7206
7419
|
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
7207
7420
|
} catch {
|
|
7208
7421
|
return void 0;
|
|
7209
7422
|
}
|
|
7210
7423
|
}
|
|
7211
7424
|
function planJetbrains(file, launch, uninstall) {
|
|
7212
|
-
const existed =
|
|
7213
|
-
const before = existed ?
|
|
7425
|
+
const existed = existsSync15(file);
|
|
7426
|
+
const before = existed ? readFileSync21(file, "utf8") : "";
|
|
7214
7427
|
const parsed = existed ? readJson3(file) : {};
|
|
7215
7428
|
if (!parsed)
|
|
7216
7429
|
return {
|
|
@@ -7239,7 +7452,7 @@ function planJetbrains(file, launch, uninstall) {
|
|
|
7239
7452
|
return { changes: [change], notes: [] };
|
|
7240
7453
|
}
|
|
7241
7454
|
async function runJetbrainsInstall(o) {
|
|
7242
|
-
const home = o.home ?? (o.env.HOME || o.env.USERPROFILE ||
|
|
7455
|
+
const home = o.home ?? (o.env.HOME || o.env.USERPROFILE || homedir13());
|
|
7243
7456
|
const file = jetbrainsFile(o.env, home);
|
|
7244
7457
|
if (!o.uninstall && !(o.found ?? jetbrainsFound(o.env, home, process.platform))) {
|
|
7245
7458
|
o.out(
|
|
@@ -7297,18 +7510,20 @@ var PREVIEW_NAMES = {
|
|
|
7297
7510
|
"gemini-cli": "Gemini CLI",
|
|
7298
7511
|
antigravity: "Antigravity CLI",
|
|
7299
7512
|
jetbrains: "JetBrains IDEs (AI Assistant)",
|
|
7300
|
-
"devin-desktop": "Devin Desktop (formerly Windsurf)"
|
|
7513
|
+
"devin-desktop": "Devin Desktop (formerly Windsurf)",
|
|
7514
|
+
cursor: "Cursor (its own agent)"
|
|
7301
7515
|
};
|
|
7302
7516
|
function installedPreviews(env, home, stateDir2) {
|
|
7303
7517
|
const checks = [
|
|
7304
7518
|
["claude-code", () => modInstalled(env, home)],
|
|
7305
7519
|
["codex", () => pluginInstalled(env, home)],
|
|
7306
|
-
["copilot-cli", () =>
|
|
7307
|
-
["grok", () =>
|
|
7308
|
-
["gemini-cli", () =>
|
|
7309
|
-
["antigravity", () =>
|
|
7520
|
+
["copilot-cli", () => existsSync16(hooksFile(env, home))],
|
|
7521
|
+
["grok", () => existsSync16(grokHooksFile(env, home))],
|
|
7522
|
+
["gemini-cli", () => existsSync16(extensionDir(stateDir2))],
|
|
7523
|
+
["antigravity", () => existsSync16(pluginDir(env, home))],
|
|
7310
7524
|
["jetbrains", () => jetbrainsInstalled(env, home)],
|
|
7311
|
-
["devin-desktop", () => devinInstalled(devinFile(env, home, process.platform))]
|
|
7525
|
+
["devin-desktop", () => devinInstalled(devinFile(env, home, process.platform))],
|
|
7526
|
+
["cursor", () => cursorInstalled(home)]
|
|
7312
7527
|
];
|
|
7313
7528
|
return checks.filter(([, on]) => on()).map(([id]) => id);
|
|
7314
7529
|
}
|
|
@@ -7318,12 +7533,12 @@ var AGENT_NAME = "Agent Rewake";
|
|
|
7318
7533
|
var TASK_LABEL = "Agent Rewake: schedules";
|
|
7319
7534
|
var FORMAT = { insertSpaces: true, tabSize: 2, eol: "\n" };
|
|
7320
7535
|
function launchCommand(execPath = stableNode(), script = process.argv[1] ?? "", npxCli = npmScript("npx-cli.js", execPath)) {
|
|
7321
|
-
const real =
|
|
7536
|
+
const real = existsSync17(script) ? realpathSync5(script) : script;
|
|
7322
7537
|
if (/[\\/]_npx[\\/]/.test(real)) {
|
|
7323
7538
|
const pinned = ["--yes", `${PACKAGE_NAME}@${VERSION2}`];
|
|
7324
7539
|
if (npxCli) return { command: execPath, args: [npxCli, ...pinned] };
|
|
7325
|
-
const npx =
|
|
7326
|
-
return { command:
|
|
7540
|
+
const npx = join25(dirname5(execPath), platform3() === "win32" ? "npx.cmd" : "npx");
|
|
7541
|
+
return { command: existsSync17(npx) ? npx : "npx", args: pinned };
|
|
7327
7542
|
}
|
|
7328
7543
|
return { command: execPath, args: [real] };
|
|
7329
7544
|
}
|
|
@@ -7336,12 +7551,12 @@ function stableNode(execPath = process.execPath, env = process.env, p = platform
|
|
|
7336
7551
|
}
|
|
7337
7552
|
};
|
|
7338
7553
|
const self = real(execPath);
|
|
7339
|
-
const candidates = p === "win32" ? [findOnWindows("node", env,
|
|
7554
|
+
const candidates = p === "win32" ? [findOnWindows("node", env, existsSync17)] : (env.PATH ?? "").split(delimiter2).filter(Boolean).map((d) => join25(d, "node"));
|
|
7340
7555
|
for (const c of candidates) if (c && c !== execPath && self && real(c) === self) return c;
|
|
7341
7556
|
return execPath;
|
|
7342
7557
|
}
|
|
7343
7558
|
function selfCommand(execPath = process.execPath, script = process.argv[1] ?? "") {
|
|
7344
|
-
return { command: execPath, args: [
|
|
7559
|
+
return { command: execPath, args: [existsSync17(script) ? realpathSync5(script) : script] };
|
|
7345
7560
|
}
|
|
7346
7561
|
function wrappedEntry(original, target, launch) {
|
|
7347
7562
|
const { type: _type, command: _command, args: _args, ...rest } = original;
|
|
@@ -7390,11 +7605,11 @@ function agentLabel(id, env) {
|
|
|
7390
7605
|
return name && name !== id ? `"${id}" (${name})` : `"${id}"`;
|
|
7391
7606
|
}
|
|
7392
7607
|
function taskEntry(launch, p = platform3()) {
|
|
7393
|
-
const
|
|
7608
|
+
const quote2 = p === "win32" ? quoteForCmdTask : quoteForPosix;
|
|
7394
7609
|
return {
|
|
7395
7610
|
label: TASK_LABEL,
|
|
7396
|
-
command:
|
|
7397
|
-
args: [...launch.args, "ui"].map(
|
|
7611
|
+
command: quote2(launch.command),
|
|
7612
|
+
args: [...launch.args, "ui"].map(quote2),
|
|
7398
7613
|
...p === "win32" && { shell: { program: "cmd" } },
|
|
7399
7614
|
reveal: "always",
|
|
7400
7615
|
use_new_terminal: false
|
|
@@ -7407,7 +7622,7 @@ function keyChord() {
|
|
|
7407
7622
|
}
|
|
7408
7623
|
var KEY_ACTION = ["task::Spawn", { task_name: TASK_LABEL }];
|
|
7409
7624
|
function read(file) {
|
|
7410
|
-
return
|
|
7625
|
+
return existsSync17(file) ? { text: readText(file), existed: true } : { text: "", existed: false };
|
|
7411
7626
|
}
|
|
7412
7627
|
function parseJsonc(text2) {
|
|
7413
7628
|
const errors = [];
|
|
@@ -7416,8 +7631,8 @@ function parseJsonc(text2) {
|
|
|
7416
7631
|
return first ? { value, error: `${printParseErrorCode(first.error)} at offset ${first.offset}` } : { value };
|
|
7417
7632
|
}
|
|
7418
7633
|
function zedAgentSetting(agentId, key2, env = process.env) {
|
|
7419
|
-
const file =
|
|
7420
|
-
if (!
|
|
7634
|
+
const file = join25(zedConfigDir(env), "settings.json");
|
|
7635
|
+
if (!existsSync17(file)) return void 0;
|
|
7421
7636
|
const value = parseJsonc(readText(file)).value;
|
|
7422
7637
|
const servers = value && typeof value === "object" ? value.agent_servers : void 0;
|
|
7423
7638
|
const entry = servers && typeof servers === "object" ? servers[agentId] : void 0;
|
|
@@ -7455,7 +7670,7 @@ function planInstall(opts) {
|
|
|
7455
7670
|
const changes = [];
|
|
7456
7671
|
const notes = [];
|
|
7457
7672
|
const env = opts.env ?? process.env;
|
|
7458
|
-
const settingsFile =
|
|
7673
|
+
const settingsFile = join25(opts.dir, "settings.json");
|
|
7459
7674
|
const settings = read(settingsFile);
|
|
7460
7675
|
const base = settings.text.trim() === "" ? "{}\n" : settings.text;
|
|
7461
7676
|
const parsed = parseJsonc(base);
|
|
@@ -7543,7 +7758,7 @@ function planInstall(opts) {
|
|
|
7543
7758
|
]
|
|
7544
7759
|
});
|
|
7545
7760
|
}
|
|
7546
|
-
const tasksFile =
|
|
7761
|
+
const tasksFile = join25(opts.dir, "tasks.json");
|
|
7547
7762
|
const tasks = read(tasksFile);
|
|
7548
7763
|
const tasksBase = tasks.text.trim() === "" ? "[]\n" : tasks.text;
|
|
7549
7764
|
const tasksParsed = parseJsonc(tasksBase);
|
|
@@ -7569,7 +7784,7 @@ function planInstall(opts) {
|
|
|
7569
7784
|
});
|
|
7570
7785
|
}
|
|
7571
7786
|
if (opts.keybinding) {
|
|
7572
|
-
const keymapFile =
|
|
7787
|
+
const keymapFile = join25(opts.dir, "keymap.json");
|
|
7573
7788
|
const keymap = read(keymapFile);
|
|
7574
7789
|
const keymapBase = keymap.text.trim() === "" ? "[]\n" : keymap.text;
|
|
7575
7790
|
const keymapParsed = parseJsonc(keymapBase);
|
|
@@ -7599,8 +7814,8 @@ function planInstall(opts) {
|
|
|
7599
7814
|
}
|
|
7600
7815
|
return { changes, notes };
|
|
7601
7816
|
}
|
|
7602
|
-
function missingLaunchFiles(dir, exists =
|
|
7603
|
-
const parsed = parseJsonc(read(
|
|
7817
|
+
function missingLaunchFiles(dir, exists = existsSync17) {
|
|
7818
|
+
const parsed = parseJsonc(read(join25(dir, "settings.json")).text || "{}");
|
|
7604
7819
|
const servers = isRecord2(parsed.value) ? parsed.value.agent_servers : void 0;
|
|
7605
7820
|
if (parsed.error || !isRecord2(servers)) return [];
|
|
7606
7821
|
const missing = [];
|
|
@@ -7616,7 +7831,7 @@ function missingLaunchFiles(dir, exists = existsSync16) {
|
|
|
7616
7831
|
function planUninstall(dir) {
|
|
7617
7832
|
const changes = [];
|
|
7618
7833
|
const notes = [];
|
|
7619
|
-
const settingsFile =
|
|
7834
|
+
const settingsFile = join25(dir, "settings.json");
|
|
7620
7835
|
const settings = read(settingsFile);
|
|
7621
7836
|
const parsed = parseJsonc(settings.text || "{}");
|
|
7622
7837
|
const servers = isRecord2(parsed.value) ? parsed.value.agent_servers : void 0;
|
|
@@ -7643,7 +7858,7 @@ function planUninstall(dir) {
|
|
|
7643
7858
|
summary
|
|
7644
7859
|
});
|
|
7645
7860
|
}
|
|
7646
|
-
const tasksFile =
|
|
7861
|
+
const tasksFile = join25(dir, "tasks.json");
|
|
7647
7862
|
const tasks = read(tasksFile);
|
|
7648
7863
|
const taskList = parseJsonc(tasks.text || "[]");
|
|
7649
7864
|
if (!taskList.error && Array.isArray(taskList.value)) {
|
|
@@ -7659,7 +7874,7 @@ function planUninstall(dir) {
|
|
|
7659
7874
|
summary: [`Remove the task "${TASK_LABEL}"`]
|
|
7660
7875
|
});
|
|
7661
7876
|
}
|
|
7662
|
-
const keymapFile =
|
|
7877
|
+
const keymapFile = join25(dir, "keymap.json");
|
|
7663
7878
|
const keymap = read(keymapFile);
|
|
7664
7879
|
const blocks = parseJsonc(keymap.text || "[]");
|
|
7665
7880
|
if (!blocks.error && Array.isArray(blocks.value)) {
|
|
@@ -7701,12 +7916,12 @@ function applyPlan(plan, now = /* @__PURE__ */ new Date()) {
|
|
|
7701
7916
|
let bom = "";
|
|
7702
7917
|
if (change.existed) {
|
|
7703
7918
|
mode = statSync4(target).mode & 511;
|
|
7704
|
-
if (
|
|
7919
|
+
if (readFileSync22(target, "utf8").charCodeAt(0) === 65279) bom = "\uFEFF";
|
|
7705
7920
|
const backup = `${target}.agent-rewake-backup-${stamp(now)}`;
|
|
7706
7921
|
copyFileSync(target, backup);
|
|
7707
7922
|
backups.push(backup);
|
|
7708
7923
|
}
|
|
7709
|
-
const tmp =
|
|
7924
|
+
const tmp = join25(dir, `.${basename4(target)}.agent-rewake.${process.pid}.tmp`);
|
|
7710
7925
|
const fd = openSync8(tmp, "w", mode);
|
|
7711
7926
|
try {
|
|
7712
7927
|
writeSync3(fd, bom + change.after);
|
|
@@ -7759,7 +7974,7 @@ async function runInstall(opts) {
|
|
|
7759
7974
|
out(describe(plan, opts.uninstall ? "remove these entries from Zed" : "set up Zed"));
|
|
7760
7975
|
let note;
|
|
7761
7976
|
if (!opts.uninstall) {
|
|
7762
|
-
const home = opts.env.HOME || opts.env.USERPROFILE ||
|
|
7977
|
+
const home = opts.env.HOME || opts.env.USERPROFILE || homedir14();
|
|
7763
7978
|
const ids = opts.previews ? [] : installedPreviews(opts.env, home, stateDir(opts.env));
|
|
7764
7979
|
const previews = opts.previews ?? ids.map((id) => PREVIEW_NAMES[id] ?? id);
|
|
7765
7980
|
note = otherAgentsNote(
|
|
@@ -8047,7 +8262,7 @@ function overviewMarkdown(groups, now, locale) {
|
|
|
8047
8262
|
|
|
8048
8263
|
// src/util/keep-awake.ts
|
|
8049
8264
|
import { spawn as spawn6 } from "node:child_process";
|
|
8050
|
-
import { existsSync as
|
|
8265
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
8051
8266
|
import { platform as osPlatform3 } from "node:os";
|
|
8052
8267
|
var CAFFEINATE = "/usr/bin/caffeinate";
|
|
8053
8268
|
var Wakefulness = class {
|
|
@@ -8062,7 +8277,7 @@ var Wakefulness = class {
|
|
|
8062
8277
|
this.pid = opts.pid ?? process.pid;
|
|
8063
8278
|
}
|
|
8064
8279
|
get supported() {
|
|
8065
|
-
return this.platform === "darwin" &&
|
|
8280
|
+
return this.platform === "darwin" && existsSync18(this.program);
|
|
8066
8281
|
}
|
|
8067
8282
|
get holding() {
|
|
8068
8283
|
return this.child !== void 0 && this.child.exitCode === null && this.child.signalCode === null;
|
|
@@ -8101,9 +8316,9 @@ var Wakefulness = class {
|
|
|
8101
8316
|
|
|
8102
8317
|
// src/util/sleep-settings.ts
|
|
8103
8318
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
8104
|
-
import { readFileSync as
|
|
8105
|
-
import { homedir as
|
|
8106
|
-
import { join as
|
|
8319
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
8320
|
+
import { homedir as homedir15, platform as osPlatform4 } from "node:os";
|
|
8321
|
+
import { join as join26 } from "node:path";
|
|
8107
8322
|
var SLEEP_DOCS_URL = "https://codizelabs.github.io/agent-rewake/docs/#keep-your-computer-awake";
|
|
8108
8323
|
var run2 = (command, args) => {
|
|
8109
8324
|
const r = spawnSync7(command, args, { encoding: "utf8", timeout: 3e3, windowsHide: true });
|
|
@@ -8117,7 +8332,7 @@ function readSleepSettings(o = {}) {
|
|
|
8117
8332
|
const exec = o.run ?? run2;
|
|
8118
8333
|
const read2 = o.readFile ?? ((path) => {
|
|
8119
8334
|
try {
|
|
8120
|
-
return
|
|
8335
|
+
return readFileSync23(path, "utf8");
|
|
8121
8336
|
} catch {
|
|
8122
8337
|
return void 0;
|
|
8123
8338
|
}
|
|
@@ -8133,7 +8348,7 @@ function readSleepSettings(o = {}) {
|
|
|
8133
8348
|
}
|
|
8134
8349
|
if (p === "win32") return { os: "windows", ...windowsSleep(exec) };
|
|
8135
8350
|
if (p === "linux")
|
|
8136
|
-
return { os: "linux", ...linuxSleep(exec, o.env ?? process.env, o.home ??
|
|
8351
|
+
return { os: "linux", ...linuxSleep(exec, o.env ?? process.env, o.home ?? homedir15(), read2) };
|
|
8137
8352
|
return { os: "other" };
|
|
8138
8353
|
}
|
|
8139
8354
|
function parsePmset(text2) {
|
|
@@ -8209,7 +8424,7 @@ function linuxSleep(exec, env, home, read2) {
|
|
|
8209
8424
|
out.pluggedInSleepMin = kind === "nothing" || kind === "blank" ? 0 : Math.ceil(seconds / 60);
|
|
8210
8425
|
}
|
|
8211
8426
|
} else if (desktops.includes("kde")) {
|
|
8212
|
-
const rc = read2(
|
|
8427
|
+
const rc = read2(join26(env.XDG_CONFIG_HOME || join26(home, ".config"), "powerdevilrc"));
|
|
8213
8428
|
out.desktop = "kde";
|
|
8214
8429
|
const group = /\[AC\]\[SuspendAndShutdown\]([\s\S]*?)(?:\n\[|$)/.exec(rc ?? "")?.[1] ?? "";
|
|
8215
8430
|
const key2 = (k) => new RegExp(`^${k}=(\\S+)`, "m").exec(group)?.[1];
|
|
@@ -10277,7 +10492,7 @@ ${this.listText(session)}`;
|
|
|
10277
10492
|
return command.on ? `Rewake: Automatic resume is on for this thread. After a usage limit, Rewake sends your resume message when the limit resets, follows the new reset time if ${this.agentName} is still limited, and never approves permission requests. Turn it off with /${COMMAND_NAME} auto off.` : "Rewake: Automatic resume is off for this thread. After a usage limit, Rewake will ask first.";
|
|
10278
10493
|
}
|
|
10279
10494
|
case "page": {
|
|
10280
|
-
const file =
|
|
10495
|
+
const file = join27(ensurePrivateDir(this.opts.stateDir), "Schedules.md");
|
|
10281
10496
|
writeFileSync3(file, overviewMarkdown(overview(this.opts.stateDir), now, this.opts.locale), {
|
|
10282
10497
|
mode: 384
|
|
10283
10498
|
});
|
|
@@ -10510,11 +10725,11 @@ ${lines.join("\n")}`;
|
|
|
10510
10725
|
const now = this.now();
|
|
10511
10726
|
const owned = [...this.sessions.values()].filter((s) => this.lock.holds(s.sessionId));
|
|
10512
10727
|
const ids = new Set(owned.map((s) => s.sessionId));
|
|
10513
|
-
const
|
|
10728
|
+
const due2 = ids.size === 0 ? [] : this.store.list().filter(
|
|
10514
10729
|
(s) => ids.has(s.sessionId) && (s.status === "scheduled" || s.status === "queued") && s.dueAt - now <= WAKE_HORIZON_MS
|
|
10515
10730
|
);
|
|
10516
10731
|
const delivering = owned.find((s) => s.delivering !== void 0);
|
|
10517
|
-
const want =
|
|
10732
|
+
const want = due2.length > 0 || delivering !== void 0;
|
|
10518
10733
|
const held = this.wake.set(want, settings.keepAwake);
|
|
10519
10734
|
if (!want || settings.keepAwake === "never") return;
|
|
10520
10735
|
if (!this.wakeUnsupportedSaid) {
|
|
@@ -10522,8 +10737,8 @@ ${lines.join("\n")}`;
|
|
|
10522
10737
|
const sleep = this.sleepSettingsNow(now);
|
|
10523
10738
|
const known = sleep.pluggedInSleepMin !== void 0 || sleep.onBattery !== void 0;
|
|
10524
10739
|
const risks = sleepRisks(sleep, hold);
|
|
10525
|
-
const where2 = this.sessions.get(
|
|
10526
|
-
const first =
|
|
10740
|
+
const where2 = this.sessions.get(due2[0]?.sessionId ?? "") ?? delivering;
|
|
10741
|
+
const first = due2[0];
|
|
10527
10742
|
if (where2 && (risks.length > 0 || !known && !held)) {
|
|
10528
10743
|
this.wakeUnsupportedSaid = true;
|
|
10529
10744
|
const what = first ? ` before ${first.kind === "user" ? "the scheduled message" : "the resume"} at ${formatWhen(first.dueAt, now, this.opts.locale)}` : "";
|
|
@@ -10534,7 +10749,7 @@ ${lines.join("\n")}`;
|
|
|
10534
10749
|
}
|
|
10535
10750
|
}
|
|
10536
10751
|
if (!held) return;
|
|
10537
|
-
for (const s of
|
|
10752
|
+
for (const s of due2) {
|
|
10538
10753
|
const key2 = s.scheduleId;
|
|
10539
10754
|
const session = this.sessions.get(s.sessionId);
|
|
10540
10755
|
if (this.wakeAnnounced.has(key2) || !session) continue;
|
|
@@ -10696,7 +10911,7 @@ ${lines.join("\n")}`;
|
|
|
10696
10911
|
}
|
|
10697
10912
|
const content = await this.form(
|
|
10698
10913
|
session,
|
|
10699
|
-
`${capitalize2(this.agentName)} wants to change the message scheduled for ${at(s.dueAt)} ("${preview(s.text)}"): ${changes.join("
|
|
10914
|
+
`${capitalize2(this.agentName)} wants to change the message scheduled for ${at(s.dueAt)} ("${preview(s.text)}"): ${changes.join("; ")}.${why}${editing ? " You can edit the new message." : ""} Submit changes it; Decline keeps it as it is.`,
|
|
10700
10915
|
editing ? { message: { type: "string", title: "Message", minLength: 1, default: r.message } } : {},
|
|
10701
10916
|
editing ? ["message"] : []
|
|
10702
10917
|
);
|
|
@@ -10798,12 +11013,12 @@ ${lines.join("\n")}`;
|
|
|
10798
11013
|
* message moves on to its next run.
|
|
10799
11014
|
*/
|
|
10800
11015
|
recoverInterrupted(session, s, now) {
|
|
10801
|
-
const
|
|
11016
|
+
const due2 = formatWhen(s.dueAt, now, this.opts.locale);
|
|
10802
11017
|
if (s.repeat) {
|
|
10803
11018
|
this.store.update(s.scheduleId, (x) => ({ ...x, status: "failed" }), now);
|
|
10804
11019
|
this.status(
|
|
10805
11020
|
session,
|
|
10806
|
-
`Rewake: The run due at ${
|
|
11021
|
+
`Rewake: The run due at ${due2} was interrupted, because Zed closed during its reply.`
|
|
10807
11022
|
);
|
|
10808
11023
|
this.repeatNext(session, s.scheduleId, now);
|
|
10809
11024
|
} else {
|
|
@@ -10812,13 +11027,13 @@ ${lines.join("\n")}`;
|
|
|
10812
11027
|
(x) => ({ ...x, status: "needs_attention", failureReason: "interrupted" }),
|
|
10813
11028
|
now
|
|
10814
11029
|
);
|
|
10815
|
-
const question = `Rewake: The message scheduled for ${
|
|
11030
|
+
const question = `Rewake: The message scheduled for ${due2} was interrupted, because Zed closed during its reply: "${preview(s.text)}". Send it again?`;
|
|
10816
11031
|
if (this.clientSupportsForms && !session.formOpen)
|
|
10817
11032
|
void this.offerRetry(session, s.scheduleId, question);
|
|
10818
11033
|
else
|
|
10819
11034
|
this.status(
|
|
10820
11035
|
session,
|
|
10821
|
-
`Rewake: The message scheduled for ${
|
|
11036
|
+
`Rewake: The message scheduled for ${due2} was interrupted, because Zed closed during its reply. Type /rewake list, then /rewake now N to send it again.`
|
|
10822
11037
|
);
|
|
10823
11038
|
}
|
|
10824
11039
|
this.refreshMarker(session);
|
|
@@ -11225,11 +11440,15 @@ async function runContinue(o) {
|
|
|
11225
11440
|
o.out("Nothing to continue: no closed session is stopped at a usage limit.\n");
|
|
11226
11441
|
return 0;
|
|
11227
11442
|
}
|
|
11228
|
-
const
|
|
11443
|
+
const failedTry = (c) => new ScheduleStore(o.deps.stateDir).listForSession(c.record.sessionId, c.host.id).filter((s) => s.status === "failed" && s.updatedAt >= (c.record.limit?.seenAt ?? 0)).sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
11444
|
+
const line = (c) => {
|
|
11445
|
+
const f = failedTry(c);
|
|
11446
|
+
return `${placeOf(c.host, c.record.cwd)}: stopped ${formatAt(c.record.limit?.seenAt ?? now, now)}${c.resetsAt && c.resetsAt + RESET_MARGIN_MS > now ? `; Rewake can continue it ${formatAt(c.resetsAt + RESET_MARGIN_MS, now)}` : ""}${f ? `; Rewake tried ${formatAt(f.lastRun?.at ?? f.updatedAt, now)}${f.failureMessage ? ` and ${c.host.name} ended with: "${f.failureMessage}"` : ", without success"}` : ""}`;
|
|
11447
|
+
};
|
|
11229
11448
|
if (!o.interactive) {
|
|
11230
11449
|
o.out(
|
|
11231
11450
|
`${list.map((c) => ` ${line(c)}`).join("\n")}
|
|
11232
|
-
Run "agent-rewake continue" in a terminal to choose.
|
|
11451
|
+
Run "agent-rewake continue" in a terminal to choose.${list.some((c) => failedTry(c)) ? " If a try failed, open that session in its agent to continue it yourself." : ""}
|
|
11233
11452
|
`
|
|
11234
11453
|
);
|
|
11235
11454
|
return 1;
|
|
@@ -11252,13 +11471,23 @@ ${list.map((c, i) => ` ${i + 1}. ${line(c)}`).join("\n")}
|
|
|
11252
11471
|
chosen = pick;
|
|
11253
11472
|
}
|
|
11254
11473
|
const place = placeOf(chosen.host, chosen.record.cwd);
|
|
11474
|
+
const latest = chosen.host.maxWaitAfterLimitMs === void 0 ? void 0 : (chosen.record.limit?.seenAt ?? now) + chosen.host.maxWaitAfterLimitMs;
|
|
11475
|
+
const tooLate = () => `Rewake can continue ${place} only until ${formatWhen(latest ?? now, now)}, 4 hours after its usage limit. Choose an earlier time, or open the ${chosen.host.noun ?? "session"} later and continue it yourself.
|
|
11476
|
+
`;
|
|
11477
|
+
if (latest !== void 0 && latest - now < 5 * 6e4) {
|
|
11478
|
+
o.out(
|
|
11479
|
+
`Rewake can continue ${place} only within 4 hours of its usage limit, and that time has passed. Open the ${chosen.host.noun ?? "session"} and continue it yourself.
|
|
11480
|
+
`
|
|
11481
|
+
);
|
|
11482
|
+
return 1;
|
|
11483
|
+
}
|
|
11255
11484
|
let at;
|
|
11256
11485
|
if (chosen.resetsAt && chosen.resetsAt + RESET_MARGIN_MS > now) {
|
|
11257
11486
|
at = chosen.resetsAt + RESET_MARGIN_MS;
|
|
11258
11487
|
} else {
|
|
11259
|
-
const presets = [1, 3, 5].map((h) => ({ h, at: now + h * 36e5 }));
|
|
11488
|
+
const presets = [1, 3, 5].map((h) => ({ h, at: now + h * 36e5 })).filter((p) => latest === void 0 || p.at <= latest);
|
|
11260
11489
|
o.out(
|
|
11261
|
-
`When should Rewake continue ${place}?
|
|
11490
|
+
`When should Rewake continue ${place}?${latest === void 0 ? "" : ` (It can wait until ${formatWhen(latest, now)}, 4 hours after the limit.)`}
|
|
11262
11491
|
${presets.map(
|
|
11263
11492
|
(p, i) => ` ${i + 1}. In ${p.h} hour${p.h === 1 ? "" : "s"} (${formatWhen(p.at, now)})`
|
|
11264
11493
|
).join("\n")}
|
|
@@ -11287,6 +11516,10 @@ ${presets.map(
|
|
|
11287
11516
|
return 1;
|
|
11288
11517
|
}
|
|
11289
11518
|
}
|
|
11519
|
+
if (latest !== void 0 && at > latest) {
|
|
11520
|
+
o.out(tooLate());
|
|
11521
|
+
return 1;
|
|
11522
|
+
}
|
|
11290
11523
|
armClosed(chosen.host, chosen.record, at, o.deps);
|
|
11291
11524
|
o.out(`${armedText(chosen.host, chosen.record.cwd, at, now)}
|
|
11292
11525
|
`);
|
|
@@ -11296,7 +11529,7 @@ ${presets.map(
|
|
|
11296
11529
|
`This computer may sleep before then: ${risks.join("; ")}. To keep it awake: ${SLEEP_DOCS_URL}
|
|
11297
11530
|
`
|
|
11298
11531
|
);
|
|
11299
|
-
if (settings.newThreads !== "on")
|
|
11532
|
+
if (settings.newThreads !== "on" && latest === void 0)
|
|
11300
11533
|
o.out(
|
|
11301
11534
|
"To let Rewake do this by itself next time (when the reset is within a day): agent-rewake continue --always\n"
|
|
11302
11535
|
);
|
|
@@ -11304,16 +11537,16 @@ ${presets.map(
|
|
|
11304
11537
|
}
|
|
11305
11538
|
|
|
11306
11539
|
// src/doctor.ts
|
|
11307
|
-
import { existsSync as
|
|
11308
|
-
import { homedir as
|
|
11309
|
-
import { join as
|
|
11540
|
+
import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync25, rmSync as rmSync12, writeFileSync as writeFileSync4 } from "node:fs";
|
|
11541
|
+
import { homedir as homedir16 } from "node:os";
|
|
11542
|
+
import { join as join29 } from "node:path";
|
|
11310
11543
|
|
|
11311
11544
|
// src/setup.ts
|
|
11312
|
-
import { existsSync as
|
|
11313
|
-
import { join as
|
|
11545
|
+
import { existsSync as existsSync19, readdirSync as readdirSync9, readFileSync as readFileSync24 } from "node:fs";
|
|
11546
|
+
import { join as join28 } from "node:path";
|
|
11314
11547
|
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11315
11548
|
function detectSetup(zedConfigDir2, stateDir2) {
|
|
11316
|
-
const file =
|
|
11549
|
+
const file = join28(zedConfigDir2, "settings.json");
|
|
11317
11550
|
const setup = {
|
|
11318
11551
|
settings: "missing",
|
|
11319
11552
|
aiOff: false,
|
|
@@ -11324,7 +11557,7 @@ function detectSetup(zedConfigDir2, stateDir2) {
|
|
|
11324
11557
|
};
|
|
11325
11558
|
const started = lastStart(stateDir2);
|
|
11326
11559
|
if (started) setup.lastStart = started;
|
|
11327
|
-
if (!
|
|
11560
|
+
if (!existsSync19(file)) return setup;
|
|
11328
11561
|
const errors = [];
|
|
11329
11562
|
const value = parse2(readText(file) || "{}", errors, { allowTrailingComma: true });
|
|
11330
11563
|
if (errors.length > 0 || !isRecord3(value)) return { ...setup, settings: "invalid" };
|
|
@@ -11347,7 +11580,7 @@ function agentName(id, env = process.env) {
|
|
|
11347
11580
|
return id === CLAUDE_REGISTRY_ID ? "Claude Agent" : id;
|
|
11348
11581
|
}
|
|
11349
11582
|
function lastStart(stateDir2) {
|
|
11350
|
-
const dir =
|
|
11583
|
+
const dir = join28(stateDir2, "logs");
|
|
11351
11584
|
let files;
|
|
11352
11585
|
try {
|
|
11353
11586
|
files = readdirSync9(dir).filter((f) => /^rewake-\d{4}-\d{2}-\d{2}\.jsonl$/.test(f)).sort().reverse();
|
|
@@ -11355,7 +11588,7 @@ function lastStart(stateDir2) {
|
|
|
11355
11588
|
return void 0;
|
|
11356
11589
|
}
|
|
11357
11590
|
for (const f of files) {
|
|
11358
|
-
const lines =
|
|
11591
|
+
const lines = readFileSync24(join28(dir, f), "utf8").trim().split("\n").reverse();
|
|
11359
11592
|
for (const line of lines) {
|
|
11360
11593
|
try {
|
|
11361
11594
|
const r = JSON.parse(line);
|
|
@@ -11379,14 +11612,14 @@ var MIN_ZED = "1.22.0";
|
|
|
11379
11612
|
var MIN_NODE = 22;
|
|
11380
11613
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
11381
11614
|
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11382
|
-
function findZedApps(platform4 = process.platform, home =
|
|
11615
|
+
function findZedApps(platform4 = process.platform, home = homedir16(), env = process.env) {
|
|
11383
11616
|
const apps2 = [];
|
|
11384
11617
|
const names = ["Zed", "Zed Preview", "Zed Nightly", "Zed Dev"];
|
|
11385
11618
|
if (platform4 === "darwin") {
|
|
11386
|
-
for (const dir of ["/Applications",
|
|
11619
|
+
for (const dir of ["/Applications", join29(home, "Applications")]) {
|
|
11387
11620
|
for (const name of names) {
|
|
11388
|
-
const plist =
|
|
11389
|
-
if (!
|
|
11621
|
+
const plist = join29(dir, `${name}.app`, "Contents", "Info.plist");
|
|
11622
|
+
if (!existsSync20(plist) || apps2.some((a) => a.name === name)) continue;
|
|
11390
11623
|
const m = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/.exec(
|
|
11391
11624
|
readTextSafe(plist)
|
|
11392
11625
|
);
|
|
@@ -11399,25 +11632,25 @@ function findZedApps(platform4 = process.platform, home = homedir14(), env = pro
|
|
|
11399
11632
|
["Zed Preview", "zed-preview.app"],
|
|
11400
11633
|
["Zed Nightly", "zed-nightly.app"]
|
|
11401
11634
|
];
|
|
11402
|
-
for (const [name, dir] of local) if (
|
|
11403
|
-
if (
|
|
11635
|
+
for (const [name, dir] of local) if (existsSync20(join29(home, ".local", dir))) apps2.push({ name });
|
|
11636
|
+
if (existsSync20(join29(home, ".var", "app", "dev.zed.Zed"))) apps2.push({ name: "Zed (Flatpak)" });
|
|
11404
11637
|
} else if (platform4 === "win32") {
|
|
11405
|
-
const programs2 = env.LOCALAPPDATA ?
|
|
11638
|
+
const programs2 = env.LOCALAPPDATA ? join29(env.LOCALAPPDATA, "Programs") : void 0;
|
|
11406
11639
|
if (programs2) {
|
|
11407
|
-
for (const name of names) if (
|
|
11640
|
+
for (const name of names) if (existsSync20(join29(programs2, name))) apps2.push({ name });
|
|
11408
11641
|
}
|
|
11409
11642
|
}
|
|
11410
11643
|
return apps2;
|
|
11411
11644
|
}
|
|
11412
11645
|
function readTextSafe(file) {
|
|
11413
11646
|
try {
|
|
11414
|
-
return
|
|
11647
|
+
return readFileSync25(file, "utf8");
|
|
11415
11648
|
} catch {
|
|
11416
11649
|
return "";
|
|
11417
11650
|
}
|
|
11418
11651
|
}
|
|
11419
11652
|
function recentLogs(state, now, days = 14) {
|
|
11420
|
-
const dir =
|
|
11653
|
+
const dir = join29(state, "logs");
|
|
11421
11654
|
let files;
|
|
11422
11655
|
try {
|
|
11423
11656
|
files = readdirSync10(dir).filter((f) => /^rewake-\d{4}-\d{2}-\d{2}\.jsonl$/.test(f));
|
|
@@ -11429,7 +11662,7 @@ function recentLogs(state, now, days = 14) {
|
|
|
11429
11662
|
const out = [];
|
|
11430
11663
|
for (const f of files.sort()) {
|
|
11431
11664
|
if (f.slice(7, 17) < cutoff) continue;
|
|
11432
|
-
for (const line of readTextSafe(
|
|
11665
|
+
for (const line of readTextSafe(join29(dir, f)).split("\n")) {
|
|
11433
11666
|
if (!line.startsWith("{")) continue;
|
|
11434
11667
|
try {
|
|
11435
11668
|
const r = JSON.parse(line);
|
|
@@ -11443,7 +11676,7 @@ function recentLogs(state, now, days = 14) {
|
|
|
11443
11676
|
return out.sort((a, b) => a.t - b.t);
|
|
11444
11677
|
}
|
|
11445
11678
|
function readSettings(file) {
|
|
11446
|
-
if (!
|
|
11679
|
+
if (!existsSync20(file)) return { state: "missing", value: {} };
|
|
11447
11680
|
const errors = [];
|
|
11448
11681
|
const value = parse2(readText(file) || "{}", errors, { allowTrailingComma: true });
|
|
11449
11682
|
if (errors.length > 0 || !isRecord4(value)) return { state: "invalid", value: {} };
|
|
@@ -11472,7 +11705,7 @@ function diagnose(ctx) {
|
|
|
11472
11705
|
const reach = otherAgentsNote(ctx.agents?.() ?? [], ctx.previews?.() ?? []);
|
|
11473
11706
|
let reachShown = false;
|
|
11474
11707
|
const apps2 = ctx.zedApps();
|
|
11475
|
-
const settingsFile =
|
|
11708
|
+
const settingsFile = join29(zedDir, "settings.json");
|
|
11476
11709
|
const settings = readSettings(settingsFile);
|
|
11477
11710
|
if (apps2.length > 0) {
|
|
11478
11711
|
const old = apps2.filter((a) => a.version && compareVersions(a.version, MIN_ZED) < 0);
|
|
@@ -11590,8 +11823,8 @@ function diagnose(ctx) {
|
|
|
11590
11823
|
text: `Not on yet for: ${names(addable)}.`,
|
|
11591
11824
|
fix: `Run: ${install}`
|
|
11592
11825
|
});
|
|
11593
|
-
const noRegistry = !
|
|
11594
|
-
|
|
11826
|
+
const noRegistry = !existsSync20(
|
|
11827
|
+
join29(zedDataDir(env), "external_agents", "registry", "registry.json")
|
|
11595
11828
|
);
|
|
11596
11829
|
for (const [id, why] of skipped) {
|
|
11597
11830
|
if (noRegistry && /copy of the ACP Registry/.test(why)) continue;
|
|
@@ -11646,7 +11879,7 @@ function diagnose(ctx) {
|
|
|
11646
11879
|
const registryIds = Object.entries(servers).filter(([id, e]) => isRecord4(e) && id !== CLAUDE_REGISTRY_ID && id !== AGENT_NAME).filter(
|
|
11647
11880
|
([, e]) => isRecord4(e) && (e.type === "registry" || (unwrappedEntry(e)?.type ?? "") === "registry")
|
|
11648
11881
|
).map(([id]) => id);
|
|
11649
|
-
if (registryIds.length > 0 && !
|
|
11882
|
+
if (registryIds.length > 0 && !existsSync20(join29(zedDataDir(env), "external_agents", "registry", "registry.json")))
|
|
11650
11883
|
add({
|
|
11651
11884
|
area: "Zed",
|
|
11652
11885
|
level: "todo",
|
|
@@ -11873,9 +12106,9 @@ function diagnose(ctx) {
|
|
|
11873
12106
|
return findings;
|
|
11874
12107
|
}
|
|
11875
12108
|
function canWrite(dir) {
|
|
11876
|
-
if (!
|
|
12109
|
+
if (!existsSync20(dir)) return true;
|
|
11877
12110
|
try {
|
|
11878
|
-
const probe =
|
|
12111
|
+
const probe = join29(dir, `.doctor-${process.pid}.tmp`);
|
|
11879
12112
|
writeFileSync4(probe, "");
|
|
11880
12113
|
rmSync12(probe, { force: true });
|
|
11881
12114
|
return true;
|
|
@@ -12001,7 +12234,7 @@ function detailLines(ctx) {
|
|
|
12001
12234
|
}
|
|
12002
12235
|
|
|
12003
12236
|
// src/hosts/codex/install.ts
|
|
12004
|
-
import { homedir as
|
|
12237
|
+
import { homedir as homedir17 } from "node:os";
|
|
12005
12238
|
var MIN_CODEX = versionOf("codex").min;
|
|
12006
12239
|
function pickCodex(programs2) {
|
|
12007
12240
|
const byVersion = (a, b) => compareVersions(b.version ?? "0", a.version ?? "0");
|
|
@@ -12009,7 +12242,7 @@ function pickCodex(programs2) {
|
|
|
12009
12242
|
return cli[0] ?? [...programs2].sort(byVersion)[0];
|
|
12010
12243
|
}
|
|
12011
12244
|
async function runCodexInstall(o) {
|
|
12012
|
-
const home = o.env.HOME || o.env.USERPROFILE ||
|
|
12245
|
+
const home = o.env.HOME || o.env.USERPROFILE || homedir17();
|
|
12013
12246
|
const programs2 = o.programs ?? codexPrograms({ env: o.env, home, platform: process.platform });
|
|
12014
12247
|
const probe = o.probe ?? (o.programs ? () => void 0 : versionProbe(o.env, o.node));
|
|
12015
12248
|
const picked = pickCodex(programs2);
|
|
@@ -12113,1275 +12346,1544 @@ async function runCodexInstall(o) {
|
|
|
12113
12346
|
return 0;
|
|
12114
12347
|
}
|
|
12115
12348
|
|
|
12116
|
-
// src/
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12134
|
-
|
|
12135
|
-
|
|
12136
|
-
|
|
12137
|
-
const off = hooksTurnedOff(p.id, f.env, f.home);
|
|
12138
|
-
if (off)
|
|
12139
|
-
add({
|
|
12140
|
-
level: "problem",
|
|
12141
|
-
text: `${p.name} has its hooks turned off, so Rewake can't see its usage limits.`,
|
|
12142
|
-
fix: `Turn hooks back on in ${p.name}'s settings, or take Rewake out of it: agent-rewake uninstall --only ${p.id}`
|
|
12349
|
+
// src/timers/waiter.ts
|
|
12350
|
+
import { readdirSync as readdirSync12, readFileSync as readFileSync27, rmSync as rmSync14, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12351
|
+
import { join as join31 } from "node:path";
|
|
12352
|
+
|
|
12353
|
+
// src/timers/timers.ts
|
|
12354
|
+
import { spawn as spawn7, spawnSync as spawnSync8 } from "node:child_process";
|
|
12355
|
+
import { existsSync as existsSync21, readdirSync as readdirSync11, readFileSync as readFileSync26, rmSync as rmSync13, writeFileSync as writeFileSync5 } from "node:fs";
|
|
12356
|
+
import { join as join30 } from "node:path";
|
|
12357
|
+
var ID = /^[a-z0-9-]{1,64}$/;
|
|
12358
|
+
function defaultTimerHost(stateDir2, node, cli) {
|
|
12359
|
+
return {
|
|
12360
|
+
platform: process.platform,
|
|
12361
|
+
stateDir: stateDir2,
|
|
12362
|
+
node,
|
|
12363
|
+
cli,
|
|
12364
|
+
run: (command, args, input) => {
|
|
12365
|
+
const r = spawnSync8(command, args, {
|
|
12366
|
+
encoding: "utf8",
|
|
12367
|
+
timeout: 5e3,
|
|
12368
|
+
windowsHide: true,
|
|
12369
|
+
...input !== void 0 && { input }
|
|
12143
12370
|
});
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
const missed = resumes.filter((s) => s.status === "missed" && recent(s));
|
|
12157
|
-
const failed = resumes.filter((s) => s.status === "failed" && recent(s));
|
|
12158
|
-
const n = (k, one) => `${k} ${one}${k === 1 ? "" : "s"}`;
|
|
12159
|
-
const next = upcoming[0];
|
|
12160
|
-
if (next)
|
|
12161
|
-
add({
|
|
12162
|
-
level: "ok",
|
|
12163
|
-
text: `${n(upcoming.length, "planned resume")}; the next one continues ${name(next.host)} ${f.when(next.dueAt, f.now)}. Keep this computer on and awake then.`
|
|
12164
|
-
});
|
|
12165
|
-
if (needsYou.length > 0)
|
|
12166
|
-
add({
|
|
12167
|
-
level: "todo",
|
|
12168
|
-
text: `${n(needsYou.length, "resume")} need${needsYou.length === 1 ? "s" : ""} you.`,
|
|
12169
|
-
fix: `Review ${needsYou.length === 1 ? "it" : "them"} with: agent-rewake ui`
|
|
12170
|
-
});
|
|
12171
|
-
if (missed.length > 0)
|
|
12172
|
-
add({
|
|
12173
|
-
level: "info",
|
|
12174
|
-
text: `${n(missed.length, "resume")} ${missed.length === 1 ? "was" : "were"} missed in the last 7 days (the computer was off or asleep at the time).`
|
|
12175
|
-
});
|
|
12176
|
-
if (failed.length > 0)
|
|
12177
|
-
add({
|
|
12178
|
-
level: "info",
|
|
12179
|
-
text: `${n(failed.length, "resume")} failed in the last 7 days.`,
|
|
12180
|
-
fix: "See why with: agent-rewake ui"
|
|
12181
|
-
});
|
|
12182
|
-
if (out.length === 0)
|
|
12183
|
-
add({
|
|
12184
|
-
level: "ok",
|
|
12185
|
-
text: `Set up in ${f.previews.map((p) => p.name).join(", ")}. Nothing planned right now.`
|
|
12186
|
-
});
|
|
12187
|
-
return out;
|
|
12371
|
+
return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
12372
|
+
},
|
|
12373
|
+
detached: (command, args) => {
|
|
12374
|
+
try {
|
|
12375
|
+
spawn7(command, args, { detached: true, stdio: "ignore", windowsHide: true }).unref();
|
|
12376
|
+
} catch {
|
|
12377
|
+
}
|
|
12378
|
+
},
|
|
12379
|
+
exists: existsSync21,
|
|
12380
|
+
uid: () => process.getuid?.(),
|
|
12381
|
+
waiterRunning: (pid) => isWaiter(pid, cli)
|
|
12382
|
+
};
|
|
12188
12383
|
}
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
// src/hosts/codex/adapter.ts
|
|
12194
|
-
function programOf(s, node) {
|
|
12195
|
-
const path = s.sessionRef?.codex;
|
|
12196
|
-
return path ? codexProgram(path, node) : void 0;
|
|
12384
|
+
var label = (id) => `codizelabs.agent-rewake.${id}`;
|
|
12385
|
+
function timerName(id, gen = 0) {
|
|
12386
|
+
return gen > 0 ? `${id}-r${gen}` : id;
|
|
12197
12387
|
}
|
|
12198
|
-
function
|
|
12199
|
-
const
|
|
12200
|
-
return
|
|
12388
|
+
function parseTimerName(name) {
|
|
12389
|
+
const m = /^(.+)-r(\d{1,3})$/.exec(name);
|
|
12390
|
+
return m?.[1] ? { id: m[1], gen: Number(m[2]) } : { id: name, gen: 0 };
|
|
12201
12391
|
}
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
const unanswered2 = /* @__PURE__ */ new Set();
|
|
12207
|
-
return {
|
|
12208
|
-
id: "codex",
|
|
12209
|
-
name: "Codex",
|
|
12210
|
-
noun: "thread",
|
|
12211
|
-
reopen: "resume the thread in Codex",
|
|
12212
|
-
again: (at) => at ? `Resume the thread in Codex and type "rewake", and Rewake continues it ${at}.` : 'Resume the thread in Codex and type "rewake" with a time, for example "rewake 3:30pm".',
|
|
12213
|
-
async check(s, now) {
|
|
12214
|
-
const facts = {};
|
|
12215
|
-
const transcript = s.sessionRef?.transcript;
|
|
12216
|
-
let limit;
|
|
12217
|
-
if (transcript) {
|
|
12218
|
-
try {
|
|
12219
|
-
limit = findCodexLimit(readTail2(transcript), now);
|
|
12220
|
-
facts.userTypedSince = !limit.limited;
|
|
12221
|
-
} catch {
|
|
12222
|
-
}
|
|
12223
|
-
}
|
|
12224
|
-
const codex2 = programOf(s, deps.node);
|
|
12225
|
-
let noAnswer = false;
|
|
12226
|
-
if (codex2) {
|
|
12227
|
-
const u = await usage2(codex2, envOf(s, deps.env));
|
|
12228
|
-
if (u.ok) {
|
|
12229
|
-
if (u.allowed !== void 0) facts.usageAllowed = u.allowed;
|
|
12230
|
-
if (u.allowed === false && u.resetsAt && u.resetsAt > now) facts.newResetsAt = u.resetsAt;
|
|
12231
|
-
} else if (u.reason === "signed-out") signedOut.add(s.scheduleId);
|
|
12232
|
-
else if (u.reason === "timeout" || u.reason === "error") noAnswer = true;
|
|
12233
|
-
}
|
|
12234
|
-
if (facts.usageAllowed === void 0 && limit?.limited && limit.resetsAt !== void 0 && limit.resetsAt > now) {
|
|
12235
|
-
facts.usageAllowed = false;
|
|
12236
|
-
facts.newResetsAt = limit.resetsAt;
|
|
12237
|
-
}
|
|
12238
|
-
unanswered2.delete(s.scheduleId);
|
|
12239
|
-
if (facts.usageAllowed === void 0 && noAnswer) {
|
|
12240
|
-
if ((s.rearms ?? 0) < MAX_REARMS) facts.usageAllowed = false;
|
|
12241
|
-
else unanswered2.add(s.scheduleId);
|
|
12242
|
-
}
|
|
12243
|
-
return facts;
|
|
12244
|
-
},
|
|
12245
|
-
delivered(s) {
|
|
12246
|
-
const transcript = s.sessionRef?.transcript;
|
|
12247
|
-
if (!transcript) return void 0;
|
|
12248
|
-
try {
|
|
12249
|
-
return userMessages(readTail2(transcript, 512 * 1024)).includes(s.text) || void 0;
|
|
12250
|
-
} catch {
|
|
12251
|
-
return void 0;
|
|
12252
|
-
}
|
|
12253
|
-
},
|
|
12254
|
-
async send(s) {
|
|
12255
|
-
if (signedOut.has(s.scheduleId)) return { ok: false, reason: "failed", detail: "signed-out" };
|
|
12256
|
-
if (unanswered2.has(s.scheduleId)) return { ok: false, reason: "failed", detail: "no-usage" };
|
|
12257
|
-
const codex2 = programOf(s, deps.node);
|
|
12258
|
-
const thread = s.sessionRef?.threadId;
|
|
12259
|
-
if (!codex2 || !thread) return { ok: false, reason: "unsupported", detail: "no Codex found" };
|
|
12260
|
-
const env = envOf(s, deps.env);
|
|
12261
|
-
let r = await queue(codex2, thread, s.text, env);
|
|
12262
|
-
if (!r.ok && r.reason === "daemon") r = await queue(codex2, thread, s.text, env, "unix://");
|
|
12263
|
-
if (r.ok) return { ok: true };
|
|
12264
|
-
return {
|
|
12265
|
-
ok: false,
|
|
12266
|
-
reason: r.reason === "archived" || r.reason === "deleted" ? "closed" : "failed",
|
|
12267
|
-
detail: r.reason
|
|
12268
|
-
};
|
|
12269
|
-
}
|
|
12270
|
-
};
|
|
12271
|
-
}
|
|
12272
|
-
|
|
12273
|
-
// src/hosts/codex/hooks.ts
|
|
12274
|
-
import { basename as basename6 } from "node:path";
|
|
12275
|
-
var CODEX_PLACE = { name: "Codex", typed: "rewake", features: /* @__PURE__ */ new Set() };
|
|
12276
|
-
var REWAKE = /^\s*\/?rewake(?:\s+([\s\S]*?))?\s*$/i;
|
|
12277
|
-
function threadOf(input) {
|
|
12278
|
-
const path = input.transcript_path;
|
|
12279
|
-
if (!isCodexRollout(path)) return void 0;
|
|
12280
|
-
const id = threadIdOf(path);
|
|
12281
|
-
return id ? { id, path } : void 0;
|
|
12282
|
-
}
|
|
12283
|
-
function pendingFor2(store, threadId) {
|
|
12284
|
-
return store.listForSession(threadId, "codex").filter(
|
|
12285
|
-
(s) => !TERMINAL_STATUSES.has(s.status) && !["missed", "needs_attention"].includes(s.status)
|
|
12286
|
-
);
|
|
12287
|
-
}
|
|
12288
|
-
function armedText2(at, now) {
|
|
12289
|
-
return `Rewake will continue this thread ${formatAt(at, now)}. Keep this computer on and awake until then. If Codex is closed at that time, the thread continues when you next open it. To cancel, send any other message in the thread.`;
|
|
12290
|
-
}
|
|
12291
|
-
function noTimerText(at, now) {
|
|
12292
|
-
return `Rewake couldn't set a timer on this computer, so it can't continue this thread by itself. After the limit resets ${formatAt(at, now)}, send a message in the thread to continue.`;
|
|
12293
|
-
}
|
|
12294
|
-
var block = (reason) => JSON.stringify({ decision: "block", reason });
|
|
12295
|
-
function codexHooks(deps) {
|
|
12296
|
-
const arm = (ctx, thread, at) => {
|
|
12297
|
-
const store = new ScheduleStore(ctx.stateDir);
|
|
12298
|
-
const settings = loadSettings(ctx.stateDir);
|
|
12299
|
-
const cwd = typeof ctx.input.cwd === "string" ? ctx.input.cwd : "";
|
|
12300
|
-
const codex2 = deps.codexPath();
|
|
12301
|
-
const s = store.create({
|
|
12302
|
-
sessionId: thread.id,
|
|
12303
|
-
cwd,
|
|
12304
|
-
text: settings.resumePrompt ?? DEFAULT_RESUME_PROMPT,
|
|
12305
|
-
dueAt: at,
|
|
12306
|
-
kind: "limit_resume",
|
|
12307
|
-
createdBy: "agent",
|
|
12308
|
-
now: ctx.now
|
|
12309
|
-
});
|
|
12310
|
-
const home = ctx.env.CODEX_HOME;
|
|
12311
|
-
const resume = {
|
|
12312
|
-
...s,
|
|
12313
|
-
host: "codex",
|
|
12314
|
-
sessionRef: {
|
|
12315
|
-
threadId: thread.id,
|
|
12316
|
-
transcript: thread.path,
|
|
12317
|
-
...codex2 && { codex: codex2 },
|
|
12318
|
-
...home && { codexHome: home }
|
|
12319
|
-
}
|
|
12320
|
-
};
|
|
12321
|
-
store.put(resume);
|
|
12322
|
-
if (deps.arm(resume.scheduleId, at) !== false) return true;
|
|
12323
|
-
store.update(resume.scheduleId, (x) => ({ ...x, status: "cancelled" }), ctx.now);
|
|
12324
|
-
return false;
|
|
12325
|
-
};
|
|
12326
|
-
const cancelPending = (ctx, threadId) => {
|
|
12327
|
-
const store = new ScheduleStore(ctx.stateDir);
|
|
12328
|
-
for (const s of pendingFor2(store, threadId)) {
|
|
12329
|
-
store.update(s.scheduleId, (x) => ({ ...x, status: "cancelled" }), ctx.now);
|
|
12330
|
-
deps.disarm(s.scheduleId);
|
|
12331
|
-
}
|
|
12332
|
-
};
|
|
12333
|
-
return {
|
|
12334
|
-
isMine: (input, env) => !env.GROK_HOOK_EVENT && isCodexRollout(input.transcript_path),
|
|
12335
|
-
sessionId: (input) => threadOf(input)?.id,
|
|
12336
|
-
async handle(ctx) {
|
|
12337
|
-
const thread = threadOf(ctx.input);
|
|
12338
|
-
if (!thread) return void 0;
|
|
12339
|
-
loadSettings(ctx.stateDir);
|
|
12340
|
-
if (ctx.event === "UserPromptSubmit") {
|
|
12341
|
-
const prompt2 = typeof ctx.input.prompt === "string" ? ctx.input.prompt : "";
|
|
12342
|
-
const m = REWAKE.exec(prompt2);
|
|
12343
|
-
if (!m) {
|
|
12344
|
-
cancelPending(ctx, thread.id);
|
|
12345
|
-
return void 0;
|
|
12346
|
-
}
|
|
12347
|
-
const c = continueOnly(CODEX_PLACE, m[1] ?? "", ctx.now);
|
|
12348
|
-
if (c.kind === "reply") return block(c.text);
|
|
12349
|
-
if (c.kind === "list") {
|
|
12350
|
-
const next = pendingFor2(new ScheduleStore(ctx.stateDir), thread.id)[0];
|
|
12351
|
-
return block(
|
|
12352
|
-
next ? `Rewake will continue this thread ${formatAt(next.dueAt, ctx.now)}. To cancel: rewake cancel` : 'Rewake: Nothing is set to continue this thread. At a usage limit, type "rewake" to continue after the reset.'
|
|
12353
|
-
);
|
|
12354
|
-
}
|
|
12355
|
-
if (c.kind === "cancel") {
|
|
12356
|
-
const had = pendingFor2(new ScheduleStore(ctx.stateDir), thread.id).length > 0;
|
|
12357
|
-
cancelPending(ctx, thread.id);
|
|
12358
|
-
return block(
|
|
12359
|
-
had ? "Rewake: Cancelled. This thread won't be continued on its own." : "Rewake: Nothing is set to continue this thread."
|
|
12360
|
-
);
|
|
12361
|
-
}
|
|
12362
|
-
let limit;
|
|
12363
|
-
try {
|
|
12364
|
-
limit = findCodexLimit(readTail2(thread.path), ctx.now);
|
|
12365
|
-
} catch {
|
|
12366
|
-
limit = { limited: false };
|
|
12367
|
-
}
|
|
12368
|
-
if (!limit.limited)
|
|
12369
|
-
return block(
|
|
12370
|
-
"Rewake: this thread isn't at a usage limit, so there's nothing to continue."
|
|
12371
|
-
);
|
|
12372
|
-
if (limit.billing)
|
|
12373
|
-
return block(
|
|
12374
|
-
"Rewake can't continue after this limit: this limit is about credits or spending, which waiting doesn't fix."
|
|
12375
|
-
);
|
|
12376
|
-
let at = c.at;
|
|
12377
|
-
if (at === void 0) {
|
|
12378
|
-
const later = new ScheduleStore(ctx.stateDir).listForSession(thread.id, "codex").filter((x) => x.failureReason === "far-reset" || x.failureReason === "expired").map((x) => x.dueAt).filter((t) => t > ctx.now);
|
|
12379
|
-
const times = [
|
|
12380
|
-
...limit.resetsAt ? [limit.resetsAt + RESET_MARGIN_MS] : [],
|
|
12381
|
-
...later
|
|
12382
|
-
].filter((t) => t > ctx.now);
|
|
12383
|
-
if (times.length > 0) at = Math.max(...times);
|
|
12384
|
-
}
|
|
12385
|
-
if (at === void 0 && limit.resetPassed) at = ctx.now + RESET_MARGIN_MS;
|
|
12386
|
-
if (at === void 0)
|
|
12387
|
-
return block(
|
|
12388
|
-
`Rewake doesn't know when this limit resets. Type "rewake" with a time, for example "rewake 3:30pm".`
|
|
12389
|
-
);
|
|
12390
|
-
cancelPending(ctx, thread.id);
|
|
12391
|
-
if (!arm(ctx, thread, at)) return block(noTimerText(at, ctx.now));
|
|
12392
|
-
return block(armedText2(at, ctx.now));
|
|
12393
|
-
}
|
|
12394
|
-
if (ctx.event === "SessionEnd") {
|
|
12395
|
-
if (pendingFor2(new ScheduleStore(ctx.stateDir), thread.id).length > 0) return void 0;
|
|
12396
|
-
let limit;
|
|
12397
|
-
try {
|
|
12398
|
-
limit = findCodexLimit(readTail2(thread.path), ctx.now);
|
|
12399
|
-
} catch {
|
|
12400
|
-
return void 0;
|
|
12401
|
-
}
|
|
12402
|
-
if (!limit.limited) return void 0;
|
|
12403
|
-
const settings = loadSettings(ctx.stateDir);
|
|
12404
|
-
const decision = decideArm({
|
|
12405
|
-
now: ctx.now,
|
|
12406
|
-
...limit.resetsAt !== void 0 && { resetsAt: limit.resetsAt },
|
|
12407
|
-
isBilling: limit.billing === true,
|
|
12408
|
-
auto: autoFor(settings)
|
|
12409
|
-
});
|
|
12410
|
-
const cwd = typeof ctx.input.cwd === "string" ? basename6(ctx.input.cwd) : "";
|
|
12411
|
-
const where2 = cwd ? `Codex in the "${cwd}" folder` : "Codex";
|
|
12412
|
-
if (decision.action === "arm") {
|
|
12413
|
-
const ok = arm(ctx, thread, decision.fireAt);
|
|
12414
|
-
deps.notify(
|
|
12415
|
-
"Agent Rewake",
|
|
12416
|
-
`${where2}: ${ok ? armedText2(decision.fireAt, ctx.now) : noTimerText(decision.fireAt, ctx.now)}`
|
|
12417
|
-
);
|
|
12418
|
-
return void 0;
|
|
12419
|
-
}
|
|
12420
|
-
if (decision.action === "offer") {
|
|
12421
|
-
const how = limit.resetsAt ? `type "rewake", and Rewake continues it ${formatAt(limit.resetsAt + RESET_MARGIN_MS, ctx.now)}, after the limit resets` : 'type "rewake" with a time, for example "rewake 3:30pm"';
|
|
12422
|
-
deps.notify(
|
|
12423
|
-
"Agent Rewake",
|
|
12424
|
-
`${where2} hit its usage limit. Resume the thread in Codex and ${how}.`
|
|
12425
|
-
);
|
|
12426
|
-
}
|
|
12427
|
-
return void 0;
|
|
12428
|
-
}
|
|
12429
|
-
return void 0;
|
|
12430
|
-
}
|
|
12431
|
-
};
|
|
12392
|
+
var unit = (id) => `codizelabs-agent-rewake-${id}`;
|
|
12393
|
+
var taskName = (id) => `\\AgentRewake\\${id}`;
|
|
12394
|
+
function timersDir(h) {
|
|
12395
|
+
return ensurePrivateDir(join30(h.stateDir, "timers"));
|
|
12432
12396
|
}
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
import { spawn as spawn7 } from "node:child_process";
|
|
12436
|
-
import { createInterface as createInterface4 } from "node:readline";
|
|
12437
|
-
var COPILOT_ID = "copilot-cli";
|
|
12438
|
-
function isCopilot(input, env) {
|
|
12439
|
-
return !env.GROK_HOOK_EVENT && !("hookEventName" in input) && safeSessionId(input.sessionId) && typeof input.timestamp === "number";
|
|
12397
|
+
function checkId(id) {
|
|
12398
|
+
if (!ID.test(id)) throw new Error(`invalid timer id: ${id}`);
|
|
12440
12399
|
}
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
return new Promise((resolve2) => {
|
|
12446
|
-
let limited = false;
|
|
12447
|
-
let resetsAt;
|
|
12448
|
-
const child = spawn7(
|
|
12449
|
-
program.command,
|
|
12450
|
-
[
|
|
12451
|
-
...program.args,
|
|
12452
|
-
`--resume=${r.sessionId}`,
|
|
12453
|
-
"-p",
|
|
12454
|
-
text2,
|
|
12455
|
-
"--no-ask-user",
|
|
12456
|
-
"--output-format",
|
|
12457
|
-
"json",
|
|
12458
|
-
"--no-auto-update"
|
|
12459
|
-
],
|
|
12460
|
-
{
|
|
12461
|
-
cwd: r.cwd || void 0,
|
|
12462
|
-
env: { ...env, COPILOT_AUTO_UPDATE: "false" },
|
|
12463
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
12464
|
-
windowsHide: true
|
|
12465
|
-
}
|
|
12466
|
-
);
|
|
12467
|
-
let err = "";
|
|
12468
|
-
child.stderr.on("data", (d) => {
|
|
12469
|
-
if (err.length < 1 << 16) err += d.toString("utf8");
|
|
12470
|
-
});
|
|
12471
|
-
createInterface4({ input: child.stdout }).on("line", (line) => {
|
|
12472
|
-
try {
|
|
12473
|
-
const e = JSON.parse(line);
|
|
12474
|
-
if (e.type === "session.error" && e.data?.errorType === "rate_limit") {
|
|
12475
|
-
limited = true;
|
|
12476
|
-
resetsAt = classifyCopilotError(e.data.message, Date.now())?.resetsAt;
|
|
12477
|
-
}
|
|
12478
|
-
} catch {
|
|
12479
|
-
}
|
|
12480
|
-
});
|
|
12481
|
-
const timedOut = resumeDeadline(child);
|
|
12482
|
-
child.on("error", () => resolve2({ ok: false, reason: "failed", detail: "spawn" }));
|
|
12483
|
-
child.on("exit", (code) => {
|
|
12484
|
-
if (timedOut()) return resolve2({ ok: false, reason: "failed", detail: "timeout" });
|
|
12485
|
-
if (limited) resolve2({ ok: false, reason: "limited", ...resetsAt && { resetsAt } });
|
|
12486
|
-
else if (code === 0) resolve2({ ok: true });
|
|
12487
|
-
else if (SESSION_GONE.test(err)) resolve2({ ok: false, reason: "closed", detail: "deleted" });
|
|
12488
|
-
else resolve2({ ok: false, reason: "failed", detail: `exit ${code ?? "signal"}` });
|
|
12489
|
-
});
|
|
12490
|
-
});
|
|
12400
|
+
var MARK = { systemd: ".systemd", schtasks: ".task" };
|
|
12401
|
+
function mark(name, kind, h) {
|
|
12402
|
+
const ext = MARK[kind];
|
|
12403
|
+
if (ext) writeFileSync5(join30(timersDir(h), `${name}${ext}`), "", { mode: 384 });
|
|
12491
12404
|
}
|
|
12492
|
-
|
|
12493
|
-
id
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
}
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
const cwd = typeof ctx.input.cwd === "string" ? ctx.input.cwd : "";
|
|
12505
|
-
const d = deps.closed(ctx);
|
|
12506
|
-
if (!ctx.env[FIRE_ENV]) ensureProgram(copilotHost, id, cwd, d, () => deps.program(ctx.env));
|
|
12507
|
-
switch (ctx.event) {
|
|
12508
|
-
case "sessionStart":
|
|
12509
|
-
onSessionStart(copilotHost, id, cwd, d, deps.program(ctx.env));
|
|
12510
|
-
break;
|
|
12511
|
-
case "userPromptSubmitted":
|
|
12512
|
-
onPrompt(copilotHost, id, cwd, d);
|
|
12513
|
-
break;
|
|
12514
|
-
case "errorOccurred": {
|
|
12515
|
-
const e = ctx.input.error;
|
|
12516
|
-
const text2 = typeof e === "string" ? e : e?.message;
|
|
12517
|
-
const recoverable = ctx.input.recoverable === true || typeof e === "object" && e?.recoverable === true;
|
|
12518
|
-
const limit = recogniseForHost(
|
|
12519
|
-
{
|
|
12520
|
-
agent: "copilot",
|
|
12521
|
-
source: "hook",
|
|
12522
|
-
...typeof text2 === "string" && { text: text2 },
|
|
12523
|
-
recovered: recoverable
|
|
12524
|
-
},
|
|
12525
|
-
ctx.now
|
|
12526
|
-
);
|
|
12527
|
-
if (limit) onLimit(copilotHost, id, cwd, limit, d);
|
|
12528
|
-
break;
|
|
12529
|
-
}
|
|
12530
|
-
case "sessionEnd":
|
|
12531
|
-
onSessionEnd(copilotHost, id, cwd, d);
|
|
12532
|
-
break;
|
|
12533
|
-
}
|
|
12534
|
-
return void 0;
|
|
12535
|
-
}
|
|
12536
|
-
};
|
|
12405
|
+
function timerNames(id, h) {
|
|
12406
|
+
const names = /* @__PURE__ */ new Set([id]);
|
|
12407
|
+
let files = [];
|
|
12408
|
+
try {
|
|
12409
|
+
files = readdirSync11(join30(h.stateDir, "timers"));
|
|
12410
|
+
} catch {
|
|
12411
|
+
}
|
|
12412
|
+
for (const f of files) {
|
|
12413
|
+
const name = f.replace(/^codizelabs\.agent-rewake\./, "").replace(/\.(plist|systemd|task|at|at-time|wait)$/, "");
|
|
12414
|
+
if (name !== f && ID.test(name) && parseTimerName(name).id === id) names.add(name);
|
|
12415
|
+
}
|
|
12416
|
+
return [...names];
|
|
12537
12417
|
}
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12418
|
+
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12419
|
+
function launchdPlist(id, at, node, cli, stateDir2) {
|
|
12420
|
+
const d = new Date(Math.ceil(at / 6e4) * 6e4);
|
|
12421
|
+
const int = (k, v) => `<key>${k}</key><integer>${v}</integer>`;
|
|
12422
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12423
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
12424
|
+
<plist version="1.0">
|
|
12425
|
+
<dict>
|
|
12426
|
+
<key>Label</key><string>${label(id)}</string>
|
|
12427
|
+
<key>ProgramArguments</key>
|
|
12428
|
+
<array><string>${xml(node)}</string><string>${xml(cli)}</string><string>fire</string><string>${id}</string>${stateDir2 ? `<string>--state-dir</string><string>${xml(stateDir2)}</string>` : ""}</array>
|
|
12429
|
+
<key>StartCalendarInterval</key>
|
|
12430
|
+
<dict>${int("Month", d.getMonth() + 1)}${int("Day", d.getDate())}${int("Hour", d.getHours())}${int("Minute", d.getMinutes())}</dict>
|
|
12431
|
+
<key>RunAtLoad</key><true/>
|
|
12432
|
+
<key>ProcessType</key><string>Background</string>
|
|
12433
|
+
<key>StandardOutPath</key><string>/dev/null</string>
|
|
12434
|
+
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
12435
|
+
</dict>
|
|
12436
|
+
</plist>
|
|
12437
|
+
`;
|
|
12438
|
+
}
|
|
12439
|
+
function launchdTarget(h) {
|
|
12440
|
+
const uid = h.uid();
|
|
12441
|
+
return uid === void 0 ? void 0 : `gui/${uid}`;
|
|
12442
|
+
}
|
|
12443
|
+
function launchdArm(id, at, h) {
|
|
12444
|
+
const domain = launchdTarget(h);
|
|
12445
|
+
if (!domain) return { ok: false, reason: "no-scheduler" };
|
|
12446
|
+
const plist = join30(timersDir(h), `${label(id)}.plist`);
|
|
12447
|
+
writeFileSync5(plist, launchdPlist(id, at, h.node, h.cli, h.stateDir), { mode: 384 });
|
|
12448
|
+
const r = h.run("launchctl", ["bootstrap", domain, plist]);
|
|
12449
|
+
if (r.status === 0) return { ok: true, via: "launchd" };
|
|
12450
|
+
rmSync13(plist, { force: true });
|
|
12451
|
+
return { ok: false, reason: "failed", detail: r.stderr.trim() };
|
|
12452
|
+
}
|
|
12453
|
+
function systemdAvailable(h) {
|
|
12454
|
+
if (!h.exists("/run/systemd/system")) return false;
|
|
12455
|
+
const r = h.run("systemctl", ["--user", "is-system-running"]);
|
|
12456
|
+
return /^(running|degraded)\b/.test(r.stdout.trim());
|
|
12457
|
+
}
|
|
12458
|
+
function utcCalendar(at) {
|
|
12459
|
+
return `${new Date(Math.ceil(at / 1e3) * 1e3).toISOString().slice(0, 19).replace("T", " ")} UTC`;
|
|
12460
|
+
}
|
|
12461
|
+
function systemdArm(id, at, h) {
|
|
12462
|
+
h.run("systemctl", ["--user", "stop", `${unit(id)}.timer`]);
|
|
12463
|
+
h.run("systemctl", ["--user", "reset-failed", `${unit(id)}.timer`, `${unit(id)}.service`]);
|
|
12464
|
+
const r = h.run("systemd-run", [
|
|
12465
|
+
"--user",
|
|
12466
|
+
`--unit=${unit(id)}`,
|
|
12467
|
+
`--on-calendar=${utcCalendar(at)}`,
|
|
12468
|
+
"--timer-property=AccuracySec=1s",
|
|
12469
|
+
"--timer-property=Persistent=true",
|
|
12470
|
+
`--description=Agent Rewake: ${id}`,
|
|
12471
|
+
h.node,
|
|
12472
|
+
h.cli,
|
|
12473
|
+
"fire",
|
|
12474
|
+
id,
|
|
12475
|
+
"--state-dir",
|
|
12476
|
+
h.stateDir
|
|
12555
12477
|
]);
|
|
12478
|
+
if (r.status !== 0) return { ok: false, reason: "failed", detail: r.stderr.trim() };
|
|
12479
|
+
mark(id, "systemd", h);
|
|
12480
|
+
return { ok: true, via: "systemd" };
|
|
12556
12481
|
}
|
|
12557
|
-
function
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
if (host === GROK_ID)
|
|
12562
|
-
return grokHooks({ closed: deps.closed, program: (env) => deps.program(host, env) });
|
|
12563
|
-
if (host === GEMINI_ID)
|
|
12564
|
-
return geminiHooks({
|
|
12565
|
-
closed: deps.closed,
|
|
12566
|
-
program: (env) => deps.program(host, env),
|
|
12567
|
-
apiKey: (env) => geminiApiKeyAuth(env, env.HOME || env.USERPROFILE || homedir16())
|
|
12568
|
-
});
|
|
12569
|
-
if (host === ANTIGRAVITY_ID)
|
|
12570
|
-
return antigravityHooks({ closed: deps.closed, program: (env) => deps.program(host, env) });
|
|
12571
|
-
return void 0;
|
|
12482
|
+
function atTime(at) {
|
|
12483
|
+
const d = new Date(Math.ceil(at / 6e4) * 6e4);
|
|
12484
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
12485
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}`;
|
|
12572
12486
|
}
|
|
12573
|
-
var
|
|
12574
|
-
function
|
|
12575
|
-
|
|
12487
|
+
var shQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
12488
|
+
function atAvailable(h) {
|
|
12489
|
+
if (h.run("atq", []).status !== 0) return false;
|
|
12490
|
+
const pg = h.run("pgrep", ["-x", "atd"]);
|
|
12491
|
+
return pg.status === 0 || pg.status === null || pg.status === 127;
|
|
12576
12492
|
}
|
|
12577
|
-
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
const
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12493
|
+
function atArm(id, at, h) {
|
|
12494
|
+
atCancel(id, h);
|
|
12495
|
+
const job = `${shQuote(h.node)} ${shQuote(h.cli)} fire ${id} --state-dir ${shQuote(h.stateDir)}
|
|
12496
|
+
`;
|
|
12497
|
+
const r = h.run("at", ["-t", atTime(at)], job);
|
|
12498
|
+
const n = /job (\d+)/.exec(`${r.stderr}
|
|
12499
|
+
${r.stdout}`)?.[1];
|
|
12500
|
+
if (r.status !== 0 || !n) return { ok: false, reason: "failed", detail: r.stderr.trim() };
|
|
12501
|
+
writeFileSync5(join30(timersDir(h), `${id}.at`), n, { mode: 384 });
|
|
12502
|
+
writeFileSync5(join30(timersDir(h), `${id}.at-time`), `${atTime(at)} ${h.node} ${h.cli}`, {
|
|
12503
|
+
mode: 384
|
|
12504
|
+
});
|
|
12505
|
+
return { ok: true, via: "at" };
|
|
12506
|
+
}
|
|
12507
|
+
function atJob(id, h) {
|
|
12508
|
+
try {
|
|
12509
|
+
const n = readFileSync26(join30(h.stateDir, "timers", `${id}.at`), "utf8").trim();
|
|
12510
|
+
return /^\d+$/.test(n) ? n : void 0;
|
|
12511
|
+
} catch {
|
|
12512
|
+
return void 0;
|
|
12588
12513
|
}
|
|
12589
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
12590
12514
|
}
|
|
12591
|
-
|
|
12592
|
-
|
|
12593
|
-
|
|
12515
|
+
function atCancel(id, h) {
|
|
12516
|
+
const n = atJob(id, h);
|
|
12517
|
+
if (n) h.run("atrm", [n]);
|
|
12518
|
+
rmSync13(join30(h.stateDir, "timers", `${id}.at`), { force: true });
|
|
12519
|
+
rmSync13(join30(h.stateDir, "timers", `${id}.at-time`), { force: true });
|
|
12520
|
+
}
|
|
12521
|
+
var waiterPid = (h) => join30(h.stateDir, "timers", "waiter.pid");
|
|
12522
|
+
function waiterAlive(h) {
|
|
12594
12523
|
try {
|
|
12595
|
-
|
|
12524
|
+
const pid = Number(readFileSync26(waiterPid(h), "utf8").trim());
|
|
12525
|
+
return Number.isInteger(pid) && pid > 0 && (h.waiterRunning?.(pid) ?? false) ? pid : void 0;
|
|
12596
12526
|
} catch {
|
|
12597
12527
|
return void 0;
|
|
12598
12528
|
}
|
|
12599
|
-
if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
|
|
12600
|
-
const record = input;
|
|
12601
|
-
if (!handler.isMine(record, env)) return void 0;
|
|
12602
|
-
const id = handler.sessionId(record);
|
|
12603
|
-
if (id && new ThreadStore(stateDir2).get(id)) return void 0;
|
|
12604
|
-
return handler.handle({ event, input: record, env, stateDir: stateDir2, now });
|
|
12605
12529
|
}
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
}
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
|
|
12530
|
+
function isWaiter(pid, cli) {
|
|
12531
|
+
try {
|
|
12532
|
+
const args = readFileSync26(`/proc/${pid}/cmdline`, "utf8").split("\0");
|
|
12533
|
+
return args.includes(cli) && args.includes("wait");
|
|
12534
|
+
} catch {
|
|
12535
|
+
return false;
|
|
12536
|
+
}
|
|
12537
|
+
}
|
|
12538
|
+
function waiterArm(id, at, h) {
|
|
12539
|
+
writeFileSync5(join30(timersDir(h), `${id}.wait`), String(at), { mode: 384 });
|
|
12540
|
+
if (waiterAlive(h) === void 0) h.detached(h.node, [h.cli, "wait", "--state-dir", h.stateDir]);
|
|
12541
|
+
return { ok: true, via: "waiter" };
|
|
12542
|
+
}
|
|
12543
|
+
function waiterHas(name, h) {
|
|
12544
|
+
return existsSync21(join30(h.stateDir, "timers", `${name}.wait`)) && waiterAlive(h) !== void 0;
|
|
12545
|
+
}
|
|
12546
|
+
function localIso(at) {
|
|
12547
|
+
const d = new Date(Math.ceil(at / 1e3) * 1e3);
|
|
12548
|
+
const p = (n) => String(Math.abs(n)).padStart(2, "0");
|
|
12549
|
+
const off = -d.getTimezoneOffset();
|
|
12550
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}${off >= 0 ? "+" : "-"}${p(Math.trunc(off / 60))}:${p(off % 60)}`;
|
|
12551
|
+
}
|
|
12552
|
+
function taskXml(id, at, node, cli, stateDir2) {
|
|
12553
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
12554
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
12555
|
+
<RegistrationInfo><Description>Agent Rewake: ${id}</Description></RegistrationInfo>
|
|
12556
|
+
<Triggers>
|
|
12557
|
+
<TimeTrigger>
|
|
12558
|
+
<StartBoundary>${localIso(at)}</StartBoundary>
|
|
12559
|
+
<EndBoundary>${localIso(at + 7 * 24 * 36e5)}</EndBoundary>
|
|
12560
|
+
<Enabled>true</Enabled>
|
|
12561
|
+
</TimeTrigger>
|
|
12562
|
+
</Triggers>
|
|
12563
|
+
<Principals><Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
|
|
12564
|
+
<Settings>
|
|
12565
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
12566
|
+
<DeleteExpiredTaskAfter>PT1H</DeleteExpiredTaskAfter>
|
|
12567
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
12568
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
12569
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
12570
|
+
<ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
|
|
12571
|
+
</Settings>
|
|
12572
|
+
<Actions Context="Author">
|
|
12573
|
+
<Exec><Command>conhost.exe</Command><Arguments>--headless "${xml(node)}" "${xml(cli)}" fire ${id}${stateDir2 ? ` --state-dir "${xml(stateDir2)}"` : ""}</Arguments></Exec>
|
|
12574
|
+
</Actions>
|
|
12575
|
+
</Task>
|
|
12576
|
+
`;
|
|
12577
|
+
}
|
|
12578
|
+
function schtasksArm(id, at, h) {
|
|
12579
|
+
h.run("schtasks", ["/Delete", "/TN", taskName(id), "/F"]);
|
|
12580
|
+
const file = join30(timersDir(h), `${id}.xml`);
|
|
12581
|
+
writeFileSync5(file, Buffer.from(`\uFEFF${taskXml(id, at, h.node, h.cli, h.stateDir)}`, "utf16le"));
|
|
12582
|
+
const r = h.run("schtasks", ["/Create", "/TN", taskName(id), "/XML", file]);
|
|
12583
|
+
rmSync13(file, { force: true });
|
|
12584
|
+
if (r.status !== 0) return { ok: false, reason: "failed", detail: r.stderr.trim() };
|
|
12585
|
+
mark(id, "schtasks", h);
|
|
12586
|
+
return { ok: true, via: "schtasks" };
|
|
12622
12587
|
}
|
|
12623
|
-
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
);
|
|
12633
|
-
return;
|
|
12634
|
-
}
|
|
12635
|
-
const box = chosen.has(p.id) ? "[x]" : "[ ]";
|
|
12636
|
-
const note = p.state === "installed" ? ": Rewake is set up here (tick to update)" : "";
|
|
12637
|
-
lines.push(`${pointer} ${box} ${label2}${note}`);
|
|
12638
|
-
lines.push(` ${p.what}`);
|
|
12639
|
-
});
|
|
12640
|
-
for (const name of missing) lines.push(` - ${name}: not found.`);
|
|
12641
|
-
lines.push("", PREVIEW_NOTE, UNREACHABLE);
|
|
12642
|
-
return lines;
|
|
12588
|
+
function timerKind(h) {
|
|
12589
|
+
if (h.platform === "darwin") return launchdTarget(h) ? "launchd" : void 0;
|
|
12590
|
+
if (h.platform === "win32") return "schtasks";
|
|
12591
|
+
if (h.platform === "linux") {
|
|
12592
|
+
if (systemdAvailable(h)) return "systemd";
|
|
12593
|
+
if (atAvailable(h)) return "at";
|
|
12594
|
+
return "waiter";
|
|
12595
|
+
}
|
|
12596
|
+
return void 0;
|
|
12643
12597
|
}
|
|
12644
|
-
function
|
|
12645
|
-
|
|
12598
|
+
function armTimer(id, at, h, gen = 0) {
|
|
12599
|
+
checkId(id);
|
|
12600
|
+
const name = timerName(id, gen);
|
|
12601
|
+
checkId(name);
|
|
12602
|
+
const kind = timerKind(h);
|
|
12603
|
+
if (!kind) return { ok: false, reason: "no-scheduler" };
|
|
12604
|
+
if (gen === 0) cancelTimer(id, h);
|
|
12605
|
+
if (kind === "launchd") return launchdArm(name, at, h);
|
|
12606
|
+
if (kind === "systemd") return systemdArm(name, at, h);
|
|
12607
|
+
if (kind === "at") return atArm(name, at, h);
|
|
12608
|
+
if (kind === "waiter") return waiterArm(name, at, h);
|
|
12609
|
+
return schtasksArm(name, at, h);
|
|
12646
12610
|
}
|
|
12647
|
-
function
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
if (data === " ") return "toggle";
|
|
12651
|
-
if (data === "a") return "all";
|
|
12652
|
-
if (data === "\r" || data === "\n") return "enter";
|
|
12653
|
-
if (data === "q" || data === "" || data === "\x1B") return "quit";
|
|
12654
|
-
return "other";
|
|
12611
|
+
function nextGen(id, current, h) {
|
|
12612
|
+
const used = timerNames(id, h).map((n) => parseTimerName(n).gen);
|
|
12613
|
+
return Math.max(current, ...used) + 1;
|
|
12655
12614
|
}
|
|
12656
|
-
function
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
if (key2 === "toggle" && pickable(state.cursor)) {
|
|
12661
|
-
const id = places[state.cursor]?.id;
|
|
12662
|
-
if (id) chosen.has(id) ? chosen.delete(id) : chosen.add(id);
|
|
12615
|
+
function nameArmed(name, kind, h) {
|
|
12616
|
+
if (kind === "launchd") {
|
|
12617
|
+
const domain = launchdTarget(h);
|
|
12618
|
+
return domain !== void 0 && h.run("launchctl", ["print", `${domain}/${label(name)}`]).status === 0;
|
|
12663
12619
|
}
|
|
12664
|
-
if (
|
|
12665
|
-
|
|
12620
|
+
if (kind === "systemd")
|
|
12621
|
+
return h.run("systemctl", ["--user", "is-active", `${unit(name)}.timer`]).stdout.trim() === "active";
|
|
12622
|
+
if (kind === "at") {
|
|
12623
|
+
const n = atJob(name, h);
|
|
12624
|
+
return n !== void 0 && new RegExp(`^${n}\\s`, "m").test(h.run("atq", []).stdout);
|
|
12666
12625
|
}
|
|
12667
|
-
if (
|
|
12668
|
-
|
|
12669
|
-
return void 0;
|
|
12626
|
+
if (kind === "waiter") return waiterHas(name, h);
|
|
12627
|
+
return h.run("schtasks", ["/Query", "/TN", taskName(name)]).status === 0;
|
|
12670
12628
|
}
|
|
12671
|
-
function
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
for (const f of found) {
|
|
12682
|
-
const min = minimum(f.id);
|
|
12683
|
-
const tooOld2 = Boolean(min && f.version && older(f.version, min));
|
|
12684
|
-
out.push({
|
|
12685
|
-
id: f.id,
|
|
12686
|
-
name: f.name,
|
|
12687
|
-
...f.version && { version: f.version },
|
|
12688
|
-
what: WHAT[f.id],
|
|
12689
|
-
state: tooOld2 ? "too-old" : installed.has(f.id) ? "installed" : "ready",
|
|
12690
|
-
...tooOld2 && min && { needs: min },
|
|
12691
|
-
...tooOld2 && update(f.id) && { update: update(f.id) },
|
|
12692
|
-
...f.surfaces.length > 0 && !f.surfaces.includes("terminal") && { surface: f.surfaces.join(", ") }
|
|
12693
|
-
});
|
|
12629
|
+
function timerStale(id, at, h) {
|
|
12630
|
+
checkId(id);
|
|
12631
|
+
const kind = timerKind(h);
|
|
12632
|
+
const dir = join30(h.stateDir, "timers");
|
|
12633
|
+
if (kind === "launchd") {
|
|
12634
|
+
const names = timerNames(id, h).filter((n) => existsSync21(join30(dir, `${label(n)}.plist`)));
|
|
12635
|
+
if (names.length === 0) return false;
|
|
12636
|
+
return !names.some(
|
|
12637
|
+
(n) => readFileSync26(join30(dir, `${label(n)}.plist`), "utf8") === launchdPlist(n, at, h.node, h.cli, h.stateDir)
|
|
12638
|
+
);
|
|
12694
12639
|
}
|
|
12695
|
-
|
|
12696
|
-
}
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
let drawn = 0;
|
|
12701
|
-
const draw = () => {
|
|
12702
|
-
const lines = [
|
|
12703
|
-
...renderChoice(places, chosen, state.cursor, missing),
|
|
12704
|
-
"",
|
|
12705
|
-
keysHelp(chosen.size)
|
|
12706
|
-
];
|
|
12707
|
-
if (drawn > 0) io.write(`\x1B[${drawn}A`);
|
|
12708
|
-
io.write(`${lines.map((l) => `\x1B[2K${l}`).join("\n")}
|
|
12709
|
-
`);
|
|
12710
|
-
drawn = lines.length;
|
|
12711
|
-
};
|
|
12712
|
-
draw();
|
|
12713
|
-
for await (const data of io.keys) {
|
|
12714
|
-
const result = press(keyOf(data), places, chosen, state);
|
|
12715
|
-
if (result === "quit") return void 0;
|
|
12716
|
-
if (result === "done") return places.filter((p) => chosen.has(p.id)).map((p) => p.id);
|
|
12717
|
-
draw();
|
|
12640
|
+
if (kind === "at") {
|
|
12641
|
+
const marks = timerNames(id, h).filter((n) => existsSync21(join30(dir, `${n}.at-time`)));
|
|
12642
|
+
if (marks.length === 0) return false;
|
|
12643
|
+
const want = `${atTime(at)} ${h.node} ${h.cli}`;
|
|
12644
|
+
return !marks.some((n) => readFileSync26(join30(dir, `${n}.at-time`), "utf8") === want);
|
|
12718
12645
|
}
|
|
12719
|
-
return
|
|
12646
|
+
return false;
|
|
12720
12647
|
}
|
|
12721
|
-
|
|
12722
|
-
|
|
12723
|
-
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
};
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
|
|
12746
|
-
|
|
12747
|
-
|
|
12748
|
-
|
|
12749
|
-
|
|
12750
|
-
type: "string",
|
|
12751
|
-
description: 'For a repeating message: a 5-field cron expression in local time, e.g. "0 9 * * 1-5" (weekdays at 09:00), or @hourly, @daily, @weekly.'
|
|
12752
|
-
},
|
|
12753
|
-
...ENDS,
|
|
12754
|
-
reason: { type: "string", description: "One sentence the user sees: why you want this." }
|
|
12755
|
-
},
|
|
12756
|
-
required: ["message"]
|
|
12757
|
-
}
|
|
12758
|
-
},
|
|
12759
|
-
{
|
|
12760
|
-
name: "about_rewake",
|
|
12761
|
-
description: "How Agent Rewake works, in detail: what it does, the Rewake menu and commands, settings, when a scheduled message is and isn't sent (Zed closed, thread closed or not open, another thread in use, computer asleep, late, busy, several windows), usage limits and automatic resume, approvals, statuses and data. Call it before answering any question the user asks about Rewake, and answer from it.",
|
|
12762
|
-
inputSchema: { type: "object", properties: {} }
|
|
12763
|
-
},
|
|
12764
|
-
{
|
|
12765
|
-
name: "list_scheduled_messages",
|
|
12766
|
-
description: "List the messages scheduled in this conversation, numbered, with the current time, each one's exact next run, status, repeat and end, how its last run went, and messages finished in the last 24 hours.",
|
|
12767
|
-
inputSchema: { type: "object", properties: {} }
|
|
12768
|
-
},
|
|
12769
|
-
{
|
|
12770
|
-
name: "update_scheduled_message",
|
|
12771
|
-
description: `Change a message scheduled in this conversation, by its number from list_scheduled_messages: its message, its time, its repeat or end, or pause or resume it. Give only what changes. The user must approve it in the thread, and may edit the message; this call waits and returns the message as saved. ${DELIVERY_NOTES}`,
|
|
12772
|
-
inputSchema: {
|
|
12773
|
-
type: "object",
|
|
12774
|
-
properties: {
|
|
12775
|
-
number: { type: "integer", minimum: 1 },
|
|
12776
|
-
message: { type: "string", description: "The new message." },
|
|
12777
|
-
when: {
|
|
12778
|
-
type: "string",
|
|
12779
|
-
description: "A new time for the next run (as in schedule_message)."
|
|
12780
|
-
},
|
|
12781
|
-
cron: { type: "string", description: "A new repeat (replaces the old one)." },
|
|
12782
|
-
stop_repeating: {
|
|
12783
|
-
type: "boolean",
|
|
12784
|
-
description: "true: send it once more at its next run, then stop."
|
|
12785
|
-
},
|
|
12786
|
-
...ENDS,
|
|
12787
|
-
paused: { type: "boolean", description: "true pauses it; false resumes it." },
|
|
12788
|
-
reason: { type: "string", description: "One sentence the user sees: why." }
|
|
12789
|
-
},
|
|
12790
|
-
required: ["number"]
|
|
12791
|
-
}
|
|
12792
|
-
},
|
|
12793
|
-
{
|
|
12794
|
-
name: "cancel_scheduled_message",
|
|
12795
|
-
description: "Delete a message scheduled in this conversation, by its number from list_scheduled_messages. The user must approve it in the thread; this call waits for that answer.",
|
|
12796
|
-
inputSchema: {
|
|
12797
|
-
type: "object",
|
|
12798
|
-
properties: {
|
|
12799
|
-
number: { type: "integer", minimum: 1 },
|
|
12800
|
-
reason: { type: "string", description: "One sentence the user sees: why." }
|
|
12801
|
-
},
|
|
12802
|
-
required: ["number"]
|
|
12648
|
+
function timerArmed(id, h) {
|
|
12649
|
+
checkId(id);
|
|
12650
|
+
const kind = timerKind(h);
|
|
12651
|
+
if (!kind) return false;
|
|
12652
|
+
return timerNames(id, h).some((name) => nameArmed(name, kind, h));
|
|
12653
|
+
}
|
|
12654
|
+
function cancelTimer(id, h, fromInsideTimer = false, keep) {
|
|
12655
|
+
checkId(id);
|
|
12656
|
+
const kind = timerKind(h);
|
|
12657
|
+
for (const name of timerNames(id, h)) {
|
|
12658
|
+
if (name === keep) continue;
|
|
12659
|
+
const dir = join30(h.stateDir, "timers");
|
|
12660
|
+
if (kind === "launchd") {
|
|
12661
|
+
const plist = join30(dir, `${label(name)}.plist`);
|
|
12662
|
+
const had = existsSync21(plist);
|
|
12663
|
+
rmSync13(plist, { force: true });
|
|
12664
|
+
const domain = launchdTarget(h);
|
|
12665
|
+
if (!domain || !had && name !== id) continue;
|
|
12666
|
+
if (fromInsideTimer)
|
|
12667
|
+
h.detached("/bin/sh", ["-c", `sleep 2; launchctl bootout ${domain}/${label(name)}`]);
|
|
12668
|
+
else h.run("launchctl", ["bootout", `${domain}/${label(name)}`]);
|
|
12669
|
+
} else if (kind === "systemd") {
|
|
12670
|
+
h.run("systemctl", ["--user", "stop", `${unit(name)}.timer`]);
|
|
12671
|
+
rmSync13(join30(dir, `${name}.systemd`), { force: true });
|
|
12672
|
+
} else if (kind === "at") atCancel(name, h);
|
|
12673
|
+
else if (kind === "waiter") rmSync13(join30(dir, `${name}.wait`), { force: true });
|
|
12674
|
+
else if (kind === "schtasks") {
|
|
12675
|
+
h.run("schtasks", ["/Delete", "/TN", taskName(name), "/F"]);
|
|
12676
|
+
rmSync13(join30(dir, `${name}.task`), { force: true });
|
|
12803
12677
|
}
|
|
12804
12678
|
}
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12808
|
-
|
|
12809
|
-
|
|
12810
|
-
|
|
12811
|
-
|
|
12679
|
+
}
|
|
12680
|
+
|
|
12681
|
+
// src/timers/waiter.ts
|
|
12682
|
+
var WAITER_POLL_MS = 3e4;
|
|
12683
|
+
var WAIT = /^([a-z0-9-]{1,64})\.wait$/;
|
|
12684
|
+
function due(dir) {
|
|
12685
|
+
let files = [];
|
|
12686
|
+
try {
|
|
12687
|
+
files = readdirSync12(dir);
|
|
12688
|
+
} catch {
|
|
12689
|
+
return [];
|
|
12690
|
+
}
|
|
12691
|
+
return files.flatMap((f) => {
|
|
12692
|
+
const name = WAIT.exec(f)?.[1];
|
|
12693
|
+
if (!name) return [];
|
|
12812
12694
|
try {
|
|
12813
|
-
|
|
12695
|
+
const at = Number(readFileSync27(join31(dir, f), "utf8").trim());
|
|
12696
|
+
return Number.isFinite(at) ? [{ name, at }] : [];
|
|
12814
12697
|
} catch {
|
|
12815
|
-
return;
|
|
12816
|
-
}
|
|
12817
|
-
if (m.id === void 0 || m.id === null) return;
|
|
12818
|
-
const reply = (result) => send({ id: m.id, result });
|
|
12819
|
-
switch (m.method) {
|
|
12820
|
-
case "initialize":
|
|
12821
|
-
reply({
|
|
12822
|
-
protocolVersion: typeof m.params?.protocolVersion === "string" ? m.params.protocolVersion : "2025-06-18",
|
|
12823
|
-
capabilities: { tools: {} },
|
|
12824
|
-
serverInfo: { name: "agent-rewake", version: VERSION2 },
|
|
12825
|
-
instructions: "Agent Rewake resumes this conversation after usage limits and schedules messages into it. The user approves every schedule, change and cancellation in the thread. When the user asks anything about Rewake (how it works, what happens if Zed or the thread is closed, settings, limits), call about_rewake first and answer from it."
|
|
12826
|
-
});
|
|
12827
|
-
return;
|
|
12828
|
-
case "ping":
|
|
12829
|
-
reply({});
|
|
12830
|
-
return;
|
|
12831
|
-
case "tools/list":
|
|
12832
|
-
reply({ tools: TOOLS });
|
|
12833
|
-
return;
|
|
12834
|
-
case "tools/call":
|
|
12835
|
-
void callTool(
|
|
12836
|
-
opts,
|
|
12837
|
-
String(m.params?.name ?? ""),
|
|
12838
|
-
m.params?.arguments ?? {}
|
|
12839
|
-
).then(reply).catch((err) => reply(text(`Agent Rewake: ${err.message}`, true)));
|
|
12840
|
-
return;
|
|
12841
|
-
default:
|
|
12842
|
-
send({ id: m.id, error: { code: -32601, message: `Unknown method ${m.method}` } });
|
|
12698
|
+
return [];
|
|
12843
12699
|
}
|
|
12844
12700
|
});
|
|
12845
|
-
return new Promise((resolve2) => rl.on("close", () => resolve2(0)));
|
|
12846
12701
|
}
|
|
12847
|
-
function
|
|
12848
|
-
const
|
|
12849
|
-
const
|
|
12850
|
-
|
|
12851
|
-
|
|
12852
|
-
|
|
12853
|
-
|
|
12854
|
-
|
|
12855
|
-
|
|
12856
|
-
|
|
12857
|
-
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12861
|
-
|
|
12862
|
-
const
|
|
12863
|
-
const
|
|
12864
|
-
|
|
12865
|
-
|
|
12866
|
-
|
|
12702
|
+
async function runWaiter(d) {
|
|
12703
|
+
const dir = ensurePrivateDir(join31(d.timers.stateDir, "timers"));
|
|
12704
|
+
const pidFile = join31(dir, "waiter.pid");
|
|
12705
|
+
const other = waiterAlive(d.timers);
|
|
12706
|
+
if (other !== void 0 && other !== d.pid) return 0;
|
|
12707
|
+
writeFileSync6(pidFile, String(d.pid), { mode: 384 });
|
|
12708
|
+
const mine = () => {
|
|
12709
|
+
try {
|
|
12710
|
+
return readFileSync27(pidFile, "utf8").trim() === String(d.pid);
|
|
12711
|
+
} catch {
|
|
12712
|
+
return false;
|
|
12713
|
+
}
|
|
12714
|
+
};
|
|
12715
|
+
for (; ; ) {
|
|
12716
|
+
if (!mine()) return 0;
|
|
12717
|
+
const waits = due(dir);
|
|
12718
|
+
const now = d.now();
|
|
12719
|
+
for (const w of waits.filter((x) => x.at <= now)) {
|
|
12720
|
+
rmSync14(join31(dir, `${w.name}.wait`), { force: true });
|
|
12721
|
+
d.fire(w.name);
|
|
12722
|
+
}
|
|
12723
|
+
const left = waits.filter((x) => x.at > now);
|
|
12724
|
+
if (left.length === 0) {
|
|
12725
|
+
rmSync14(pidFile, { force: true });
|
|
12726
|
+
if (due(dir).length === 0) return 0;
|
|
12727
|
+
writeFileSync6(pidFile, String(d.pid), { mode: 384 });
|
|
12728
|
+
continue;
|
|
12729
|
+
}
|
|
12730
|
+
const next = Math.min(...left.map((x) => x.at));
|
|
12731
|
+
await d.sleep(Math.max(1e3, Math.min(WAITER_POLL_MS, next - now)));
|
|
12867
12732
|
}
|
|
12868
|
-
return `${AGENT_GUIDE}
|
|
12869
|
-
${lines.join("\n")}
|
|
12870
|
-
`;
|
|
12871
12733
|
}
|
|
12872
|
-
function
|
|
12873
|
-
return
|
|
12734
|
+
function isWsl(version = readProcVersion()) {
|
|
12735
|
+
return /microsoft/i.test(version);
|
|
12874
12736
|
}
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
if (!link)
|
|
12881
|
-
return text(
|
|
12882
|
-
"Agent Rewake isn't linked to this conversation yet. Ask the user to reopen the thread.",
|
|
12883
|
-
true
|
|
12884
|
-
);
|
|
12885
|
-
const schedules = new ScheduleStore(opts.stateDir);
|
|
12886
|
-
const requests = new RequestStore(opts.stateDir);
|
|
12887
|
-
const pending = () => schedules.listForSession(link.sessionId).filter((s) => !TERMINAL_STATUSES.has(s.status));
|
|
12888
|
-
const exact = (t) => formatExact(t, opts.locale);
|
|
12889
|
-
const clockLine = () => `Current time: ${exact(now())}.`;
|
|
12890
|
-
const reason = () => typeof args.reason === "string" && args.reason.trim() ? { reason: args.reason.trim() } : {};
|
|
12891
|
-
const byNumber = () => {
|
|
12892
|
-
const n = Number(args.number);
|
|
12893
|
-
return Number.isInteger(n) && n >= 1 ? pending()[n - 1] : void 0;
|
|
12894
|
-
};
|
|
12895
|
-
const ends = () => {
|
|
12896
|
-
const out = {};
|
|
12897
|
-
if (args.ends_after_runs !== void 0) {
|
|
12898
|
-
const n = Number(args.ends_after_runs);
|
|
12899
|
-
if (!Number.isInteger(n) || n < 1)
|
|
12900
|
-
return "`ends_after_runs` must be a whole number, 1 or more.";
|
|
12901
|
-
out.times = n;
|
|
12902
|
-
}
|
|
12903
|
-
if (typeof args.ends_at === "string" && args.ends_at.trim()) {
|
|
12904
|
-
const t = parseWhen(args.ends_at, now());
|
|
12905
|
-
if (!t.ok) return `ends_at: ${t.error}`;
|
|
12906
|
-
out.until = t.at;
|
|
12907
|
-
}
|
|
12908
|
-
return out;
|
|
12909
|
-
};
|
|
12910
|
-
const describe2 = (sid) => {
|
|
12911
|
-
const list = pending();
|
|
12912
|
-
const i = list.findIndex((x) => x.scheduleId === sid);
|
|
12913
|
-
const s = list[i] ?? schedules.get(sid);
|
|
12914
|
-
if (!s) return "";
|
|
12915
|
-
const p = s.repeat ? parseCron(s.repeat.cron) : void 0;
|
|
12916
|
-
const end = [
|
|
12917
|
-
s.repeat?.remaining !== void 0 ? `${s.repeat.remaining} run${s.repeat.remaining === 1 ? "" : "s"} left` : "",
|
|
12918
|
-
s.repeat?.until !== void 0 ? `no runs after ${exact(s.repeat.until)}` : ""
|
|
12919
|
-
].filter(Boolean);
|
|
12920
|
-
const repeat2 = p?.ok ? `Repeats: ${describeCron(p.cron)}${end.length ? ` (${end.join(", ")})` : " (until cancelled)"}` : "Once";
|
|
12921
|
-
const last = s.lastRun ? ` \xB7 Last run: ${s.lastRun.outcome}, due ${exact(s.lastRun.at)}` : "";
|
|
12922
|
-
const by = s.createdBy === "agent" ? "you asked, the user approved" : s.kind === "user" ? "the user" : "the user (resume after a usage limit)";
|
|
12923
|
-
return [
|
|
12924
|
-
`${i >= 0 ? `${i + 1}. ` : ""}Next: ${exact(s.dueAt)} \xB7 ${formatWhen(s.dueAt, now(), opts.locale)}`,
|
|
12925
|
-
` Status: ${s.status} \xB7 ${repeat2}${last} \xB7 Added by: ${by}`,
|
|
12926
|
-
` Message: ${s.text}`
|
|
12927
|
-
].join("\n");
|
|
12928
|
-
};
|
|
12929
|
-
if (name === "list_scheduled_messages") {
|
|
12930
|
-
const list = pending();
|
|
12931
|
-
const recent = schedules.listForSession(link.sessionId).filter((s) => TERMINAL_STATUSES.has(s.status) && now() - s.updatedAt < 24 * 36e5);
|
|
12932
|
-
const out = [clockLine()];
|
|
12933
|
-
out.push(
|
|
12934
|
-
list.length === 0 ? "Nothing is scheduled in this conversation." : list.map((s) => describe2(s.scheduleId)).join("\n")
|
|
12935
|
-
);
|
|
12936
|
-
if (recent.length > 0)
|
|
12937
|
-
out.push(
|
|
12938
|
-
"Finished in the last 24 hours:",
|
|
12939
|
-
...recent.map((s) => `- ${s.status}: due ${exact(s.dueAt)} \xB7 ${s.text}`)
|
|
12940
|
-
);
|
|
12941
|
-
return text(out.join("\n"));
|
|
12737
|
+
function readProcVersion() {
|
|
12738
|
+
try {
|
|
12739
|
+
return readFileSync27("/proc/version", "utf8");
|
|
12740
|
+
} catch {
|
|
12741
|
+
return "";
|
|
12942
12742
|
}
|
|
12943
|
-
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
return text([r.text, saved && `As saved:
|
|
12949
|
-
${saved}`, clockLine()].filter(Boolean).join("\n"));
|
|
12743
|
+
}
|
|
12744
|
+
function waiterNote(wsl) {
|
|
12745
|
+
return {
|
|
12746
|
+
text: "Planned resumes run at their times while this computer stays on. With no systemd or at service here, Rewake runs them with its own background process, which stops when you log out or restart and starts again the next time you use an agent. A resume missed by more than half an hour isn't sent.",
|
|
12747
|
+
fix: wsl ? "So resumes don't wait for you after a restart, turn on systemd in WSL: add the lines [boot] and systemd=true to /etc/wsl.conf, then run wsl.exe --shutdown in Windows and open WSL again." : "So resumes don't wait for you after a restart, install and start the at service (Debian, Ubuntu: sudo apt install at, then sudo service atd start)."
|
|
12950
12748
|
};
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
|
|
12960
|
-
|
|
12961
|
-
|
|
12962
|
-
|
|
12963
|
-
|
|
12964
|
-
);
|
|
12965
|
-
let dueAt;
|
|
12966
|
-
let cron;
|
|
12967
|
-
if (cronText) {
|
|
12968
|
-
const parsed = parseCron(cronText);
|
|
12969
|
-
if (!parsed.ok) return text(parsed.error, true);
|
|
12970
|
-
const first = nextRun(parsed.cron, now());
|
|
12971
|
-
if (first === void 0) return text(`"${cronText}" never runs.`, true);
|
|
12972
|
-
if (end.until !== void 0 && first > end.until)
|
|
12973
|
-
return text(`It would never run: the first run, ${exact(first)}, is after ends_at.`, true);
|
|
12974
|
-
dueAt = first;
|
|
12975
|
-
cron = parsed.cron.source;
|
|
12976
|
-
} else {
|
|
12977
|
-
const parsed = parseWhen(when2, now());
|
|
12978
|
-
if (!parsed.ok) return text(`${parsed.error} ${clockLine()}`, true);
|
|
12979
|
-
dueAt = parsed.at;
|
|
12980
|
-
}
|
|
12981
|
-
const same2 = (t) => t.trim().toLowerCase();
|
|
12982
|
-
const twin = pending().find((s) => same2(s.text) === same2(message));
|
|
12983
|
-
const r = requests.create({
|
|
12984
|
-
sessionId: link.sessionId,
|
|
12985
|
-
kind: "schedule",
|
|
12986
|
-
message,
|
|
12987
|
-
dueAt,
|
|
12988
|
-
...cron && { cron },
|
|
12989
|
-
...end,
|
|
12990
|
-
...twin && { duplicateOf: twin.scheduleId },
|
|
12991
|
-
...reason(),
|
|
12992
|
-
createdAt: now()
|
|
12993
|
-
});
|
|
12994
|
-
return settle(r.requestId);
|
|
12749
|
+
}
|
|
12750
|
+
|
|
12751
|
+
// src/hosts/claude-code/records.ts
|
|
12752
|
+
import { readdirSync as readdirSync13, readFileSync as readFileSync28 } from "node:fs";
|
|
12753
|
+
import { join as join32 } from "node:path";
|
|
12754
|
+
var STALE_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
12755
|
+
function claudeCodeRecords(stateDir2, now) {
|
|
12756
|
+
const dir = join32(stateDir2, "hosts", "claude-code", "sessions");
|
|
12757
|
+
let names;
|
|
12758
|
+
try {
|
|
12759
|
+
names = readdirSync13(dir).filter((n) => n.endsWith(".json"));
|
|
12760
|
+
} catch {
|
|
12761
|
+
return [];
|
|
12995
12762
|
}
|
|
12996
|
-
|
|
12997
|
-
|
|
12998
|
-
|
|
12999
|
-
|
|
13000
|
-
|
|
13001
|
-
|
|
13002
|
-
);
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
}
|
|
13013
|
-
if (typeof args.cron === "string" && args.cron.trim()) {
|
|
13014
|
-
const parsed = parseCron(args.cron);
|
|
13015
|
-
if (!parsed.ok) return text(parsed.error, true);
|
|
13016
|
-
if (change.dueAt !== void 0)
|
|
13017
|
-
return text(
|
|
13018
|
-
"Give either `when` or `cron`, not both: a new repeat sets its own next run.",
|
|
13019
|
-
true
|
|
13020
|
-
);
|
|
13021
|
-
change.cron = parsed.cron.source;
|
|
12763
|
+
const out = [];
|
|
12764
|
+
for (const n of names) {
|
|
12765
|
+
try {
|
|
12766
|
+
const r = JSON.parse(readFileSync28(join32(dir, n), "utf8"));
|
|
12767
|
+
if (r.host !== "claude-code" || typeof r.sessionId !== "string") continue;
|
|
12768
|
+
if (typeof r.state !== "string" || typeof r.updatedAt !== "number") continue;
|
|
12769
|
+
if (now - r.updatedAt > STALE_MS) continue;
|
|
12770
|
+
out.push({
|
|
12771
|
+
sessionId: r.sessionId,
|
|
12772
|
+
state: r.state,
|
|
12773
|
+
updatedAt: r.updatedAt,
|
|
12774
|
+
...typeof r.cwd === "string" && { cwd: r.cwd },
|
|
12775
|
+
...typeof r.resetAt === "number" && { resetAt: r.resetAt },
|
|
12776
|
+
...typeof r.fireAt === "number" && { fireAt: r.fireAt }
|
|
12777
|
+
});
|
|
12778
|
+
} catch {
|
|
13022
12779
|
}
|
|
13023
|
-
if (args.stop_repeating === true) change.stopRepeating = true;
|
|
13024
|
-
if (typeof args.paused === "boolean") change.paused = args.paused;
|
|
13025
|
-
if ((change.until !== void 0 || change.times !== void 0) && !target.repeat && change.cron === void 0)
|
|
13026
|
-
return text("That message doesn't repeat, so it has no end to set.", true);
|
|
13027
|
-
if (Object.keys(change).length === 0) return text("Give at least one thing to change.", true);
|
|
13028
|
-
const r = requests.create({
|
|
13029
|
-
sessionId: link.sessionId,
|
|
13030
|
-
kind: "update",
|
|
13031
|
-
scheduleId: target.scheduleId,
|
|
13032
|
-
...change,
|
|
13033
|
-
...reason(),
|
|
13034
|
-
createdAt: now()
|
|
13035
|
-
});
|
|
13036
|
-
return settle(r.requestId);
|
|
13037
12780
|
}
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
12781
|
+
return out;
|
|
12782
|
+
}
|
|
12783
|
+
|
|
12784
|
+
// src/hosts/doctor.ts
|
|
12785
|
+
var DAY2 = 24 * 60 * 60 * 1e3;
|
|
12786
|
+
function diagnoseOutside(f) {
|
|
12787
|
+
const out = [];
|
|
12788
|
+
const add = (x) => out.push({ area: "Outside Zed", ...x });
|
|
12789
|
+
const setUp = new Set(f.previews.map((p) => p.id));
|
|
12790
|
+
for (const a of f.agents ?? []) {
|
|
12791
|
+
const v = AGENT_VERSIONS[a.id];
|
|
12792
|
+
if (!v || !a.version) continue;
|
|
12793
|
+
if (tooOld(a.id, a.version)) {
|
|
12794
|
+
const set = setUp.has(a.id);
|
|
12795
|
+
add({
|
|
12796
|
+
level: set ? "problem" : "info",
|
|
12797
|
+
text: `${v.name} ${a.version} is too old for Rewake (it needs ${v.min} or newer), so ${set ? "Rewake may miss its usage limits" : "Rewake isn't set up for it"}.`,
|
|
12798
|
+
fix: `Update it with: ${v.update}${set ? "" : `, then run: agent-rewake install --only ${a.id}`}`
|
|
12799
|
+
});
|
|
12800
|
+
} else if (setUp.has(a.id) && newerThanTested(a.id, a.version))
|
|
12801
|
+
add({ level: "info", text: untestedText(a.id, a.version) });
|
|
12802
|
+
}
|
|
12803
|
+
if (f.previews.length === 0) return out;
|
|
12804
|
+
for (const p of f.previews) {
|
|
12805
|
+
const off = hooksTurnedOff(p.id, f.env, f.home);
|
|
12806
|
+
if (off)
|
|
12807
|
+
add({
|
|
12808
|
+
level: "problem",
|
|
12809
|
+
text: `${p.name} has its hooks turned off, so Rewake can't see its usage limits.`,
|
|
12810
|
+
fix: `Turn hooks back on in ${p.name}'s settings, or take Rewake out of it: agent-rewake uninstall --only ${p.id}`
|
|
12811
|
+
});
|
|
12812
|
+
}
|
|
12813
|
+
if (!f.hasTimer)
|
|
12814
|
+
add({
|
|
12815
|
+
level: "problem",
|
|
12816
|
+
text: "Rewake can't continue sessions outside Zed by itself on this computer: it has no scheduler Rewake can use.",
|
|
12817
|
+
fix: f.platform === "linux" ? "Sign in to a normal desktop session, or install and start the at service." : "Run agent-rewake doctor again after a restart; if it stays, report it with agent-rewake doctor --details."
|
|
12818
|
+
});
|
|
12819
|
+
if (f.timerKind === "waiter") add({ level: "info", ...waiterNote(f.wsl ?? false) });
|
|
12820
|
+
const resumes = new ScheduleStore(f.stateDir).list().filter((s) => s.host !== void 0);
|
|
12821
|
+
const name = (host) => host && f.hosts.get(host)?.name || (host === "claude-code" ? "Claude Code" : host) || "";
|
|
12822
|
+
const cc = setUp.has("claude-code") ? claudeCodeRecords(f.stateDir, f.now) : [];
|
|
12823
|
+
const upcoming = [
|
|
12824
|
+
...resumes.filter((s) => s.status === "scheduled" || s.status === "sending"),
|
|
12825
|
+
...cc.filter((r) => r.state === "armed" && r.fireAt !== void 0 && r.fireAt > f.now - 5 * 6e4).map((r) => ({ host: "claude-code", dueAt: r.fireAt }))
|
|
12826
|
+
].sort((a, b) => a.dueAt - b.dueAt);
|
|
12827
|
+
const asking = cc.filter((r) => r.state === "offered" || r.state === "waiting");
|
|
12828
|
+
const needsYou = resumes.filter((s) => s.status === "needs_attention");
|
|
12829
|
+
const recent = (s) => s.dueAt > f.now - 7 * DAY2;
|
|
12830
|
+
const missed = resumes.filter((s) => s.status === "missed" && recent(s));
|
|
12831
|
+
const failed = resumes.filter((s) => s.status === "failed" && recent(s));
|
|
12832
|
+
const n = (k, one) => `${k} ${one}${k === 1 ? "" : "s"}`;
|
|
12833
|
+
const next = upcoming[0];
|
|
12834
|
+
if (next)
|
|
12835
|
+
add({
|
|
12836
|
+
level: "ok",
|
|
12837
|
+
text: `${n(upcoming.length, "planned resume")}; the next one continues ${name(next.host)} ${next.dueAt <= f.now ? "now" : f.when(next.dueAt, f.now)}. Keep this computer on and awake${next.host === "claude-code" ? ", and Claude Code open," : ""} then.`
|
|
12838
|
+
});
|
|
12839
|
+
if (asking.length > 0) {
|
|
12840
|
+
const where2 = (r) => r.cwd ? ` (in the "${r.cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? r.cwd}" folder)` : "";
|
|
12841
|
+
add({
|
|
12842
|
+
level: "todo",
|
|
12843
|
+
text: asking.length === 1 ? `1 Claude Code session${where2(asking[0])} is waiting for your answer: continue it after the reset?` : `${asking.length} Claude Code sessions are waiting for your answer: continue them after the reset?`,
|
|
12844
|
+
fix: "Answer Rewake's question there, or type /rewake."
|
|
13051
12845
|
});
|
|
13052
|
-
return settle(r.requestId);
|
|
13053
12846
|
}
|
|
13054
|
-
|
|
12847
|
+
if (needsYou.length > 0)
|
|
12848
|
+
add({
|
|
12849
|
+
level: "todo",
|
|
12850
|
+
text: `${n(needsYou.length, "resume")} need${needsYou.length === 1 ? "s" : ""} you.`,
|
|
12851
|
+
fix: `Review ${needsYou.length === 1 ? "it" : "them"} with: agent-rewake ui`
|
|
12852
|
+
});
|
|
12853
|
+
if (missed.length > 0)
|
|
12854
|
+
add({
|
|
12855
|
+
level: "info",
|
|
12856
|
+
text: `${n(missed.length, "resume")} ${missed.length === 1 ? "was" : "were"} missed in the last 7 days (the computer was off or asleep at the time).`
|
|
12857
|
+
});
|
|
12858
|
+
const lastFailed = [...failed].sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
12859
|
+
if (failed.length > 0)
|
|
12860
|
+
add({
|
|
12861
|
+
level: "info",
|
|
12862
|
+
text: `${n(failed.length, "resume")} failed in the last 7 days.${lastFailed?.failureMessage ? ` The last one, in ${name(lastFailed.host)}, ended with: "${lastFailed.failureMessage}"` : ""}`,
|
|
12863
|
+
fix: lastFailed?.failureMessage ? "See all of them with: agent-rewake ui" : "See why with: agent-rewake ui"
|
|
12864
|
+
});
|
|
12865
|
+
if (out.length === 0)
|
|
12866
|
+
add({
|
|
12867
|
+
level: "ok",
|
|
12868
|
+
text: `Set up in ${f.previews.map((p) => p.name).join(", ")}. Nothing planned right now.`
|
|
12869
|
+
});
|
|
12870
|
+
return out;
|
|
13055
12871
|
}
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
12872
|
+
|
|
12873
|
+
// src/hosts/index.ts
|
|
12874
|
+
import { homedir as homedir18 } from "node:os";
|
|
12875
|
+
|
|
12876
|
+
// src/hosts/codex/adapter.ts
|
|
12877
|
+
function programOf(s, node) {
|
|
12878
|
+
const path = s.sessionRef?.codex;
|
|
12879
|
+
return path ? codexProgram(path, node) : void 0;
|
|
12880
|
+
}
|
|
12881
|
+
function envOf(s, base) {
|
|
12882
|
+
const home = s.sessionRef?.codexHome;
|
|
12883
|
+
return home ? { ...base, CODEX_HOME: home } : base;
|
|
12884
|
+
}
|
|
12885
|
+
function codexAdapter(deps) {
|
|
12886
|
+
const usage2 = deps.readUsage ?? ((codex2, env) => readUsage(codex2, env));
|
|
12887
|
+
const queue = deps.queue ?? queueMessage;
|
|
12888
|
+
const signedOut = /* @__PURE__ */ new Set();
|
|
12889
|
+
const unanswered2 = /* @__PURE__ */ new Set();
|
|
12890
|
+
return {
|
|
12891
|
+
id: "codex",
|
|
12892
|
+
name: "Codex",
|
|
12893
|
+
noun: "thread",
|
|
12894
|
+
reopen: "resume the thread in Codex",
|
|
12895
|
+
again: (at) => at ? `Resume the thread in Codex and type "rewake", and Rewake continues it ${at}.` : 'Resume the thread in Codex and type "rewake" with a time, for example "rewake 3:30pm".',
|
|
12896
|
+
async check(s, now) {
|
|
12897
|
+
const facts = {};
|
|
12898
|
+
const transcript = s.sessionRef?.transcript;
|
|
12899
|
+
let limit;
|
|
12900
|
+
if (transcript) {
|
|
12901
|
+
try {
|
|
12902
|
+
limit = findCodexLimit(readTail2(transcript), now);
|
|
12903
|
+
facts.userTypedSince = !limit.limited;
|
|
12904
|
+
} catch {
|
|
12905
|
+
}
|
|
12906
|
+
}
|
|
12907
|
+
const codex2 = programOf(s, deps.node);
|
|
12908
|
+
let noAnswer = false;
|
|
12909
|
+
if (codex2) {
|
|
12910
|
+
const u = await usage2(codex2, envOf(s, deps.env));
|
|
12911
|
+
if (u.ok) {
|
|
12912
|
+
if (u.allowed !== void 0) facts.usageAllowed = u.allowed;
|
|
12913
|
+
if (u.allowed === false && u.resetsAt && u.resetsAt > now) facts.newResetsAt = u.resetsAt;
|
|
12914
|
+
} else if (u.reason === "signed-out") signedOut.add(s.scheduleId);
|
|
12915
|
+
else if (u.reason === "timeout" || u.reason === "error") noAnswer = true;
|
|
12916
|
+
}
|
|
12917
|
+
if (facts.usageAllowed === void 0 && limit?.limited && limit.resetsAt !== void 0 && limit.resetsAt > now) {
|
|
12918
|
+
facts.usageAllowed = false;
|
|
12919
|
+
facts.newResetsAt = limit.resetsAt;
|
|
12920
|
+
}
|
|
12921
|
+
unanswered2.delete(s.scheduleId);
|
|
12922
|
+
if (facts.usageAllowed === void 0 && noAnswer) {
|
|
12923
|
+
if ((s.rearms ?? 0) < MAX_REARMS) facts.usageAllowed = false;
|
|
12924
|
+
else unanswered2.add(s.scheduleId);
|
|
12925
|
+
}
|
|
12926
|
+
return facts;
|
|
12927
|
+
},
|
|
12928
|
+
delivered(s) {
|
|
12929
|
+
const transcript = s.sessionRef?.transcript;
|
|
12930
|
+
if (!transcript) return void 0;
|
|
12931
|
+
try {
|
|
12932
|
+
return userMessages(readTail2(transcript, 512 * 1024)).includes(s.text) || void 0;
|
|
12933
|
+
} catch {
|
|
12934
|
+
return void 0;
|
|
12935
|
+
}
|
|
12936
|
+
},
|
|
12937
|
+
async send(s) {
|
|
12938
|
+
if (signedOut.has(s.scheduleId)) return { ok: false, reason: "failed", detail: "signed-out" };
|
|
12939
|
+
if (unanswered2.has(s.scheduleId)) return { ok: false, reason: "failed", detail: "no-usage" };
|
|
12940
|
+
const codex2 = programOf(s, deps.node);
|
|
12941
|
+
const thread = s.sessionRef?.threadId;
|
|
12942
|
+
if (!codex2 || !thread) return { ok: false, reason: "unsupported", detail: "no Codex found" };
|
|
12943
|
+
const env = envOf(s, deps.env);
|
|
12944
|
+
let r = await queue(codex2, thread, s.text, env);
|
|
12945
|
+
if (!r.ok && r.reason === "daemon") r = await queue(codex2, thread, s.text, env, "unix://");
|
|
12946
|
+
if (r.ok) return { ok: true };
|
|
13068
12947
|
return {
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
12948
|
+
ok: false,
|
|
12949
|
+
reason: r.reason === "archived" || r.reason === "deleted" ? "closed" : "failed",
|
|
12950
|
+
detail: r.reason
|
|
13072
12951
|
};
|
|
13073
12952
|
}
|
|
13074
|
-
await new Promise((res) => setTimeout(res, opts.pollMs ?? 500));
|
|
13075
|
-
}
|
|
13076
|
-
requests.remove(requestId);
|
|
13077
|
-
return {
|
|
13078
|
-
status: "declined",
|
|
13079
|
-
text: "The user didn't answer within 10 minutes, so nothing was changed."
|
|
13080
12953
|
};
|
|
13081
12954
|
}
|
|
13082
12955
|
|
|
13083
|
-
// src/
|
|
13084
|
-
import { basename as
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
13088
|
-
|
|
13089
|
-
|
|
13090
|
-
|
|
13091
|
-
|
|
13092
|
-
|
|
13093
|
-
|
|
13094
|
-
|
|
13095
|
-
|
|
13096
|
-
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
12956
|
+
// src/hosts/codex/hooks.ts
|
|
12957
|
+
import { basename as basename6 } from "node:path";
|
|
12958
|
+
var CODEX_PLACE = { name: "Codex", typed: "rewake", features: /* @__PURE__ */ new Set() };
|
|
12959
|
+
var REWAKE = /^\s*\/?rewake(?:\s+([\s\S]*?))?\s*$/i;
|
|
12960
|
+
function threadOf(input) {
|
|
12961
|
+
const path = input.transcript_path;
|
|
12962
|
+
if (!isCodexRollout(path)) return void 0;
|
|
12963
|
+
const id = threadIdOf(path);
|
|
12964
|
+
return id ? { id, path } : void 0;
|
|
12965
|
+
}
|
|
12966
|
+
function pendingFor2(store, threadId) {
|
|
12967
|
+
return store.listForSession(threadId, "codex").filter(
|
|
12968
|
+
(s) => !TERMINAL_STATUSES.has(s.status) && !["missed", "needs_attention"].includes(s.status)
|
|
12969
|
+
);
|
|
12970
|
+
}
|
|
12971
|
+
function armedText2(at, now) {
|
|
12972
|
+
return `Rewake will continue this thread ${formatAt(at, now)}. Keep this computer on and awake until then. If Codex is closed at that time, the thread continues when you next open it. To cancel, send any other message in the thread.`;
|
|
12973
|
+
}
|
|
12974
|
+
function noTimerText(at, now) {
|
|
12975
|
+
return `Rewake couldn't set a timer on this computer, so it can't continue this thread by itself. After the limit resets ${formatAt(at, now)}, send a message in the thread to continue.`;
|
|
12976
|
+
}
|
|
12977
|
+
var block = (reason) => JSON.stringify({ decision: "block", reason });
|
|
12978
|
+
function codexHooks(deps) {
|
|
12979
|
+
const arm = (ctx, thread, at) => {
|
|
12980
|
+
const store = new ScheduleStore(ctx.stateDir);
|
|
12981
|
+
const settings = loadSettings(ctx.stateDir);
|
|
12982
|
+
const cwd = typeof ctx.input.cwd === "string" ? ctx.input.cwd : "";
|
|
12983
|
+
const codex2 = deps.codexPath();
|
|
12984
|
+
const s = store.create({
|
|
12985
|
+
sessionId: thread.id,
|
|
12986
|
+
cwd,
|
|
12987
|
+
text: settings.resumePrompt ?? DEFAULT_RESUME_PROMPT,
|
|
12988
|
+
dueAt: at,
|
|
12989
|
+
kind: "limit_resume",
|
|
12990
|
+
createdBy: "agent",
|
|
12991
|
+
now: ctx.now
|
|
12992
|
+
});
|
|
12993
|
+
const home = ctx.env.CODEX_HOME;
|
|
12994
|
+
const resume = {
|
|
12995
|
+
...s,
|
|
12996
|
+
host: "codex",
|
|
12997
|
+
sessionRef: {
|
|
12998
|
+
threadId: thread.id,
|
|
12999
|
+
transcript: thread.path,
|
|
13000
|
+
...codex2 && { codex: codex2 },
|
|
13001
|
+
...home && { codexHome: home }
|
|
13002
|
+
}
|
|
13003
|
+
};
|
|
13004
|
+
store.put(resume);
|
|
13005
|
+
if (deps.arm(resume.scheduleId, at) !== false) return true;
|
|
13006
|
+
store.update(resume.scheduleId, (x) => ({ ...x, status: "cancelled" }), ctx.now);
|
|
13007
|
+
return false;
|
|
13008
|
+
};
|
|
13009
|
+
const cancelPending = (ctx, threadId) => {
|
|
13010
|
+
const store = new ScheduleStore(ctx.stateDir);
|
|
13011
|
+
for (const s of pendingFor2(store, threadId)) {
|
|
13012
|
+
store.update(s.scheduleId, (x) => ({ ...x, status: "cancelled" }), ctx.now);
|
|
13013
|
+
deps.disarm(s.scheduleId);
|
|
13014
|
+
}
|
|
13015
|
+
};
|
|
13016
|
+
return {
|
|
13017
|
+
isMine: (input, env) => !env.GROK_HOOK_EVENT && isCodexRollout(input.transcript_path),
|
|
13018
|
+
sessionId: (input) => threadOf(input)?.id,
|
|
13019
|
+
async handle(ctx) {
|
|
13020
|
+
const thread = threadOf(ctx.input);
|
|
13021
|
+
if (!thread) return void 0;
|
|
13022
|
+
loadSettings(ctx.stateDir);
|
|
13023
|
+
if (ctx.event === "UserPromptSubmit") {
|
|
13024
|
+
const prompt2 = typeof ctx.input.prompt === "string" ? ctx.input.prompt : "";
|
|
13025
|
+
const m = REWAKE.exec(prompt2);
|
|
13026
|
+
if (!m) {
|
|
13027
|
+
cancelPending(ctx, thread.id);
|
|
13028
|
+
return void 0;
|
|
13029
|
+
}
|
|
13030
|
+
const c = continueOnly(CODEX_PLACE, m[1] ?? "", ctx.now);
|
|
13031
|
+
if (c.kind === "reply") return block(c.text);
|
|
13032
|
+
if (c.kind === "list") {
|
|
13033
|
+
const next = pendingFor2(new ScheduleStore(ctx.stateDir), thread.id)[0];
|
|
13034
|
+
return block(
|
|
13035
|
+
next ? `Rewake will continue this thread ${formatAt(next.dueAt, ctx.now)}. To cancel: rewake cancel` : 'Rewake: Nothing is set to continue this thread. At a usage limit, type "rewake" to continue after the reset.'
|
|
13036
|
+
);
|
|
13037
|
+
}
|
|
13038
|
+
if (c.kind === "cancel") {
|
|
13039
|
+
const had = pendingFor2(new ScheduleStore(ctx.stateDir), thread.id).length > 0;
|
|
13040
|
+
cancelPending(ctx, thread.id);
|
|
13041
|
+
return block(
|
|
13042
|
+
had ? "Rewake: Cancelled. This thread won't be continued on its own." : "Rewake: Nothing is set to continue this thread."
|
|
13043
|
+
);
|
|
13044
|
+
}
|
|
13045
|
+
let limit;
|
|
13046
|
+
try {
|
|
13047
|
+
limit = findCodexLimit(readTail2(thread.path), ctx.now);
|
|
13048
|
+
} catch {
|
|
13049
|
+
limit = { limited: false };
|
|
13050
|
+
}
|
|
13051
|
+
if (!limit.limited)
|
|
13052
|
+
return block(
|
|
13053
|
+
"Rewake: this thread isn't at a usage limit, so there's nothing to continue."
|
|
13054
|
+
);
|
|
13055
|
+
if (limit.billing)
|
|
13056
|
+
return block(
|
|
13057
|
+
"Rewake can't continue after this limit: this limit is about credits or spending, which waiting doesn't fix."
|
|
13058
|
+
);
|
|
13059
|
+
let at = c.at;
|
|
13060
|
+
if (at === void 0) {
|
|
13061
|
+
const later = new ScheduleStore(ctx.stateDir).listForSession(thread.id, "codex").filter((x) => x.failureReason === "far-reset" || x.failureReason === "expired").map((x) => x.dueAt).filter((t) => t > ctx.now);
|
|
13062
|
+
const times = [
|
|
13063
|
+
...limit.resetsAt ? [limit.resetsAt + RESET_MARGIN_MS] : [],
|
|
13064
|
+
...later
|
|
13065
|
+
].filter((t) => t > ctx.now);
|
|
13066
|
+
if (times.length > 0) at = Math.max(...times);
|
|
13067
|
+
}
|
|
13068
|
+
if (at === void 0 && limit.resetPassed) at = ctx.now + RESET_MARGIN_MS;
|
|
13069
|
+
if (at === void 0)
|
|
13070
|
+
return block(
|
|
13071
|
+
`Rewake doesn't know when this limit resets. Type "rewake" with a time, for example "rewake 3:30pm".`
|
|
13072
|
+
);
|
|
13073
|
+
cancelPending(ctx, thread.id);
|
|
13074
|
+
if (!arm(ctx, thread, at)) return block(noTimerText(at, ctx.now));
|
|
13075
|
+
return block(armedText2(at, ctx.now));
|
|
13076
|
+
}
|
|
13077
|
+
if (ctx.event === "SessionEnd") {
|
|
13078
|
+
if (pendingFor2(new ScheduleStore(ctx.stateDir), thread.id).length > 0) return void 0;
|
|
13079
|
+
let limit;
|
|
13080
|
+
try {
|
|
13081
|
+
limit = findCodexLimit(readTail2(thread.path), ctx.now);
|
|
13082
|
+
} catch {
|
|
13083
|
+
return void 0;
|
|
13084
|
+
}
|
|
13085
|
+
if (!limit.limited) return void 0;
|
|
13086
|
+
const settings = loadSettings(ctx.stateDir);
|
|
13087
|
+
const decision = decideArm({
|
|
13088
|
+
now: ctx.now,
|
|
13089
|
+
...limit.resetsAt !== void 0 && { resetsAt: limit.resetsAt },
|
|
13090
|
+
isBilling: limit.billing === true,
|
|
13091
|
+
auto: autoFor(settings)
|
|
13092
|
+
});
|
|
13093
|
+
const cwd = typeof ctx.input.cwd === "string" ? basename6(ctx.input.cwd) : "";
|
|
13094
|
+
const where2 = cwd ? `Codex in the "${cwd}" folder` : "Codex";
|
|
13095
|
+
if (decision.action === "arm") {
|
|
13096
|
+
const ok = arm(ctx, thread, decision.fireAt);
|
|
13097
|
+
deps.notify(
|
|
13098
|
+
"Agent Rewake",
|
|
13099
|
+
`${where2}: ${ok ? armedText2(decision.fireAt, ctx.now) : noTimerText(decision.fireAt, ctx.now)}`
|
|
13100
|
+
);
|
|
13101
|
+
return void 0;
|
|
13102
|
+
}
|
|
13103
|
+
if (decision.action === "offer") {
|
|
13104
|
+
const how = limit.resetsAt ? `type "rewake", and Rewake continues it ${formatAt(limit.resetsAt + RESET_MARGIN_MS, ctx.now)}, after the limit resets` : 'type "rewake" with a time, for example "rewake 3:30pm"';
|
|
13105
|
+
deps.notify(
|
|
13106
|
+
"Agent Rewake",
|
|
13107
|
+
`${where2} hit its usage limit. Resume the thread in Codex and ${how}.`
|
|
13108
|
+
);
|
|
13109
|
+
}
|
|
13110
|
+
return void 0;
|
|
13111
|
+
}
|
|
13112
|
+
return void 0;
|
|
13113
|
+
}
|
|
13114
|
+
};
|
|
13115
|
+
}
|
|
13116
|
+
|
|
13117
|
+
// src/hosts/copilot/host.ts
|
|
13118
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
13119
|
+
import { createInterface as createInterface4 } from "node:readline";
|
|
13120
|
+
var COPILOT_ID = "copilot-cli";
|
|
13121
|
+
function isCopilot(input, env) {
|
|
13122
|
+
return !env.GROK_HOOK_EVENT && !("hookEventName" in input) && safeSessionId(input.sessionId) && typeof input.timestamp === "number";
|
|
13123
|
+
}
|
|
13124
|
+
function resumeCopilot(r, text2, env, node = process.execPath) {
|
|
13125
|
+
if (!r.program)
|
|
13126
|
+
return Promise.resolve({ ok: false, reason: "unsupported", detail: "no Copilot found" });
|
|
13127
|
+
const program = codexProgram(r.program, node);
|
|
13128
|
+
return new Promise((resolve2) => {
|
|
13129
|
+
let limited = false;
|
|
13130
|
+
let resetsAt;
|
|
13131
|
+
const child = spawn8(
|
|
13132
|
+
program.command,
|
|
13133
|
+
[
|
|
13134
|
+
...program.args,
|
|
13135
|
+
`--resume=${r.sessionId}`,
|
|
13136
|
+
"-p",
|
|
13137
|
+
text2,
|
|
13138
|
+
"--no-ask-user",
|
|
13139
|
+
"--output-format",
|
|
13140
|
+
"json",
|
|
13141
|
+
"--no-auto-update"
|
|
13142
|
+
],
|
|
13143
|
+
{
|
|
13144
|
+
cwd: r.cwd || void 0,
|
|
13145
|
+
env: { ...env, COPILOT_AUTO_UPDATE: "false" },
|
|
13146
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13147
|
+
windowsHide: true
|
|
13148
|
+
}
|
|
13149
|
+
);
|
|
13150
|
+
let err = "";
|
|
13151
|
+
child.stderr.on("data", (d) => {
|
|
13152
|
+
if (err.length < 1 << 16) err += d.toString("utf8");
|
|
13153
|
+
});
|
|
13154
|
+
createInterface4({ input: child.stdout }).on("line", (line) => {
|
|
13107
13155
|
try {
|
|
13108
|
-
|
|
13156
|
+
const e = JSON.parse(line);
|
|
13157
|
+
if (e.type === "session.error" && e.data?.errorType === "rate_limit") {
|
|
13158
|
+
limited = true;
|
|
13159
|
+
resetsAt = classifyCopilotError(e.data.message, Date.now())?.resetsAt;
|
|
13160
|
+
}
|
|
13109
13161
|
} catch {
|
|
13110
13162
|
}
|
|
13111
|
-
}
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
}
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
}
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
}
|
|
13129
|
-
function checkId(id) {
|
|
13130
|
-
if (!ID.test(id)) throw new Error(`invalid timer id: ${id}`);
|
|
13131
|
-
}
|
|
13132
|
-
var MARK = { systemd: ".systemd", schtasks: ".task" };
|
|
13133
|
-
function mark(name, kind, h) {
|
|
13134
|
-
const ext = MARK[kind];
|
|
13135
|
-
if (ext) writeFileSync5(join29(timersDir(h), `${name}${ext}`), "", { mode: 384 });
|
|
13136
|
-
}
|
|
13137
|
-
function timerNames(id, h) {
|
|
13138
|
-
const names = /* @__PURE__ */ new Set([id]);
|
|
13139
|
-
let files = [];
|
|
13140
|
-
try {
|
|
13141
|
-
files = readdirSync11(join29(h.stateDir, "timers"));
|
|
13142
|
-
} catch {
|
|
13143
|
-
}
|
|
13144
|
-
for (const f of files) {
|
|
13145
|
-
const name = f.replace(/^codizelabs\.agent-rewake\./, "").replace(/\.(plist|systemd|task|at|at-time)$/, "");
|
|
13146
|
-
if (name !== f && ID.test(name) && parseTimerName(name).id === id) names.add(name);
|
|
13147
|
-
}
|
|
13148
|
-
return [...names];
|
|
13149
|
-
}
|
|
13150
|
-
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
13151
|
-
function launchdPlist(id, at, node, cli, stateDir2) {
|
|
13152
|
-
const d = new Date(Math.ceil(at / 6e4) * 6e4);
|
|
13153
|
-
const int = (k, v) => `<key>${k}</key><integer>${v}</integer>`;
|
|
13154
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
13155
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
13156
|
-
<plist version="1.0">
|
|
13157
|
-
<dict>
|
|
13158
|
-
<key>Label</key><string>${label(id)}</string>
|
|
13159
|
-
<key>ProgramArguments</key>
|
|
13160
|
-
<array><string>${xml(node)}</string><string>${xml(cli)}</string><string>fire</string><string>${id}</string>${stateDir2 ? `<string>--state-dir</string><string>${xml(stateDir2)}</string>` : ""}</array>
|
|
13161
|
-
<key>StartCalendarInterval</key>
|
|
13162
|
-
<dict>${int("Month", d.getMonth() + 1)}${int("Day", d.getDate())}${int("Hour", d.getHours())}${int("Minute", d.getMinutes())}</dict>
|
|
13163
|
-
<key>RunAtLoad</key><true/>
|
|
13164
|
-
<key>ProcessType</key><string>Background</string>
|
|
13165
|
-
<key>StandardOutPath</key><string>/dev/null</string>
|
|
13166
|
-
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
13167
|
-
</dict>
|
|
13168
|
-
</plist>
|
|
13169
|
-
`;
|
|
13163
|
+
});
|
|
13164
|
+
const timedOut = resumeDeadline(child);
|
|
13165
|
+
child.on("error", () => resolve2({ ok: false, reason: "failed", detail: "spawn" }));
|
|
13166
|
+
child.on("exit", (code) => {
|
|
13167
|
+
if (timedOut()) return resolve2({ ok: false, reason: "failed", detail: "timeout" });
|
|
13168
|
+
if (limited) resolve2({ ok: false, reason: "limited", ...resetsAt && { resetsAt } });
|
|
13169
|
+
else if (code === 0) resolve2({ ok: true });
|
|
13170
|
+
else if (SESSION_GONE.test(err)) resolve2({ ok: false, reason: "closed", detail: "deleted" });
|
|
13171
|
+
else
|
|
13172
|
+
resolve2({
|
|
13173
|
+
ok: false,
|
|
13174
|
+
reason: "failed",
|
|
13175
|
+
detail: `exit ${code ?? "signal"}`,
|
|
13176
|
+
...withMessage(err)
|
|
13177
|
+
});
|
|
13178
|
+
});
|
|
13179
|
+
});
|
|
13170
13180
|
}
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
|
|
13181
|
+
var copilotHost = {
|
|
13182
|
+
id: COPILOT_ID,
|
|
13183
|
+
name: "GitHub Copilot CLI",
|
|
13184
|
+
reopen: 'resume the session with "copilot --resume"',
|
|
13185
|
+
resume: (r, text2, env) => resumeCopilot(r, text2, env),
|
|
13186
|
+
settingsVars: [
|
|
13187
|
+
"COPILOT_HOME",
|
|
13188
|
+
"COPILOT_CACHE_HOME",
|
|
13189
|
+
"COPILOT_OFFLINE",
|
|
13190
|
+
"COPILOT_PROVIDER_BASE_URL",
|
|
13191
|
+
"COPILOT_PROVIDER_TYPE",
|
|
13192
|
+
"COPILOT_PROVIDER_WIRE_API",
|
|
13193
|
+
"COPILOT_MODEL"
|
|
13194
|
+
],
|
|
13195
|
+
keyVars: ["COPILOT_PROVIDER_API_KEY", "GH_TOKEN", "GITHUB_TOKEN"]
|
|
13196
|
+
};
|
|
13197
|
+
function copilotHooks(deps) {
|
|
13198
|
+
return {
|
|
13199
|
+
isMine: isCopilot,
|
|
13200
|
+
sessionId: (input) => safeSessionId(input.sessionId) ? input.sessionId : void 0,
|
|
13201
|
+
async handle(ctx) {
|
|
13202
|
+
const id = ctx.input.sessionId;
|
|
13203
|
+
const cwd = typeof ctx.input.cwd === "string" ? ctx.input.cwd : "";
|
|
13204
|
+
const d = deps.closed(ctx);
|
|
13205
|
+
if (!ctx.env[FIRE_ENV]) ensureProgram(copilotHost, id, cwd, d, () => deps.program(ctx.env));
|
|
13206
|
+
switch (ctx.event) {
|
|
13207
|
+
case "sessionStart":
|
|
13208
|
+
onSessionStart(copilotHost, id, cwd, d, deps.program(ctx.env));
|
|
13209
|
+
break;
|
|
13210
|
+
case "userPromptSubmitted":
|
|
13211
|
+
onPrompt(copilotHost, id, cwd, d);
|
|
13212
|
+
break;
|
|
13213
|
+
case "errorOccurred": {
|
|
13214
|
+
const e = ctx.input.error;
|
|
13215
|
+
const text2 = typeof e === "string" ? e : e?.message;
|
|
13216
|
+
const recoverable = ctx.input.recoverable === true || typeof e === "object" && e?.recoverable === true;
|
|
13217
|
+
const limit = recogniseForHost(
|
|
13218
|
+
{
|
|
13219
|
+
agent: "copilot",
|
|
13220
|
+
source: "hook",
|
|
13221
|
+
...typeof text2 === "string" && { text: text2 },
|
|
13222
|
+
recovered: recoverable
|
|
13223
|
+
},
|
|
13224
|
+
ctx.now
|
|
13225
|
+
);
|
|
13226
|
+
if (limit) onLimit(copilotHost, id, cwd, limit, d);
|
|
13227
|
+
break;
|
|
13228
|
+
}
|
|
13229
|
+
case "sessionEnd":
|
|
13230
|
+
onSessionEnd(copilotHost, id, cwd, d);
|
|
13231
|
+
break;
|
|
13232
|
+
}
|
|
13233
|
+
return void 0;
|
|
13234
|
+
}
|
|
13235
|
+
};
|
|
13174
13236
|
}
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13237
|
+
|
|
13238
|
+
// src/hosts/cursor/host.ts
|
|
13239
|
+
import { readFileSync as readFileSync29 } from "node:fs";
|
|
13240
|
+
var CURSOR_ID = "cursor";
|
|
13241
|
+
var FALLBACK_MS = 3 * 6e4;
|
|
13242
|
+
var POLL_MS = 5e3;
|
|
13243
|
+
var cursorHost = {
|
|
13244
|
+
id: CURSOR_ID,
|
|
13245
|
+
name: "Cursor",
|
|
13246
|
+
noun: "chat",
|
|
13247
|
+
reopen: "open the chat in Cursor",
|
|
13248
|
+
fallbackDelayMs: FALLBACK_MS,
|
|
13249
|
+
maxWaitAfterLimitMs: WAIT_SECONDS * 1e3 - 6e4,
|
|
13250
|
+
keepOpen: "Keep that Cursor window open until then: reloading or quitting Cursor cancels it.",
|
|
13251
|
+
// Reached only when the waiting hook didn't deliver (the window was closed): nothing to run.
|
|
13252
|
+
resume: async () => ({ ok: false, reason: "failed", detail: "window-closed" })
|
|
13253
|
+
};
|
|
13254
|
+
function transcriptError(path) {
|
|
13255
|
+
if (typeof path !== "string" || path === "") return void 0;
|
|
13256
|
+
try {
|
|
13257
|
+
const lines = readFileSync29(path, "utf8").trimEnd().split("\n");
|
|
13258
|
+
const last = JSON.parse(lines.at(-1) ?? "{}");
|
|
13259
|
+
if (last.type === "turn_ended" && last.status === "error" && typeof last.error === "string")
|
|
13260
|
+
return last.error;
|
|
13261
|
+
} catch {
|
|
13262
|
+
}
|
|
13263
|
+
return void 0;
|
|
13184
13264
|
}
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
const
|
|
13188
|
-
|
|
13265
|
+
var isCursor = (input) => typeof input.cursor_version === "string" && typeof input.hook_event_name === "string";
|
|
13266
|
+
function cursorHooks(deps) {
|
|
13267
|
+
const now = deps.now ?? Date.now;
|
|
13268
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13269
|
+
return {
|
|
13270
|
+
isMine: isCursor,
|
|
13271
|
+
sessionId: (input) => safeSessionId(input.conversation_id) ? input.conversation_id : void 0,
|
|
13272
|
+
async handle(ctx) {
|
|
13273
|
+
const id = ctx.input.conversation_id;
|
|
13274
|
+
if (!safeSessionId(id)) return void 0;
|
|
13275
|
+
const roots = ctx.input.workspace_roots;
|
|
13276
|
+
const cwd = Array.isArray(roots) && typeof roots[0] === "string" ? roots[0] : "";
|
|
13277
|
+
const d = deps.closed(ctx);
|
|
13278
|
+
if (ctx.event === "beforeSubmitPrompt") {
|
|
13279
|
+
onPrompt(cursorHost, id, cwd, d);
|
|
13280
|
+
return void 0;
|
|
13281
|
+
}
|
|
13282
|
+
if (ctx.event !== "stop") return void 0;
|
|
13283
|
+
if (ctx.input.status !== "error") return "{}";
|
|
13284
|
+
const limit = recogniseForHost(
|
|
13285
|
+
{ agent: "cursor", source: "hook", text: transcriptError(ctx.input.transcript_path) ?? "" },
|
|
13286
|
+
ctx.now
|
|
13287
|
+
);
|
|
13288
|
+
if (!limit) return "{}";
|
|
13289
|
+
onLimit(cursorHost, id, cwd, limit, d);
|
|
13290
|
+
onSessionEnd(cursorHost, id, cwd, d);
|
|
13291
|
+
if (limit.billing) {
|
|
13292
|
+
d.notify(
|
|
13293
|
+
"Agent Rewake",
|
|
13294
|
+
`${placeOf(cursorHost, cwd)} hit a usage limit that waiting won't lift (it needs a paid plan, more usage or a new month), so Rewake can't continue it. Continue the chat in Cursor when you can.`
|
|
13295
|
+
);
|
|
13296
|
+
return "{}";
|
|
13297
|
+
}
|
|
13298
|
+
return waitAndDeliver(id, ctx, d, now, sleep, deps.waitMs ?? WAIT_SECONDS * 1e3 - 6e4);
|
|
13299
|
+
}
|
|
13300
|
+
};
|
|
13189
13301
|
}
|
|
13190
|
-
function
|
|
13191
|
-
|
|
13302
|
+
async function waitAndDeliver(id, ctx, d, now, sleep, waitMs) {
|
|
13303
|
+
const records = new SessionRecords(ctx.stateDir, CURSOR_ID);
|
|
13304
|
+
const store = new ScheduleStore(ctx.stateDir);
|
|
13305
|
+
const seenAt = records.get(id)?.limit?.seenAt ?? ctx.now;
|
|
13306
|
+
const deadline = ctx.now + waitMs;
|
|
13307
|
+
while (now() < deadline) {
|
|
13308
|
+
if ((records.get(id)?.lastPromptAt ?? 0) > seenAt) return "{}";
|
|
13309
|
+
const s = pendingFor(ctx.stateDir, CURSOR_ID, id).sort((a, b) => a.dueAt - b.dueAt)[0];
|
|
13310
|
+
if (s && s.dueAt > deadline) {
|
|
13311
|
+
store.update(
|
|
13312
|
+
s.scheduleId,
|
|
13313
|
+
(x) => ({ ...x, status: "failed", failureReason: "too-late" }),
|
|
13314
|
+
now()
|
|
13315
|
+
);
|
|
13316
|
+
d.disarm(s.scheduleId);
|
|
13317
|
+
d.notify(
|
|
13318
|
+
"Agent Rewake",
|
|
13319
|
+
`${placeOf(cursorHost, records.get(id)?.cwd ?? "")}: Rewake can continue a Cursor chat only within 4 hours of its usage limit, so it won't continue this one ${formatAt(s.dueAt, now())}. Open the chat in Cursor then to continue it.`
|
|
13320
|
+
);
|
|
13321
|
+
return "{}";
|
|
13322
|
+
}
|
|
13323
|
+
if (s && s.dueAt <= now()) {
|
|
13324
|
+
store.update(
|
|
13325
|
+
s.scheduleId,
|
|
13326
|
+
(x) => ({ ...x, status: "sent", lastRun: { at: now(), outcome: "sent" } }),
|
|
13327
|
+
now()
|
|
13328
|
+
);
|
|
13329
|
+
d.disarm(s.scheduleId);
|
|
13330
|
+
return JSON.stringify({ followup_message: s.text });
|
|
13331
|
+
}
|
|
13332
|
+
await sleep(Math.min(POLL_MS, Math.max(0, deadline - now())));
|
|
13333
|
+
}
|
|
13334
|
+
return "{}";
|
|
13192
13335
|
}
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13336
|
+
|
|
13337
|
+
// src/hosts/index.ts
|
|
13338
|
+
registerHost("codex");
|
|
13339
|
+
registerHost(COPILOT_ID);
|
|
13340
|
+
registerHost(GROK_ID);
|
|
13341
|
+
registerHost(GEMINI_ID);
|
|
13342
|
+
registerHost(ANTIGRAVITY_ID);
|
|
13343
|
+
registerHost(CURSOR_ID);
|
|
13344
|
+
var CLOSED_HOSTS = [
|
|
13345
|
+
copilotHost,
|
|
13346
|
+
grokHost(process.env),
|
|
13347
|
+
geminiHost,
|
|
13348
|
+
antigravityHost(),
|
|
13349
|
+
cursorHost
|
|
13350
|
+
];
|
|
13351
|
+
function hostAdapters(env, node, stateDir2) {
|
|
13352
|
+
return new Map([
|
|
13353
|
+
["codex", codexAdapter({ env, node })],
|
|
13354
|
+
...CLOSED_HOSTS.map((h) => [h.id, closedAdapter(h, stateDir2, env)])
|
|
13209
13355
|
]);
|
|
13210
|
-
if (r.status !== 0) return { ok: false, reason: "failed", detail: r.stderr.trim() };
|
|
13211
|
-
mark(id, "systemd", h);
|
|
13212
|
-
return { ok: true, via: "systemd" };
|
|
13213
13356
|
}
|
|
13214
|
-
function
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13357
|
+
function hookHandler(host, deps) {
|
|
13358
|
+
if (host === "codex") return codexHooks(deps);
|
|
13359
|
+
if (host === COPILOT_ID)
|
|
13360
|
+
return copilotHooks({ closed: deps.closed, program: (env) => deps.program(host, env) });
|
|
13361
|
+
if (host === GROK_ID)
|
|
13362
|
+
return grokHooks({ closed: deps.closed, program: (env) => deps.program(host, env) });
|
|
13363
|
+
if (host === GEMINI_ID)
|
|
13364
|
+
return geminiHooks({
|
|
13365
|
+
closed: deps.closed,
|
|
13366
|
+
program: (env) => deps.program(host, env),
|
|
13367
|
+
apiKey: (env) => geminiApiKeyAuth(env, env.HOME || env.USERPROFILE || homedir18())
|
|
13368
|
+
});
|
|
13369
|
+
if (host === CURSOR_ID) return cursorHooks({ closed: deps.closed });
|
|
13370
|
+
if (host === ANTIGRAVITY_ID)
|
|
13371
|
+
return antigravityHooks({ closed: deps.closed, program: (env) => deps.program(host, env) });
|
|
13372
|
+
return void 0;
|
|
13218
13373
|
}
|
|
13219
|
-
var
|
|
13220
|
-
function
|
|
13221
|
-
|
|
13222
|
-
const pg = h.run("pgrep", ["-x", "atd"]);
|
|
13223
|
-
return pg.status === 0 || pg.status === null || pg.status === 127;
|
|
13374
|
+
var OWNER_ENV = "AGENT_REWAKE_OWNER";
|
|
13375
|
+
function ownedByZed(env) {
|
|
13376
|
+
return env[OWNER_ENV] === "acp";
|
|
13224
13377
|
}
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
const
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
}
|
|
13237
|
-
return
|
|
13378
|
+
|
|
13379
|
+
// src/hosts/hook.ts
|
|
13380
|
+
var MAX_INPUT = 4 * 1024 * 1024;
|
|
13381
|
+
async function readStdin(stream = process.stdin) {
|
|
13382
|
+
const chunks = [];
|
|
13383
|
+
let size = 0;
|
|
13384
|
+
for await (const chunk of stream) {
|
|
13385
|
+
const b = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
13386
|
+
size += b.length;
|
|
13387
|
+
if (size > MAX_INPUT) return "";
|
|
13388
|
+
chunks.push(b);
|
|
13389
|
+
}
|
|
13390
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
13238
13391
|
}
|
|
13239
|
-
function
|
|
13392
|
+
async function runHook(handler, event, stdin, env, stateDir2, now) {
|
|
13393
|
+
if (!handler || ownedByZed(env)) return void 0;
|
|
13394
|
+
let input;
|
|
13240
13395
|
try {
|
|
13241
|
-
|
|
13242
|
-
return /^\d+$/.test(n) ? n : void 0;
|
|
13396
|
+
input = JSON.parse(stdin || "{}");
|
|
13243
13397
|
} catch {
|
|
13244
13398
|
return void 0;
|
|
13245
13399
|
}
|
|
13400
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
|
|
13401
|
+
const record = input;
|
|
13402
|
+
if (!handler.isMine(record, env)) return void 0;
|
|
13403
|
+
const id = handler.sessionId(record);
|
|
13404
|
+
if (id && new ThreadStore(stateDir2).get(id)) return void 0;
|
|
13405
|
+
return handler.handle({ event, input: record, env, stateDir: stateDir2, now });
|
|
13246
13406
|
}
|
|
13247
|
-
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13407
|
+
|
|
13408
|
+
// src/install/select.ts
|
|
13409
|
+
var WHAT = {
|
|
13410
|
+
zed: "Resumes and schedules messages in Zed's agent threads.",
|
|
13411
|
+
jetbrains: "Adds Claude Agent and Codex with Rewake to AI Assistant in your JetBrains IDEs.",
|
|
13412
|
+
"devin-desktop": "Adds Claude Agent and Codex with Rewake to Devin Desktop's agent selector.",
|
|
13413
|
+
cursor: "At a usage limit in Cursor's own agent, continues the chat at the time you choose (within 4 hours), while its window stays open.",
|
|
13414
|
+
"claude-code": "Asks at a usage limit, then continues the same session when the limit resets.",
|
|
13415
|
+
codex: 'Type "rewake" in a thread at a usage limit; it continues when the limit resets.',
|
|
13416
|
+
"copilot-cli": "Continues a closed session when its usage limit resets.",
|
|
13417
|
+
grok: "Continues a closed session when its usage limit resets.",
|
|
13418
|
+
"gemini-cli": "Continues a closed session when its usage limit resets.",
|
|
13419
|
+
antigravity: "Continues a closed CLI conversation when its limit resets; in the app and IDE, says when it resets."
|
|
13420
|
+
};
|
|
13421
|
+
var UNREACHABLE = "Rewake can't reach claude.ai, the Claude desktop app's chat, or Zed's own agent.";
|
|
13422
|
+
function defaultChoice(places) {
|
|
13423
|
+
return new Set(places.filter((p) => p.state === "ready").map((p) => p.id));
|
|
13252
13424
|
}
|
|
13253
|
-
|
|
13254
|
-
|
|
13255
|
-
const
|
|
13256
|
-
|
|
13257
|
-
|
|
13425
|
+
var PREVIEW_NOTE = "Everything except Zed is a preview: new, and may change.";
|
|
13426
|
+
function renderChoice(places, chosen, cursor, missing) {
|
|
13427
|
+
const lines = [];
|
|
13428
|
+
places.forEach((p, i) => {
|
|
13429
|
+
const pointer = i === cursor ? ">" : " ";
|
|
13430
|
+
const label2 = `${p.name}${p.version ? ` ${p.version}` : ""}${p.surface ? ` (${p.surface})` : ""}`;
|
|
13431
|
+
if (p.state === "too-old") {
|
|
13432
|
+
lines.push(
|
|
13433
|
+
`${pointer} - ${label2}: too old for Rewake (it needs ${p.needs} or newer). Update it: ${p.update ?? "see its own docs"}`
|
|
13434
|
+
);
|
|
13435
|
+
return;
|
|
13436
|
+
}
|
|
13437
|
+
const box = chosen.has(p.id) ? "[x]" : "[ ]";
|
|
13438
|
+
const note = p.state === "installed" ? ": Rewake is set up here (tick to update)" : "";
|
|
13439
|
+
lines.push(`${pointer} ${box} ${label2}${note}`);
|
|
13440
|
+
lines.push(` ${p.what}`);
|
|
13441
|
+
});
|
|
13442
|
+
for (const name of missing) lines.push(` - ${name}: not found.`);
|
|
13443
|
+
lines.push("", PREVIEW_NOTE, UNREACHABLE);
|
|
13444
|
+
return lines;
|
|
13258
13445
|
}
|
|
13259
|
-
function
|
|
13260
|
-
return
|
|
13261
|
-
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
13262
|
-
<RegistrationInfo><Description>Agent Rewake: ${id}</Description></RegistrationInfo>
|
|
13263
|
-
<Triggers>
|
|
13264
|
-
<TimeTrigger>
|
|
13265
|
-
<StartBoundary>${localIso(at)}</StartBoundary>
|
|
13266
|
-
<EndBoundary>${localIso(at + 7 * 24 * 36e5)}</EndBoundary>
|
|
13267
|
-
<Enabled>true</Enabled>
|
|
13268
|
-
</TimeTrigger>
|
|
13269
|
-
</Triggers>
|
|
13270
|
-
<Principals><Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
|
|
13271
|
-
<Settings>
|
|
13272
|
-
<StartWhenAvailable>true</StartWhenAvailable>
|
|
13273
|
-
<DeleteExpiredTaskAfter>PT1H</DeleteExpiredTaskAfter>
|
|
13274
|
-
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
13275
|
-
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
13276
|
-
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
13277
|
-
<ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
|
|
13278
|
-
</Settings>
|
|
13279
|
-
<Actions Context="Author">
|
|
13280
|
-
<Exec><Command>conhost.exe</Command><Arguments>--headless "${xml(node)}" "${xml(cli)}" fire ${id}${stateDir2 ? ` --state-dir "${xml(stateDir2)}"` : ""}</Arguments></Exec>
|
|
13281
|
-
</Actions>
|
|
13282
|
-
</Task>
|
|
13283
|
-
`;
|
|
13446
|
+
function keysHelp(ticked) {
|
|
13447
|
+
return `Set up Rewake in the ${ticked === 1 ? "1 ticked place" : `${ticked} ticked places`}? Up and Down move, Space ticks or unticks, a ticks all, Enter continues, q quits.`;
|
|
13284
13448
|
}
|
|
13285
|
-
function
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
if (
|
|
13292
|
-
|
|
13293
|
-
|
|
13449
|
+
function keyOf(data) {
|
|
13450
|
+
if (data === "\x1B[A" || data === "k") return "up";
|
|
13451
|
+
if (data === "\x1B[B" || data === "j") return "down";
|
|
13452
|
+
if (data === " ") return "toggle";
|
|
13453
|
+
if (data === "a") return "all";
|
|
13454
|
+
if (data === "\r" || data === "\n") return "enter";
|
|
13455
|
+
if (data === "q" || data === "" || data === "\x1B") return "quit";
|
|
13456
|
+
return "other";
|
|
13457
|
+
}
|
|
13458
|
+
function press(key2, places, chosen, state) {
|
|
13459
|
+
const pickable = (i) => places[i]?.state !== "too-old";
|
|
13460
|
+
if (key2 === "up") state.cursor = Math.max(0, state.cursor - 1);
|
|
13461
|
+
if (key2 === "down") state.cursor = Math.min(places.length - 1, state.cursor + 1);
|
|
13462
|
+
if (key2 === "toggle" && pickable(state.cursor)) {
|
|
13463
|
+
const id = places[state.cursor]?.id;
|
|
13464
|
+
if (id) chosen.has(id) ? chosen.delete(id) : chosen.add(id);
|
|
13465
|
+
}
|
|
13466
|
+
if (key2 === "all") {
|
|
13467
|
+
for (const p of places) if (p.state !== "too-old") chosen.add(p.id);
|
|
13468
|
+
}
|
|
13469
|
+
if (key2 === "enter") return "done";
|
|
13470
|
+
if (key2 === "quit") return "quit";
|
|
13471
|
+
return void 0;
|
|
13472
|
+
}
|
|
13473
|
+
function placesFrom(found, zed, installed, minimum, older, update = () => void 0) {
|
|
13474
|
+
const out = [];
|
|
13475
|
+
if (zed.found)
|
|
13476
|
+
out.push({
|
|
13477
|
+
id: "zed",
|
|
13478
|
+
name: "Zed",
|
|
13479
|
+
...zed.version && { version: zed.version },
|
|
13480
|
+
what: WHAT.zed,
|
|
13481
|
+
state: "ready"
|
|
13482
|
+
});
|
|
13483
|
+
for (const f of found) {
|
|
13484
|
+
const min = minimum(f.id);
|
|
13485
|
+
const tooOld2 = Boolean(min && f.version && older(f.version, min));
|
|
13486
|
+
out.push({
|
|
13487
|
+
id: f.id,
|
|
13488
|
+
name: f.name,
|
|
13489
|
+
...f.version && { version: f.version },
|
|
13490
|
+
what: WHAT[f.id],
|
|
13491
|
+
state: tooOld2 ? "too-old" : installed.has(f.id) ? "installed" : "ready",
|
|
13492
|
+
...tooOld2 && min && { needs: min },
|
|
13493
|
+
...tooOld2 && update(f.id) && { update: update(f.id) },
|
|
13494
|
+
...f.surfaces.length > 0 && !f.surfaces.includes("terminal") && { surface: f.surfaces.join(", ") }
|
|
13495
|
+
});
|
|
13496
|
+
}
|
|
13497
|
+
return out;
|
|
13294
13498
|
}
|
|
13295
|
-
function
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13499
|
+
async function choosePlaces(places, missing, io) {
|
|
13500
|
+
const chosen = defaultChoice(places);
|
|
13501
|
+
const state = { cursor: 0 };
|
|
13502
|
+
let drawn = 0;
|
|
13503
|
+
const draw = () => {
|
|
13504
|
+
const lines = [
|
|
13505
|
+
...renderChoice(places, chosen, state.cursor, missing),
|
|
13506
|
+
"",
|
|
13507
|
+
keysHelp(chosen.size)
|
|
13508
|
+
];
|
|
13509
|
+
if (drawn > 0) io.write(`\x1B[${drawn}A`);
|
|
13510
|
+
io.write(`${lines.map((l) => `\x1B[2K${l}`).join("\n")}
|
|
13511
|
+
`);
|
|
13512
|
+
drawn = lines.length;
|
|
13513
|
+
};
|
|
13514
|
+
draw();
|
|
13515
|
+
for await (const data of io.keys) {
|
|
13516
|
+
const result = press(keyOf(data), places, chosen, state);
|
|
13517
|
+
if (result === "quit") return void 0;
|
|
13518
|
+
if (result === "done") return places.filter((p) => chosen.has(p.id)).map((p) => p.id);
|
|
13519
|
+
draw();
|
|
13301
13520
|
}
|
|
13302
13521
|
return void 0;
|
|
13303
13522
|
}
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13523
|
+
|
|
13524
|
+
// src/mcp.ts
|
|
13525
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
13526
|
+
var APPROVAL_TIMEOUT_MS = 10 * 6e4;
|
|
13527
|
+
var DELIVERY_NOTES = "Times are the user's local time; a clock time that has passed today means tomorrow, and one-off messages can be at most 30 days ahead. A message is sent only while Zed is running with this project, and after a Zed restart only once the thread has been opened again; the user can work in other threads meanwhile. If a reply is in progress at that time, it's sent right after the reply. If Zed or the computer wasn't running, a one-off message more than 15 minutes late isn't sent: it's marked missed and the user is asked whether to send it; a repeating message skips that run. For anything else, call about_rewake.";
|
|
13528
|
+
var ENDS = {
|
|
13529
|
+
ends_after_runs: {
|
|
13530
|
+
type: "integer",
|
|
13531
|
+
minimum: 1,
|
|
13532
|
+
description: "Repeating only: stop after this many runs."
|
|
13533
|
+
},
|
|
13534
|
+
ends_at: {
|
|
13535
|
+
type: "string",
|
|
13536
|
+
description: 'Repeating only: no runs after this time, e.g. "2026-10-31 18:00" (local time).'
|
|
13537
|
+
}
|
|
13538
|
+
};
|
|
13539
|
+
var TOOLS = [
|
|
13540
|
+
{
|
|
13541
|
+
name: "schedule_message",
|
|
13542
|
+
description: `Schedule a message to be sent into this conversation later, as if the user typed it. Use it when the user asks you to schedule something, or to schedule a follow-up for yourself (for example to check on a long build). The user must approve it in the thread, and may edit the message first; this call waits for that answer and returns what was saved: number, exact time, repeat, message. Give either \`when\` (once) or \`cron\` (repeating); a repeat runs until cancelled unless you give an end. ${DELIVERY_NOTES}`,
|
|
13543
|
+
inputSchema: {
|
|
13544
|
+
type: "object",
|
|
13545
|
+
properties: {
|
|
13546
|
+
message: { type: "string", description: "The message to send at that time." },
|
|
13547
|
+
when: {
|
|
13548
|
+
type: "string",
|
|
13549
|
+
description: 'For a one-off: "in 30m", "in 2h", "09:00", "tomorrow 09:00" or "2026-10-06 09:00" (local time).'
|
|
13550
|
+
},
|
|
13551
|
+
cron: {
|
|
13552
|
+
type: "string",
|
|
13553
|
+
description: 'For a repeating message: a 5-field cron expression in local time, e.g. "0 9 * * 1-5" (weekdays at 09:00), or @hourly, @daily, @weekly.'
|
|
13554
|
+
},
|
|
13555
|
+
...ENDS,
|
|
13556
|
+
reason: { type: "string", description: "One sentence the user sees: why you want this." }
|
|
13557
|
+
},
|
|
13558
|
+
required: ["message"]
|
|
13559
|
+
}
|
|
13560
|
+
},
|
|
13561
|
+
{
|
|
13562
|
+
name: "about_rewake",
|
|
13563
|
+
description: "How Agent Rewake works, in detail: what it does, the Rewake menu and commands, settings, when a scheduled message is and isn't sent (Zed closed, thread closed or not open, another thread in use, computer asleep, late, busy, several windows), usage limits and automatic resume, approvals, statuses and data. Call it before answering any question the user asks about Rewake, and answer from it.",
|
|
13564
|
+
inputSchema: { type: "object", properties: {} }
|
|
13565
|
+
},
|
|
13566
|
+
{
|
|
13567
|
+
name: "list_scheduled_messages",
|
|
13568
|
+
description: "List the messages scheduled in this conversation, numbered, with the current time, each one's exact next run, status, repeat and end, how its last run went, and messages finished in the last 24 hours.",
|
|
13569
|
+
inputSchema: { type: "object", properties: {} }
|
|
13570
|
+
},
|
|
13571
|
+
{
|
|
13572
|
+
name: "update_scheduled_message",
|
|
13573
|
+
description: `Change a message scheduled in this conversation, by its number from list_scheduled_messages: its message, its time, its repeat or end, or pause or resume it. Give only what changes. The user must approve it in the thread, and may edit the message; this call waits and returns the message as saved. ${DELIVERY_NOTES}`,
|
|
13574
|
+
inputSchema: {
|
|
13575
|
+
type: "object",
|
|
13576
|
+
properties: {
|
|
13577
|
+
number: { type: "integer", minimum: 1 },
|
|
13578
|
+
message: { type: "string", description: "The new message." },
|
|
13579
|
+
when: {
|
|
13580
|
+
type: "string",
|
|
13581
|
+
description: "A new time for the next run (as in schedule_message)."
|
|
13582
|
+
},
|
|
13583
|
+
cron: { type: "string", description: "A new repeat (replaces the old one)." },
|
|
13584
|
+
stop_repeating: {
|
|
13585
|
+
type: "boolean",
|
|
13586
|
+
description: "true: send it once more at its next run, then stop."
|
|
13587
|
+
},
|
|
13588
|
+
...ENDS,
|
|
13589
|
+
paused: { type: "boolean", description: "true pauses it; false resumes it." },
|
|
13590
|
+
reason: { type: "string", description: "One sentence the user sees: why." }
|
|
13591
|
+
},
|
|
13592
|
+
required: ["number"]
|
|
13593
|
+
}
|
|
13594
|
+
},
|
|
13595
|
+
{
|
|
13596
|
+
name: "cancel_scheduled_message",
|
|
13597
|
+
description: "Delete a message scheduled in this conversation, by its number from list_scheduled_messages. The user must approve it in the thread; this call waits for that answer.",
|
|
13598
|
+
inputSchema: {
|
|
13599
|
+
type: "object",
|
|
13600
|
+
properties: {
|
|
13601
|
+
number: { type: "integer", minimum: 1 },
|
|
13602
|
+
reason: { type: "string", description: "One sentence the user sees: why." }
|
|
13603
|
+
},
|
|
13604
|
+
required: ["number"]
|
|
13605
|
+
}
|
|
13606
|
+
}
|
|
13607
|
+
];
|
|
13608
|
+
function runMcp(opts, input = process.stdin, output = process.stdout) {
|
|
13609
|
+
const send = (m) => output.write(`${JSON.stringify({ jsonrpc: "2.0", ...m })}
|
|
13610
|
+
`);
|
|
13611
|
+
const rl = createInterface5({ input });
|
|
13612
|
+
rl.on("line", (line) => {
|
|
13613
|
+
let m;
|
|
13614
|
+
try {
|
|
13615
|
+
m = JSON.parse(line);
|
|
13616
|
+
} catch {
|
|
13617
|
+
return;
|
|
13618
|
+
}
|
|
13619
|
+
if (m.id === void 0 || m.id === null) return;
|
|
13620
|
+
const reply = (result) => send({ id: m.id, result });
|
|
13621
|
+
switch (m.method) {
|
|
13622
|
+
case "initialize":
|
|
13623
|
+
reply({
|
|
13624
|
+
protocolVersion: typeof m.params?.protocolVersion === "string" ? m.params.protocolVersion : "2025-06-18",
|
|
13625
|
+
capabilities: { tools: {} },
|
|
13626
|
+
serverInfo: { name: "agent-rewake", version: VERSION2 },
|
|
13627
|
+
instructions: "Agent Rewake resumes this conversation after usage limits and schedules messages into it. The user approves every schedule, change and cancellation in the thread. When the user asks anything about Rewake (how it works, what happens if Zed or the thread is closed, settings, limits), call about_rewake first and answer from it."
|
|
13628
|
+
});
|
|
13629
|
+
return;
|
|
13630
|
+
case "ping":
|
|
13631
|
+
reply({});
|
|
13632
|
+
return;
|
|
13633
|
+
case "tools/list":
|
|
13634
|
+
reply({ tools: TOOLS });
|
|
13635
|
+
return;
|
|
13636
|
+
case "tools/call":
|
|
13637
|
+
void callTool(
|
|
13638
|
+
opts,
|
|
13639
|
+
String(m.params?.name ?? ""),
|
|
13640
|
+
m.params?.arguments ?? {}
|
|
13641
|
+
).then(reply).catch((err) => reply(text(`Agent Rewake: ${err.message}`, true)));
|
|
13642
|
+
return;
|
|
13643
|
+
default:
|
|
13644
|
+
send({ id: m.id, error: { code: -32601, message: `Unknown method ${m.method}` } });
|
|
13645
|
+
}
|
|
13646
|
+
});
|
|
13647
|
+
return new Promise((resolve2) => rl.on("close", () => resolve2(0)));
|
|
13315
13648
|
}
|
|
13316
|
-
function
|
|
13317
|
-
const
|
|
13318
|
-
|
|
13649
|
+
function aboutRewake(opts, sessionId, now) {
|
|
13650
|
+
const settings = loadSettings(opts.stateDir);
|
|
13651
|
+
const auto = {
|
|
13652
|
+
off: "Off",
|
|
13653
|
+
ask: "Ask when a new thread opens",
|
|
13654
|
+
on: settings.autoWhenPromptsSkipped ? "On, even when permissions are bypassed" : "On, except when permissions are bypassed"
|
|
13655
|
+
}[settings.newThreads];
|
|
13656
|
+
const lines = [
|
|
13657
|
+
"## Right now",
|
|
13658
|
+
"",
|
|
13659
|
+
`- Current time: ${formatExact(now, opts.locale)}.`,
|
|
13660
|
+
`- Agent Rewake version: ${VERSION2}.`,
|
|
13661
|
+
`- Settings: automatic resume for new threads: ${auto}${settings.newThreads !== "on" ? `; threads that bypass permissions ${settings.autoWhenPromptsSkipped ? "included" : "excluded"}` : ""}. Time format: ${settings.clock === "24h" ? "24-hour" : "12-hour"}.`
|
|
13662
|
+
];
|
|
13663
|
+
if (sessionId) {
|
|
13664
|
+
const thread = new ThreadStore(opts.stateDir).get(sessionId);
|
|
13665
|
+
const pending = new ScheduleStore(opts.stateDir).listForSession(sessionId).filter((s) => !TERMINAL_STATUSES.has(s.status));
|
|
13666
|
+
lines.push(
|
|
13667
|
+
`- This thread: automatic resume after usage limits is ${thread?.autoResume ? "on" : "off"}; ${pending.length === 0 ? "nothing is scheduled" : `${pending.length} scheduled ${pending.length === 1 ? "message" : "messages"} (list_scheduled_messages shows them)`}.`
|
|
13668
|
+
);
|
|
13669
|
+
}
|
|
13670
|
+
return `${AGENT_GUIDE}
|
|
13671
|
+
${lines.join("\n")}
|
|
13672
|
+
`;
|
|
13319
13673
|
}
|
|
13320
|
-
function
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13674
|
+
function text(t, isError = false) {
|
|
13675
|
+
return { content: [{ type: "text", text: t }], ...isError && { isError: true } };
|
|
13676
|
+
}
|
|
13677
|
+
async function callTool(opts, name, args) {
|
|
13678
|
+
const now = opts.now ?? Date.now;
|
|
13679
|
+
applySettings(opts.stateDir);
|
|
13680
|
+
const link = opts.link ? new LinkStore(opts.stateDir).get(opts.link) : void 0;
|
|
13681
|
+
if (name === "about_rewake") return text(aboutRewake(opts, link?.sessionId, now()));
|
|
13682
|
+
if (!link)
|
|
13683
|
+
return text(
|
|
13684
|
+
"Agent Rewake isn't linked to this conversation yet. Ask the user to reopen the thread.",
|
|
13685
|
+
true
|
|
13686
|
+
);
|
|
13687
|
+
const schedules = new ScheduleStore(opts.stateDir);
|
|
13688
|
+
const requests = new RequestStore(opts.stateDir);
|
|
13689
|
+
const pending = () => schedules.listForSession(link.sessionId).filter((s) => !TERMINAL_STATUSES.has(s.status));
|
|
13690
|
+
const exact = (t) => formatExact(t, opts.locale);
|
|
13691
|
+
const clockLine = () => `Current time: ${exact(now())}.`;
|
|
13692
|
+
const reason = () => typeof args.reason === "string" && args.reason.trim() ? { reason: args.reason.trim() } : {};
|
|
13693
|
+
const byNumber = () => {
|
|
13694
|
+
const n = Number(args.number);
|
|
13695
|
+
return Number.isInteger(n) && n >= 1 ? pending()[n - 1] : void 0;
|
|
13696
|
+
};
|
|
13697
|
+
const ends = () => {
|
|
13698
|
+
const out = {};
|
|
13699
|
+
if (args.ends_after_runs !== void 0) {
|
|
13700
|
+
const n = Number(args.ends_after_runs);
|
|
13701
|
+
if (!Number.isInteger(n) || n < 1)
|
|
13702
|
+
return "`ends_after_runs` must be a whole number, 1 or more.";
|
|
13703
|
+
out.times = n;
|
|
13704
|
+
}
|
|
13705
|
+
if (typeof args.ends_at === "string" && args.ends_at.trim()) {
|
|
13706
|
+
const t = parseWhen(args.ends_at, now());
|
|
13707
|
+
if (!t.ok) return `ends_at: ${t.error}`;
|
|
13708
|
+
out.until = t.at;
|
|
13709
|
+
}
|
|
13710
|
+
return out;
|
|
13711
|
+
};
|
|
13712
|
+
const describe2 = (sid) => {
|
|
13713
|
+
const list = pending();
|
|
13714
|
+
const i = list.findIndex((x) => x.scheduleId === sid);
|
|
13715
|
+
const s = list[i] ?? schedules.get(sid);
|
|
13716
|
+
if (!s) return "";
|
|
13717
|
+
const p = s.repeat ? parseCron(s.repeat.cron) : void 0;
|
|
13718
|
+
const end = [
|
|
13719
|
+
s.repeat?.remaining !== void 0 ? `${s.repeat.remaining} run${s.repeat.remaining === 1 ? "" : "s"} left` : "",
|
|
13720
|
+
s.repeat?.until !== void 0 ? `no runs after ${exact(s.repeat.until)}` : ""
|
|
13721
|
+
].filter(Boolean);
|
|
13722
|
+
const repeat2 = p?.ok ? `Repeats: ${describeCron(p.cron)}${end.length ? ` (${end.join(", ")})` : " (until cancelled)"}` : "Once";
|
|
13723
|
+
const last = s.lastRun ? ` \xB7 Last run: ${s.lastRun.outcome}, due ${exact(s.lastRun.at)}` : "";
|
|
13724
|
+
const by = s.createdBy === "agent" ? "you asked, the user approved" : s.kind === "user" ? "the user" : "the user (resume after a usage limit)";
|
|
13725
|
+
return [
|
|
13726
|
+
`${i >= 0 ? `${i + 1}. ` : ""}Next: ${exact(s.dueAt)} \xB7 ${formatWhen(s.dueAt, now(), opts.locale)}`,
|
|
13727
|
+
` Status: ${s.status} \xB7 ${repeat2}${last} \xB7 Added by: ${by}`,
|
|
13728
|
+
` Message: ${s.text}`
|
|
13729
|
+
].join("\n");
|
|
13730
|
+
};
|
|
13731
|
+
if (name === "list_scheduled_messages") {
|
|
13732
|
+
const list = pending();
|
|
13733
|
+
const recent = schedules.listForSession(link.sessionId).filter((s) => TERMINAL_STATUSES.has(s.status) && now() - s.updatedAt < 24 * 36e5);
|
|
13734
|
+
const out = [clockLine()];
|
|
13735
|
+
out.push(
|
|
13736
|
+
list.length === 0 ? "Nothing is scheduled in this conversation." : list.map((s) => describe2(s.scheduleId)).join("\n")
|
|
13737
|
+
);
|
|
13738
|
+
if (recent.length > 0)
|
|
13739
|
+
out.push(
|
|
13740
|
+
"Finished in the last 24 hours:",
|
|
13741
|
+
...recent.map((s) => `- ${s.status}: due ${exact(s.dueAt)} \xB7 ${s.text}`)
|
|
13742
|
+
);
|
|
13743
|
+
return text(out.join("\n"));
|
|
13324
13744
|
}
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13745
|
+
const settle = async (requestId) => {
|
|
13746
|
+
const r = await wait(opts, requests, requestId);
|
|
13747
|
+
if (r.status !== "approved") return text(`${r.text}
|
|
13748
|
+
${clockLine()}`, r.isError);
|
|
13749
|
+
const saved = r.scheduleId ? describe2(r.scheduleId) : "";
|
|
13750
|
+
return text([r.text, saved && `As saved:
|
|
13751
|
+
${saved}`, clockLine()].filter(Boolean).join("\n"));
|
|
13752
|
+
};
|
|
13753
|
+
if (name === "schedule_message") {
|
|
13754
|
+
const message = typeof args.message === "string" ? args.message.trim() : "";
|
|
13755
|
+
if (!message) return text("Give the message to send.", true);
|
|
13756
|
+
const when2 = typeof args.when === "string" ? args.when.trim() : "";
|
|
13757
|
+
const cronText = typeof args.cron === "string" ? args.cron.trim() : "";
|
|
13758
|
+
if (!when2 === !cronText)
|
|
13759
|
+
return text("Give either `when` (once) or `cron` (repeating), not both.", true);
|
|
13760
|
+
const end = ends();
|
|
13761
|
+
if (typeof end === "string") return text(end, true);
|
|
13762
|
+
if (!cronText && (end.until !== void 0 || end.times !== void 0))
|
|
13763
|
+
return text(
|
|
13764
|
+
"`ends_after_runs` and `ends_at` are only for repeating messages (`cron`).",
|
|
13765
|
+
true
|
|
13766
|
+
);
|
|
13767
|
+
let dueAt;
|
|
13768
|
+
let cron;
|
|
13769
|
+
if (cronText) {
|
|
13770
|
+
const parsed = parseCron(cronText);
|
|
13771
|
+
if (!parsed.ok) return text(parsed.error, true);
|
|
13772
|
+
const first = nextRun(parsed.cron, now());
|
|
13773
|
+
if (first === void 0) return text(`"${cronText}" never runs.`, true);
|
|
13774
|
+
if (end.until !== void 0 && first > end.until)
|
|
13775
|
+
return text(`It would never run: the first run, ${exact(first)}, is after ends_at.`, true);
|
|
13776
|
+
dueAt = first;
|
|
13777
|
+
cron = parsed.cron.source;
|
|
13778
|
+
} else {
|
|
13779
|
+
const parsed = parseWhen(when2, now());
|
|
13780
|
+
if (!parsed.ok) return text(`${parsed.error} ${clockLine()}`, true);
|
|
13781
|
+
dueAt = parsed.at;
|
|
13782
|
+
}
|
|
13783
|
+
const same2 = (t) => t.trim().toLowerCase();
|
|
13784
|
+
const twin = pending().find((s) => same2(s.text) === same2(message));
|
|
13785
|
+
const r = requests.create({
|
|
13786
|
+
sessionId: link.sessionId,
|
|
13787
|
+
kind: "schedule",
|
|
13788
|
+
message,
|
|
13789
|
+
dueAt,
|
|
13790
|
+
...cron && { cron },
|
|
13791
|
+
...end,
|
|
13792
|
+
...twin && { duplicateOf: twin.scheduleId },
|
|
13793
|
+
...reason(),
|
|
13794
|
+
createdAt: now()
|
|
13795
|
+
});
|
|
13796
|
+
return settle(r.requestId);
|
|
13330
13797
|
}
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
const
|
|
13339
|
-
if (
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13798
|
+
if (name === "update_scheduled_message") {
|
|
13799
|
+
const target = byNumber();
|
|
13800
|
+
if (!target)
|
|
13801
|
+
return text(
|
|
13802
|
+
`There's no scheduled message number ${args.number}. Use list_scheduled_messages.`,
|
|
13803
|
+
true
|
|
13804
|
+
);
|
|
13805
|
+
const end = ends();
|
|
13806
|
+
if (typeof end === "string") return text(end, true);
|
|
13807
|
+
const change = { ...end };
|
|
13808
|
+
if (typeof args.message === "string" && args.message.trim())
|
|
13809
|
+
change.message = args.message.trim();
|
|
13810
|
+
if (typeof args.when === "string" && args.when.trim()) {
|
|
13811
|
+
const t = parseWhen(args.when, now());
|
|
13812
|
+
if (!t.ok) return text(`${t.error} ${clockLine()}`, true);
|
|
13813
|
+
change.dueAt = t.at;
|
|
13814
|
+
}
|
|
13815
|
+
if (typeof args.cron === "string" && args.cron.trim()) {
|
|
13816
|
+
const parsed = parseCron(args.cron);
|
|
13817
|
+
if (!parsed.ok) return text(parsed.error, true);
|
|
13818
|
+
if (change.dueAt !== void 0)
|
|
13819
|
+
return text(
|
|
13820
|
+
"Give either `when` or `cron`, not both: a new repeat sets its own next run.",
|
|
13821
|
+
true
|
|
13822
|
+
);
|
|
13823
|
+
change.cron = parsed.cron.source;
|
|
13824
|
+
}
|
|
13825
|
+
if (args.stop_repeating === true) change.stopRepeating = true;
|
|
13826
|
+
if (typeof args.paused === "boolean") change.paused = args.paused;
|
|
13827
|
+
if ((change.until !== void 0 || change.times !== void 0) && !target.repeat && change.cron === void 0)
|
|
13828
|
+
return text("That message doesn't repeat, so it has no end to set.", true);
|
|
13829
|
+
if (Object.keys(change).length === 0) return text("Give at least one thing to change.", true);
|
|
13830
|
+
const r = requests.create({
|
|
13831
|
+
sessionId: link.sessionId,
|
|
13832
|
+
kind: "update",
|
|
13833
|
+
scheduleId: target.scheduleId,
|
|
13834
|
+
...change,
|
|
13835
|
+
...reason(),
|
|
13836
|
+
createdAt: now()
|
|
13837
|
+
});
|
|
13838
|
+
return settle(r.requestId);
|
|
13343
13839
|
}
|
|
13344
|
-
if (
|
|
13345
|
-
const
|
|
13346
|
-
if (
|
|
13347
|
-
|
|
13348
|
-
|
|
13840
|
+
if (name === "cancel_scheduled_message") {
|
|
13841
|
+
const target = byNumber();
|
|
13842
|
+
if (!target)
|
|
13843
|
+
return text(
|
|
13844
|
+
`There's no scheduled message number ${args.number}. Use list_scheduled_messages.`,
|
|
13845
|
+
true
|
|
13846
|
+
);
|
|
13847
|
+
const r = requests.create({
|
|
13848
|
+
sessionId: link.sessionId,
|
|
13849
|
+
kind: "cancel",
|
|
13850
|
+
scheduleId: target.scheduleId,
|
|
13851
|
+
...reason(),
|
|
13852
|
+
createdAt: now()
|
|
13853
|
+
});
|
|
13854
|
+
return settle(r.requestId);
|
|
13349
13855
|
}
|
|
13350
|
-
return
|
|
13351
|
-
}
|
|
13352
|
-
function timerArmed(id, h) {
|
|
13353
|
-
checkId(id);
|
|
13354
|
-
const kind = timerKind(h);
|
|
13355
|
-
if (!kind) return false;
|
|
13356
|
-
return timerNames(id, h).some((name) => nameArmed(name, kind, h));
|
|
13856
|
+
return text(`Unknown tool ${name}.`, true);
|
|
13357
13857
|
}
|
|
13358
|
-
function
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
if (
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
rmSync13(join29(dir, `${name}.systemd`), { force: true });
|
|
13376
|
-
} else if (kind === "at") atCancel(name, h);
|
|
13377
|
-
else if (kind === "schtasks") {
|
|
13378
|
-
h.run("schtasks", ["/Delete", "/TN", taskName(name), "/F"]);
|
|
13379
|
-
rmSync13(join29(dir, `${name}.task`), { force: true });
|
|
13858
|
+
async function wait(opts, requests, requestId) {
|
|
13859
|
+
const deadline = Date.now() + (opts.timeoutMs ?? APPROVAL_TIMEOUT_MS);
|
|
13860
|
+
while (Date.now() < deadline) {
|
|
13861
|
+
const r = requests.get(requestId);
|
|
13862
|
+
if (!r)
|
|
13863
|
+
return {
|
|
13864
|
+
status: "declined",
|
|
13865
|
+
text: "The request disappeared; nothing was changed.",
|
|
13866
|
+
isError: true
|
|
13867
|
+
};
|
|
13868
|
+
if (r.status !== "pending") {
|
|
13869
|
+
requests.remove(requestId);
|
|
13870
|
+
return {
|
|
13871
|
+
status: r.status,
|
|
13872
|
+
text: r.answer ?? (r.status === "approved" ? "The user approved it." : "The user declined."),
|
|
13873
|
+
...r.scheduleId && r.kind !== "cancel" && { scheduleId: r.scheduleId }
|
|
13874
|
+
};
|
|
13380
13875
|
}
|
|
13876
|
+
await new Promise((res) => setTimeout(res, opts.pollMs ?? 500));
|
|
13381
13877
|
}
|
|
13878
|
+
requests.remove(requestId);
|
|
13879
|
+
return {
|
|
13880
|
+
status: "declined",
|
|
13881
|
+
text: "The user didn't answer within 10 minutes, so nothing was changed."
|
|
13882
|
+
};
|
|
13382
13883
|
}
|
|
13383
13884
|
|
|
13384
13885
|
// src/timers/fire.ts
|
|
13886
|
+
import { basename as basename7 } from "node:path";
|
|
13385
13887
|
var EARLY_MS = 3e4;
|
|
13386
13888
|
var MIN_ARM_MS = 6e4;
|
|
13387
13889
|
var SENDING_STALE_MS = 40 * 6e4;
|
|
@@ -13413,6 +13915,10 @@ function notice(why, agent, now, f) {
|
|
|
13413
13915
|
return `${agent}: Rewake couldn't continue the ${n} because you're signed out of ${f.agentName}. Sign in, then ${reopen} to continue.`;
|
|
13414
13916
|
if (f.cause === "archived")
|
|
13415
13917
|
return `${agent}: Rewake couldn't continue the ${n} because it's archived. Unarchive it, then ${reopen} to continue.`;
|
|
13918
|
+
if (f.cause === "missing-key")
|
|
13919
|
+
return `${agent}: Rewake couldn't continue the ${n} because it used a key or token from your shell, and Rewake never stores those. ${cap(reopen)} to continue. Next time, sign in to ${f.agentName} and remove the key from your shell profile.`;
|
|
13920
|
+
if (f.cause === "window-closed")
|
|
13921
|
+
return `${agent}: the time you chose has come, but Rewake couldn't continue the ${n} (was its window closed or reloaded?). ${cap(reopen)} to continue.`;
|
|
13416
13922
|
if (f.cause === "deleted")
|
|
13417
13923
|
return `${agent}: Rewake couldn't continue the ${n} because it no longer exists.`;
|
|
13418
13924
|
return `${agent}: Rewake couldn't continue the ${n}. ${cap(reopen)} to continue.`;
|
|
@@ -13471,12 +13977,13 @@ async function fire(id, deps) {
|
|
|
13471
13977
|
alreadySent: s.attempts.some((a) => a.idempotencyKey === key2)
|
|
13472
13978
|
});
|
|
13473
13979
|
const at = where(host, s);
|
|
13474
|
-
const settle = (status, failureReason) => store.update(
|
|
13980
|
+
const settle = (status, failureReason, failureMessage) => store.update(
|
|
13475
13981
|
id,
|
|
13476
13982
|
(x) => ({
|
|
13477
13983
|
...x,
|
|
13478
13984
|
status,
|
|
13479
13985
|
...failureReason !== void 0 && { failureReason },
|
|
13986
|
+
...failureMessage !== void 0 && { failureMessage },
|
|
13480
13987
|
lastRun: { at: now, outcome: status }
|
|
13481
13988
|
}),
|
|
13482
13989
|
now
|
|
@@ -13497,8 +14004,8 @@ async function fire(id, deps) {
|
|
|
13497
14004
|
log("fire.wait", { why, armed: armed?.ok ?? false });
|
|
13498
14005
|
return "waiting";
|
|
13499
14006
|
};
|
|
13500
|
-
const tell = (why, status, cause) => {
|
|
13501
|
-
settle(status, cause ?? why);
|
|
14007
|
+
const tell = (why, status, cause, message) => {
|
|
14008
|
+
settle(status, cause ?? why, message);
|
|
13502
14009
|
if (facts.newResetsAt !== void 0 && (why === "far-reset" || why === "expired"))
|
|
13503
14010
|
store.update(id, (x) => ({ ...x, dueAt: (facts.newResetsAt ?? 0) + RESET_MARGIN_MS }), now);
|
|
13504
14011
|
deps.notify(
|
|
@@ -13570,8 +14077,15 @@ async function fire(id, deps) {
|
|
|
13570
14077
|
}
|
|
13571
14078
|
return wait2(now + backoffMs(rearms), result.reason);
|
|
13572
14079
|
}
|
|
13573
|
-
const cause = [
|
|
13574
|
-
|
|
14080
|
+
const cause = [
|
|
14081
|
+
"signed-out",
|
|
14082
|
+
"archived",
|
|
14083
|
+
"deleted",
|
|
14084
|
+
"timeout",
|
|
14085
|
+
"missing-key",
|
|
14086
|
+
"window-closed"
|
|
14087
|
+
].includes(result.detail ?? "") ? result.detail : void 0;
|
|
14088
|
+
return tell("failed", "failed", cause, result.message);
|
|
13575
14089
|
}
|
|
13576
14090
|
case "wait":
|
|
13577
14091
|
return wait2(decision.until, decision.why);
|
|
@@ -13632,6 +14146,98 @@ function settleStale(id, s, host, now, deps, removeTimer) {
|
|
|
13632
14146
|
}
|
|
13633
14147
|
}
|
|
13634
14148
|
|
|
14149
|
+
// src/timers/login.ts
|
|
14150
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync3, readFileSync as readFileSync30, rmSync as rmSync15, writeFileSync as writeFileSync7 } from "node:fs";
|
|
14151
|
+
import { dirname as dirname6, join as join33 } from "node:path";
|
|
14152
|
+
var LOGIN_LABEL = "codizelabs.agent-rewake.sweep";
|
|
14153
|
+
var UNIT = "agent-rewake-sweep.service";
|
|
14154
|
+
var xml2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
14155
|
+
var dq = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
14156
|
+
function loginItem(h) {
|
|
14157
|
+
const args = [h.node, h.cli, "sweep", "--state-dir", h.stateDir];
|
|
14158
|
+
if (h.platform === "darwin")
|
|
14159
|
+
return {
|
|
14160
|
+
kind: "launchd",
|
|
14161
|
+
path: join33(h.home, "Library", "LaunchAgents", `${LOGIN_LABEL}.plist`),
|
|
14162
|
+
text: `<?xml version="1.0" encoding="UTF-8"?>
|
|
14163
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
14164
|
+
<plist version="1.0">
|
|
14165
|
+
<dict>
|
|
14166
|
+
<key>Label</key><string>${LOGIN_LABEL}</string>
|
|
14167
|
+
<key>ProgramArguments</key>
|
|
14168
|
+
<array>${args.map((a) => `<string>${xml2(a)}</string>`).join("")}</array>
|
|
14169
|
+
<key>RunAtLoad</key><true/>
|
|
14170
|
+
<key>ProcessType</key><string>Background</string>
|
|
14171
|
+
<key>StandardOutPath</key><string>/dev/null</string>
|
|
14172
|
+
<key>StandardErrorPath</key><string>/dev/null</string>
|
|
14173
|
+
</dict>
|
|
14174
|
+
</plist>
|
|
14175
|
+
`
|
|
14176
|
+
};
|
|
14177
|
+
if (h.platform === "linux") {
|
|
14178
|
+
if (h.exists("/run/systemd/system"))
|
|
14179
|
+
return {
|
|
14180
|
+
kind: "systemd",
|
|
14181
|
+
path: join33(h.home, ".config", "systemd", "user", UNIT),
|
|
14182
|
+
text: `[Unit]
|
|
14183
|
+
Description=Agent Rewake: set planned resumes again after a restart
|
|
14184
|
+
|
|
14185
|
+
[Service]
|
|
14186
|
+
Type=oneshot
|
|
14187
|
+
ExecStart=${args.map(dq).join(" ")}
|
|
14188
|
+
|
|
14189
|
+
[Install]
|
|
14190
|
+
WantedBy=default.target
|
|
14191
|
+
`
|
|
14192
|
+
};
|
|
14193
|
+
return {
|
|
14194
|
+
kind: "autostart",
|
|
14195
|
+
path: join33(h.home, ".config", "autostart", "agent-rewake-sweep.desktop"),
|
|
14196
|
+
text: `[Desktop Entry]
|
|
14197
|
+
Type=Application
|
|
14198
|
+
Name=Agent Rewake
|
|
14199
|
+
Comment=Set planned resumes again after a restart
|
|
14200
|
+
Exec=${args.map(dq).join(" ")}
|
|
14201
|
+
NoDisplay=true
|
|
14202
|
+
X-GNOME-Autostart-enabled=true
|
|
14203
|
+
`
|
|
14204
|
+
};
|
|
14205
|
+
}
|
|
14206
|
+
return void 0;
|
|
14207
|
+
}
|
|
14208
|
+
function syncLoginItem(h, wanted) {
|
|
14209
|
+
const item = loginItem(h);
|
|
14210
|
+
if (!item) return "unchanged";
|
|
14211
|
+
const had = existsSync22(item.path);
|
|
14212
|
+
if (wanted) {
|
|
14213
|
+
const same2 = had && readFileSync30(item.path, "utf8") === item.text;
|
|
14214
|
+
if (same2) return "unchanged";
|
|
14215
|
+
mkdirSync3(dirname6(item.path), { recursive: true });
|
|
14216
|
+
writeFileSync7(item.path, item.text, { mode: 420 });
|
|
14217
|
+
if (item.kind === "systemd") {
|
|
14218
|
+
h.run("systemctl", ["--user", "daemon-reload"]);
|
|
14219
|
+
h.run("systemctl", ["--user", "enable", UNIT]);
|
|
14220
|
+
}
|
|
14221
|
+
return had ? "unchanged" : "added";
|
|
14222
|
+
}
|
|
14223
|
+
if (!had) return "unchanged";
|
|
14224
|
+
if (item.kind === "systemd") h.run("systemctl", ["--user", "disable", UNIT]);
|
|
14225
|
+
rmSync15(item.path, { force: true });
|
|
14226
|
+
if (item.kind === "systemd") h.run("systemctl", ["--user", "daemon-reload"]);
|
|
14227
|
+
return "removed";
|
|
14228
|
+
}
|
|
14229
|
+
var WHO = "Codex, Copilot CLI, Gemini CLI, Grok Build and Antigravity CLI";
|
|
14230
|
+
var NODE_NOTE = ` macOS may show "Background Items Added"; it's listed as "node" (it runs Agent Rewake) under Login Items, Allow in the Background.`;
|
|
14231
|
+
function loginItemPlanText(platform4) {
|
|
14232
|
+
return `Rewake will also add a login item, so planned resumes are set again after a restart.${platform4 === "darwin" ? NODE_NOTE : ""}
|
|
14233
|
+
`;
|
|
14234
|
+
}
|
|
14235
|
+
function loginItemText(change, platform4) {
|
|
14236
|
+
if (change === "removed") return "Removed Rewake's login item: no remaining preview needs it.\n";
|
|
14237
|
+
return `Added a login item, so planned resumes are set again after a restart.${platform4 === "darwin" ? NODE_NOTE : ""} It's removed when you uninstall the last of ${WHO}.
|
|
14238
|
+
`;
|
|
14239
|
+
}
|
|
14240
|
+
|
|
13635
14241
|
// src/timers/notify.ts
|
|
13636
14242
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
13637
14243
|
function appleString(s) {
|
|
@@ -13811,8 +14417,8 @@ function textAfterLineBreak(data) {
|
|
|
13811
14417
|
}
|
|
13812
14418
|
|
|
13813
14419
|
// src/ui/page.ts
|
|
13814
|
-
import { readFileSync as
|
|
13815
|
-
import { join as
|
|
14420
|
+
import { readFileSync as readFileSync31, writeFileSync as writeFileSync8 } from "node:fs";
|
|
14421
|
+
import { join as join34 } from "node:path";
|
|
13816
14422
|
var TIPS = [
|
|
13817
14423
|
"In any thread, the Rewake menu under the message box does everything this page does.",
|
|
13818
14424
|
"Type /rewake in a thread to open the scheduling form, or /rewake 09:00 Run the tests to skip it.",
|
|
@@ -13952,11 +14558,11 @@ var SchedulesPage = class {
|
|
|
13952
14558
|
return this.rows[this.selected];
|
|
13953
14559
|
}
|
|
13954
14560
|
prefsFile() {
|
|
13955
|
-
return
|
|
14561
|
+
return join34(this.opts.stateDir, "ui.json");
|
|
13956
14562
|
}
|
|
13957
14563
|
prefs() {
|
|
13958
14564
|
try {
|
|
13959
|
-
return JSON.parse(
|
|
14565
|
+
return JSON.parse(readFileSync31(this.prefsFile(), "utf8"));
|
|
13960
14566
|
} catch {
|
|
13961
14567
|
return {};
|
|
13962
14568
|
}
|
|
@@ -13964,7 +14570,7 @@ var SchedulesPage = class {
|
|
|
13964
14570
|
savePrefs(p) {
|
|
13965
14571
|
try {
|
|
13966
14572
|
ensurePrivateDir(this.opts.stateDir);
|
|
13967
|
-
|
|
14573
|
+
writeFileSync8(this.prefsFile(), `${JSON.stringify(p)}
|
|
13968
14574
|
`, { mode: 384 });
|
|
13969
14575
|
} catch {
|
|
13970
14576
|
}
|
|
@@ -14970,7 +15576,7 @@ async function runTui(stateDir2, opts = {}) {
|
|
|
14970
15576
|
|
|
14971
15577
|
// src/util/log.ts
|
|
14972
15578
|
import { appendFileSync } from "node:fs";
|
|
14973
|
-
import { join as
|
|
15579
|
+
import { join as join35 } from "node:path";
|
|
14974
15580
|
var Logger = class {
|
|
14975
15581
|
constructor(env = process.env) {
|
|
14976
15582
|
this.env = env;
|
|
@@ -14992,8 +15598,8 @@ var Logger = class {
|
|
|
14992
15598
|
try {
|
|
14993
15599
|
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
14994
15600
|
if (!this.file?.endsWith(`rewake-${day}.jsonl`)) {
|
|
14995
|
-
const dir = ensurePrivateDir(
|
|
14996
|
-
this.file =
|
|
15601
|
+
const dir = ensurePrivateDir(join35(stateDir(this.env), "logs"));
|
|
15602
|
+
this.file = join35(dir, `rewake-${day}.jsonl`);
|
|
14997
15603
|
}
|
|
14998
15604
|
const record = { t: (/* @__PURE__ */ new Date()).toISOString(), level, event, pid: process.pid, ...fields };
|
|
14999
15605
|
appendFileSync(this.file, `${JSON.stringify(record)}
|
|
@@ -15021,7 +15627,8 @@ var INSTALL_PLACES = /* @__PURE__ */ new Set([
|
|
|
15021
15627
|
"gemini-cli",
|
|
15022
15628
|
"antigravity",
|
|
15023
15629
|
"jetbrains",
|
|
15024
|
-
"devin-desktop"
|
|
15630
|
+
"devin-desktop",
|
|
15631
|
+
"cursor"
|
|
15025
15632
|
]);
|
|
15026
15633
|
var USAGE = `agent-rewake ${VERSION2}
|
|
15027
15634
|
|
|
@@ -15052,6 +15659,7 @@ Usage:
|
|
|
15052
15659
|
--ask: go back to asking each time.
|
|
15053
15660
|
--cancel: cancel every planned resume.
|
|
15054
15661
|
agent-rewake fire <id> Run by Rewake's timers at a resume's time (safe to run any time)
|
|
15662
|
+
agent-rewake sweep Run at login by Rewake's login item: sets planned resumes again
|
|
15055
15663
|
agent-rewake hook <agent> <event>
|
|
15056
15664
|
Run by an agent's hooks outside Zed, not by you
|
|
15057
15665
|
agent-rewake --version
|
|
@@ -15217,6 +15825,7 @@ async function main(argv, env = process.env) {
|
|
|
15217
15825
|
out: o.out,
|
|
15218
15826
|
ask
|
|
15219
15827
|
});
|
|
15828
|
+
if (id === "cursor") return runCursorInstall(common);
|
|
15220
15829
|
if (id === "jetbrains")
|
|
15221
15830
|
return runJetbrainsInstall({
|
|
15222
15831
|
uninstall,
|
|
@@ -15247,6 +15856,9 @@ ${placeName(id)}
|
|
|
15247
15856
|
out: (t) => print(t.replace(/^Dry run: nothing was (changed|written)\.\n/gm, ""))
|
|
15248
15857
|
});
|
|
15249
15858
|
}
|
|
15859
|
+
if (!uninstall && loginWouldAdd(env, order))
|
|
15860
|
+
print(`
|
|
15861
|
+
${loginItemPlanText(process.platform)}`);
|
|
15250
15862
|
if (!await ask(`
|
|
15251
15863
|
Apply these changes to ${order.length} places? [y/N] `)) {
|
|
15252
15864
|
print("Nothing was changed.\n");
|
|
@@ -15266,11 +15878,20 @@ Apply these changes to ${order.length} places? [y/N] `)) {
|
|
|
15266
15878
|
code2 = Math.max(code2, c);
|
|
15267
15879
|
}
|
|
15268
15880
|
print(summaryText(results));
|
|
15269
|
-
} else
|
|
15881
|
+
} else {
|
|
15882
|
+
if (!uninstall && loginWouldAdd(env, order)) print(loginItemPlanText(process.platform));
|
|
15270
15883
|
for (const id of order)
|
|
15271
15884
|
code2 = Math.max(code2, await runPlace(id, { yes, dryRun, out: print }));
|
|
15885
|
+
}
|
|
15272
15886
|
if (uninstall && !dryRun) cancelResumesOf(chosen, env);
|
|
15273
|
-
if (!dryRun)
|
|
15887
|
+
if (!dryRun) {
|
|
15888
|
+
const change = syncLogin(env);
|
|
15889
|
+
if (change !== "unchanged") print(loginItemText(change, process.platform));
|
|
15890
|
+
if (!uninstall && order.some((p) => TIMER_PLACES.has(p)) && usesWaiter(env))
|
|
15891
|
+
print(((n) => `${n.text} ${n.fix}
|
|
15892
|
+
`)(waiterNote(isWsl())));
|
|
15893
|
+
sweepQuietly(env);
|
|
15894
|
+
}
|
|
15274
15895
|
return code2;
|
|
15275
15896
|
}
|
|
15276
15897
|
if (first === "continue") {
|
|
@@ -15284,6 +15905,24 @@ Apply these changes to ${order.length} places? [y/N] `)) {
|
|
|
15284
15905
|
}
|
|
15285
15906
|
return runContinueCommand(env, mode);
|
|
15286
15907
|
}
|
|
15908
|
+
if (first === "sweep") {
|
|
15909
|
+
const at = argv.indexOf("--state-dir");
|
|
15910
|
+
const dir = at !== -1 ? argv[at + 1] : void 0;
|
|
15911
|
+
sweepQuietly(dir ? { ...env, AGENT_REWAKE_STATE_DIR: dir } : env);
|
|
15912
|
+
return 0;
|
|
15913
|
+
}
|
|
15914
|
+
if (first === "wait") {
|
|
15915
|
+
const at = argv.indexOf("--state-dir");
|
|
15916
|
+
const dir = at !== -1 ? argv[at + 1] : void 0;
|
|
15917
|
+
const { state, node, timers } = timerDeps(dir ? { ...env, AGENT_REWAKE_STATE_DIR: dir } : env);
|
|
15918
|
+
return runWaiter({
|
|
15919
|
+
timers,
|
|
15920
|
+
pid: process.pid,
|
|
15921
|
+
now: Date.now,
|
|
15922
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
15923
|
+
fire: (name) => timers.detached(node, [timers.cli, "fire", name, "--state-dir", state])
|
|
15924
|
+
});
|
|
15925
|
+
}
|
|
15287
15926
|
if (first === "fire") {
|
|
15288
15927
|
const at = argv.indexOf("--state-dir");
|
|
15289
15928
|
const dir = at !== -1 ? argv[at + 1] : void 0;
|
|
@@ -15303,7 +15942,7 @@ Apply these changes to ${order.length} places? [y/N] `)) {
|
|
|
15303
15942
|
const sep2 = argv.indexOf("--");
|
|
15304
15943
|
if (wrap) {
|
|
15305
15944
|
try {
|
|
15306
|
-
const npmLog =
|
|
15945
|
+
const npmLog = join36(ensurePrivateDir(join36(stateDir(env), "logs")), "npm-install.log");
|
|
15307
15946
|
agent = await wrappedAgentCommand(wrap.target, wrap.extra, env, stateDir(env), npmLog);
|
|
15308
15947
|
} catch (err) {
|
|
15309
15948
|
log.error("agent.resolve_failed", {
|
|
@@ -15367,11 +16006,11 @@ function doctor(env, details) {
|
|
|
15367
16006
|
env,
|
|
15368
16007
|
now: Date.now(),
|
|
15369
16008
|
platform: process.platform,
|
|
15370
|
-
home:
|
|
15371
|
-
zedApps: () => findZedApps(process.platform,
|
|
16009
|
+
home: homedir19(),
|
|
16010
|
+
zedApps: () => findZedApps(process.platform, homedir19(), env),
|
|
15372
16011
|
agents: () => {
|
|
15373
|
-
const ids = new Set(installedPreviews(env,
|
|
15374
|
-
return detectAgents({ env, home:
|
|
16012
|
+
const ids = new Set(installedPreviews(env, homedir19(), stateDir(env)));
|
|
16013
|
+
return detectAgents({ env, home: homedir19(), platform: process.platform }).filter(
|
|
15375
16014
|
(f) => !ids.has(f.id)
|
|
15376
16015
|
);
|
|
15377
16016
|
},
|
|
@@ -15393,16 +16032,19 @@ function doctor(env, details) {
|
|
|
15393
16032
|
...diagnoseOutside({
|
|
15394
16033
|
stateDir: state,
|
|
15395
16034
|
env,
|
|
15396
|
-
home:
|
|
16035
|
+
home: homedir19(),
|
|
15397
16036
|
now: ctx.now,
|
|
15398
16037
|
platform: process.platform,
|
|
15399
|
-
previews: installedPreviews(env,
|
|
16038
|
+
previews: installedPreviews(env, homedir19(), state).map((id) => ({
|
|
15400
16039
|
id,
|
|
15401
16040
|
name: PREVIEW_NAMES[id] ?? id
|
|
15402
16041
|
})),
|
|
15403
16042
|
hosts: hostAdapters(env, node, state),
|
|
15404
|
-
hasTimer:
|
|
15405
|
-
|
|
16043
|
+
...((kind) => ({ hasTimer: kind !== void 0, ...kind && { timerKind: kind } }))(
|
|
16044
|
+
timerKind(timers)
|
|
16045
|
+
),
|
|
16046
|
+
wsl: process.platform === "linux" && isWsl(),
|
|
16047
|
+
agents: terminalAgents({ env, home: homedir19(), platform: process.platform }),
|
|
15406
16048
|
when
|
|
15407
16049
|
})
|
|
15408
16050
|
);
|
|
@@ -15426,16 +16068,16 @@ function doctor(env, details) {
|
|
|
15426
16068
|
return findings.some((f) => f.level === "problem") ? 1 : 0;
|
|
15427
16069
|
}
|
|
15428
16070
|
function toolsRefused(state) {
|
|
15429
|
-
const dir =
|
|
16071
|
+
const dir = join36(state, "logs");
|
|
15430
16072
|
const agents = /* @__PURE__ */ new Set();
|
|
15431
16073
|
let files = [];
|
|
15432
16074
|
try {
|
|
15433
|
-
files =
|
|
16075
|
+
files = readdirSync14(dir).filter((f) => /^rewake-\d{4}-\d{2}-\d{2}\.jsonl$/.test(f));
|
|
15434
16076
|
} catch {
|
|
15435
16077
|
return [];
|
|
15436
16078
|
}
|
|
15437
16079
|
for (const f of files) {
|
|
15438
|
-
for (const line of
|
|
16080
|
+
for (const line of readFileSync32(join36(dir, f), "utf8").split("\n")) {
|
|
15439
16081
|
if (!line.includes("agent_tools.refused")) continue;
|
|
15440
16082
|
try {
|
|
15441
16083
|
const r = JSON.parse(line);
|
|
@@ -15456,7 +16098,7 @@ function setupZedText() {
|
|
|
15456
16098
|
const task = [taskEntry(launch)];
|
|
15457
16099
|
const keymap = [{ bindings: { [keyChord()]: ["task::Spawn", { task_name: TASK_LABEL }] } }];
|
|
15458
16100
|
const dir = zedConfigDir();
|
|
15459
|
-
const file = (name) =>
|
|
16101
|
+
const file = (name) => join36(dir, name);
|
|
15460
16102
|
return [
|
|
15461
16103
|
"Easiest: run `agent-rewake install`, which adds these for you after asking.",
|
|
15462
16104
|
"To add them by hand instead:",
|
|
@@ -15500,14 +16142,15 @@ function testTiming(env) {
|
|
|
15500
16142
|
return out;
|
|
15501
16143
|
}
|
|
15502
16144
|
function placesHere(env) {
|
|
15503
|
-
const host = { env, home:
|
|
16145
|
+
const host = { env, home: homedir19(), platform: process.platform };
|
|
15504
16146
|
const found = detectAgents(host);
|
|
15505
|
-
const zedApps = findZedApps(process.platform,
|
|
15506
|
-
const installed = new Set(installedPreviews(env,
|
|
16147
|
+
const zedApps = findZedApps(process.platform, homedir19(), env);
|
|
16148
|
+
const installed = new Set(installedPreviews(env, homedir19(), stateDir(env)));
|
|
15507
16149
|
const withIde = [
|
|
15508
16150
|
...found,
|
|
15509
|
-
...jetbrainsFound(env,
|
|
15510
|
-
...devinFound(
|
|
16151
|
+
...jetbrainsFound(env, homedir19(), process.platform) ? [{ id: "jetbrains", name: "JetBrains IDEs", surfaces: ["app"] }] : [],
|
|
16152
|
+
...devinFound(homedir19()) ? [{ id: "devin-desktop", name: "Devin Desktop", surfaces: ["app"] }] : [],
|
|
16153
|
+
...cursorFound(env, homedir19(), process.platform) ? [{ id: "cursor", name: "Cursor", surfaces: ["app"] }] : []
|
|
15511
16154
|
];
|
|
15512
16155
|
const places = placesFrom(
|
|
15513
16156
|
withIde,
|
|
@@ -15602,13 +16245,13 @@ async function pickPlaces(env) {
|
|
|
15602
16245
|
}
|
|
15603
16246
|
function rewakeCli(state) {
|
|
15604
16247
|
const stable = launcherPath(state);
|
|
15605
|
-
if (
|
|
16248
|
+
if (existsSync23(stable)) return stable;
|
|
15606
16249
|
const bundle = process.argv[1] ?? "";
|
|
15607
16250
|
return bundle && ensureLauncher(state, bundle) || bundle || stable;
|
|
15608
16251
|
}
|
|
15609
16252
|
function cancelResumesOf(places, env) {
|
|
15610
16253
|
const { state, timers } = timerDeps(env);
|
|
15611
|
-
const still = new Set(installedPreviews(env,
|
|
16254
|
+
const still = new Set(installedPreviews(env, homedir19(), state));
|
|
15612
16255
|
const gone = places.filter((p) => p !== "zed" && !still.has(p));
|
|
15613
16256
|
if (gone.length === 0) return;
|
|
15614
16257
|
const store = new ScheduleStore(state);
|
|
@@ -15629,6 +16272,56 @@ function cancelResumesOf(places, env) {
|
|
|
15629
16272
|
);
|
|
15630
16273
|
}
|
|
15631
16274
|
}
|
|
16275
|
+
var TIMER_PLACES = /* @__PURE__ */ new Set([
|
|
16276
|
+
"codex",
|
|
16277
|
+
"copilot-cli",
|
|
16278
|
+
"gemini-cli",
|
|
16279
|
+
"grok",
|
|
16280
|
+
"antigravity",
|
|
16281
|
+
"cursor"
|
|
16282
|
+
]);
|
|
16283
|
+
function usesWaiter(env) {
|
|
16284
|
+
try {
|
|
16285
|
+
return timerKind(timerDeps(env).timers) === "waiter";
|
|
16286
|
+
} catch {
|
|
16287
|
+
return false;
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16290
|
+
function loginWouldAdd(env, places) {
|
|
16291
|
+
if (!places.some((p) => TIMER_PLACES.has(p))) return false;
|
|
16292
|
+
const { state, node } = timerDeps(env);
|
|
16293
|
+
const item = loginItem({
|
|
16294
|
+
platform: process.platform,
|
|
16295
|
+
home: env.HOME || env.USERPROFILE || homedir19(),
|
|
16296
|
+
node,
|
|
16297
|
+
cli: rewakeCli(state),
|
|
16298
|
+
stateDir: state,
|
|
16299
|
+
run: () => ({ status: 0 }),
|
|
16300
|
+
exists: existsSync23
|
|
16301
|
+
});
|
|
16302
|
+
return item !== void 0 && !existsSync23(item.path);
|
|
16303
|
+
}
|
|
16304
|
+
function syncLogin(env) {
|
|
16305
|
+
try {
|
|
16306
|
+
const { state, node, timers } = timerDeps(env);
|
|
16307
|
+
const home = env.HOME || env.USERPROFILE || homedir19();
|
|
16308
|
+
const wanted = installedPreviews(env, home, state).some((p) => TIMER_PLACES.has(p));
|
|
16309
|
+
return syncLoginItem(
|
|
16310
|
+
{
|
|
16311
|
+
platform: process.platform,
|
|
16312
|
+
home,
|
|
16313
|
+
node,
|
|
16314
|
+
cli: rewakeCli(state),
|
|
16315
|
+
stateDir: state,
|
|
16316
|
+
run: (cmd, args) => timers.run(cmd, args),
|
|
16317
|
+
exists: existsSync23
|
|
16318
|
+
},
|
|
16319
|
+
wanted
|
|
16320
|
+
);
|
|
16321
|
+
} catch {
|
|
16322
|
+
return "unchanged";
|
|
16323
|
+
}
|
|
16324
|
+
}
|
|
15632
16325
|
function sweepQuietly(env) {
|
|
15633
16326
|
try {
|
|
15634
16327
|
const { sweepDeps } = timerDeps(env);
|
|
@@ -15722,7 +16415,7 @@ async function runHookCommand(host, event, env) {
|
|
|
15722
16415
|
agent: () => agentProcess()
|
|
15723
16416
|
}),
|
|
15724
16417
|
program: (h, e) => {
|
|
15725
|
-
const host2 = { env: e, home:
|
|
16418
|
+
const host2 = { env: e, home: homedir19(), platform: process.platform };
|
|
15726
16419
|
if (h === "copilot-cli") return copilotPrograms(host2)[0]?.path;
|
|
15727
16420
|
if (h === "grok") return grokPrograms(host2)[0]?.path;
|
|
15728
16421
|
if (h === "gemini-cli") return geminiPrograms(host2)[0]?.path;
|
|
@@ -15730,7 +16423,7 @@ async function runHookCommand(host, event, env) {
|
|
|
15730
16423
|
return void 0;
|
|
15731
16424
|
},
|
|
15732
16425
|
codexPath: () => {
|
|
15733
|
-
const programs2 = codexPrograms({ env, home:
|
|
16426
|
+
const programs2 = codexPrograms({ env, home: homedir19(), platform: process.platform });
|
|
15734
16427
|
const cli = programs2.filter((p) => p.surface === "terminal");
|
|
15735
16428
|
const pool = cli.length > 0 ? cli : programs2;
|
|
15736
16429
|
return pool.sort((a, b) => compareVersions(b.version ?? "0", a.version ?? "0"))[0]?.path;
|
|
@@ -15783,7 +16476,7 @@ async function runContinueCommand(env, mode) {
|
|
|
15783
16476
|
});
|
|
15784
16477
|
}
|
|
15785
16478
|
function previewNames(env) {
|
|
15786
|
-
return installedPreviews(env,
|
|
16479
|
+
return installedPreviews(env, homedir19(), stateDir(env)).map((id) => PREVIEW_NAMES[id] ?? id);
|
|
15787
16480
|
}
|
|
15788
16481
|
|
|
15789
16482
|
// src/main.ts
|