@slock-ai/daemon 0.46.2 → 0.47.0-staging.20260511155804
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 +4 -4
- package/dist/{chunk-Z3PCMYZO.js → chunk-B7XIMLOT.js} +44 -1
- package/dist/{chunk-FG5JGA67.js → chunk-WTTEYI5T.js} +392 -48
- package/dist/cli/index.js +14162 -228
- package/dist/core.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
3
|
+
SLOCK_HOME_ENV,
|
|
3
4
|
buildWebSocketOptions,
|
|
4
5
|
executeJsonRequest,
|
|
5
6
|
executeResponseRequest,
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
listLegacySlockStatePaths,
|
|
8
|
+
logger,
|
|
9
|
+
resolveSlockHome,
|
|
10
|
+
resolveSlockHomePath
|
|
11
|
+
} from "./chunk-B7XIMLOT.js";
|
|
8
12
|
|
|
9
13
|
// src/core.ts
|
|
10
14
|
import path15 from "path";
|
|
@@ -576,6 +580,66 @@ function summarizeToolInput(toolName, input) {
|
|
|
576
580
|
// ../shared/src/attachmentPreview.ts
|
|
577
581
|
var CSV_PREVIEW_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
578
582
|
|
|
583
|
+
// ../shared/src/actionCards.ts
|
|
584
|
+
import { z } from "zod";
|
|
585
|
+
var uuidSchema = z.uuid();
|
|
586
|
+
var idOrHandleSchema = z.string().min(1).max(120);
|
|
587
|
+
var draftHintSchema = z.string().trim().max(2e3).optional().describe(
|
|
588
|
+
"Why the agent prepared this for you. Shows below the form on the card; not the action itself."
|
|
589
|
+
);
|
|
590
|
+
var channelCreateOperationSchema = z.object({
|
|
591
|
+
type: z.literal("channel:create"),
|
|
592
|
+
name: z.string().trim().min(1).max(80),
|
|
593
|
+
visibility: z.enum(["public", "private"]).default("public"),
|
|
594
|
+
description: z.string().trim().max(500).optional(),
|
|
595
|
+
/**
|
|
596
|
+
* Humans to add to the channel on creation. Each entry is a handle
|
|
597
|
+
* (`@alice` or bare `alice`) or a UUID. Server resolves via
|
|
598
|
+
* `resolveUserByName` at prepare time; resolved UUIDs are stored.
|
|
599
|
+
*/
|
|
600
|
+
initialHumans: z.array(idOrHandleSchema).max(64).optional(),
|
|
601
|
+
/**
|
|
602
|
+
* Agents to add to the channel on creation. Each entry is a handle
|
|
603
|
+
* (`@scout` or bare `scout`) or a UUID. Server resolves via
|
|
604
|
+
* `resolveAgentByName` at prepare time; resolved UUIDs are stored.
|
|
605
|
+
*/
|
|
606
|
+
initialAgents: z.array(idOrHandleSchema).max(64).optional(),
|
|
607
|
+
draftHint: draftHintSchema
|
|
608
|
+
});
|
|
609
|
+
var agentCreateOperationSchema = z.object({
|
|
610
|
+
type: z.literal("agent:create"),
|
|
611
|
+
name: z.string().trim().min(1).max(60),
|
|
612
|
+
description: z.string().trim().max(500).optional(),
|
|
613
|
+
/**
|
|
614
|
+
* Agent can only suggest semantic intent (name + description). Technical
|
|
615
|
+
* configuration (which computer / runtime / model / reasoning effort) is
|
|
616
|
+
* a user prerogative — the human picks those in the create dialog when
|
|
617
|
+
* they click "Create Agent" on the card. Per stdrc 2026-05-10
|
|
618
|
+
* #proj-approval msg=ae4ecedd: "为什么 computer、runtime 和 model 还是
|
|
619
|
+
* 帮用户选了?" — don't let the agent prefill those.
|
|
620
|
+
*/
|
|
621
|
+
draftHint: draftHintSchema
|
|
622
|
+
});
|
|
623
|
+
var channelAddMemberOperationSchema = z.object({
|
|
624
|
+
type: z.literal("channel:add_member"),
|
|
625
|
+
/**
|
|
626
|
+
* Target channel. Handle (`#general` or bare `general`) or UUID.
|
|
627
|
+
* Server resolves via `resolveChannelByName` at prepare time; the
|
|
628
|
+
* resolved UUID is stored in the card metadata.
|
|
629
|
+
*/
|
|
630
|
+
channel: idOrHandleSchema,
|
|
631
|
+
/** Same resolution rule as `channelCreateOperationSchema.initialHumans`. */
|
|
632
|
+
humans: z.array(idOrHandleSchema).max(64).optional(),
|
|
633
|
+
/** Same resolution rule as `channelCreateOperationSchema.initialAgents`. */
|
|
634
|
+
agents: z.array(idOrHandleSchema).max(64).optional(),
|
|
635
|
+
draftHint: draftHintSchema
|
|
636
|
+
});
|
|
637
|
+
var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
638
|
+
channelCreateOperationSchema,
|
|
639
|
+
agentCreateOperationSchema,
|
|
640
|
+
channelAddMemberOperationSchema
|
|
641
|
+
]);
|
|
642
|
+
|
|
579
643
|
// ../shared/src/testing/failpoints.ts
|
|
580
644
|
var NoopFailpointRegistry = class {
|
|
581
645
|
get enabled() {
|
|
@@ -686,8 +750,9 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
686
750
|
};
|
|
687
751
|
|
|
688
752
|
// src/agentProcessManager.ts
|
|
689
|
-
import { mkdirSync as mkdirSync4, readdirSync as
|
|
753
|
+
import { mkdirSync as mkdirSync4, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
690
754
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
755
|
+
import { createHash as createHash2 } from "crypto";
|
|
691
756
|
import path11 from "path";
|
|
692
757
|
import os5 from "os";
|
|
693
758
|
|
|
@@ -796,6 +861,7 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
796
861
|
21. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
797
862
|
22. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
798
863
|
23. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
864
|
+
24. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
|
|
799
865
|
|
|
800
866
|
The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
|
|
801
867
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -1272,6 +1338,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1272
1338
|
...ctx.config.envVars || {},
|
|
1273
1339
|
...extraEnv,
|
|
1274
1340
|
...runtimeContextEnv(ctx.config),
|
|
1341
|
+
[SLOCK_HOME_ENV]: resolveSlockHome(),
|
|
1275
1342
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
1276
1343
|
...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
|
|
1277
1344
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
@@ -1705,7 +1772,7 @@ var ClaudeDriver = class {
|
|
|
1705
1772
|
|
|
1706
1773
|
// src/drivers/codex.ts
|
|
1707
1774
|
import { spawn as spawn2, execSync } from "child_process";
|
|
1708
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1775
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1709
1776
|
import os2 from "os";
|
|
1710
1777
|
import path4 from "path";
|
|
1711
1778
|
function getCodexNotificationErrorMessage(params) {
|
|
@@ -1734,11 +1801,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
|
1734
1801
|
);
|
|
1735
1802
|
}
|
|
1736
1803
|
var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
1804
|
+
function resolveWindowsSandboxRunner(deps = {}) {
|
|
1805
|
+
const homeDir = deps.homeDir ?? os2.homedir();
|
|
1806
|
+
const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
|
|
1807
|
+
if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
|
|
1808
|
+
try {
|
|
1809
|
+
const files = readdirSync2(sandboxBinDir);
|
|
1810
|
+
const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
|
|
1811
|
+
if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
|
|
1812
|
+
} catch {
|
|
1813
|
+
}
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1737
1816
|
function resolveCodexCommand(deps = {}) {
|
|
1738
1817
|
const pathCommand = resolveCommandOnPath("codex", deps);
|
|
1739
1818
|
if (pathCommand) return pathCommand;
|
|
1740
|
-
|
|
1741
|
-
|
|
1819
|
+
const platform = deps.platform ?? process.platform;
|
|
1820
|
+
if (platform === "darwin") {
|
|
1821
|
+
return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
|
|
1822
|
+
}
|
|
1823
|
+
if (platform === "win32") {
|
|
1824
|
+
return resolveWindowsSandboxRunner(deps);
|
|
1825
|
+
}
|
|
1826
|
+
return null;
|
|
1742
1827
|
}
|
|
1743
1828
|
function probeCodex(deps = {}) {
|
|
1744
1829
|
if ((deps.platform ?? process.platform) === "win32") {
|
|
@@ -1779,6 +1864,13 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1779
1864
|
}
|
|
1780
1865
|
}
|
|
1781
1866
|
if (!codexEntry) {
|
|
1867
|
+
const sandboxRunner = resolveWindowsSandboxRunner(deps);
|
|
1868
|
+
if (sandboxRunner) {
|
|
1869
|
+
return {
|
|
1870
|
+
command: sandboxRunner,
|
|
1871
|
+
args: commandArgs
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1782
1874
|
throw new Error(
|
|
1783
1875
|
"Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
|
|
1784
1876
|
);
|
|
@@ -2228,6 +2320,15 @@ function detectCodexModels(home = os2.homedir()) {
|
|
|
2228
2320
|
import { spawn as spawn3 } from "child_process";
|
|
2229
2321
|
import path5 from "path";
|
|
2230
2322
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2323
|
+
function buildCopilotSpawnEnv(ctx) {
|
|
2324
|
+
return {
|
|
2325
|
+
...process.env,
|
|
2326
|
+
FORCE_COLOR: "0",
|
|
2327
|
+
NO_COLOR: "1",
|
|
2328
|
+
...ctx.config.envVars || {},
|
|
2329
|
+
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2231
2332
|
var CopilotDriver = class {
|
|
2232
2333
|
id = "copilot";
|
|
2233
2334
|
lifecycle = {
|
|
@@ -2286,7 +2387,7 @@ var CopilotDriver = class {
|
|
|
2286
2387
|
if (ctx.config.sessionId) {
|
|
2287
2388
|
args.push(`--resume=${ctx.config.sessionId}`);
|
|
2288
2389
|
}
|
|
2289
|
-
const spawnEnv =
|
|
2390
|
+
const spawnEnv = buildCopilotSpawnEnv(ctx);
|
|
2290
2391
|
const proc = spawn3("copilot", args, {
|
|
2291
2392
|
cwd: ctx.workingDirectory,
|
|
2292
2393
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2383,6 +2484,15 @@ var CopilotDriver = class {
|
|
|
2383
2484
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2384
2485
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
2385
2486
|
import path6 from "path";
|
|
2487
|
+
function buildCursorSpawnEnv(ctx) {
|
|
2488
|
+
return {
|
|
2489
|
+
...process.env,
|
|
2490
|
+
FORCE_COLOR: "0",
|
|
2491
|
+
NO_COLOR: "1",
|
|
2492
|
+
...ctx.config.envVars || {},
|
|
2493
|
+
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2386
2496
|
var CursorDriver = class {
|
|
2387
2497
|
id = "cursor";
|
|
2388
2498
|
lifecycle = {
|
|
@@ -2437,7 +2547,7 @@ var CursorDriver = class {
|
|
|
2437
2547
|
args.push("--resume", ctx.config.sessionId);
|
|
2438
2548
|
}
|
|
2439
2549
|
args.push(ctx.prompt);
|
|
2440
|
-
const spawnEnv =
|
|
2550
|
+
const spawnEnv = buildCursorSpawnEnv(ctx);
|
|
2441
2551
|
const proc = spawn4("cursor-agent", args, {
|
|
2442
2552
|
cwd: ctx.workingDirectory,
|
|
2443
2553
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3527,8 +3637,86 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
3527
3637
|
}
|
|
3528
3638
|
}
|
|
3529
3639
|
|
|
3640
|
+
// src/runtimeErrorDiagnostics.ts
|
|
3641
|
+
import { createHash } from "crypto";
|
|
3642
|
+
var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
|
|
3643
|
+
function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
3644
|
+
const rawMessage = String(message || "");
|
|
3645
|
+
const scrubbed = scrubRuntimeErrorDiagnosticText(rawMessage);
|
|
3646
|
+
const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
|
|
3647
|
+
const httpStatus = extractHttpStatus(rawMessage);
|
|
3648
|
+
const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
|
|
3649
|
+
const fingerprint = fingerprintRuntimeError(scrubbed);
|
|
3650
|
+
const spanAttrs = {
|
|
3651
|
+
runtime_error_class: runtimeErrorClass,
|
|
3652
|
+
runtime_error_fingerprint: fingerprint,
|
|
3653
|
+
runtime_error_message_present: rawMessage.length > 0,
|
|
3654
|
+
runtime_error_message_length_bucket: bucketLength(rawMessage.length),
|
|
3655
|
+
runtime_error_message_truncated: truncated
|
|
3656
|
+
};
|
|
3657
|
+
if (httpStatus !== null) {
|
|
3658
|
+
spanAttrs.runtime_error_http_status = httpStatus;
|
|
3659
|
+
}
|
|
3660
|
+
return {
|
|
3661
|
+
spanAttrs,
|
|
3662
|
+
eventAttrs: {
|
|
3663
|
+
...spanAttrs,
|
|
3664
|
+
runtime_error_message_excerpt: excerpt
|
|
3665
|
+
}
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
function scrubRuntimeErrorDiagnosticText(value) {
|
|
3669
|
+
return value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, "Bearer [REDACTED_TOKEN]").replace(/\b(?:sk|sk-ant|sk-proj|xox[baprs]?)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED_TOKEN]").replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[REDACTED_EMAIL]").replace(/https?:\/\/[^\s"'<>]+/gi, (url) => redactUrlQuery(url)).replace(/\/Users\/[^\s"'<>:]+(?:\/[^\s"'<>:]+)*/g, "[REDACTED_PATH]").replace(/\/home\/[^\s"'<>:]+(?:\/[^\s"'<>:]+)*/g, "[REDACTED_PATH]").replace(/[A-Za-z]:\\Users\\[^\s"'<>:]+(?:\\[^\s"'<>:]+)*/g, "[REDACTED_PATH]");
|
|
3670
|
+
}
|
|
3671
|
+
function truncateDiagnosticText(value, maxChars) {
|
|
3672
|
+
if (value.length <= maxChars) return { value, truncated: false };
|
|
3673
|
+
return { value: value.slice(0, maxChars), truncated: true };
|
|
3674
|
+
}
|
|
3675
|
+
function extractHttpStatus(message) {
|
|
3676
|
+
const match = /\b(?:HTTP|status(?:\s+code)?|API\s+Error)[:\s]+([45]\d{2})\b/i.exec(message) ?? /\b([45]\d{2})\s+(?:Bad Request|Unauthorized|Forbidden|Not Found|Conflict|Too Many Requests|Internal Server Error|Service Unavailable)\b/i.exec(message);
|
|
3677
|
+
if (!match) return null;
|
|
3678
|
+
const status = Number(match[1]);
|
|
3679
|
+
return Number.isInteger(status) ? status : null;
|
|
3680
|
+
}
|
|
3681
|
+
function classifyRuntimeError(message, httpStatus) {
|
|
3682
|
+
const explicit = /\b([A-Z][A-Za-z0-9_]*(?:Error|Exception))\b/.exec(message);
|
|
3683
|
+
if (explicit) return explicit[1];
|
|
3684
|
+
if (httpStatus !== null) {
|
|
3685
|
+
if (httpStatus === 429) return "RateLimitError";
|
|
3686
|
+
if (httpStatus === 401 || httpStatus === 403) return "AuthError";
|
|
3687
|
+
if (httpStatus === 404) return "NotFoundError";
|
|
3688
|
+
if (httpStatus >= 500) return "ProviderServerError";
|
|
3689
|
+
return "ProviderApiError";
|
|
3690
|
+
}
|
|
3691
|
+
if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
|
|
3692
|
+
if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
|
|
3693
|
+
if (/\bnot found\b/i.test(message)) return "NotFoundError";
|
|
3694
|
+
return "RuntimeError";
|
|
3695
|
+
}
|
|
3696
|
+
function fingerprintRuntimeError(value) {
|
|
3697
|
+
const normalized = value.toLowerCase().replace(/[0-9a-f]{12,}/g, "<hex>").replace(/\b\d+\b/g, "<num>").replace(/\s+/g, " ").trim();
|
|
3698
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
3699
|
+
}
|
|
3700
|
+
function bucketLength(length) {
|
|
3701
|
+
if (length === 0) return "0";
|
|
3702
|
+
if (length < 1024) return "<1k";
|
|
3703
|
+
if (length < 4096) return "1k-4k";
|
|
3704
|
+
if (length < 16384) return "4k-16k";
|
|
3705
|
+
return "16k+";
|
|
3706
|
+
}
|
|
3707
|
+
function redactUrlQuery(value) {
|
|
3708
|
+
try {
|
|
3709
|
+
const url = new URL(value);
|
|
3710
|
+
if (url.search) url.search = "?[REDACTED_QUERY]";
|
|
3711
|
+
if (url.username) url.username = "[REDACTED_USER]";
|
|
3712
|
+
if (url.password) url.password = "[REDACTED_PASSWORD]";
|
|
3713
|
+
return url.toString();
|
|
3714
|
+
} catch {
|
|
3715
|
+
return value.replace(/\?.*$/, "?[REDACTED_QUERY]");
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3530
3719
|
// src/agentProcessManager.ts
|
|
3531
|
-
var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
|
|
3532
3720
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
3533
3721
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
3534
3722
|
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
@@ -3609,7 +3797,7 @@ function findSessionJsonl(root, predicate) {
|
|
|
3609
3797
|
if (depth < 0 || visited >= maxEntries) return null;
|
|
3610
3798
|
let entries;
|
|
3611
3799
|
try {
|
|
3612
|
-
entries =
|
|
3800
|
+
entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3613
3801
|
} catch {
|
|
3614
3802
|
return null;
|
|
3615
3803
|
}
|
|
@@ -3857,15 +4045,27 @@ After confirming language preference, do not give a generic product introduction
|
|
|
3857
4045
|
- Use a low-pressure prompt and guide to one concrete starter action.
|
|
3858
4046
|
|
|
3859
4047
|
### Starter Plan Output
|
|
3860
|
-
A starter plan should make the next action executable, not just descriptive
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
-
|
|
3864
|
-
-
|
|
4048
|
+
A starter plan should make the next action executable, not just descriptive.
|
|
4049
|
+
For new channels, new agents, and adding members to an existing channel, post an **action card** rather than a copyable spec:
|
|
4050
|
+
|
|
4051
|
+
- Use \`slock action prepare --target <onboarding-channel>\` and pipe an \`ActionCardAction\` JSON. Identity references are handles (\`@alice\` / \`@scout\` / \`#general\` \u2014 bare names work too), never UUIDs. Server resolves at prepare time.
|
|
4052
|
+
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
4053
|
+
- \`{type: "agent:create", name, description?, draftHint?}\`
|
|
4054
|
+
- \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty
|
|
4055
|
+
- The owner clicks the button on the card; the matching dialog opens **prefilled with your values** (editable, deselectable for add_member). They review, adjust, and submit; the action is committed under their identity.
|
|
4056
|
+
- Technical fields the owner must pick themselves are NOT yours to prefill on \`agent:create\`: computer, runtime, model, reasoning effort. Stay on semantic intent (name + description).
|
|
4057
|
+
- For \`channel:add_member\`, only suggest people who are actually likely candidates (already in the server, relevant to the channel's topic). The owner will deselect anyone they don't want \u2014 make their default-yes list useful, not exhaustive.
|
|
4058
|
+
- Do not just describe or list copyable specs once action cards are available \u2014 the human input cost should land at "click the card, review, submit", not "copy this name into the dialog yourself".
|
|
4059
|
+
- Do not imply the resource has been created or members added until the card flips to "Done".
|
|
4060
|
+
|
|
4061
|
+
Other plan elements still apply:
|
|
4062
|
+
- suggested channel or workstream pairing
|
|
4063
|
+
- first task to send after the card is committed
|
|
4064
|
+
- who to pull in once the channel exists (often a follow-up \`channel:add_member\` card)
|
|
3865
4065
|
|
|
3866
4066
|
Do not use a rigid keyword routing table. Use examples as inspiration, then adapt to the user's context.
|
|
3867
|
-
If details are missing but not blocking, state reasonable defaults and invite correction.
|
|
3868
|
-
Only ask one blocking question first if the answer is required before any useful
|
|
4067
|
+
If details are missing but not blocking, state reasonable defaults inside the action card payload (the owner can edit) and invite correction in chat.
|
|
4068
|
+
Only ask one blocking question first if the answer is required before any useful card can be prepared.
|
|
3869
4069
|
Do not imply you have already created agents or channels unless the action has actually happened.
|
|
3870
4070
|
|
|
3871
4071
|
### Capability Boundary Pivot
|
|
@@ -4103,16 +4303,22 @@ Do not copy these answers verbatim.
|
|
|
4103
4303
|
|
|
4104
4304
|
## FAQ 15: How do I create agents or channels?
|
|
4105
4305
|
### Answer idea
|
|
4106
|
-
-
|
|
4107
|
-
-
|
|
4108
|
-
-
|
|
4306
|
+
- When the owner agrees to a new agent or channel, **post an action card** with \`slock action prepare\`. The card lives inline in chat; the owner clicks the action button, the matching create dialog opens prefilled with your values (editable), and the resource is created under their identity when they submit.
|
|
4307
|
+
- v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
|
|
4308
|
+
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
4309
|
+
- \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
|
|
4310
|
+
- \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty. The owner clicks "Add Members" on the card; an AddMembers dialog opens with your suggested list (each row toggleable) and the owner submits to actually add them.
|
|
4311
|
+
|
|
4312
|
+
- **Identity references are handles, not UUIDs.** Use \`@alice\` / \`@scout\` / \`#general\` (or bare \`alice\` / \`scout\` / \`general\`). The server resolves to UUIDs at prepare time. If a handle doesn't match a real human / agent / channel in this server you get a 422 INVALID_HANDLE error pointing at the field \u2014 fix the handle and retry. You should never see or write UUIDs in action card payloads.
|
|
4313
|
+
- Manual fallback (only when the owner explicitly wants to do it themselves or asks how to repeat it): the + buttons in the Agents and Channels sidebar sections. Lead with the action card; mention the + button only on request.
|
|
4109
4314
|
|
|
4110
4315
|
### Next step
|
|
4111
|
-
-
|
|
4316
|
+
- Prefill the values you have, post the action card with a short \`draftHint\` explaining why these values, and tell the owner: "click the button on the card to review and commit." Then propose the first task to send once the card flips to Done.
|
|
4112
4317
|
|
|
4113
4318
|
### Guardrail
|
|
4114
|
-
- Do not imply you already created agents or channels unless
|
|
4115
|
-
-
|
|
4319
|
+
- Do not imply you already created agents or channels unless the card state is \`executed\`.
|
|
4320
|
+
- Do not prefill technical fields on \`agent:create\` (runtime / model / computer / reasoning effort). The owner picks those in the dialog.
|
|
4321
|
+
- If the action type the user wants is not yet supported (e.g. \`channel:add_member\`), say so plainly and offer the manual UI path; do not invent action types the schema does not accept.
|
|
4116
4322
|
`;
|
|
4117
4323
|
}
|
|
4118
4324
|
function buildOnboardingSeedFiles() {
|
|
@@ -4212,6 +4418,20 @@ function runtimeProfileNotificationFromMessage(message) {
|
|
|
4212
4418
|
function runtimeProfileNotificationTitle(kind) {
|
|
4213
4419
|
return kind === "migration" ? "Runtime Profile migration" : "Runtime Profile notice";
|
|
4214
4420
|
}
|
|
4421
|
+
function hashRuntimeProfileKey(key) {
|
|
4422
|
+
if (!key) return void 0;
|
|
4423
|
+
return createHash2("sha256").update(key).digest("hex").slice(0, 16);
|
|
4424
|
+
}
|
|
4425
|
+
function runtimeProfileTurnControl(kind, key, source) {
|
|
4426
|
+
return {
|
|
4427
|
+
kind,
|
|
4428
|
+
source,
|
|
4429
|
+
keyHash: hashRuntimeProfileKey(key) ?? null,
|
|
4430
|
+
keyPresent: Boolean(key),
|
|
4431
|
+
injectedAtMs: Date.now(),
|
|
4432
|
+
migrationDoneToolObserved: false
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4215
4435
|
function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
|
|
4216
4436
|
const next = [...lines];
|
|
4217
4437
|
for (const rawLine of chunk.split(/\r?\n/)) {
|
|
@@ -4400,13 +4620,14 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4400
4620
|
defaultAgentEnvVarsProvider;
|
|
4401
4621
|
tracer;
|
|
4402
4622
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
4623
|
+
processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
4403
4624
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
4404
4625
|
this.chatBridgePath = chatBridgePath;
|
|
4405
4626
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
4406
4627
|
this.sendToServer = sendToServer;
|
|
4407
4628
|
this.daemonApiKey = daemonApiKey;
|
|
4408
4629
|
this.serverUrl = opts.serverUrl;
|
|
4409
|
-
this.dataDir = opts.dataDir ||
|
|
4630
|
+
this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
|
|
4410
4631
|
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
|
|
4411
4632
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
4412
4633
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
@@ -4798,6 +5019,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4798
5019
|
exitCode: null,
|
|
4799
5020
|
exitSignal: null,
|
|
4800
5021
|
expectedTerminationReason: null,
|
|
5022
|
+
runtimeProfileTurnControl: runtimeConfig.runtimeProfileControl ? runtimeProfileTurnControl(runtimeConfig.runtimeProfileControl.kind, runtimeConfig.runtimeProfileControl.key, "agent_config") : null,
|
|
4801
5023
|
pendingTrajectory: null,
|
|
4802
5024
|
gatedSteering: createGatedSteeringState()
|
|
4803
5025
|
};
|
|
@@ -4872,7 +5094,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4872
5094
|
clean_exit: code === 0,
|
|
4873
5095
|
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
4874
5096
|
inbox_count: current?.inbox.length ?? 0,
|
|
4875
|
-
pending_notification_count: current?.pendingNotificationCount ?? 0
|
|
5097
|
+
pending_notification_count: current?.pendingNotificationCount ?? 0,
|
|
5098
|
+
...this.processExitTraceAttrs.get(proc)
|
|
4876
5099
|
});
|
|
4877
5100
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
4878
5101
|
});
|
|
@@ -4900,7 +5123,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4900
5123
|
outcome: processEndedCleanly ? "process-exit" : "process-crash",
|
|
4901
5124
|
expectedTerminationReason: ap.expectedTerminationReason || void 0,
|
|
4902
5125
|
exitCode: finalCode,
|
|
4903
|
-
exitSignal: finalSignal
|
|
5126
|
+
exitSignal: finalSignal,
|
|
5127
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
|
|
4904
5128
|
});
|
|
4905
5129
|
if (processEndedCleanly) {
|
|
4906
5130
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
|
|
@@ -5063,6 +5287,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5063
5287
|
agentId,
|
|
5064
5288
|
kind,
|
|
5065
5289
|
key_present: Boolean(key),
|
|
5290
|
+
key_hash: hashRuntimeProfileKey(key),
|
|
5066
5291
|
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
5067
5292
|
runtime: ap.config.runtime,
|
|
5068
5293
|
session_id_present: Boolean(ap.sessionId),
|
|
@@ -5085,6 +5310,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5085
5310
|
agentId,
|
|
5086
5311
|
kind,
|
|
5087
5312
|
key_present: Boolean(key),
|
|
5313
|
+
key_hash: hashRuntimeProfileKey(key),
|
|
5088
5314
|
outcome: "queued_during_start",
|
|
5089
5315
|
startup_pending: true,
|
|
5090
5316
|
starting_inbox_count: pending.length,
|
|
@@ -5121,6 +5347,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5121
5347
|
}
|
|
5122
5348
|
this.clearCompactionWatchdog(ap);
|
|
5123
5349
|
this.agents.delete(agentId);
|
|
5350
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
5351
|
+
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
5352
|
+
stop_wait_requested: wait,
|
|
5353
|
+
stop_silent: silent
|
|
5354
|
+
});
|
|
5124
5355
|
ap.process.kill("SIGTERM");
|
|
5125
5356
|
if (!silent) {
|
|
5126
5357
|
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
@@ -5411,7 +5642,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5411
5642
|
attrs: {
|
|
5412
5643
|
agentId,
|
|
5413
5644
|
control_kind: kind,
|
|
5414
|
-
key_present: Boolean(key)
|
|
5645
|
+
key_present: Boolean(key),
|
|
5646
|
+
key_hash: hashRuntimeProfileKey(key)
|
|
5415
5647
|
}
|
|
5416
5648
|
});
|
|
5417
5649
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5444,7 +5676,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5444
5676
|
}
|
|
5445
5677
|
if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
|
|
5446
5678
|
ap.isIdle = false;
|
|
5447
|
-
this.startRuntimeTrace(agentId, ap, "runtime-profile");
|
|
5679
|
+
this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
|
|
5448
5680
|
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
5449
5681
|
span.end(written ? "ok" : "error", {
|
|
5450
5682
|
attrs: {
|
|
@@ -5539,6 +5771,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5539
5771
|
agentId,
|
|
5540
5772
|
control_kind: control.kind,
|
|
5541
5773
|
key_present: Boolean(control.key),
|
|
5774
|
+
key_hash: hashRuntimeProfileKey(control.key),
|
|
5542
5775
|
launchId: launchId || void 0,
|
|
5543
5776
|
source: "agent_config"
|
|
5544
5777
|
}
|
|
@@ -5903,7 +6136,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5903
6136
|
deliveryId: context.deliveryId,
|
|
5904
6137
|
delivery_correlation_id: context.deliveryId,
|
|
5905
6138
|
control_kind: notification.kind,
|
|
5906
|
-
key_present: Boolean(notification.key)
|
|
6139
|
+
key_present: Boolean(notification.key),
|
|
6140
|
+
runtime_profile_control_kind: notification.kind,
|
|
6141
|
+
runtime_profile_key_hash: hashRuntimeProfileKey(notification.key),
|
|
6142
|
+
runtime_profile_key_present: Boolean(notification.key)
|
|
5907
6143
|
};
|
|
5908
6144
|
}
|
|
5909
6145
|
return {
|
|
@@ -5914,8 +6150,63 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5914
6150
|
delivery_correlation_id: context.deliveryId ?? first.message_id
|
|
5915
6151
|
};
|
|
5916
6152
|
}
|
|
6153
|
+
runtimeProfileTurnControlTraceAttrs(control) {
|
|
6154
|
+
if (!control) return {};
|
|
6155
|
+
const pendingAgeMs = Math.max(0, Date.now() - control.injectedAtMs);
|
|
6156
|
+
return {
|
|
6157
|
+
runtime_profile_control_kind: control.kind,
|
|
6158
|
+
runtime_profile_control_source: control.source,
|
|
6159
|
+
runtime_profile_key_hash: control.keyHash || void 0,
|
|
6160
|
+
runtime_profile_key_present: control.keyPresent,
|
|
6161
|
+
runtime_profile_pending_age_ms: pendingAgeMs,
|
|
6162
|
+
runtime_profile_requires_ack: control.kind === "migration",
|
|
6163
|
+
runtime_profile_migration_done_observed: control.kind === "migration" ? control.migrationDoneToolObserved : void 0
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
activateRuntimeProfileTurnControl(ap, control) {
|
|
6167
|
+
ap.runtimeProfileTurnControl = control;
|
|
6168
|
+
}
|
|
6169
|
+
runtimeProfileTurnControlFromMessages(messages, source = "message") {
|
|
6170
|
+
const notifications = messages?.map((message) => runtimeProfileNotificationFromMessage(message)).filter((candidate) => Boolean(candidate)) ?? [];
|
|
6171
|
+
const notification = notifications.find((candidate) => candidate.kind === "migration") ?? notifications[0];
|
|
6172
|
+
if (!notification) return null;
|
|
6173
|
+
return runtimeProfileTurnControl(notification.kind, notification.key, source);
|
|
6174
|
+
}
|
|
6175
|
+
noteRuntimeProfileToolCall(agentId, ap, toolName) {
|
|
6176
|
+
const control = ap.runtimeProfileTurnControl;
|
|
6177
|
+
if (!control || control.kind !== "migration") return;
|
|
6178
|
+
if (!toolName.includes("runtime_profile_migration_done")) return;
|
|
6179
|
+
control.migrationDoneToolObserved = true;
|
|
6180
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration_done_tool.observed", {
|
|
6181
|
+
...this.runtimeProfileTurnControlTraceAttrs(control),
|
|
6182
|
+
tool: toolName
|
|
6183
|
+
});
|
|
6184
|
+
}
|
|
6185
|
+
finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
|
|
6186
|
+
const control = ap.runtimeProfileTurnControl;
|
|
6187
|
+
if (!control) return {};
|
|
6188
|
+
const attrs = this.runtimeProfileTurnControlTraceAttrs(control);
|
|
6189
|
+
if (control.kind === "migration" && !control.migrationDoneToolObserved) {
|
|
6190
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration.turn_without_ack", {
|
|
6191
|
+
...attrs,
|
|
6192
|
+
terminal,
|
|
6193
|
+
inbox_count: ap.inbox.length,
|
|
6194
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
6195
|
+
});
|
|
6196
|
+
}
|
|
6197
|
+
ap.runtimeProfileTurnControl = null;
|
|
6198
|
+
return {
|
|
6199
|
+
...attrs,
|
|
6200
|
+
runtime_profile_turn_terminal: terminal,
|
|
6201
|
+
runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
|
|
6202
|
+
};
|
|
6203
|
+
}
|
|
5917
6204
|
startRuntimeTrace(agentId, ap, reason, messages) {
|
|
5918
6205
|
if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
|
|
6206
|
+
const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
|
|
6207
|
+
if (messageControl) {
|
|
6208
|
+
this.activateRuntimeProfileTurnControl(ap, messageControl);
|
|
6209
|
+
}
|
|
5919
6210
|
const span = this.tracer.startSpan("daemon.runtime.turn", {
|
|
5920
6211
|
surface: "daemon",
|
|
5921
6212
|
kind: "internal",
|
|
@@ -5925,10 +6216,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5925
6216
|
model: ap.config.model,
|
|
5926
6217
|
reason,
|
|
5927
6218
|
hasSession: Boolean(ap.sessionId),
|
|
5928
|
-
...this.messagesTraceAttrs(messages)
|
|
6219
|
+
...this.messagesTraceAttrs(messages),
|
|
6220
|
+
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
5929
6221
|
}
|
|
5930
6222
|
});
|
|
5931
|
-
span.addEvent("daemon.turn.started", {
|
|
6223
|
+
span.addEvent("daemon.turn.started", {
|
|
6224
|
+
reason,
|
|
6225
|
+
...this.messagesTraceAttrs(messages),
|
|
6226
|
+
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
6227
|
+
});
|
|
5932
6228
|
ap.runtimeTraceSpan = span;
|
|
5933
6229
|
return span;
|
|
5934
6230
|
}
|
|
@@ -6034,7 +6330,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6034
6330
|
ageMs: staleForMs,
|
|
6035
6331
|
lastActivity: ap.lastActivity,
|
|
6036
6332
|
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6037
|
-
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
|
|
6333
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6334
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6038
6335
|
});
|
|
6039
6336
|
this.broadcastActivity(agentId, "error", diagnostic.detail);
|
|
6040
6337
|
return true;
|
|
@@ -6065,7 +6362,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6065
6362
|
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6066
6363
|
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6067
6364
|
pendingMessages: ap.inbox.length,
|
|
6068
|
-
recovery: "terminate_for_queued_message"
|
|
6365
|
+
recovery: "terminate_for_queued_message",
|
|
6366
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6069
6367
|
});
|
|
6070
6368
|
ap.expectedTerminationReason = "stalled_recovery";
|
|
6071
6369
|
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
|
|
@@ -6074,6 +6372,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6074
6372
|
);
|
|
6075
6373
|
this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
|
|
6076
6374
|
try {
|
|
6375
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
6376
|
+
stop_source: "stalled_recovery",
|
|
6377
|
+
expectedTerminationReason: "stalled_recovery",
|
|
6378
|
+
queued_messages_count: ap.inbox.length
|
|
6379
|
+
});
|
|
6077
6380
|
ap.process.kill("SIGTERM");
|
|
6078
6381
|
} catch (err) {
|
|
6079
6382
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6122,6 +6425,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6122
6425
|
if (ap) {
|
|
6123
6426
|
ap.gatedSteering.outstandingToolUses++;
|
|
6124
6427
|
this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
6428
|
+
this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
|
|
6125
6429
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
6126
6430
|
this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
|
|
6127
6431
|
event: "tool_call",
|
|
@@ -6211,11 +6515,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6211
6515
|
this.broadcastActivity(agentId, "online", "Idle");
|
|
6212
6516
|
}
|
|
6213
6517
|
}
|
|
6214
|
-
this.endRuntimeTrace(ap, "ok", {
|
|
6518
|
+
this.endRuntimeTrace(ap, "ok", {
|
|
6519
|
+
outcome: "turn-completed",
|
|
6520
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
|
|
6521
|
+
});
|
|
6215
6522
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
6216
6523
|
ap.expectedTerminationReason = "turn_end";
|
|
6217
6524
|
logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
|
|
6218
6525
|
try {
|
|
6526
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
6527
|
+
stop_source: "turn_end",
|
|
6528
|
+
expectedTerminationReason: "turn_end"
|
|
6529
|
+
});
|
|
6219
6530
|
ap.process.kill("SIGTERM");
|
|
6220
6531
|
} catch (err) {
|
|
6221
6532
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6233,6 +6544,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6233
6544
|
this.flushPendingTrajectory(agentId);
|
|
6234
6545
|
if (ap) ap.lastRuntimeError = event.message;
|
|
6235
6546
|
if (ap) {
|
|
6547
|
+
const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
|
|
6236
6548
|
this.setGatedSteeringPhase(agentId, ap, "error", { event: "error" });
|
|
6237
6549
|
if (ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message)) {
|
|
6238
6550
|
this.requeueGatedInFlightBatch(agentId, ap, "thinking_block_mutation_error");
|
|
@@ -6246,8 +6558,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6246
6558
|
`[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
|
|
6247
6559
|
);
|
|
6248
6560
|
}
|
|
6249
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error",
|
|
6250
|
-
this.endRuntimeTrace(ap, "error", {
|
|
6561
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
|
|
6562
|
+
this.endRuntimeTrace(ap, "error", {
|
|
6563
|
+
outcome: "runtime-error",
|
|
6564
|
+
...runtimeErrorDiagnostics.spanAttrs,
|
|
6565
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
6566
|
+
});
|
|
6251
6567
|
if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
|
|
6252
6568
|
ap.isIdle = true;
|
|
6253
6569
|
ap.pendingNotificationCount = 0;
|
|
@@ -6721,11 +7037,10 @@ var ReminderCache = class {
|
|
|
6721
7037
|
};
|
|
6722
7038
|
|
|
6723
7039
|
// src/machineLock.ts
|
|
6724
|
-
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
7040
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
6725
7041
|
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
6726
7042
|
import os6 from "os";
|
|
6727
7043
|
import path12 from "path";
|
|
6728
|
-
var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
|
|
6729
7044
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
6730
7045
|
var DaemonMachineLockConflictError = class extends Error {
|
|
6731
7046
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -6738,11 +7053,14 @@ var DaemonMachineLockConflictError = class extends Error {
|
|
|
6738
7053
|
}
|
|
6739
7054
|
};
|
|
6740
7055
|
function apiKeyFingerprint(apiKey) {
|
|
6741
|
-
return
|
|
7056
|
+
return createHash3("sha256").update(apiKey).digest("hex");
|
|
6742
7057
|
}
|
|
6743
7058
|
function getDaemonMachineLockId(apiKey) {
|
|
6744
7059
|
return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
|
|
6745
7060
|
}
|
|
7061
|
+
function resolveDefaultMachineStateRoot() {
|
|
7062
|
+
return resolveSlockHomePath("machines");
|
|
7063
|
+
}
|
|
6746
7064
|
function ownerPath(lockDir) {
|
|
6747
7065
|
return path12.join(lockDir, "owner.json");
|
|
6748
7066
|
}
|
|
@@ -6771,7 +7089,7 @@ function isProcessAlive(pid) {
|
|
|
6771
7089
|
}
|
|
6772
7090
|
}
|
|
6773
7091
|
function acquireDaemonMachineLock(options) {
|
|
6774
|
-
const rootDir = options.rootDir ??
|
|
7092
|
+
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
6775
7093
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
6776
7094
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
6777
7095
|
const machineDir = path12.join(rootDir, lockId);
|
|
@@ -6827,7 +7145,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
6827
7145
|
}
|
|
6828
7146
|
|
|
6829
7147
|
// src/localTraceSink.ts
|
|
6830
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as
|
|
7148
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
6831
7149
|
import path13 from "path";
|
|
6832
7150
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6833
7151
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
@@ -6844,6 +7162,15 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
|
6844
7162
|
"deliveryCorrelationId",
|
|
6845
7163
|
"delivery_correlation_id"
|
|
6846
7164
|
]);
|
|
7165
|
+
var DIAGNOSTIC_ERROR_ATTRS = /* @__PURE__ */ new Set([
|
|
7166
|
+
"runtime_error_class",
|
|
7167
|
+
"runtime_error_fingerprint",
|
|
7168
|
+
"runtime_error_http_status",
|
|
7169
|
+
"runtime_error_message_present",
|
|
7170
|
+
"runtime_error_message_length_bucket",
|
|
7171
|
+
"runtime_error_message_truncated",
|
|
7172
|
+
"runtime_error_message_excerpt"
|
|
7173
|
+
]);
|
|
6847
7174
|
var LocalRotatingTraceSink = class {
|
|
6848
7175
|
traceDir;
|
|
6849
7176
|
maxFileBytes;
|
|
@@ -6896,7 +7223,7 @@ var LocalRotatingTraceSink = class {
|
|
|
6896
7223
|
}
|
|
6897
7224
|
}
|
|
6898
7225
|
pruneOldFiles() {
|
|
6899
|
-
const files =
|
|
7226
|
+
const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
|
|
6900
7227
|
const excess = files.length - this.maxFiles;
|
|
6901
7228
|
if (excess <= 0) return;
|
|
6902
7229
|
for (const file of files.slice(0, excess)) {
|
|
@@ -6941,6 +7268,11 @@ function sanitizeAttrs(attrs) {
|
|
|
6941
7268
|
sanitized[key] = sanitizeValue(value);
|
|
6942
7269
|
continue;
|
|
6943
7270
|
}
|
|
7271
|
+
if (isDiagnosticErrorAttr(key)) {
|
|
7272
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
7273
|
+
sanitized[key] = sanitizeValue(value);
|
|
7274
|
+
continue;
|
|
7275
|
+
}
|
|
6944
7276
|
if (shouldDropAttr(key)) continue;
|
|
6945
7277
|
sanitized[key] = sanitizeValue(value);
|
|
6946
7278
|
}
|
|
@@ -6974,9 +7306,12 @@ function shouldDropAttr(key) {
|
|
|
6974
7306
|
function isDiagnosticIdAttr(key) {
|
|
6975
7307
|
return DIAGNOSTIC_ID_ATTRS.has(key);
|
|
6976
7308
|
}
|
|
7309
|
+
function isDiagnosticErrorAttr(key) {
|
|
7310
|
+
return DIAGNOSTIC_ERROR_ATTRS.has(key);
|
|
7311
|
+
}
|
|
6977
7312
|
|
|
6978
7313
|
// src/traceBundleUpload.ts
|
|
6979
|
-
import { createHash as
|
|
7314
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
6980
7315
|
import { gzipSync } from "zlib";
|
|
6981
7316
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
6982
7317
|
import path14 from "path";
|
|
@@ -7106,12 +7441,12 @@ async function uploadWithSignedCapability({
|
|
|
7106
7441
|
}
|
|
7107
7442
|
|
|
7108
7443
|
// src/traceJitter.ts
|
|
7109
|
-
import { createHash as
|
|
7444
|
+
import { createHash as createHash4 } from "crypto";
|
|
7110
7445
|
var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
|
|
7111
7446
|
var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
|
|
7112
7447
|
var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
|
|
7113
7448
|
function computeTraceJitter(lockId) {
|
|
7114
|
-
const seed =
|
|
7449
|
+
const seed = createHash4("sha256").update(lockId).digest();
|
|
7115
7450
|
return {
|
|
7116
7451
|
initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
|
|
7117
7452
|
uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
|
|
@@ -7312,7 +7647,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7312
7647
|
}
|
|
7313
7648
|
};
|
|
7314
7649
|
function sha256Hex(body) {
|
|
7315
|
-
return
|
|
7650
|
+
return createHash5("sha256").update(body).digest("hex");
|
|
7316
7651
|
}
|
|
7317
7652
|
function readPositiveIntegerEnv2(name, fallback) {
|
|
7318
7653
|
const value = process.env[name];
|
|
@@ -7491,6 +7826,7 @@ var DaemonCore = class {
|
|
|
7491
7826
|
daemonVersion;
|
|
7492
7827
|
chatBridgePath;
|
|
7493
7828
|
slockCliPath;
|
|
7829
|
+
slockHome;
|
|
7494
7830
|
runtimeDetector;
|
|
7495
7831
|
agentManager;
|
|
7496
7832
|
connection;
|
|
@@ -7505,6 +7841,8 @@ var DaemonCore = class {
|
|
|
7505
7841
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
7506
7842
|
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
7507
7843
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
7844
|
+
this.slockHome = resolveSlockHome();
|
|
7845
|
+
process.env[SLOCK_HOME_ENV] = this.slockHome;
|
|
7508
7846
|
this.injectedTracer = Boolean(options.tracer);
|
|
7509
7847
|
this.tracer = options.tracer ?? noopTracer;
|
|
7510
7848
|
this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
|
|
@@ -7514,7 +7852,7 @@ var DaemonCore = class {
|
|
|
7514
7852
|
});
|
|
7515
7853
|
let connection;
|
|
7516
7854
|
const agentManagerOptions = {
|
|
7517
|
-
dataDir: options.dataDir,
|
|
7855
|
+
dataDir: options.dataDir ?? resolveSlockHomePath("agents", this.slockHome),
|
|
7518
7856
|
serverUrl: options.serverUrl,
|
|
7519
7857
|
defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
|
|
7520
7858
|
slockCliPath: this.slockCliPath,
|
|
@@ -7536,7 +7874,7 @@ var DaemonCore = class {
|
|
|
7536
7874
|
resolveMachineStateRoot() {
|
|
7537
7875
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
7538
7876
|
if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
|
|
7539
|
-
return
|
|
7877
|
+
return resolveDefaultMachineStateRoot();
|
|
7540
7878
|
}
|
|
7541
7879
|
shouldEnableLocalTrace() {
|
|
7542
7880
|
if (this.injectedTracer) return false;
|
|
@@ -7580,6 +7918,12 @@ var DaemonCore = class {
|
|
|
7580
7918
|
}
|
|
7581
7919
|
start() {
|
|
7582
7920
|
logger.info("[Slock Daemon] Starting...");
|
|
7921
|
+
logger.info(`[Slock Daemon] ${SLOCK_HOME_ENV}=${this.slockHome}`);
|
|
7922
|
+
for (const legacy of listLegacySlockStatePaths(this.slockHome)) {
|
|
7923
|
+
logger.warn(
|
|
7924
|
+
`[Slock Daemon] Legacy Slock state exists outside ${SLOCK_HOME_ENV}: ${legacy.path}. This daemon will use ${legacy.destination}; migrate manually if that ${legacy.description} should move with this installation.`
|
|
7925
|
+
);
|
|
7926
|
+
}
|
|
7583
7927
|
if (!this.machineLock) {
|
|
7584
7928
|
this.machineLock = acquireDaemonMachineLock({
|
|
7585
7929
|
apiKey: this.options.apiKey,
|