@slock-ai/daemon 0.43.0 → 0.44.2
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/chat-bridge.js +1 -1
- package/dist/{chunk-37O7EHYE.js → chunk-NM2MFLQ7.js} +397 -9
- package/dist/cli/index.js +201 -35
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -1323,7 +1323,7 @@ Thread messages and system messages (e.g. task-claim / task-status announcements
|
|
|
1323
1323
|
const msgShort = r.messageId ? r.messageId.slice(0, 8) : "";
|
|
1324
1324
|
return `${label} (msg:${msgShort}): claimed`;
|
|
1325
1325
|
}
|
|
1326
|
-
return `${label}: FAILED \u2014 ${r.reason || "already claimed"}
|
|
1326
|
+
return `${label}: FAILED \u2014 ${r.reason || "already claimed"}. Do not reply.`;
|
|
1327
1327
|
});
|
|
1328
1328
|
const succeeded = data.results.filter((r) => r.success).length;
|
|
1329
1329
|
const failed = data.results.length - succeeded;
|
|
@@ -1328,8 +1328,25 @@ function probeClaude(deps = {}) {
|
|
|
1328
1328
|
}
|
|
1329
1329
|
var ClaudeDriver = class {
|
|
1330
1330
|
id = "claude";
|
|
1331
|
+
lifecycle = {
|
|
1332
|
+
kind: "persistent",
|
|
1333
|
+
stdin: "gated",
|
|
1334
|
+
inFlightWake: "queue"
|
|
1335
|
+
};
|
|
1336
|
+
communication = {
|
|
1337
|
+
chat: "slock_cli",
|
|
1338
|
+
runtimeControl: "mcp_runtime_actions"
|
|
1339
|
+
};
|
|
1340
|
+
session = {
|
|
1341
|
+
recovery: "resume_or_fresh"
|
|
1342
|
+
};
|
|
1343
|
+
model = {
|
|
1344
|
+
detectedModelsVerifiedAs: "launchable",
|
|
1345
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
1346
|
+
};
|
|
1331
1347
|
supportsStdinNotification = true;
|
|
1332
1348
|
mcpToolPrefix = "mcp__chat__";
|
|
1349
|
+
usesSlockCliForCommunication = true;
|
|
1333
1350
|
// Claude Code supports same-turn steering, but raw stdin injection at an
|
|
1334
1351
|
// arbitrary busy instant can collide with active signed thinking blocks. The
|
|
1335
1352
|
// daemon therefore gates busy delivery on Claude stream-json boundaries.
|
|
@@ -1631,8 +1648,25 @@ function joinReasoningText(item) {
|
|
|
1631
1648
|
}
|
|
1632
1649
|
var CodexDriver = class {
|
|
1633
1650
|
id = "codex";
|
|
1651
|
+
lifecycle = {
|
|
1652
|
+
kind: "persistent",
|
|
1653
|
+
stdin: "direct",
|
|
1654
|
+
inFlightWake: "steer"
|
|
1655
|
+
};
|
|
1656
|
+
communication = {
|
|
1657
|
+
chat: "slock_cli",
|
|
1658
|
+
runtimeControl: "mcp_runtime_actions"
|
|
1659
|
+
};
|
|
1660
|
+
session = {
|
|
1661
|
+
recovery: "resume_or_fresh"
|
|
1662
|
+
};
|
|
1663
|
+
model = {
|
|
1664
|
+
detectedModelsVerifiedAs: "launchable",
|
|
1665
|
+
toLaunchSpec: (modelId) => ({ params: { model: modelId } })
|
|
1666
|
+
};
|
|
1634
1667
|
supportsStdinNotification = true;
|
|
1635
1668
|
mcpToolPrefix = "mcp_chat_";
|
|
1669
|
+
usesSlockCliForCommunication = true;
|
|
1636
1670
|
busyDeliveryMode = "direct";
|
|
1637
1671
|
supportsNativeStandingPrompt = true;
|
|
1638
1672
|
probe() {
|
|
@@ -2027,7 +2061,7 @@ function detectCodexModels(home = os.homedir()) {
|
|
|
2027
2061
|
if (entry?.visibility && entry.visibility !== "public") continue;
|
|
2028
2062
|
if (entry?.supported_in_api === false) continue;
|
|
2029
2063
|
const label = typeof entry?.display_name === "string" && entry.display_name.length > 0 ? entry.display_name : slug;
|
|
2030
|
-
models.push({ id: slug, label });
|
|
2064
|
+
models.push({ id: slug, label, verified: "launchable" });
|
|
2031
2065
|
}
|
|
2032
2066
|
} catch {
|
|
2033
2067
|
return null;
|
|
@@ -2049,6 +2083,23 @@ import path5 from "path";
|
|
|
2049
2083
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2050
2084
|
var CopilotDriver = class {
|
|
2051
2085
|
id = "copilot";
|
|
2086
|
+
lifecycle = {
|
|
2087
|
+
kind: "per_turn",
|
|
2088
|
+
start: "immediate",
|
|
2089
|
+
exit: "natural",
|
|
2090
|
+
inFlightWake: "spawn_new"
|
|
2091
|
+
};
|
|
2092
|
+
communication = {
|
|
2093
|
+
chat: "mcp_chat_bridge",
|
|
2094
|
+
runtimeControl: "mcp_runtime_actions"
|
|
2095
|
+
};
|
|
2096
|
+
session = {
|
|
2097
|
+
recovery: "resume_or_fresh"
|
|
2098
|
+
};
|
|
2099
|
+
model = {
|
|
2100
|
+
detectedModelsVerifiedAs: "launchable",
|
|
2101
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
2102
|
+
};
|
|
2052
2103
|
supportsStdinNotification = false;
|
|
2053
2104
|
mcpToolPrefix = "";
|
|
2054
2105
|
busyDeliveryMode = "none";
|
|
@@ -2182,11 +2233,28 @@ var CopilotDriver = class {
|
|
|
2182
2233
|
};
|
|
2183
2234
|
|
|
2184
2235
|
// src/drivers/cursor.ts
|
|
2185
|
-
import { spawn as spawn4 } from "child_process";
|
|
2236
|
+
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2186
2237
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
2187
2238
|
import path6 from "path";
|
|
2188
2239
|
var CursorDriver = class {
|
|
2189
2240
|
id = "cursor";
|
|
2241
|
+
lifecycle = {
|
|
2242
|
+
kind: "per_turn",
|
|
2243
|
+
start: "immediate",
|
|
2244
|
+
exit: "natural",
|
|
2245
|
+
inFlightWake: "spawn_new"
|
|
2246
|
+
};
|
|
2247
|
+
communication = {
|
|
2248
|
+
chat: "mcp_chat_bridge",
|
|
2249
|
+
runtimeControl: "mcp_runtime_actions"
|
|
2250
|
+
};
|
|
2251
|
+
session = {
|
|
2252
|
+
recovery: "resume_or_fresh"
|
|
2253
|
+
};
|
|
2254
|
+
model = {
|
|
2255
|
+
detectedModelsVerifiedAs: "launchable",
|
|
2256
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
2257
|
+
};
|
|
2190
2258
|
supportsStdinNotification = false;
|
|
2191
2259
|
mcpToolPrefix = "mcp__chat__";
|
|
2192
2260
|
busyDeliveryMode = "none";
|
|
@@ -2299,7 +2367,48 @@ var CursorDriver = class {
|
|
|
2299
2367
|
messageNotificationStyle: "poll"
|
|
2300
2368
|
});
|
|
2301
2369
|
}
|
|
2370
|
+
async detectModels() {
|
|
2371
|
+
return detectCursorModels();
|
|
2372
|
+
}
|
|
2302
2373
|
};
|
|
2374
|
+
function parseCursorModelsOutput(output) {
|
|
2375
|
+
const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
2376
|
+
const models = [];
|
|
2377
|
+
let defaultModel;
|
|
2378
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
|
|
2379
|
+
const line = rawLine.trim();
|
|
2380
|
+
if (!line || /^available models$/i.test(line) || /^tip:/i.test(line)) continue;
|
|
2381
|
+
if (/^no models available/i.test(line) || /^failed to load models:/i.test(line)) continue;
|
|
2382
|
+
let modelLine = line;
|
|
2383
|
+
const markerMatch = modelLine.match(/\s+\(([^)]+)\)$/);
|
|
2384
|
+
const markers = markerMatch?.[1]?.split(",").map((part) => part.trim().toLowerCase()) ?? [];
|
|
2385
|
+
if (markers.length > 0 && markers.every((part) => part === "current" || part === "default")) {
|
|
2386
|
+
const markerStart = markerMatch?.index ?? modelLine.length;
|
|
2387
|
+
modelLine = modelLine.slice(0, markerStart).trim();
|
|
2388
|
+
}
|
|
2389
|
+
const match = modelLine.match(/^(\S+)(?:\s+-\s+(.+))?$/);
|
|
2390
|
+
if (!match) continue;
|
|
2391
|
+
const id = match[1]?.trim();
|
|
2392
|
+
if (!id || id.startsWith("-")) continue;
|
|
2393
|
+
const label = match[2]?.trim() || id;
|
|
2394
|
+
models.push({ id, label, verified: "launchable" });
|
|
2395
|
+
if (markers.includes("default")) defaultModel = id;
|
|
2396
|
+
}
|
|
2397
|
+
if (models.length === 0) return null;
|
|
2398
|
+
return { models, default: defaultModel };
|
|
2399
|
+
}
|
|
2400
|
+
function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
2401
|
+
const result = runCommand();
|
|
2402
|
+
if (result.error || result.status !== 0) return null;
|
|
2403
|
+
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
2404
|
+
}
|
|
2405
|
+
function runCursorModelsCommand() {
|
|
2406
|
+
return spawnSync("cursor-agent", ["models"], {
|
|
2407
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
2408
|
+
encoding: "utf8",
|
|
2409
|
+
timeout: 5e3
|
|
2410
|
+
});
|
|
2411
|
+
}
|
|
2303
2412
|
|
|
2304
2413
|
// src/drivers/gemini.ts
|
|
2305
2414
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -2318,6 +2427,23 @@ function buildGeminiSpawnEnv(ctx) {
|
|
|
2318
2427
|
}
|
|
2319
2428
|
var GeminiDriver = class {
|
|
2320
2429
|
id = "gemini";
|
|
2430
|
+
lifecycle = {
|
|
2431
|
+
kind: "per_turn",
|
|
2432
|
+
start: "immediate",
|
|
2433
|
+
exit: "natural",
|
|
2434
|
+
inFlightWake: "spawn_new"
|
|
2435
|
+
};
|
|
2436
|
+
communication = {
|
|
2437
|
+
chat: "mcp_chat_bridge",
|
|
2438
|
+
runtimeControl: "mcp_runtime_actions"
|
|
2439
|
+
};
|
|
2440
|
+
session = {
|
|
2441
|
+
recovery: "resume_or_fresh"
|
|
2442
|
+
};
|
|
2443
|
+
model = {
|
|
2444
|
+
detectedModelsVerifiedAs: "launchable",
|
|
2445
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
2446
|
+
};
|
|
2321
2447
|
supportsStdinNotification = false;
|
|
2322
2448
|
mcpToolPrefix = "";
|
|
2323
2449
|
busyDeliveryMode = "none";
|
|
@@ -2447,6 +2573,22 @@ function parseToolArguments(raw) {
|
|
|
2447
2573
|
}
|
|
2448
2574
|
var KimiDriver = class {
|
|
2449
2575
|
id = "kimi";
|
|
2576
|
+
lifecycle = {
|
|
2577
|
+
kind: "persistent",
|
|
2578
|
+
stdin: "direct",
|
|
2579
|
+
inFlightWake: "steer"
|
|
2580
|
+
};
|
|
2581
|
+
communication = {
|
|
2582
|
+
chat: "mcp_chat_bridge",
|
|
2583
|
+
runtimeControl: "mcp_runtime_actions"
|
|
2584
|
+
};
|
|
2585
|
+
session = {
|
|
2586
|
+
recovery: "resume_or_fresh"
|
|
2587
|
+
};
|
|
2588
|
+
model = {
|
|
2589
|
+
detectedModelsVerifiedAs: "launchable",
|
|
2590
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
2591
|
+
};
|
|
2450
2592
|
supportsStdinNotification = true;
|
|
2451
2593
|
mcpToolPrefix = "";
|
|
2452
2594
|
busyDeliveryMode = "direct";
|
|
@@ -2647,7 +2789,7 @@ function detectKimiModels(home = os2.homedir()) {
|
|
|
2647
2789
|
let key = match[1].trim();
|
|
2648
2790
|
if (key.startsWith('"') && key.endsWith('"')) key = key.slice(1, -1);
|
|
2649
2791
|
if (!key) continue;
|
|
2650
|
-
models.push({ id: key, label: key });
|
|
2792
|
+
models.push({ id: key, label: key, verified: "launchable" });
|
|
2651
2793
|
}
|
|
2652
2794
|
void sectionRe;
|
|
2653
2795
|
if (models.length === 0) return null;
|
|
@@ -2802,7 +2944,10 @@ function buildOpenCodeLaunchOptions(ctx, home = os3.homedir()) {
|
|
|
2802
2944
|
return { args, env, config };
|
|
2803
2945
|
}
|
|
2804
2946
|
function detectOpenCodeModels(home = os3.homedir()) {
|
|
2805
|
-
const models =
|
|
2947
|
+
const models = (RUNTIME_MODELS.opencode || []).map((model) => ({
|
|
2948
|
+
...model,
|
|
2949
|
+
verified: "suggestion_only"
|
|
2950
|
+
}));
|
|
2806
2951
|
const providers = recordField(readLocalOpenCodeConfig(home).provider);
|
|
2807
2952
|
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
2808
2953
|
const providerModels = recordField(recordField(providerConfig).models);
|
|
@@ -2811,7 +2956,7 @@ function detectOpenCodeModels(home = os3.homedir()) {
|
|
|
2811
2956
|
if (models.some((model2) => model2.id === fullId)) continue;
|
|
2812
2957
|
const model = recordField(modelConfig);
|
|
2813
2958
|
const name = typeof model.name === "string" && model.name.length > 0 ? model.name : fullId;
|
|
2814
|
-
models.push({ id: fullId, label: name });
|
|
2959
|
+
models.push({ id: fullId, label: name, verified: "launchable" });
|
|
2815
2960
|
}
|
|
2816
2961
|
}
|
|
2817
2962
|
return models.length > 0 ? { models } : null;
|
|
@@ -2834,6 +2979,38 @@ function buildOpenCodeSystemPrompt(config) {
|
|
|
2834
2979
|
}
|
|
2835
2980
|
var OpenCodeDriver = class {
|
|
2836
2981
|
id = "opencode";
|
|
2982
|
+
lifecycle = {
|
|
2983
|
+
kind: "per_turn",
|
|
2984
|
+
start: "defer_until_concrete_message",
|
|
2985
|
+
exit: "terminate_on_turn_end",
|
|
2986
|
+
inFlightWake: "coalesce_into_pending"
|
|
2987
|
+
};
|
|
2988
|
+
communication = {
|
|
2989
|
+
chat: "slock_cli",
|
|
2990
|
+
runtimeControl: "mcp_runtime_actions"
|
|
2991
|
+
};
|
|
2992
|
+
session = {
|
|
2993
|
+
recovery: "resume_or_fresh"
|
|
2994
|
+
};
|
|
2995
|
+
model = {
|
|
2996
|
+
detectedModelsVerifiedAs: "launchable",
|
|
2997
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
2998
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
2999
|
+
const launchCtx = {
|
|
3000
|
+
...ctx,
|
|
3001
|
+
config: {
|
|
3002
|
+
...ctx.config,
|
|
3003
|
+
model: modelId
|
|
3004
|
+
}
|
|
3005
|
+
};
|
|
3006
|
+
const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home);
|
|
3007
|
+
return {
|
|
3008
|
+
args: launch.args,
|
|
3009
|
+
env: launch.env,
|
|
3010
|
+
config: launch.config
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
2837
3014
|
supportsStdinNotification = false;
|
|
2838
3015
|
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX;
|
|
2839
3016
|
busyDeliveryMode = "none";
|
|
@@ -3060,6 +3237,22 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
3060
3237
|
|
|
3061
3238
|
// src/agentProcessManager.ts
|
|
3062
3239
|
var DATA_DIR = path11.join(os4.homedir(), ".slock", "agents");
|
|
3240
|
+
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 1;
|
|
3241
|
+
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
3242
|
+
function readPositiveIntegerEnv(name, fallback) {
|
|
3243
|
+
const raw = process.env[name];
|
|
3244
|
+
if (!raw) return fallback;
|
|
3245
|
+
const parsed = Number(raw);
|
|
3246
|
+
if (!Number.isFinite(parsed) || parsed < 1) return fallback;
|
|
3247
|
+
return Math.floor(parsed);
|
|
3248
|
+
}
|
|
3249
|
+
function readNonNegativeIntegerEnv(name, fallback) {
|
|
3250
|
+
const raw = process.env[name];
|
|
3251
|
+
if (!raw) return fallback;
|
|
3252
|
+
const parsed = Number(raw);
|
|
3253
|
+
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
3254
|
+
return Math.floor(parsed);
|
|
3255
|
+
}
|
|
3063
3256
|
function toLocalTime(iso) {
|
|
3064
3257
|
const d = new Date(iso);
|
|
3065
3258
|
if (isNaN(d.getTime())) return iso;
|
|
@@ -3783,6 +3976,15 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3783
3976
|
agents = /* @__PURE__ */ new Map();
|
|
3784
3977
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
3785
3978
|
// Prevent concurrent starts of same agent
|
|
3979
|
+
queuedAgentStarts = /* @__PURE__ */ new Map();
|
|
3980
|
+
agentStartQueue = [];
|
|
3981
|
+
activeAgentStartPermits = /* @__PURE__ */ new Set();
|
|
3982
|
+
activeAgentStartCount = 0;
|
|
3983
|
+
agentStartPumpTimer = null;
|
|
3984
|
+
lastAgentStartAt = 0;
|
|
3985
|
+
lastAgentStartAgentId = null;
|
|
3986
|
+
maxConcurrentAgentStarts;
|
|
3987
|
+
agentStartIntervalMs;
|
|
3786
3988
|
startingInboxes = /* @__PURE__ */ new Map();
|
|
3787
3989
|
/** Cached configs for agents whose process exited normally — enables auto-restart on next message */
|
|
3788
3990
|
idleAgentConfigs = /* @__PURE__ */ new Map();
|
|
@@ -3807,8 +4009,137 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3807
4009
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
3808
4010
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
3809
4011
|
this.tracer = opts.tracer ?? noopTracer;
|
|
4012
|
+
this.maxConcurrentAgentStarts = Math.max(
|
|
4013
|
+
1,
|
|
4014
|
+
Math.floor(
|
|
4015
|
+
opts.runtimeStartScheduler?.maxConcurrentStarts ?? readPositiveIntegerEnv("SLOCK_DAEMON_MAX_CONCURRENT_AGENT_STARTS", DEFAULT_MAX_CONCURRENT_AGENT_STARTS)
|
|
4016
|
+
)
|
|
4017
|
+
);
|
|
4018
|
+
this.agentStartIntervalMs = Math.max(
|
|
4019
|
+
0,
|
|
4020
|
+
Math.floor(
|
|
4021
|
+
opts.runtimeStartScheduler?.minStartIntervalMs ?? readNonNegativeIntegerEnv("SLOCK_DAEMON_AGENT_START_INTERVAL_MS", DEFAULT_AGENT_START_INTERVAL_MS)
|
|
4022
|
+
)
|
|
4023
|
+
);
|
|
3810
4024
|
}
|
|
3811
4025
|
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
4026
|
+
if (this.agents.has(agentId)) {
|
|
4027
|
+
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
if (this.agentsStarting.has(agentId)) {
|
|
4031
|
+
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
if (this.queuedAgentStarts.has(agentId)) {
|
|
4035
|
+
logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
return new Promise((resolve, reject) => {
|
|
4039
|
+
const item = {
|
|
4040
|
+
agentId,
|
|
4041
|
+
config,
|
|
4042
|
+
wakeMessage,
|
|
4043
|
+
unreadSummary,
|
|
4044
|
+
resumePrompt,
|
|
4045
|
+
launchId,
|
|
4046
|
+
resolve,
|
|
4047
|
+
reject
|
|
4048
|
+
};
|
|
4049
|
+
this.agentStartQueue.push(item);
|
|
4050
|
+
this.queuedAgentStarts.set(agentId, item);
|
|
4051
|
+
logger.info(
|
|
4052
|
+
`[Agent ${agentId}] Start queued (queue=${this.agentStartQueue.length}, active=${this.activeAgentStartCount}, max=${this.maxConcurrentAgentStarts}, interval=${this.agentStartIntervalMs}ms)`
|
|
4053
|
+
);
|
|
4054
|
+
this.pumpAgentStartQueue();
|
|
4055
|
+
});
|
|
4056
|
+
}
|
|
4057
|
+
pumpAgentStartQueue() {
|
|
4058
|
+
if (this.agentStartPumpTimer) return;
|
|
4059
|
+
if (this.agentStartQueue.length === 0) return;
|
|
4060
|
+
if (this.activeAgentStartCount >= this.maxConcurrentAgentStarts) return;
|
|
4061
|
+
const next = this.agentStartQueue[0];
|
|
4062
|
+
const shouldRateLimit = next ? next.agentId !== this.lastAgentStartAgentId : true;
|
|
4063
|
+
const elapsed = Date.now() - this.lastAgentStartAt;
|
|
4064
|
+
const waitMs = shouldRateLimit ? Math.max(0, this.agentStartIntervalMs - elapsed) : 0;
|
|
4065
|
+
if (waitMs > 0) {
|
|
4066
|
+
this.agentStartPumpTimer = setTimeout(() => {
|
|
4067
|
+
this.agentStartPumpTimer = null;
|
|
4068
|
+
this.pumpAgentStartQueue();
|
|
4069
|
+
}, waitMs);
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
const item = this.agentStartQueue.shift();
|
|
4073
|
+
if (!item) return;
|
|
4074
|
+
if (this.queuedAgentStarts.get(item.agentId) !== item) {
|
|
4075
|
+
this.pumpAgentStartQueue();
|
|
4076
|
+
return;
|
|
4077
|
+
}
|
|
4078
|
+
this.queuedAgentStarts.delete(item.agentId);
|
|
4079
|
+
if (this.agents.has(item.agentId) || this.agentsStarting.has(item.agentId)) {
|
|
4080
|
+
logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
|
|
4081
|
+
item.resolve();
|
|
4082
|
+
this.pumpAgentStartQueue();
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
this.activeAgentStartCount++;
|
|
4086
|
+
this.activeAgentStartPermits.add(item.agentId);
|
|
4087
|
+
this.lastAgentStartAt = Date.now();
|
|
4088
|
+
this.lastAgentStartAgentId = item.agentId;
|
|
4089
|
+
logger.info(
|
|
4090
|
+
`[Agent ${item.agentId}] Dequeued start (remaining=${this.agentStartQueue.length}, active=${this.activeAgentStartCount})`
|
|
4091
|
+
);
|
|
4092
|
+
this.startAgentNow(
|
|
4093
|
+
item.agentId,
|
|
4094
|
+
item.config,
|
|
4095
|
+
item.wakeMessage,
|
|
4096
|
+
item.unreadSummary,
|
|
4097
|
+
item.resumePrompt,
|
|
4098
|
+
item.launchId
|
|
4099
|
+
).then(item.resolve, (err) => {
|
|
4100
|
+
this.releaseAgentStartPermit(item.agentId, "start failed");
|
|
4101
|
+
item.reject(err);
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
releaseAgentStartPermit(agentId, reason) {
|
|
4105
|
+
if (!this.activeAgentStartPermits.delete(agentId)) return false;
|
|
4106
|
+
this.activeAgentStartCount = Math.max(0, this.activeAgentStartCount - 1);
|
|
4107
|
+
logger.info(
|
|
4108
|
+
`[Agent ${agentId}] Start permit released (${reason}) (active=${this.activeAgentStartCount}, queue=${this.agentStartQueue.length})`
|
|
4109
|
+
);
|
|
4110
|
+
this.pumpAgentStartQueue();
|
|
4111
|
+
return true;
|
|
4112
|
+
}
|
|
4113
|
+
cancelQueuedAgentStart(agentId, reason) {
|
|
4114
|
+
const item = this.queuedAgentStarts.get(agentId);
|
|
4115
|
+
if (!item) return false;
|
|
4116
|
+
this.queuedAgentStarts.delete(agentId);
|
|
4117
|
+
this.agentStartQueue = this.agentStartQueue.filter((candidate) => candidate !== item);
|
|
4118
|
+
this.startingInboxes.delete(agentId);
|
|
4119
|
+
if (this.agentStartQueue.length === 0 && this.agentStartPumpTimer) {
|
|
4120
|
+
clearTimeout(this.agentStartPumpTimer);
|
|
4121
|
+
this.agentStartPumpTimer = null;
|
|
4122
|
+
}
|
|
4123
|
+
logger.info(`[Agent ${agentId}] Queued start cancelled (${reason})`);
|
|
4124
|
+
item.resolve();
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
cancelAllQueuedAgentStarts(reason) {
|
|
4128
|
+
for (const item of this.agentStartQueue) {
|
|
4129
|
+
if (this.queuedAgentStarts.get(item.agentId) === item) {
|
|
4130
|
+
logger.info(`[Agent ${item.agentId}] Queued start cancelled (${reason})`);
|
|
4131
|
+
item.resolve();
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
this.agentStartQueue = [];
|
|
4135
|
+
this.queuedAgentStarts.clear();
|
|
4136
|
+
this.startingInboxes.clear();
|
|
4137
|
+
if (this.agentStartPumpTimer) {
|
|
4138
|
+
clearTimeout(this.agentStartPumpTimer);
|
|
4139
|
+
this.agentStartPumpTimer = null;
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
3812
4143
|
if (this.agents.has(agentId)) {
|
|
3813
4144
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
3814
4145
|
return;
|
|
@@ -3910,6 +4241,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
3910
4241
|
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
3911
4242
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
3912
4243
|
logger.info(`[Agent ${agentId}] Deferred ${driver.id} spawn until first concrete message`);
|
|
4244
|
+
this.releaseAgentStartPermit(agentId, "spawn deferred");
|
|
3913
4245
|
for (const message of pendingMessages) {
|
|
3914
4246
|
this.deliverMessage(agentId, message);
|
|
3915
4247
|
}
|
|
@@ -3947,6 +4279,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
3947
4279
|
runtimeTraceSpan: null,
|
|
3948
4280
|
lastActivity: "",
|
|
3949
4281
|
lastActivityDetail: "",
|
|
4282
|
+
activityClientSeq: 0,
|
|
3950
4283
|
recentStdout: [],
|
|
3951
4284
|
recentStderr: [],
|
|
3952
4285
|
lastRuntimeError: null,
|
|
@@ -4012,6 +4345,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4012
4345
|
if (this.agents.has(agentId)) {
|
|
4013
4346
|
const ap = this.agents.get(agentId);
|
|
4014
4347
|
if (ap.process !== proc) return;
|
|
4348
|
+
this.releaseAgentStartPermit(agentId, "process closed");
|
|
4015
4349
|
if (ap.notificationTimer) {
|
|
4016
4350
|
clearTimeout(ap.notificationTimer);
|
|
4017
4351
|
}
|
|
@@ -4176,6 +4510,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4176
4510
|
return leftKeys.every((key) => left?.[key] === right?.[key]);
|
|
4177
4511
|
}
|
|
4178
4512
|
async stopAgent(agentId, { wait = false, silent = false } = {}) {
|
|
4513
|
+
this.cancelQueuedAgentStart(agentId, "stop requested");
|
|
4179
4514
|
this.idleAgentConfigs.delete(agentId);
|
|
4180
4515
|
const ap = this.agents.get(agentId);
|
|
4181
4516
|
if (!ap) {
|
|
@@ -4184,6 +4519,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4184
4519
|
}
|
|
4185
4520
|
return;
|
|
4186
4521
|
}
|
|
4522
|
+
this.releaseAgentStartPermit(agentId, "stop requested");
|
|
4187
4523
|
if (ap.notificationTimer) {
|
|
4188
4524
|
clearTimeout(ap.notificationTimer);
|
|
4189
4525
|
}
|
|
@@ -4225,7 +4561,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4225
4561
|
deliverMessage(agentId, message) {
|
|
4226
4562
|
const ap = this.agents.get(agentId);
|
|
4227
4563
|
if (!ap) {
|
|
4228
|
-
if (this.agentsStarting.has(agentId)) {
|
|
4564
|
+
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
4229
4565
|
const pending = this.startingInboxes.get(agentId) || [];
|
|
4230
4566
|
pending.push(message);
|
|
4231
4567
|
this.startingInboxes.set(agentId, pending);
|
|
@@ -4285,6 +4621,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4285
4621
|
}
|
|
4286
4622
|
}
|
|
4287
4623
|
async stopAll() {
|
|
4624
|
+
this.cancelAllQueuedAgentStarts("daemon shutdown");
|
|
4288
4625
|
this.idleAgentConfigs.clear();
|
|
4289
4626
|
const ids = [...this.agents.keys()];
|
|
4290
4627
|
await Promise.all(ids.map((id) => this.stopAgent(id, { wait: true, silent: true })));
|
|
@@ -4619,13 +4956,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4619
4956
|
if (!hasToolStart) {
|
|
4620
4957
|
entries.push({ kind: "status", activity, detail });
|
|
4621
4958
|
}
|
|
4959
|
+
if (ap) ap.activityClientSeq += 1;
|
|
4622
4960
|
this.sendToServer({
|
|
4623
4961
|
type: "agent:activity",
|
|
4624
4962
|
agentId,
|
|
4625
4963
|
activity,
|
|
4626
4964
|
detail,
|
|
4627
4965
|
entries,
|
|
4628
|
-
launchId: launchIdOverride || ap?.launchId || void 0
|
|
4966
|
+
launchId: launchIdOverride || ap?.launchId || void 0,
|
|
4967
|
+
clientSeq: ap?.activityClientSeq
|
|
4629
4968
|
});
|
|
4630
4969
|
if (ap) {
|
|
4631
4970
|
ap.lastActivity = activity;
|
|
@@ -4637,12 +4976,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4637
4976
|
this.recordRuntimeTraceEvent(agentId, ap, "activity.heartbeat.sent", {
|
|
4638
4977
|
activity: ap.lastActivity
|
|
4639
4978
|
});
|
|
4979
|
+
ap.activityClientSeq += 1;
|
|
4640
4980
|
this.sendToServer({
|
|
4641
4981
|
type: "agent:activity",
|
|
4642
4982
|
agentId,
|
|
4643
4983
|
activity: ap.lastActivity,
|
|
4644
4984
|
detail: ap.lastActivityDetail,
|
|
4645
|
-
launchId: launchIdOverride || ap.launchId || void 0
|
|
4985
|
+
launchId: launchIdOverride || ap.launchId || void 0,
|
|
4986
|
+
clientSeq: ap.activityClientSeq
|
|
4646
4987
|
});
|
|
4647
4988
|
}, ACTIVITY_HEARTBEAT_MS);
|
|
4648
4989
|
}
|
|
@@ -4654,6 +4995,42 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4654
4995
|
}
|
|
4655
4996
|
}
|
|
4656
4997
|
}
|
|
4998
|
+
/**
|
|
4999
|
+
* Respond to a server-issued `agent:activity_probe`. Echoes the
|
|
5000
|
+
* agent's current `lastActivity` back through the existing
|
|
5001
|
+
* `agent:activity` upstream channel with the matching `probeId`.
|
|
5002
|
+
*
|
|
5003
|
+
* Why this exists: the server's stale-activity sweep used to
|
|
5004
|
+
* synthesize `online` whenever a transient state went 90s without
|
|
5005
|
+
* an update. That invented state without consulting ground truth
|
|
5006
|
+
* and produced "agent shows green/idle but is actually working" UI
|
|
5007
|
+
* staleness (#engineering:72283cf7 task #340 RCA).
|
|
5008
|
+
*
|
|
5009
|
+
* The new flow: server sends `agent:activity_probe` for stale
|
|
5010
|
+
* agents, daemon replies here with the *real* current activity, and
|
|
5011
|
+
* the server only falls back to synth-online if the probe times out
|
|
5012
|
+
* (5s). The body is intentionally minimal — no entries, no
|
|
5013
|
+
* heartbeat side-effects, no state mutation. We just echo what we
|
|
5014
|
+
* already know.
|
|
5015
|
+
*
|
|
5016
|
+
* If the agent is no longer running locally (`ap` undefined), we
|
|
5017
|
+
* report `offline` so the server stops believing the agent is busy.
|
|
5018
|
+
*/
|
|
5019
|
+
respondToActivityProbe(agentId, probeId) {
|
|
5020
|
+
const ap = this.agents.get(agentId);
|
|
5021
|
+
const activity = ap?.lastActivity || "offline";
|
|
5022
|
+
const detail = ap?.lastActivityDetail || (ap ? "" : "Agent not running");
|
|
5023
|
+
if (ap) ap.activityClientSeq += 1;
|
|
5024
|
+
this.sendToServer({
|
|
5025
|
+
type: "agent:activity",
|
|
5026
|
+
agentId,
|
|
5027
|
+
activity,
|
|
5028
|
+
detail,
|
|
5029
|
+
launchId: ap?.launchId || void 0,
|
|
5030
|
+
probeId,
|
|
5031
|
+
clientSeq: ap?.activityClientSeq
|
|
5032
|
+
});
|
|
5033
|
+
}
|
|
4657
5034
|
flushPendingTrajectory(agentId) {
|
|
4658
5035
|
const ap = this.agents.get(agentId);
|
|
4659
5036
|
const pending = ap?.pendingTrajectory;
|
|
@@ -4945,6 +5322,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4945
5322
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)");
|
|
4946
5323
|
this.flushPendingTrajectory(agentId);
|
|
4947
5324
|
if (ap) {
|
|
5325
|
+
this.releaseAgentStartPermit(agentId, "initial turn ended");
|
|
4948
5326
|
this.clearGatedInFlightBatch(agentId, ap, "turn_end");
|
|
4949
5327
|
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
4950
5328
|
ap.gatedSteering.outstandingToolUses = 0;
|
|
@@ -5534,6 +5912,8 @@ function summarizeIncomingMessage(msg) {
|
|
|
5534
5912
|
return `(agent=${msg.agentId}, path=${msg.path})`;
|
|
5535
5913
|
case "agent:skills:list":
|
|
5536
5914
|
return `(agent=${msg.agentId}, runtime=${msg.runtime || "auto"})`;
|
|
5915
|
+
case "agent:activity_probe":
|
|
5916
|
+
return `(agent=${msg.agentId}, probe=${msg.probeId.slice(0, 8)}, purpose=${msg.purpose})`;
|
|
5537
5917
|
case "machine:workspace:delete":
|
|
5538
5918
|
return `(directory=${msg.directoryName})`;
|
|
5539
5919
|
case "machine:runtime_models:detect":
|
|
@@ -5721,6 +6101,9 @@ var DaemonCore = class {
|
|
|
5721
6101
|
this.connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global: [], workspace: [] });
|
|
5722
6102
|
});
|
|
5723
6103
|
break;
|
|
6104
|
+
case "agent:activity_probe":
|
|
6105
|
+
this.agentManager.respondToActivityProbe(msg.agentId, msg.probeId);
|
|
6106
|
+
break;
|
|
5724
6107
|
case "machine:workspace:scan":
|
|
5725
6108
|
logger.info("[Daemon] Scanning all workspace directories");
|
|
5726
6109
|
this.agentManager.scanAllWorkspaces().then((directories) => {
|
|
@@ -5738,7 +6121,12 @@ var DaemonCore = class {
|
|
|
5738
6121
|
const detect = typeof driver?.detectModels === "function" ? driver.detectModels() : Promise.resolve(null);
|
|
5739
6122
|
Promise.resolve(detect).then((result) => {
|
|
5740
6123
|
if (result) {
|
|
5741
|
-
|
|
6124
|
+
const verified = driver.model.detectedModelsVerifiedAs;
|
|
6125
|
+
const models = result.models.map((model) => ({
|
|
6126
|
+
...model,
|
|
6127
|
+
verified: model.verified ?? verified
|
|
6128
|
+
}));
|
|
6129
|
+
this.connection.send({ type: "machine:runtime_models:result", requestId: msg.requestId, models, default: result.default });
|
|
5742
6130
|
} else {
|
|
5743
6131
|
this.connection.send({ type: "machine:runtime_models:result", requestId: msg.requestId, error: "unsupported" });
|
|
5744
6132
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1049,7 +1049,7 @@ function formatClaimResults(channel, data) {
|
|
|
1049
1049
|
const msgShort = r.messageId ? r.messageId.slice(0, 8) : "";
|
|
1050
1050
|
return `${label} (msg:${msgShort}): claimed`;
|
|
1051
1051
|
}
|
|
1052
|
-
return `${label}: FAILED \u2014 ${r.reason || "already claimed"}
|
|
1052
|
+
return `${label}: FAILED \u2014 ${r.reason || "already claimed"}. Do not reply.`;
|
|
1053
1053
|
});
|
|
1054
1054
|
const succeeded = data.results.filter((r) => r.success).length;
|
|
1055
1055
|
const failed = data.results.length - succeeded;
|
|
@@ -1681,20 +1681,18 @@ function registerProfileUpdateCommand(parent) {
|
|
|
1681
1681
|
}
|
|
1682
1682
|
|
|
1683
1683
|
// src/commands/reminder/_format.ts
|
|
1684
|
-
|
|
1685
|
-
const d = new Date(iso);
|
|
1686
|
-
if (isNaN(d.getTime())) return iso;
|
|
1687
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
1688
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1689
|
-
}
|
|
1684
|
+
var MODIFY_HINT = "(to modify: snooze/update/cancel; slock reminder --help)";
|
|
1690
1685
|
function formatReminder(r) {
|
|
1691
|
-
const fireLocal = toLocalTime2(r.fireAt);
|
|
1692
1686
|
const ref = r.msgRef ? ` ref=${r.msgRef}` : "";
|
|
1693
|
-
const
|
|
1694
|
-
return `#${r.reminderId.slice(0, 8)} [${r.status}]
|
|
1687
|
+
const timeField = r.status === "fired" ? `fired_at=${r.firedAt ?? r.fireAt}` : `next=${r.fireAt}`;
|
|
1688
|
+
return `#${r.reminderId.slice(0, 8)} [${r.status}] ${formatReminderType(r)} ${timeField} "${r.title}"${ref}`;
|
|
1695
1689
|
}
|
|
1696
1690
|
function formatReminderScheduled(r, warning) {
|
|
1697
|
-
const lines = [
|
|
1691
|
+
const lines = [
|
|
1692
|
+
`Reminder scheduled: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
|
|
1693
|
+
`Next: ${r.fireAt}`,
|
|
1694
|
+
MODIFY_HINT
|
|
1695
|
+
];
|
|
1698
1696
|
if (warning) lines.push(`Warning: ${warning}`);
|
|
1699
1697
|
return lines.join("\n");
|
|
1700
1698
|
}
|
|
@@ -1703,7 +1701,34 @@ function formatReminderList(reminders) {
|
|
|
1703
1701
|
return reminders.map(formatReminder).join("\n");
|
|
1704
1702
|
}
|
|
1705
1703
|
function formatReminderCanceled(r) {
|
|
1706
|
-
return `Reminder canceled:
|
|
1704
|
+
return `Reminder canceled: #${r.reminderId.slice(0, 8)} [${r.status}] "${r.title}"`;
|
|
1705
|
+
}
|
|
1706
|
+
function formatReminderSnoozed(r) {
|
|
1707
|
+
return [
|
|
1708
|
+
`Reminder snoozed: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
|
|
1709
|
+
`Next: ${r.fireAt}`,
|
|
1710
|
+
MODIFY_HINT
|
|
1711
|
+
].join("\n");
|
|
1712
|
+
}
|
|
1713
|
+
function formatReminderUpdated(r, warning) {
|
|
1714
|
+
const lines = [
|
|
1715
|
+
`Reminder updated: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
|
|
1716
|
+
`Next: ${r.fireAt}`,
|
|
1717
|
+
MODIFY_HINT
|
|
1718
|
+
];
|
|
1719
|
+
if (warning) lines.push(`Warning: ${warning}`);
|
|
1720
|
+
return lines.join("\n");
|
|
1721
|
+
}
|
|
1722
|
+
function formatReminderLog(events) {
|
|
1723
|
+
if (events.length === 0) return "No reminder events.";
|
|
1724
|
+
return events.map((event) => {
|
|
1725
|
+
const next = event.nextFireAt ? ` next=${event.nextFireAt}` : " next=none";
|
|
1726
|
+
return `${event.occurredAt} ${event.eventType.toUpperCase()} by ${event.actorType}${event.actorId ? `:${event.actorId}` : ""}${next}`;
|
|
1727
|
+
}).join("\n");
|
|
1728
|
+
}
|
|
1729
|
+
function formatReminderType(r) {
|
|
1730
|
+
if (!r.recurrence) return "(one-time)";
|
|
1731
|
+
return `(recurring \xB7 ${r.recurrence.description})`;
|
|
1707
1732
|
}
|
|
1708
1733
|
|
|
1709
1734
|
// src/commands/reminder/schedule.ts
|
|
@@ -1799,9 +1824,9 @@ function registerReminderScheduleCommand(parent) {
|
|
|
1799
1824
|
// src/commands/reminder/list.ts
|
|
1800
1825
|
var VALID_STATUSES2 = /* @__PURE__ */ new Set(["scheduled", "fired", "canceled"]);
|
|
1801
1826
|
function registerReminderListCommand(parent) {
|
|
1802
|
-
parent.command("list").description("List your own reminders (defaults to scheduled)").option(
|
|
1827
|
+
parent.command("list").description("List your own reminders (defaults to scheduled and fired)").option("--all", "Include canceled reminders").option(
|
|
1803
1828
|
"--status <s>",
|
|
1804
|
-
"Comma-separated statuses (scheduled,fired,canceled). Default: scheduled"
|
|
1829
|
+
"Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired"
|
|
1805
1830
|
).action(async (opts) => {
|
|
1806
1831
|
let ctx;
|
|
1807
1832
|
try {
|
|
@@ -1810,14 +1835,18 @@ function registerReminderListCommand(parent) {
|
|
|
1810
1835
|
if (err instanceof AgentBootstrapError) fail(err.code, err.message);
|
|
1811
1836
|
throw err;
|
|
1812
1837
|
}
|
|
1813
|
-
const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled";
|
|
1838
|
+
const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled,fired";
|
|
1814
1839
|
for (const s of statusRaw.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
1815
1840
|
if (!VALID_STATUSES2.has(s)) {
|
|
1816
1841
|
fail("INVALID_ARG", `--status entries must be one of ${Array.from(VALID_STATUSES2).join("|")}; got ${s}`);
|
|
1817
1842
|
}
|
|
1818
1843
|
}
|
|
1819
1844
|
const params = new URLSearchParams();
|
|
1820
|
-
|
|
1845
|
+
if (opts.all && !opts.status) {
|
|
1846
|
+
params.set("all", "true");
|
|
1847
|
+
} else {
|
|
1848
|
+
params.set("status", statusRaw);
|
|
1849
|
+
}
|
|
1821
1850
|
const client = new ApiClient(ctx);
|
|
1822
1851
|
const res = await client.request(
|
|
1823
1852
|
"GET",
|
|
@@ -1831,6 +1860,37 @@ function registerReminderListCommand(parent) {
|
|
|
1831
1860
|
});
|
|
1832
1861
|
}
|
|
1833
1862
|
|
|
1863
|
+
// src/commands/reminder/_resolve.ts
|
|
1864
|
+
async function resolveReminderId(client, agentId, id, opts) {
|
|
1865
|
+
const trimmed = id.trim();
|
|
1866
|
+
if (!trimmed) fail("INVALID_ARG", "--id is required");
|
|
1867
|
+
if (trimmed.length >= 32) return trimmed;
|
|
1868
|
+
const params = new URLSearchParams();
|
|
1869
|
+
if (opts.all) {
|
|
1870
|
+
params.set("all", "true");
|
|
1871
|
+
} else if (opts.statuses && opts.statuses.length > 0) {
|
|
1872
|
+
params.set("status", opts.statuses.join(","));
|
|
1873
|
+
}
|
|
1874
|
+
const query = params.toString();
|
|
1875
|
+
const res = await client.request(
|
|
1876
|
+
"GET",
|
|
1877
|
+
`/internal/agent/${encodeURIComponent(agentId)}/reminders${query ? `?${query}` : ""}`
|
|
1878
|
+
);
|
|
1879
|
+
if (!res.ok) {
|
|
1880
|
+
const code = res.status >= 500 ? "SERVER_5XX" : opts.failureCode;
|
|
1881
|
+
fail(code, res.error ?? `HTTP ${res.status}`);
|
|
1882
|
+
}
|
|
1883
|
+
const matches = (res.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(trimmed));
|
|
1884
|
+
if (matches.length === 0) {
|
|
1885
|
+
const scope = opts.all ? "reminder" : `${opts.statuses?.join("/") ?? "active"} reminder`;
|
|
1886
|
+
fail("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
|
|
1887
|
+
}
|
|
1888
|
+
if (matches.length > 1) {
|
|
1889
|
+
fail("AMBIGUOUS", `Ambiguous id prefix '${trimmed}' matches ${matches.length} reminders; pass a longer id.`);
|
|
1890
|
+
}
|
|
1891
|
+
return matches[0].reminderId;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1834
1894
|
// src/commands/reminder/cancel.ts
|
|
1835
1895
|
function registerReminderCancelCommand(parent) {
|
|
1836
1896
|
parent.command("cancel").description("Cancel a scheduled reminder by id (full uuid or 8-char prefix)").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
|
|
@@ -1845,25 +1905,10 @@ function registerReminderCancelCommand(parent) {
|
|
|
1845
1905
|
fail("INVALID_ARG", "--id is required");
|
|
1846
1906
|
}
|
|
1847
1907
|
const client = new ApiClient(ctx);
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
`/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders?status=scheduled`
|
|
1853
|
-
);
|
|
1854
|
-
if (!listRes.ok) {
|
|
1855
|
-
const code = listRes.status >= 500 ? "SERVER_5XX" : "CANCEL_FAILED";
|
|
1856
|
-
fail(code, listRes.error ?? `HTTP ${listRes.status}`);
|
|
1857
|
-
}
|
|
1858
|
-
const matches = (listRes.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(opts.id));
|
|
1859
|
-
if (matches.length === 0) {
|
|
1860
|
-
fail("NOT_FOUND", `No scheduled reminder matches id prefix '${opts.id}'.`);
|
|
1861
|
-
}
|
|
1862
|
-
if (matches.length > 1) {
|
|
1863
|
-
fail("AMBIGUOUS", `Ambiguous id prefix '${opts.id}' matches ${matches.length} reminders; pass a longer id.`);
|
|
1864
|
-
}
|
|
1865
|
-
fullId = matches[0].reminderId;
|
|
1866
|
-
}
|
|
1908
|
+
const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
|
|
1909
|
+
statuses: ["scheduled", "fired"],
|
|
1910
|
+
failureCode: "CANCEL_FAILED"
|
|
1911
|
+
});
|
|
1867
1912
|
const res = await client.request(
|
|
1868
1913
|
"DELETE",
|
|
1869
1914
|
`/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`
|
|
@@ -1876,6 +1921,124 @@ function registerReminderCancelCommand(parent) {
|
|
|
1876
1921
|
});
|
|
1877
1922
|
}
|
|
1878
1923
|
|
|
1924
|
+
// src/commands/reminder/_duration.ts
|
|
1925
|
+
function parseDurationSeconds(input) {
|
|
1926
|
+
const raw = input.trim();
|
|
1927
|
+
const match = /^(\d+)(s|m|h|d)?$/.exec(raw);
|
|
1928
|
+
if (!match) return null;
|
|
1929
|
+
const value = Number(match[1]);
|
|
1930
|
+
const unit = match[2] ?? "s";
|
|
1931
|
+
const multiplier = unit === "s" ? 1 : unit === "m" ? 60 : unit === "h" ? 3600 : 86400;
|
|
1932
|
+
const seconds = value * multiplier;
|
|
1933
|
+
if (!Number.isSafeInteger(seconds) || seconds <= 0) return null;
|
|
1934
|
+
return seconds;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/commands/reminder/snooze.ts
|
|
1938
|
+
function registerReminderSnoozeCommand(parent) {
|
|
1939
|
+
parent.command("snooze").description("Snooze a scheduled or fired reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").requiredOption("--by <duration>", "Snooze duration, e.g. 30m, 2h, 1d").action(async (opts) => {
|
|
1940
|
+
let ctx;
|
|
1941
|
+
try {
|
|
1942
|
+
ctx = loadAgentContext();
|
|
1943
|
+
} catch (err) {
|
|
1944
|
+
if (err instanceof AgentBootstrapError) fail(err.code, err.message);
|
|
1945
|
+
throw err;
|
|
1946
|
+
}
|
|
1947
|
+
const delaySeconds = parseDurationSeconds(opts.by);
|
|
1948
|
+
if (delaySeconds == null) {
|
|
1949
|
+
fail("INVALID_ARG", "--by must be a positive duration like 30m, 2h, or 1d");
|
|
1950
|
+
}
|
|
1951
|
+
const client = new ApiClient(ctx);
|
|
1952
|
+
const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
|
|
1953
|
+
statuses: ["scheduled", "fired"],
|
|
1954
|
+
failureCode: "SNOOZE_FAILED"
|
|
1955
|
+
});
|
|
1956
|
+
const res = await client.request(
|
|
1957
|
+
"POST",
|
|
1958
|
+
`/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
|
|
1959
|
+
{ delaySeconds }
|
|
1960
|
+
);
|
|
1961
|
+
if (!res.ok || !res.data?.reminder) {
|
|
1962
|
+
const code = res.status >= 500 ? "SERVER_5XX" : "SNOOZE_FAILED";
|
|
1963
|
+
fail(code, res.error ?? `HTTP ${res.status}`);
|
|
1964
|
+
}
|
|
1965
|
+
process.stdout.write(formatReminderSnoozed(res.data.reminder) + "\n");
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
// src/commands/reminder/update.ts
|
|
1970
|
+
function registerReminderUpdateCommand(parent) {
|
|
1971
|
+
parent.command("update").description("Update one field on a scheduled reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").option("--fire-at <iso>", "New absolute next fire time").option("--in <duration>", "New relative next fire time, e.g. 30m, 2h").option("--cadence <rule>", "New recurrence rule: every:15m | daily@09:00 | weekly:mon,fri@09:00").option("--title <text>", "New reminder title").action(async (opts) => {
|
|
1972
|
+
let ctx;
|
|
1973
|
+
try {
|
|
1974
|
+
ctx = loadAgentContext();
|
|
1975
|
+
} catch (err) {
|
|
1976
|
+
if (err instanceof AgentBootstrapError) fail(err.code, err.message);
|
|
1977
|
+
throw err;
|
|
1978
|
+
}
|
|
1979
|
+
const mutationCount = [opts.fireAt, opts.in, opts.cadence, opts.title].filter((x) => x !== void 0 && x !== null).length;
|
|
1980
|
+
if (mutationCount !== 1) {
|
|
1981
|
+
fail("INVALID_ARG", "Pass exactly one of --fire-at, --in, --cadence, or --title");
|
|
1982
|
+
}
|
|
1983
|
+
const body = {};
|
|
1984
|
+
if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
|
|
1985
|
+
if (opts.in !== void 0) {
|
|
1986
|
+
const delaySeconds = parseDurationSeconds(opts.in);
|
|
1987
|
+
if (delaySeconds == null) {
|
|
1988
|
+
fail("INVALID_ARG", "--in must be a positive duration like 30m, 2h, or 1d");
|
|
1989
|
+
}
|
|
1990
|
+
body.delaySeconds = delaySeconds;
|
|
1991
|
+
}
|
|
1992
|
+
if (opts.cadence !== void 0) {
|
|
1993
|
+
body.repeat = opts.cadence;
|
|
1994
|
+
body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1995
|
+
}
|
|
1996
|
+
if (opts.title !== void 0) body.title = opts.title;
|
|
1997
|
+
const client = new ApiClient(ctx);
|
|
1998
|
+
const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
|
|
1999
|
+
all: true,
|
|
2000
|
+
failureCode: "UPDATE_FAILED"
|
|
2001
|
+
});
|
|
2002
|
+
const res = await client.request(
|
|
2003
|
+
"PATCH",
|
|
2004
|
+
`/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`,
|
|
2005
|
+
body
|
|
2006
|
+
);
|
|
2007
|
+
if (!res.ok || !res.data?.reminder) {
|
|
2008
|
+
const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
|
|
2009
|
+
fail(code, res.error ?? `HTTP ${res.status}`);
|
|
2010
|
+
}
|
|
2011
|
+
process.stdout.write(formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
// src/commands/reminder/log.ts
|
|
2016
|
+
function registerReminderLogCommand(parent) {
|
|
2017
|
+
parent.command("log").description("Show lifecycle events for one reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
|
|
2018
|
+
let ctx;
|
|
2019
|
+
try {
|
|
2020
|
+
ctx = loadAgentContext();
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
if (err instanceof AgentBootstrapError) fail(err.code, err.message);
|
|
2023
|
+
throw err;
|
|
2024
|
+
}
|
|
2025
|
+
const client = new ApiClient(ctx);
|
|
2026
|
+
const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
|
|
2027
|
+
all: true,
|
|
2028
|
+
failureCode: "LOG_FAILED"
|
|
2029
|
+
});
|
|
2030
|
+
const res = await client.request(
|
|
2031
|
+
"GET",
|
|
2032
|
+
`/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
|
|
2033
|
+
);
|
|
2034
|
+
if (!res.ok || !res.data?.events) {
|
|
2035
|
+
const code = res.status >= 500 ? "SERVER_5XX" : "LOG_FAILED";
|
|
2036
|
+
fail(code, res.error ?? `HTTP ${res.status}`);
|
|
2037
|
+
}
|
|
2038
|
+
process.stdout.write(formatReminderLog(res.data.events) + "\n");
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
|
|
1879
2042
|
// src/index.ts
|
|
1880
2043
|
var program = new Command();
|
|
1881
2044
|
program.name("slock").description(
|
|
@@ -1911,6 +2074,9 @@ var reminderCmd = program.command("reminder").description("Reminder operations")
|
|
|
1911
2074
|
registerReminderScheduleCommand(reminderCmd);
|
|
1912
2075
|
registerReminderListCommand(reminderCmd);
|
|
1913
2076
|
registerReminderCancelCommand(reminderCmd);
|
|
2077
|
+
registerReminderSnoozeCommand(reminderCmd);
|
|
2078
|
+
registerReminderUpdateCommand(reminderCmd);
|
|
2079
|
+
registerReminderLogCommand(reminderCmd);
|
|
1914
2080
|
program.parseAsync().catch((err) => {
|
|
1915
2081
|
if (err instanceof CliExit) {
|
|
1916
2082
|
process.exitCode = err.exitCode;
|
package/dist/core.js
CHANGED
package/dist/index.js
CHANGED