@slock-ai/daemon 0.46.0 → 0.46.1-staging.20260509070837
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 +5 -5
- package/dist/{chunk-Q4XUZB34.js → chunk-FG5JGA67.js} +314 -50
- package/dist/cli/index.js +2 -2
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -34,7 +34,7 @@ var searchMessagesDeprecatedSchema = {
|
|
|
34
34
|
};
|
|
35
35
|
var listTasksDeprecatedSchema = {
|
|
36
36
|
channel: z.string().describe("Deprecated channel argument."),
|
|
37
|
-
status: z.enum(["all", "todo", "in_progress", "in_review", "done"]).optional().describe("Deprecated status argument.")
|
|
37
|
+
status: z.enum(["all", "todo", "in_progress", "in_review", "done", "closed"]).optional().describe("Deprecated status argument.")
|
|
38
38
|
};
|
|
39
39
|
var claimTasksDeprecatedSchema = {
|
|
40
40
|
channel: z.string().describe("Deprecated channel argument."),
|
|
@@ -48,7 +48,7 @@ var unclaimTaskDeprecatedSchema = {
|
|
|
48
48
|
var updateTaskStatusDeprecatedSchema = {
|
|
49
49
|
channel: z.string().describe("Deprecated channel argument."),
|
|
50
50
|
task_number: z.number().describe("Deprecated task number."),
|
|
51
|
-
status: z.enum(["todo", "in_progress", "in_review", "done"]).describe("Deprecated status argument.")
|
|
51
|
+
status: z.enum(["todo", "in_progress", "in_review", "done", "closed"]).describe("Deprecated status argument.")
|
|
52
52
|
};
|
|
53
53
|
var DEPRECATED_MCP_TOOL_DEFINITIONS = [
|
|
54
54
|
{
|
|
@@ -1087,7 +1087,7 @@ ${formatted}${footer}`
|
|
|
1087
1087
|
"List all tasks in a channel. Returns each task's number, title, status, assignee, and message ID. Use this to see what work exists before claiming. Tasks marked as legacy are from an older system and cannot be claimed or modified.",
|
|
1088
1088
|
{
|
|
1089
1089
|
channel: z2.string().describe("The channel whose task board to view \u2014 e.g. '#engineering', '#proj-slock'"),
|
|
1090
|
-
status: z2.enum(["all", "todo", "in_progress", "in_review", "done"]).default("all").describe("Filter by status (default: all)")
|
|
1090
|
+
status: z2.enum(["all", "todo", "in_progress", "in_review", "done", "closed"]).default("all").describe("Filter by status (default: all)")
|
|
1091
1091
|
},
|
|
1092
1092
|
async ({ channel, status }) => {
|
|
1093
1093
|
try {
|
|
@@ -1311,11 +1311,11 @@ ${lines.join("\n")}${threadHint}`
|
|
|
1311
1311
|
);
|
|
1312
1312
|
server.tool(
|
|
1313
1313
|
"update_task_status",
|
|
1314
|
-
"Update a task's progress status. You must be the task's assignee to update it. Use in_review when your work is ready for human validation. Only set done for trivial tasks or after explicit approval. Valid transitions: todo\
|
|
1314
|
+
"Update a task's progress status. You must be the task's assignee to update it. Use in_review when your work is ready for human validation. Only set done for trivial tasks or after explicit approval. Use closed to mark a task as won't-do (cancelled / abandoned / out-of-scope) \u2014 distinct from done. Valid transitions: todo\u2192{in_progress,closed}, in_progress\u2192{in_review,done,closed}, in_review\u2192{done,in_progress,closed}, done\u2192{todo,in_progress,in_review,closed}, closed\u2192todo (reopen).",
|
|
1315
1315
|
{
|
|
1316
1316
|
channel: z2.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
1317
1317
|
task_number: z2.number().describe("The task number to update (e.g. 3)"),
|
|
1318
|
-
status: z2.enum(["todo", "in_progress", "in_review", "done"]).describe("The new status")
|
|
1318
|
+
status: z2.enum(["todo", "in_progress", "in_review", "done", "closed"]).describe("The new status")
|
|
1319
1319
|
},
|
|
1320
1320
|
async ({ channel, task_number, status }) => {
|
|
1321
1321
|
try {
|
|
@@ -2558,8 +2558,8 @@ function runCursorModelsCommand() {
|
|
|
2558
2558
|
}
|
|
2559
2559
|
|
|
2560
2560
|
// src/drivers/gemini.ts
|
|
2561
|
-
import { spawn as spawn5 } from "child_process";
|
|
2562
|
-
import {
|
|
2561
|
+
import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
|
|
2562
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
2563
2563
|
import path7 from "path";
|
|
2564
2564
|
function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
2565
2565
|
const { spawnEnv } = prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
|
|
@@ -2568,6 +2568,71 @@ function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
|
2568
2568
|
}
|
|
2569
2569
|
return spawnEnv;
|
|
2570
2570
|
}
|
|
2571
|
+
function normalizeExecOutput2(raw) {
|
|
2572
|
+
return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
|
|
2573
|
+
}
|
|
2574
|
+
function buildGeminiArgs(config) {
|
|
2575
|
+
const args = [
|
|
2576
|
+
"--output-format",
|
|
2577
|
+
"stream-json",
|
|
2578
|
+
"--yolo",
|
|
2579
|
+
// Gemini CLI headless mode is selected by -p/--prompt. Keep the actual
|
|
2580
|
+
// prompt off argv and feed it through stdin below; this avoids Windows
|
|
2581
|
+
// cmd.exe's 8191-character command-line limit and keeps long wake payloads
|
|
2582
|
+
// below CreateProcess argv pressure too.
|
|
2583
|
+
"-p",
|
|
2584
|
+
""
|
|
2585
|
+
];
|
|
2586
|
+
if (config.model && config.model !== "default") {
|
|
2587
|
+
args.push("--model", config.model);
|
|
2588
|
+
}
|
|
2589
|
+
if (config.sessionId) {
|
|
2590
|
+
args.push("--resume", config.sessionId);
|
|
2591
|
+
}
|
|
2592
|
+
return args;
|
|
2593
|
+
}
|
|
2594
|
+
function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
2595
|
+
const platform = deps.platform ?? process.platform;
|
|
2596
|
+
if (platform !== "win32") {
|
|
2597
|
+
return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
|
|
2598
|
+
}
|
|
2599
|
+
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
|
|
2600
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
2601
|
+
const env = deps.env ?? process.env;
|
|
2602
|
+
const winPath = path7.win32;
|
|
2603
|
+
let geminiEntry = null;
|
|
2604
|
+
try {
|
|
2605
|
+
const globalRoot = normalizeExecOutput2(execFileSyncFn("npm", ["root", "-g"], {
|
|
2606
|
+
encoding: "utf8",
|
|
2607
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2608
|
+
env
|
|
2609
|
+
})).trim();
|
|
2610
|
+
const candidate = winPath.join(globalRoot, "@google", "gemini-cli", "bundle", "gemini.js");
|
|
2611
|
+
if (existsSyncFn(candidate)) geminiEntry = candidate;
|
|
2612
|
+
} catch {
|
|
2613
|
+
}
|
|
2614
|
+
if (!geminiEntry) {
|
|
2615
|
+
try {
|
|
2616
|
+
const cmdPath = normalizeExecOutput2(execFileSyncFn("where.exe", ["gemini"], {
|
|
2617
|
+
encoding: "utf8",
|
|
2618
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2619
|
+
env
|
|
2620
|
+
})).trim().split(/\r?\n/)[0];
|
|
2621
|
+
const candidate = winPath.join(winPath.dirname(cmdPath), "node_modules", "@google", "gemini-cli", "bundle", "gemini.js");
|
|
2622
|
+
if (existsSyncFn(candidate)) geminiEntry = candidate;
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
if (!geminiEntry) {
|
|
2627
|
+
throw new Error(
|
|
2628
|
+
"Cannot resolve Gemini CLI entry point on Windows. Ensure @google/gemini-cli is installed globally via npm (npm i -g @google/gemini-cli)."
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
return {
|
|
2632
|
+
command: process.execPath,
|
|
2633
|
+
args: [geminiEntry, ...commandArgs]
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2571
2636
|
var GeminiDriver = class {
|
|
2572
2637
|
id = "gemini";
|
|
2573
2638
|
lifecycle = {
|
|
@@ -2597,26 +2662,15 @@ var GeminiDriver = class {
|
|
|
2597
2662
|
this.sessionId = ctx.config.sessionId || null;
|
|
2598
2663
|
this.sessionAnnounced = false;
|
|
2599
2664
|
this.writeGeminiSettings(ctx);
|
|
2600
|
-
const args =
|
|
2601
|
-
"--output-format",
|
|
2602
|
-
"stream-json",
|
|
2603
|
-
"--yolo",
|
|
2604
|
-
"-p",
|
|
2605
|
-
ctx.prompt
|
|
2606
|
-
];
|
|
2607
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2608
|
-
args.push("--model", ctx.config.model);
|
|
2609
|
-
}
|
|
2610
|
-
if (ctx.config.sessionId) {
|
|
2611
|
-
args.push("--resume", ctx.config.sessionId);
|
|
2612
|
-
}
|
|
2665
|
+
const { command, args } = resolveGeminiSpawn(buildGeminiArgs(ctx.config));
|
|
2613
2666
|
const spawnEnv = buildGeminiSpawnEnv(ctx);
|
|
2614
|
-
const proc = spawn5(
|
|
2667
|
+
const proc = spawn5(command, args, {
|
|
2615
2668
|
cwd: ctx.workingDirectory,
|
|
2616
2669
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2617
2670
|
env: spawnEnv,
|
|
2618
|
-
shell:
|
|
2671
|
+
shell: false
|
|
2619
2672
|
});
|
|
2673
|
+
proc.stdin?.end(ctx.prompt);
|
|
2620
2674
|
return { process: proc };
|
|
2621
2675
|
}
|
|
2622
2676
|
parseLine(line) {
|
|
@@ -2718,7 +2772,7 @@ var GeminiDriver = class {
|
|
|
2718
2772
|
// src/drivers/kimi.ts
|
|
2719
2773
|
import { randomUUID } from "crypto";
|
|
2720
2774
|
import { spawn as spawn6 } from "child_process";
|
|
2721
|
-
import { existsSync as
|
|
2775
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2722
2776
|
import os3 from "os";
|
|
2723
2777
|
import path8 from "path";
|
|
2724
2778
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
@@ -2783,7 +2837,7 @@ var KimiDriver = class {
|
|
|
2783
2837
|
const systemPromptPath = path8.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
|
|
2784
2838
|
const agentFilePath = path8.join(ctx.workingDirectory, KIMI_AGENT_FILE);
|
|
2785
2839
|
const mcpConfigPath = path8.join(ctx.workingDirectory, KIMI_MCP_FILE);
|
|
2786
|
-
if (!isResume || !
|
|
2840
|
+
if (!isResume || !existsSync6(systemPromptPath)) {
|
|
2787
2841
|
writeFileSync6(systemPromptPath, ctx.prompt, "utf8");
|
|
2788
2842
|
}
|
|
2789
2843
|
writeFileSync6(agentFilePath, [
|
|
@@ -2972,6 +3026,14 @@ var SLOCK_AGENT_NAME = "slock";
|
|
|
2972
3026
|
var NO_MESSAGE_PROMPT = "No new messages are pending. Stop now.";
|
|
2973
3027
|
var FIRST_MESSAGE_TASK_PREFIX = "First message task (system-triggered):";
|
|
2974
3028
|
var MIN_SUPPORTED_OPENCODE_VERSION = "1.14.30";
|
|
3029
|
+
var OPENCODE_PROVIDER_LABELS = {
|
|
3030
|
+
opencode: "OpenCode",
|
|
3031
|
+
"opencode-go": "OpenCode Go",
|
|
3032
|
+
openai: "OpenAI",
|
|
3033
|
+
openrouter: "OpenRouter",
|
|
3034
|
+
deepseek: "DeepSeek",
|
|
3035
|
+
fusecode: "FuseCode"
|
|
3036
|
+
};
|
|
2975
3037
|
function buildChatBridgeCommand(ctx) {
|
|
2976
3038
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2977
3039
|
return [
|
|
@@ -3118,12 +3180,62 @@ function parseOpenCodeModelsOutput(output) {
|
|
|
3118
3180
|
seen.add(line);
|
|
3119
3181
|
models.push({
|
|
3120
3182
|
id: line,
|
|
3121
|
-
label: line,
|
|
3183
|
+
label: formatOpenCodeModelLabel(line),
|
|
3122
3184
|
verified: "launchable"
|
|
3123
3185
|
});
|
|
3124
3186
|
}
|
|
3125
3187
|
return models.length > 0 ? { models } : null;
|
|
3126
3188
|
}
|
|
3189
|
+
function formatOpenCodeModelLabel(modelId) {
|
|
3190
|
+
const separatorIndex = modelId.indexOf("/");
|
|
3191
|
+
if (separatorIndex <= 0 || separatorIndex === modelId.length - 1) return modelId;
|
|
3192
|
+
const providerId = modelId.slice(0, separatorIndex);
|
|
3193
|
+
const modelName = modelId.slice(separatorIndex + 1);
|
|
3194
|
+
const providerLabel = OPENCODE_PROVIDER_LABELS[providerId] || humanizeOpenCodeSegment(providerId);
|
|
3195
|
+
const modelParts = modelName.split("/");
|
|
3196
|
+
const modelLabel = humanizeOpenCodeSegment(modelParts[modelParts.length - 1] || modelName);
|
|
3197
|
+
if (modelParts.length === 1) return `${modelLabel} \xB7 ${providerLabel}`;
|
|
3198
|
+
const upstreamLabel = modelParts.slice(0, -1).map(humanizeOpenCodeSegment).join(" / ");
|
|
3199
|
+
return `${modelLabel} \xB7 ${upstreamLabel} via ${providerLabel}`;
|
|
3200
|
+
}
|
|
3201
|
+
function humanizeOpenCodeSegment(value) {
|
|
3202
|
+
return value.replace(/\[(\d+)m\]/gi, "-$1m").split(/[-_]/).filter(Boolean).map(formatOpenCodeLabelToken).join(" ");
|
|
3203
|
+
}
|
|
3204
|
+
function formatOpenCodeLabelToken(token) {
|
|
3205
|
+
const normalized = token.toLowerCase();
|
|
3206
|
+
const specialCases = {
|
|
3207
|
+
ai: "AI",
|
|
3208
|
+
api: "API",
|
|
3209
|
+
b: "B",
|
|
3210
|
+
chatgpt: "ChatGPT",
|
|
3211
|
+
claude: "Claude",
|
|
3212
|
+
codestral: "Codestral",
|
|
3213
|
+
deepseek: "DeepSeek",
|
|
3214
|
+
flash: "Flash",
|
|
3215
|
+
free: "Free",
|
|
3216
|
+
gemini: "Gemini",
|
|
3217
|
+
glm: "GLM",
|
|
3218
|
+
gpt: "GPT",
|
|
3219
|
+
hy3: "HY3",
|
|
3220
|
+
kimi: "Kimi",
|
|
3221
|
+
m: "M",
|
|
3222
|
+
minimax: "MiniMax",
|
|
3223
|
+
nano: "Nano",
|
|
3224
|
+
nemotron: "Nemotron",
|
|
3225
|
+
omni: "Omni",
|
|
3226
|
+
opus: "Opus",
|
|
3227
|
+
pro: "Pro",
|
|
3228
|
+
sonnet: "Sonnet",
|
|
3229
|
+
super: "Super"
|
|
3230
|
+
};
|
|
3231
|
+
if (specialCases[normalized]) return specialCases[normalized];
|
|
3232
|
+
if (/^v\d+(\.\d+)?$/.test(normalized)) return normalized.toUpperCase();
|
|
3233
|
+
if (/^\d+m$/i.test(token)) return token.toUpperCase();
|
|
3234
|
+
if (/^\d+[bk]$/i.test(token)) return token.toUpperCase();
|
|
3235
|
+
if (/^m\d+(\.\d+)?$/i.test(token)) return token.toUpperCase();
|
|
3236
|
+
if (/^\d/.test(token)) return token;
|
|
3237
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
3238
|
+
}
|
|
3127
3239
|
function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
|
|
3128
3240
|
const commandResult = runCommand(home);
|
|
3129
3241
|
if (commandResult.error || commandResult.status !== 0) return null;
|
|
@@ -4183,6 +4295,66 @@ function isMissingResumeSession(ap) {
|
|
|
4183
4295
|
}
|
|
4184
4296
|
return false;
|
|
4185
4297
|
}
|
|
4298
|
+
function classifyActivityDetailForTrace(detail) {
|
|
4299
|
+
if (!detail) return void 0;
|
|
4300
|
+
if (detail === "Message received") return "message_received";
|
|
4301
|
+
if (detail === "Starting\u2026") return "starting";
|
|
4302
|
+
if (detail === "Running command\u2026") return "running_command";
|
|
4303
|
+
if (detail === "Checking messages\u2026") return "checking_messages";
|
|
4304
|
+
if (detail === "Compacting context") return "compacting_context";
|
|
4305
|
+
if (detail === "Context compaction finished") return "compaction_finished";
|
|
4306
|
+
if (detail === "Context compaction still running; no finish event observed") return "compaction_stale";
|
|
4307
|
+
if (detail === "Idle" || detail === "Process idle") return "idle";
|
|
4308
|
+
if (detail.startsWith("Restarting stalled ") && detail.endsWith(" runtime for queued message")) return "stalled_recovery";
|
|
4309
|
+
if (detail.startsWith("Runtime stalled: no runtime events for ")) return "runtime_stalled";
|
|
4310
|
+
return "other";
|
|
4311
|
+
}
|
|
4312
|
+
function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
4313
|
+
const context = [];
|
|
4314
|
+
if (ap.lastActivityDetail) {
|
|
4315
|
+
context.push(`after ${ap.lastActivityDetail}`);
|
|
4316
|
+
}
|
|
4317
|
+
if (ap.driver.busyDeliveryMode === "gated") {
|
|
4318
|
+
context.push(`phase=${ap.gatedSteering.phase}`);
|
|
4319
|
+
}
|
|
4320
|
+
if (ap.gatedSteering.outstandingToolUses > 0) {
|
|
4321
|
+
context.push(`tools=${ap.gatedSteering.outstandingToolUses}`);
|
|
4322
|
+
}
|
|
4323
|
+
if (ap.gatedSteering.compacting) {
|
|
4324
|
+
context.push("compacting");
|
|
4325
|
+
}
|
|
4326
|
+
if (ap.inbox.length > 0) {
|
|
4327
|
+
context.push(`queued=${ap.inbox.length}`);
|
|
4328
|
+
}
|
|
4329
|
+
const detail = [
|
|
4330
|
+
`Runtime stalled: no runtime events for ${staleForMinutes}m`,
|
|
4331
|
+
context.length > 0 ? ` (${context.join(", ")})` : ""
|
|
4332
|
+
].join("");
|
|
4333
|
+
return {
|
|
4334
|
+
detail,
|
|
4335
|
+
traceAttrs: {
|
|
4336
|
+
ageMs: staleForMs,
|
|
4337
|
+
staleForMinutes,
|
|
4338
|
+
lastActivity: ap.lastActivity,
|
|
4339
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
4340
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
4341
|
+
runtime: ap.config.runtime,
|
|
4342
|
+
model: ap.config.model,
|
|
4343
|
+
launchId: ap.launchId || void 0,
|
|
4344
|
+
sessionIdPresent: Boolean(ap.sessionId),
|
|
4345
|
+
inboxCount: ap.inbox.length,
|
|
4346
|
+
pendingNotificationCount: ap.pendingNotificationCount,
|
|
4347
|
+
processPidPresent: typeof ap.process.pid === "number",
|
|
4348
|
+
busyDeliveryMode: ap.driver.busyDeliveryMode,
|
|
4349
|
+
supportsStdinNotification: ap.driver.supportsStdinNotification,
|
|
4350
|
+
gatedPhase: ap.driver.busyDeliveryMode === "gated" ? ap.gatedSteering.phase : void 0,
|
|
4351
|
+
outstandingToolUses: ap.gatedSteering.outstandingToolUses,
|
|
4352
|
+
compacting: ap.gatedSteering.compacting,
|
|
4353
|
+
recentStderrCount: ap.recentStderr.length,
|
|
4354
|
+
recentStdoutCount: ap.recentStdout.length
|
|
4355
|
+
}
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4186
4358
|
function getMessageDeliveryText(driver) {
|
|
4187
4359
|
return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
4188
4360
|
}
|
|
@@ -5855,17 +6027,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5855
6027
|
if (staleForMs < RUNTIME_PROGRESS_STALE_MS) return false;
|
|
5856
6028
|
ap.runtimeProgressStaleSince = Date.now();
|
|
5857
6029
|
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
staleForMinutes,
|
|
5861
|
-
lastActivity: ap.lastActivity
|
|
5862
|
-
});
|
|
6030
|
+
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
6031
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", diagnostic.traceAttrs);
|
|
5863
6032
|
this.endRuntimeTrace(ap, "error", {
|
|
5864
6033
|
outcome: "runtime-stalled",
|
|
5865
6034
|
ageMs: staleForMs,
|
|
5866
|
-
lastActivity: ap.lastActivity
|
|
6035
|
+
lastActivity: ap.lastActivity,
|
|
6036
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6037
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
|
|
5867
6038
|
});
|
|
5868
|
-
this.broadcastActivity(agentId, "error",
|
|
6039
|
+
this.broadcastActivity(agentId, "error", diagnostic.detail);
|
|
5869
6040
|
return true;
|
|
5870
6041
|
}
|
|
5871
6042
|
recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap) {
|
|
@@ -5881,10 +6052,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5881
6052
|
if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgressStaleSince) return false;
|
|
5882
6053
|
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
5883
6054
|
ap.runtimeProgressStaleSince ??= Date.now();
|
|
6055
|
+
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
5884
6056
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
|
|
5885
|
-
|
|
5886
|
-
staleForMinutes,
|
|
5887
|
-
lastActivity: ap.lastActivity,
|
|
6057
|
+
...diagnostic.traceAttrs,
|
|
5888
6058
|
pendingMessages: ap.inbox.length,
|
|
5889
6059
|
recovery: "terminate_for_queued_message"
|
|
5890
6060
|
});
|
|
@@ -5892,6 +6062,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5892
6062
|
outcome: "runtime-stalled",
|
|
5893
6063
|
ageMs: staleForMs,
|
|
5894
6064
|
lastActivity: ap.lastActivity,
|
|
6065
|
+
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6066
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
5895
6067
|
pendingMessages: ap.inbox.length,
|
|
5896
6068
|
recovery: "terminate_for_queued_message"
|
|
5897
6069
|
});
|
|
@@ -6658,6 +6830,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
6658
6830
|
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
6659
6831
|
import path13 from "path";
|
|
6660
6832
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6833
|
+
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
6661
6834
|
var DEFAULT_MAX_FILES = 8;
|
|
6662
6835
|
var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
6663
6836
|
"serverId",
|
|
@@ -6674,14 +6847,25 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
|
6674
6847
|
var LocalRotatingTraceSink = class {
|
|
6675
6848
|
traceDir;
|
|
6676
6849
|
maxFileBytes;
|
|
6850
|
+
maxFileAgeMs;
|
|
6677
6851
|
maxFiles;
|
|
6852
|
+
nowMsProvider;
|
|
6678
6853
|
currentFile = null;
|
|
6854
|
+
currentFileOpenedAtMs = null;
|
|
6679
6855
|
currentSize = 0;
|
|
6680
6856
|
sequence = 0;
|
|
6681
6857
|
constructor(options) {
|
|
6682
6858
|
this.traceDir = path13.join(options.machineDir, "traces");
|
|
6683
6859
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
6860
|
+
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
6861
|
+
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
6862
|
+
this.maxFileAgeMs = baseAgeMs + ageJitterMs;
|
|
6684
6863
|
this.maxFiles = Math.max(1, Math.floor(options.maxFiles ?? DEFAULT_MAX_FILES));
|
|
6864
|
+
this.nowMsProvider = options.nowMsProvider ?? Date.now;
|
|
6865
|
+
}
|
|
6866
|
+
/** Exposed for observability — the effective rotation age after jitter. */
|
|
6867
|
+
getMaxFileAgeMs() {
|
|
6868
|
+
return this.maxFileAgeMs;
|
|
6685
6869
|
}
|
|
6686
6870
|
record(span) {
|
|
6687
6871
|
try {
|
|
@@ -6698,13 +6882,16 @@ var LocalRotatingTraceSink = class {
|
|
|
6698
6882
|
}
|
|
6699
6883
|
ensureFile(nextBytes) {
|
|
6700
6884
|
mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
|
|
6701
|
-
|
|
6885
|
+
const nowMs = this.nowMsProvider();
|
|
6886
|
+
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
6887
|
+
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
6702
6888
|
this.currentFile = path13.join(
|
|
6703
6889
|
this.traceDir,
|
|
6704
|
-
`daemon-trace-${safeTimestamp(
|
|
6890
|
+
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
6705
6891
|
);
|
|
6706
6892
|
writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
|
|
6707
6893
|
this.currentSize = statSync4(this.currentFile).size;
|
|
6894
|
+
this.currentFileOpenedAtMs = nowMs;
|
|
6708
6895
|
this.pruneOldFiles();
|
|
6709
6896
|
}
|
|
6710
6897
|
}
|
|
@@ -6789,7 +6976,7 @@ function isDiagnosticIdAttr(key) {
|
|
|
6789
6976
|
}
|
|
6790
6977
|
|
|
6791
6978
|
// src/traceBundleUpload.ts
|
|
6792
|
-
import { createHash as
|
|
6979
|
+
import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
|
|
6793
6980
|
import { gzipSync } from "zlib";
|
|
6794
6981
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
6795
6982
|
import path14 from "path";
|
|
@@ -6918,6 +7105,35 @@ async function uploadWithSignedCapability({
|
|
|
6918
7105
|
return { capability, session, uploadResponse };
|
|
6919
7106
|
}
|
|
6920
7107
|
|
|
7108
|
+
// src/traceJitter.ts
|
|
7109
|
+
import { createHash as createHash2 } from "crypto";
|
|
7110
|
+
var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
|
|
7111
|
+
var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
|
|
7112
|
+
var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
|
|
7113
|
+
function computeTraceJitter(lockId) {
|
|
7114
|
+
const seed = createHash2("sha256").update(lockId).digest();
|
|
7115
|
+
return {
|
|
7116
|
+
initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
|
|
7117
|
+
uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
|
|
7118
|
+
maxFileAgeJitterMs: seed.readUInt32BE(8) % MAX_FILE_AGE_JITTER_SPAN_MS
|
|
7119
|
+
};
|
|
7120
|
+
}
|
|
7121
|
+
var NO_JITTER = {
|
|
7122
|
+
initialUploadDelayMs: 0,
|
|
7123
|
+
uploadIntervalJitterMs: 0,
|
|
7124
|
+
maxFileAgeJitterMs: 0
|
|
7125
|
+
};
|
|
7126
|
+
function bucketDelayMs(delayMs) {
|
|
7127
|
+
if (delayMs < 1e3) return "0-1s";
|
|
7128
|
+
if (delayMs < 5e3) return "1-5s";
|
|
7129
|
+
if (delayMs < 15e3) return "5-15s";
|
|
7130
|
+
if (delayMs < 3e4) return "15-30s";
|
|
7131
|
+
if (delayMs < 6e4) return "30-60s";
|
|
7132
|
+
if (delayMs < 3e5) return "60s-5m";
|
|
7133
|
+
if (delayMs < 6e5) return "5-10m";
|
|
7134
|
+
return "10m+";
|
|
7135
|
+
}
|
|
7136
|
+
|
|
6921
7137
|
// src/traceBundleUpload.ts
|
|
6922
7138
|
var TRACE_UPLOAD_SCOPE = "daemon-trace-bundle:create";
|
|
6923
7139
|
var DEFAULT_UPLOAD_INTERVAL_MS = 5 * 60 * 1e3;
|
|
@@ -6925,30 +7141,66 @@ var DEFAULT_MIN_FILE_AGE_MS = 60 * 1e3;
|
|
|
6925
7141
|
var DEFAULT_MAX_FILES_PER_RUN = 4;
|
|
6926
7142
|
var DaemonTraceBundleUploader = class {
|
|
6927
7143
|
options;
|
|
6928
|
-
|
|
7144
|
+
jitter;
|
|
7145
|
+
timers;
|
|
7146
|
+
initialDelayTimer = null;
|
|
7147
|
+
intervalTimer = null;
|
|
7148
|
+
stopped = false;
|
|
6929
7149
|
constructor(options) {
|
|
6930
7150
|
this.options = options;
|
|
7151
|
+
this.jitter = options.jitter ?? (options.lockId ? computeTraceJitter(options.lockId) : NO_JITTER);
|
|
7152
|
+
this.timers = options.timers ?? {
|
|
7153
|
+
setTimeout: globalThis.setTimeout.bind(globalThis),
|
|
7154
|
+
setInterval: globalThis.setInterval.bind(globalThis),
|
|
7155
|
+
clearTimeout: globalThis.clearTimeout.bind(globalThis),
|
|
7156
|
+
clearInterval: globalThis.clearInterval.bind(globalThis)
|
|
7157
|
+
};
|
|
6931
7158
|
}
|
|
6932
7159
|
start() {
|
|
6933
|
-
if (this.
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
7160
|
+
if (this.stopped) return;
|
|
7161
|
+
if (this.initialDelayTimer || this.intervalTimer) return;
|
|
7162
|
+
const initialDelayMs = this.jitter.initialUploadDelayMs;
|
|
7163
|
+
this.initialDelayTimer = this.timers.setTimeout(() => {
|
|
7164
|
+
this.initialDelayTimer = null;
|
|
7165
|
+
if (this.stopped) return;
|
|
7166
|
+
void this.uploadOnce("initial");
|
|
7167
|
+
this.scheduleNextTick();
|
|
7168
|
+
}, initialDelayMs);
|
|
6938
7169
|
}
|
|
6939
7170
|
stop() {
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
7171
|
+
this.stopped = true;
|
|
7172
|
+
if (this.initialDelayTimer) {
|
|
7173
|
+
this.timers.clearTimeout(this.initialDelayTimer);
|
|
7174
|
+
this.initialDelayTimer = null;
|
|
7175
|
+
}
|
|
7176
|
+
if (this.intervalTimer) {
|
|
7177
|
+
this.timers.clearTimeout(this.intervalTimer);
|
|
7178
|
+
this.intervalTimer = null;
|
|
7179
|
+
}
|
|
6943
7180
|
}
|
|
6944
|
-
|
|
7181
|
+
/**
|
|
7182
|
+
* Drive a single upload pass. `trigger` is surfaced as a span attribute so
|
|
7183
|
+
* we can distinguish startup drain vs steady-state ticks in ScopeDB.
|
|
7184
|
+
*/
|
|
7185
|
+
async uploadOnce(trigger = "manual") {
|
|
6945
7186
|
const files = await this.findUploadCandidates();
|
|
6946
7187
|
let uploaded = 0;
|
|
6947
7188
|
for (const file of files.slice(0, this.options.maxFilesPerRun ?? DEFAULT_MAX_FILES_PER_RUN)) {
|
|
6948
|
-
if (await this.uploadFile(file)) uploaded += 1;
|
|
7189
|
+
if (await this.uploadFile(file, trigger)) uploaded += 1;
|
|
6949
7190
|
}
|
|
6950
7191
|
return { attempted: files.length, uploaded };
|
|
6951
7192
|
}
|
|
7193
|
+
scheduleNextTick() {
|
|
7194
|
+
if (this.stopped) return;
|
|
7195
|
+
const baseIntervalMs = this.options.intervalMs ?? readPositiveIntegerEnv2("SLOCK_DAEMON_TRACE_UPLOAD_INTERVAL_MS", DEFAULT_UPLOAD_INTERVAL_MS);
|
|
7196
|
+
const nextMs = baseIntervalMs + this.jitter.uploadIntervalJitterMs;
|
|
7197
|
+
this.intervalTimer = this.timers.setTimeout(() => {
|
|
7198
|
+
this.intervalTimer = null;
|
|
7199
|
+
if (this.stopped) return;
|
|
7200
|
+
void this.uploadOnce("interval");
|
|
7201
|
+
this.scheduleNextTick();
|
|
7202
|
+
}, nextMs);
|
|
7203
|
+
}
|
|
6952
7204
|
async findUploadCandidates() {
|
|
6953
7205
|
const traceDir = path14.join(this.options.machineDir, "traces");
|
|
6954
7206
|
let names;
|
|
@@ -6975,13 +7227,16 @@ var DaemonTraceBundleUploader = class {
|
|
|
6975
7227
|
}
|
|
6976
7228
|
return candidates;
|
|
6977
7229
|
}
|
|
6978
|
-
async uploadFile(file) {
|
|
7230
|
+
async uploadFile(file, trigger) {
|
|
6979
7231
|
const span = this.options.tracer?.startSpan("daemon.bundle.upload", {
|
|
6980
7232
|
surface: "daemon",
|
|
6981
7233
|
kind: "producer",
|
|
6982
7234
|
attrs: {
|
|
6983
7235
|
file_present: true,
|
|
6984
|
-
worker_url_present: Boolean(this.options.workerUrl)
|
|
7236
|
+
worker_url_present: Boolean(this.options.workerUrl),
|
|
7237
|
+
upload_trigger: trigger,
|
|
7238
|
+
initial_delay_ms_bucket: bucketDelayMs(this.jitter.initialUploadDelayMs),
|
|
7239
|
+
interval_jitter_ms_bucket: bucketDelayMs(this.jitter.uploadIntervalJitterMs)
|
|
6985
7240
|
}
|
|
6986
7241
|
});
|
|
6987
7242
|
try {
|
|
@@ -7057,7 +7312,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7057
7312
|
}
|
|
7058
7313
|
};
|
|
7059
7314
|
function sha256Hex(body) {
|
|
7060
|
-
return
|
|
7315
|
+
return createHash3("sha256").update(body).digest("hex");
|
|
7061
7316
|
}
|
|
7062
7317
|
function readPositiveIntegerEnv2(name, fallback) {
|
|
7063
7318
|
const value = process.env[name];
|
|
@@ -7067,6 +7322,7 @@ function readPositiveIntegerEnv2(name, fallback) {
|
|
|
7067
7322
|
}
|
|
7068
7323
|
|
|
7069
7324
|
// src/core.ts
|
|
7325
|
+
var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
7070
7326
|
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
|
|
7071
7327
|
function parseDaemonCliArgs(args) {
|
|
7072
7328
|
let serverUrl = "";
|
|
@@ -7287,11 +7543,18 @@ var DaemonCore = class {
|
|
|
7287
7543
|
if (!this.options.localTrace) return false;
|
|
7288
7544
|
return process.env.SLOCK_DAEMON_LOCAL_TRACE !== "0";
|
|
7289
7545
|
}
|
|
7546
|
+
resolveTraceJitter() {
|
|
7547
|
+
const lockId = this.machineLock?.lockId;
|
|
7548
|
+
return lockId ? computeTraceJitter(lockId) : NO_JITTER;
|
|
7549
|
+
}
|
|
7290
7550
|
installLocalTraceSink(machineDir) {
|
|
7291
7551
|
if (!this.shouldEnableLocalTrace()) return;
|
|
7552
|
+
const jitter = this.resolveTraceJitter();
|
|
7292
7553
|
this.localTraceSink = new LocalRotatingTraceSink({
|
|
7293
7554
|
machineDir,
|
|
7294
7555
|
maxFileBytes: this.options.localTraceMaxFileBytes ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_BYTES", 5 * 1024 * 1024),
|
|
7556
|
+
maxFileAgeMs: this.options.localTraceMaxFileAgeMs ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILE_AGE_MS", 5 * 60 * 1e3),
|
|
7557
|
+
maxFileAgeJitterMs: jitter.maxFileAgeJitterMs,
|
|
7295
7558
|
maxFiles: this.options.localTraceMaxFiles ?? readPositiveIntegerEnv3("SLOCK_DAEMON_TRACE_MAX_FILES", 8)
|
|
7296
7559
|
});
|
|
7297
7560
|
this.tracer = new BasicTracer({
|
|
@@ -7302,15 +7565,16 @@ var DaemonCore = class {
|
|
|
7302
7565
|
installTraceBundleUploader(machineDir) {
|
|
7303
7566
|
if (!this.shouldEnableLocalTrace()) return;
|
|
7304
7567
|
if (this.traceBundleUploader) return;
|
|
7305
|
-
|
|
7306
|
-
|
|
7568
|
+
if (process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1") return;
|
|
7569
|
+
const workerUrl = process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL || DEFAULT_TRACE_UPLOAD_URL;
|
|
7307
7570
|
this.traceBundleUploader = new DaemonTraceBundleUploader({
|
|
7308
7571
|
machineDir,
|
|
7309
7572
|
serverUrl: this.options.serverUrl,
|
|
7310
7573
|
apiKey: this.options.apiKey,
|
|
7311
7574
|
workerUrl,
|
|
7312
7575
|
tracer: this.tracer,
|
|
7313
|
-
currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null
|
|
7576
|
+
currentFileProvider: () => this.localTraceSink?.getCurrentFile() ?? null,
|
|
7577
|
+
lockId: this.machineLock?.lockId
|
|
7314
7578
|
});
|
|
7315
7579
|
this.traceBundleUploader.start();
|
|
7316
7580
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -1119,7 +1119,7 @@ function formatTaskStatusUpdated(taskNumber, status) {
|
|
|
1119
1119
|
}
|
|
1120
1120
|
|
|
1121
1121
|
// src/commands/task/list.ts
|
|
1122
|
-
var VALID_STATUSES = /* @__PURE__ */ new Set(["all", "todo", "in_progress", "in_review", "done"]);
|
|
1122
|
+
var VALID_STATUSES = /* @__PURE__ */ new Set(["all", "todo", "in_progress", "in_review", "done", "closed"]);
|
|
1123
1123
|
function registerTaskListCommand(parent) {
|
|
1124
1124
|
parent.command("list").description("List tasks in a channel").requiredOption("--channel <target>", "Channel target: '#channel'").option("--status <s>", "Filter: all|todo|in_progress|in_review|done (default: server-side)").action(async (opts) => {
|
|
1125
1125
|
let ctx;
|
|
@@ -1253,7 +1253,7 @@ function registerTaskUnclaimCommand(parent) {
|
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
1255
1255
|
// src/commands/task/update.ts
|
|
1256
|
-
var STATUSES = ["todo", "in_progress", "in_review", "done"];
|
|
1256
|
+
var STATUSES = ["todo", "in_progress", "in_review", "done", "closed"];
|
|
1257
1257
|
function registerTaskUpdateCommand(parent) {
|
|
1258
1258
|
parent.command("update").description("Update task status").requiredOption("--channel <target>", "Channel target: '#channel'").requiredOption("--number <n>", "Task number to update").requiredOption(
|
|
1259
1259
|
"--status <status>",
|
package/dist/core.js
CHANGED
package/dist/index.js
CHANGED