@slock-ai/daemon 0.46.2 → 0.47.0-play.20260512173159
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-FG5JGA67.js → chunk-5DPZMLC3.js} +900 -147
- package/dist/{chunk-Z3PCMYZO.js → chunk-B7XIMLOT.js} +44 -1
- package/dist/cli/index.js +14308 -312
- package/dist/core.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +2 -1
|
@@ -1,17 +1,21 @@
|
|
|
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
|
-
import
|
|
14
|
+
import path16 from "path";
|
|
11
15
|
import os7 from "os";
|
|
12
16
|
import { createRequire } from "module";
|
|
13
17
|
import { accessSync } from "fs";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
18
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15
19
|
|
|
16
20
|
// ../shared/src/tracing/index.ts
|
|
17
21
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -576,6 +580,82 @@ 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
|
+
|
|
643
|
+
// ../shared/src/translationLanguages.ts
|
|
644
|
+
var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
|
|
645
|
+
"en",
|
|
646
|
+
"zh-cn",
|
|
647
|
+
"zh-tw",
|
|
648
|
+
"ja",
|
|
649
|
+
"ko",
|
|
650
|
+
"es",
|
|
651
|
+
"fr",
|
|
652
|
+
"de",
|
|
653
|
+
"pt-br"
|
|
654
|
+
];
|
|
655
|
+
var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
|
|
656
|
+
SUPPORTED_TRANSLATION_LANGUAGE_CODES
|
|
657
|
+
);
|
|
658
|
+
|
|
579
659
|
// ../shared/src/testing/failpoints.ts
|
|
580
660
|
var NoopFailpointRegistry = class {
|
|
581
661
|
get enabled() {
|
|
@@ -645,6 +725,7 @@ var SERVER_CAPABILITY_MATRIX = {
|
|
|
645
725
|
var RUNTIMES = [
|
|
646
726
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
647
727
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
728
|
+
{ id: "pi", displayName: "Pi", binary: "pi", supported: true },
|
|
648
729
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
649
730
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
650
731
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -686,9 +767,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
686
767
|
};
|
|
687
768
|
|
|
688
769
|
// src/agentProcessManager.ts
|
|
689
|
-
import { mkdirSync as
|
|
770
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
690
771
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
691
|
-
import
|
|
772
|
+
import { createHash as createHash2 } from "crypto";
|
|
773
|
+
import path12 from "path";
|
|
692
774
|
import os5 from "os";
|
|
693
775
|
|
|
694
776
|
// src/drivers/claude.ts
|
|
@@ -781,21 +863,23 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
781
863
|
6. **\`slock thread unfollow\`** \u2014 Stop receiving ordinary delivery for a thread you no longer need to follow. This only affects your own agent attention state.
|
|
782
864
|
7. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
|
|
783
865
|
8. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
784
|
-
9. **\`slock
|
|
785
|
-
10. **\`slock task
|
|
786
|
-
11. **\`slock task
|
|
787
|
-
12. **\`slock task
|
|
788
|
-
13. **\`slock task
|
|
789
|
-
14. **\`slock
|
|
790
|
-
15. **\`slock attachment
|
|
791
|
-
16. **\`slock
|
|
792
|
-
17. **\`slock profile
|
|
793
|
-
18. **\`slock
|
|
794
|
-
19. **\`slock reminder
|
|
795
|
-
20. **\`slock reminder
|
|
796
|
-
21. **\`slock reminder
|
|
797
|
-
22. **\`slock reminder
|
|
798
|
-
23. **\`slock reminder
|
|
866
|
+
9. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
|
|
867
|
+
10. **\`slock task list\`** \u2014 View a channel's task board.
|
|
868
|
+
11. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
869
|
+
12. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
870
|
+
13. **\`slock task unclaim\`** \u2014 Release your claim on a task.
|
|
871
|
+
14. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
|
|
872
|
+
15. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
|
|
873
|
+
16. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
874
|
+
17. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
875
|
+
18. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
876
|
+
19. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
877
|
+
20. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
878
|
+
21. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
879
|
+
22. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
880
|
+
23. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
881
|
+
24. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
882
|
+
25. **\`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
883
|
|
|
800
884
|
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
885
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -1093,6 +1177,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1093
1177
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1094
1178
|
- When done, summarize the result.
|
|
1095
1179
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1180
|
+
- For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
|
|
1181
|
+
|
|
1182
|
+
\`\`\`html
|
|
1183
|
+
<details>
|
|
1184
|
+
<summary>Evidence, logs, or edge cases</summary>
|
|
1185
|
+
|
|
1186
|
+
Detailed notes go here.
|
|
1187
|
+
</details>
|
|
1188
|
+
\`\`\`
|
|
1189
|
+
|
|
1190
|
+
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1096
1191
|
|
|
1097
1192
|
### Conversation etiquette
|
|
1098
1193
|
|
|
@@ -1272,6 +1367,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1272
1367
|
...ctx.config.envVars || {},
|
|
1273
1368
|
...extraEnv,
|
|
1274
1369
|
...runtimeContextEnv(ctx.config),
|
|
1370
|
+
[SLOCK_HOME_ENV]: resolveSlockHome(),
|
|
1275
1371
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
1276
1372
|
...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
|
|
1277
1373
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
@@ -1705,7 +1801,7 @@ var ClaudeDriver = class {
|
|
|
1705
1801
|
|
|
1706
1802
|
// src/drivers/codex.ts
|
|
1707
1803
|
import { spawn as spawn2, execSync } from "child_process";
|
|
1708
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1804
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1709
1805
|
import os2 from "os";
|
|
1710
1806
|
import path4 from "path";
|
|
1711
1807
|
function getCodexNotificationErrorMessage(params) {
|
|
@@ -1734,11 +1830,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
|
1734
1830
|
);
|
|
1735
1831
|
}
|
|
1736
1832
|
var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
1833
|
+
function resolveWindowsSandboxRunner(deps = {}) {
|
|
1834
|
+
const homeDir = deps.homeDir ?? os2.homedir();
|
|
1835
|
+
const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
|
|
1836
|
+
if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
|
|
1837
|
+
try {
|
|
1838
|
+
const files = readdirSync2(sandboxBinDir);
|
|
1839
|
+
const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
|
|
1840
|
+
if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
|
|
1841
|
+
} catch {
|
|
1842
|
+
}
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1737
1845
|
function resolveCodexCommand(deps = {}) {
|
|
1738
1846
|
const pathCommand = resolveCommandOnPath("codex", deps);
|
|
1739
1847
|
if (pathCommand) return pathCommand;
|
|
1740
|
-
|
|
1741
|
-
|
|
1848
|
+
const platform = deps.platform ?? process.platform;
|
|
1849
|
+
if (platform === "darwin") {
|
|
1850
|
+
return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
|
|
1851
|
+
}
|
|
1852
|
+
if (platform === "win32") {
|
|
1853
|
+
return resolveWindowsSandboxRunner(deps);
|
|
1854
|
+
}
|
|
1855
|
+
return null;
|
|
1742
1856
|
}
|
|
1743
1857
|
function probeCodex(deps = {}) {
|
|
1744
1858
|
if ((deps.platform ?? process.platform) === "win32") {
|
|
@@ -1779,6 +1893,13 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1779
1893
|
}
|
|
1780
1894
|
}
|
|
1781
1895
|
if (!codexEntry) {
|
|
1896
|
+
const sandboxRunner = resolveWindowsSandboxRunner(deps);
|
|
1897
|
+
if (sandboxRunner) {
|
|
1898
|
+
return {
|
|
1899
|
+
command: sandboxRunner,
|
|
1900
|
+
args: commandArgs
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1782
1903
|
throw new Error(
|
|
1783
1904
|
"Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
|
|
1784
1905
|
);
|
|
@@ -2228,6 +2349,15 @@ function detectCodexModels(home = os2.homedir()) {
|
|
|
2228
2349
|
import { spawn as spawn3 } from "child_process";
|
|
2229
2350
|
import path5 from "path";
|
|
2230
2351
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2352
|
+
function buildCopilotSpawnEnv(ctx) {
|
|
2353
|
+
return {
|
|
2354
|
+
...process.env,
|
|
2355
|
+
FORCE_COLOR: "0",
|
|
2356
|
+
NO_COLOR: "1",
|
|
2357
|
+
...ctx.config.envVars || {},
|
|
2358
|
+
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2231
2361
|
var CopilotDriver = class {
|
|
2232
2362
|
id = "copilot";
|
|
2233
2363
|
lifecycle = {
|
|
@@ -2286,7 +2416,7 @@ var CopilotDriver = class {
|
|
|
2286
2416
|
if (ctx.config.sessionId) {
|
|
2287
2417
|
args.push(`--resume=${ctx.config.sessionId}`);
|
|
2288
2418
|
}
|
|
2289
|
-
const spawnEnv =
|
|
2419
|
+
const spawnEnv = buildCopilotSpawnEnv(ctx);
|
|
2290
2420
|
const proc = spawn3("copilot", args, {
|
|
2291
2421
|
cwd: ctx.workingDirectory,
|
|
2292
2422
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2383,6 +2513,15 @@ var CopilotDriver = class {
|
|
|
2383
2513
|
import { spawn as spawn4, spawnSync } from "child_process";
|
|
2384
2514
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
2385
2515
|
import path6 from "path";
|
|
2516
|
+
function buildCursorSpawnEnv(ctx) {
|
|
2517
|
+
return {
|
|
2518
|
+
...process.env,
|
|
2519
|
+
FORCE_COLOR: "0",
|
|
2520
|
+
NO_COLOR: "1",
|
|
2521
|
+
...ctx.config.envVars || {},
|
|
2522
|
+
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2523
|
+
};
|
|
2524
|
+
}
|
|
2386
2525
|
var CursorDriver = class {
|
|
2387
2526
|
id = "cursor";
|
|
2388
2527
|
lifecycle = {
|
|
@@ -2437,7 +2576,7 @@ var CursorDriver = class {
|
|
|
2437
2576
|
args.push("--resume", ctx.config.sessionId);
|
|
2438
2577
|
}
|
|
2439
2578
|
args.push(ctx.prompt);
|
|
2440
|
-
const spawnEnv =
|
|
2579
|
+
const spawnEnv = buildCursorSpawnEnv(ctx);
|
|
2441
2580
|
const proc = spawn4("cursor-agent", args, {
|
|
2442
2581
|
cwd: ctx.workingDirectory,
|
|
2443
2582
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2772,13 +2911,16 @@ var GeminiDriver = class {
|
|
|
2772
2911
|
// src/drivers/kimi.ts
|
|
2773
2912
|
import { randomUUID } from "crypto";
|
|
2774
2913
|
import { spawn as spawn6 } from "child_process";
|
|
2775
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2914
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2776
2915
|
import os3 from "os";
|
|
2777
2916
|
import path8 from "path";
|
|
2778
2917
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
2779
2918
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
2780
2919
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
2781
2920
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
2921
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
2922
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
2923
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
2782
2924
|
function parseToolArguments(raw) {
|
|
2783
2925
|
if (typeof raw !== "string") return raw;
|
|
2784
2926
|
try {
|
|
@@ -2787,6 +2929,73 @@ function parseToolArguments(raw) {
|
|
|
2787
2929
|
return raw;
|
|
2788
2930
|
}
|
|
2789
2931
|
}
|
|
2932
|
+
function readKimiConfigSource(home = os3.homedir(), env = process.env) {
|
|
2933
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2934
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
2935
|
+
return {
|
|
2936
|
+
raw: inlineConfig,
|
|
2937
|
+
explicitPath: null,
|
|
2938
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2942
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
|
|
2943
|
+
try {
|
|
2944
|
+
return {
|
|
2945
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
2946
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2947
|
+
sourcePath: configPath
|
|
2948
|
+
};
|
|
2949
|
+
} catch {
|
|
2950
|
+
return {
|
|
2951
|
+
raw: null,
|
|
2952
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2953
|
+
sourcePath: configPath
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
2958
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2959
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2960
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2961
|
+
return spawnEnv;
|
|
2962
|
+
}
|
|
2963
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
2964
|
+
return {
|
|
2965
|
+
...process.env,
|
|
2966
|
+
...ctx.config.envVars || {},
|
|
2967
|
+
...overrideEnv || {}
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
2971
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
2972
|
+
const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
|
|
2973
|
+
const args = [];
|
|
2974
|
+
let configFilePath = null;
|
|
2975
|
+
let configContent = null;
|
|
2976
|
+
if (source.explicitPath) {
|
|
2977
|
+
configFilePath = source.explicitPath;
|
|
2978
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
2979
|
+
configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
2980
|
+
configContent = source.raw;
|
|
2981
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
2982
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
2983
|
+
chmodSync(configFilePath, 384);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
if (configFilePath) {
|
|
2987
|
+
args.push("--config-file", configFilePath);
|
|
2988
|
+
}
|
|
2989
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2990
|
+
args.push("--model", ctx.config.model);
|
|
2991
|
+
}
|
|
2992
|
+
return {
|
|
2993
|
+
args,
|
|
2994
|
+
env: buildKimiSpawnEnv(env),
|
|
2995
|
+
configFilePath,
|
|
2996
|
+
configContent
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2790
2999
|
var KimiDriver = class {
|
|
2791
3000
|
id = "kimi";
|
|
2792
3001
|
lifecycle = {
|
|
@@ -2803,7 +3012,25 @@ var KimiDriver = class {
|
|
|
2803
3012
|
};
|
|
2804
3013
|
model = {
|
|
2805
3014
|
detectedModelsVerifiedAs: "launchable",
|
|
2806
|
-
toLaunchSpec: (modelId) =>
|
|
3015
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3016
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3017
|
+
const launchCtx = {
|
|
3018
|
+
...ctx,
|
|
3019
|
+
config: {
|
|
3020
|
+
...ctx.config,
|
|
3021
|
+
model: modelId
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3025
|
+
home: opts?.home,
|
|
3026
|
+
writeGeneratedConfig: false
|
|
3027
|
+
});
|
|
3028
|
+
return {
|
|
3029
|
+
args: launch.args,
|
|
3030
|
+
env: launch.env,
|
|
3031
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
2807
3034
|
};
|
|
2808
3035
|
supportsStdinNotification = true;
|
|
2809
3036
|
mcpToolPrefix = "";
|
|
@@ -2855,6 +3082,7 @@ var KimiDriver = class {
|
|
|
2855
3082
|
}
|
|
2856
3083
|
}
|
|
2857
3084
|
}), "utf8");
|
|
3085
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
2858
3086
|
const args = [
|
|
2859
3087
|
"--wire",
|
|
2860
3088
|
"--yolo",
|
|
@@ -2863,16 +3091,13 @@ var KimiDriver = class {
|
|
|
2863
3091
|
"--mcp-config-file",
|
|
2864
3092
|
mcpConfigPath,
|
|
2865
3093
|
"--session",
|
|
2866
|
-
this.sessionId
|
|
3094
|
+
this.sessionId,
|
|
3095
|
+
...launch.args
|
|
2867
3096
|
];
|
|
2868
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2869
|
-
args.push("--model", ctx.config.model);
|
|
2870
|
-
}
|
|
2871
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2872
3097
|
const proc = spawn6("kimi", args, {
|
|
2873
3098
|
cwd: ctx.workingDirectory,
|
|
2874
3099
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2875
|
-
env:
|
|
3100
|
+
env: launch.env,
|
|
2876
3101
|
shell: process.platform === "win32"
|
|
2877
3102
|
});
|
|
2878
3103
|
proc.stdin?.write(JSON.stringify({
|
|
@@ -2989,14 +3214,9 @@ var KimiDriver = class {
|
|
|
2989
3214
|
return detectKimiModels();
|
|
2990
3215
|
}
|
|
2991
3216
|
};
|
|
2992
|
-
function detectKimiModels(home = os3.homedir()) {
|
|
2993
|
-
const
|
|
2994
|
-
|
|
2995
|
-
try {
|
|
2996
|
-
raw = readFileSync3(configPath, "utf8");
|
|
2997
|
-
} catch {
|
|
2998
|
-
return null;
|
|
2999
|
-
}
|
|
3217
|
+
function detectKimiModels(home = os3.homedir(), opts = {}) {
|
|
3218
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3219
|
+
if (raw === null) return null;
|
|
3000
3220
|
const models = [];
|
|
3001
3221
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3002
3222
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3399,6 +3619,304 @@ var OpenCodeDriver = class {
|
|
|
3399
3619
|
}
|
|
3400
3620
|
};
|
|
3401
3621
|
|
|
3622
|
+
// src/drivers/pi.ts
|
|
3623
|
+
import { spawn as spawn8, spawnSync as spawnSync3 } from "child_process";
|
|
3624
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3625
|
+
import { fileURLToPath } from "url";
|
|
3626
|
+
import path10 from "path";
|
|
3627
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
3628
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
3629
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
3630
|
+
var MIN_BUNDLED_PI_VERSION = "0.74.0";
|
|
3631
|
+
function defaultPackageResolver(specifier) {
|
|
3632
|
+
return import.meta.resolve(specifier);
|
|
3633
|
+
}
|
|
3634
|
+
function parseSemver2(version) {
|
|
3635
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
3636
|
+
if (!match) return null;
|
|
3637
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
3638
|
+
}
|
|
3639
|
+
function isSupportedPiVersion(version) {
|
|
3640
|
+
if (!version) return true;
|
|
3641
|
+
const actual = parseSemver2(version);
|
|
3642
|
+
const minimum = parseSemver2(MIN_BUNDLED_PI_VERSION);
|
|
3643
|
+
if (!actual || !minimum) return true;
|
|
3644
|
+
for (let i = 0; i < 3; i += 1) {
|
|
3645
|
+
if (actual[i] > minimum[i]) return true;
|
|
3646
|
+
if (actual[i] < minimum[i]) return false;
|
|
3647
|
+
}
|
|
3648
|
+
return true;
|
|
3649
|
+
}
|
|
3650
|
+
function resolveBundledPiCliPath(resolver = defaultPackageResolver) {
|
|
3651
|
+
try {
|
|
3652
|
+
const mainPath = fileURLToPath(resolver("@earendil-works/pi-coding-agent"));
|
|
3653
|
+
const cliPath = path10.join(path10.dirname(mainPath), "cli.js");
|
|
3654
|
+
return existsSync7(cliPath) ? cliPath : null;
|
|
3655
|
+
} catch {
|
|
3656
|
+
return null;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
function readBundledPiVersion(cliPath) {
|
|
3660
|
+
const result = spawnSync3(process.execPath, [cliPath, "--version"], {
|
|
3661
|
+
encoding: "utf8",
|
|
3662
|
+
timeout: 5e3
|
|
3663
|
+
});
|
|
3664
|
+
if (result.error || result.status !== 0) return null;
|
|
3665
|
+
return String(result.stdout || result.stderr || "").trim() || null;
|
|
3666
|
+
}
|
|
3667
|
+
function unsupportedPiVersionMessage(version) {
|
|
3668
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
3669
|
+
return `Bundled Pi CLI ${version} is unsupported; requires Pi >= ${MIN_BUNDLED_PI_VERSION}.`;
|
|
3670
|
+
}
|
|
3671
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
3672
|
+
const cliPath = opts.cliPath ?? resolveBundledPiCliPath();
|
|
3673
|
+
if (!cliPath) {
|
|
3674
|
+
throw new Error("Bundled Pi CLI not found in @earendil-works/pi-coding-agent");
|
|
3675
|
+
}
|
|
3676
|
+
const piDir = path10.join(ctx.workingDirectory, ".slock", "pi");
|
|
3677
|
+
const sessionDir = path10.join(piDir, "sessions");
|
|
3678
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
3679
|
+
const slock = prepareCliTransport(ctx, {
|
|
3680
|
+
NO_COLOR: "1",
|
|
3681
|
+
PI_CODING_AGENT_DIR: piDir,
|
|
3682
|
+
PI_CODING_AGENT_SESSION_DIR: sessionDir
|
|
3683
|
+
});
|
|
3684
|
+
const systemPromptPath = path10.join(slock.slockDir, "pi-system-prompt.md");
|
|
3685
|
+
writeFileSync7(systemPromptPath, ctx.standingPrompt, { encoding: "utf8", mode: 384 });
|
|
3686
|
+
const args = [
|
|
3687
|
+
cliPath,
|
|
3688
|
+
"--mode",
|
|
3689
|
+
"json",
|
|
3690
|
+
"--session-dir",
|
|
3691
|
+
sessionDir,
|
|
3692
|
+
"--append-system-prompt",
|
|
3693
|
+
systemPromptPath,
|
|
3694
|
+
"--no-context-files",
|
|
3695
|
+
"--no-extensions",
|
|
3696
|
+
"--no-skills",
|
|
3697
|
+
"--no-prompt-templates",
|
|
3698
|
+
"--no-themes"
|
|
3699
|
+
];
|
|
3700
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3701
|
+
args.push("--model", ctx.config.model);
|
|
3702
|
+
}
|
|
3703
|
+
if (ctx.config.sessionId) {
|
|
3704
|
+
args.push("--session", ctx.config.sessionId);
|
|
3705
|
+
}
|
|
3706
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
3707
|
+
args.push(turnPrompt);
|
|
3708
|
+
return {
|
|
3709
|
+
command: process.execPath,
|
|
3710
|
+
args,
|
|
3711
|
+
env: slock.spawnEnv,
|
|
3712
|
+
sessionDir,
|
|
3713
|
+
systemPromptPath
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function isSystemFirstMessageTask2(message) {
|
|
3717
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
3718
|
+
}
|
|
3719
|
+
function buildPiSystemPrompt(config) {
|
|
3720
|
+
return buildCliTransportSystemPrompt(config, {
|
|
3721
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
3722
|
+
extraCriticalRules: [
|
|
3723
|
+
"- Runtime Profile migration controls are not available in the Pi runtime yet. If asked to acknowledge a runtime migration, explain the blocker instead of inventing a command."
|
|
3724
|
+
],
|
|
3725
|
+
postStartupNotes: [
|
|
3726
|
+
"**Pi runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
|
|
3727
|
+
],
|
|
3728
|
+
includeStdinNotificationSection: false,
|
|
3729
|
+
messageNotificationStyle: "poll"
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
function contentText(content) {
|
|
3733
|
+
if (!content) return "";
|
|
3734
|
+
const chunks = [];
|
|
3735
|
+
for (const item of content) {
|
|
3736
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
3737
|
+
chunks.push(item.text);
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
return chunks.join("");
|
|
3741
|
+
}
|
|
3742
|
+
function apiKeyErrorMessage(line) {
|
|
3743
|
+
const trimmed = line.trim();
|
|
3744
|
+
if (!trimmed) return null;
|
|
3745
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
3746
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
3747
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
3748
|
+
return null;
|
|
3749
|
+
}
|
|
3750
|
+
var PiDriver = class {
|
|
3751
|
+
id = "pi";
|
|
3752
|
+
lifecycle = {
|
|
3753
|
+
kind: "per_turn",
|
|
3754
|
+
start: "defer_until_concrete_message",
|
|
3755
|
+
exit: "terminate_on_turn_end",
|
|
3756
|
+
inFlightWake: "coalesce_into_pending"
|
|
3757
|
+
};
|
|
3758
|
+
communication = {
|
|
3759
|
+
chat: "slock_cli",
|
|
3760
|
+
runtimeControl: "none"
|
|
3761
|
+
};
|
|
3762
|
+
session = {
|
|
3763
|
+
recovery: "resume_or_fresh"
|
|
3764
|
+
};
|
|
3765
|
+
model = {
|
|
3766
|
+
detectedModelsVerifiedAs: "launchable",
|
|
3767
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
3768
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3769
|
+
const launchCtx = {
|
|
3770
|
+
...ctx,
|
|
3771
|
+
config: {
|
|
3772
|
+
...ctx.config,
|
|
3773
|
+
model: modelId
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
3777
|
+
return {
|
|
3778
|
+
args: launch.args,
|
|
3779
|
+
env: launch.env,
|
|
3780
|
+
configFiles: [launch.systemPromptPath]
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
};
|
|
3784
|
+
supportsStdinNotification = false;
|
|
3785
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
3786
|
+
busyDeliveryMode = "none";
|
|
3787
|
+
terminateProcessOnTurnEnd = true;
|
|
3788
|
+
deferSpawnUntilMessage = true;
|
|
3789
|
+
usesSlockCliForCommunication = true;
|
|
3790
|
+
sessionId = null;
|
|
3791
|
+
sessionAnnounced = false;
|
|
3792
|
+
apiKeyErrorAnnounced = false;
|
|
3793
|
+
turnEnded = false;
|
|
3794
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
3795
|
+
shouldDeferWakeMessage(message) {
|
|
3796
|
+
return isSystemFirstMessageTask2(message);
|
|
3797
|
+
}
|
|
3798
|
+
probe() {
|
|
3799
|
+
const cliPath = resolveBundledPiCliPath();
|
|
3800
|
+
if (!cliPath) return { available: false };
|
|
3801
|
+
const version = readBundledPiVersion(cliPath) || void 0;
|
|
3802
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
3803
|
+
if (unsupportedMessage) {
|
|
3804
|
+
return {
|
|
3805
|
+
available: false,
|
|
3806
|
+
version: `${version} (requires >= ${MIN_BUNDLED_PI_VERSION})`
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
return { available: true, version };
|
|
3810
|
+
}
|
|
3811
|
+
async detectModels() {
|
|
3812
|
+
return null;
|
|
3813
|
+
}
|
|
3814
|
+
spawn(ctx) {
|
|
3815
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
3816
|
+
this.sessionAnnounced = false;
|
|
3817
|
+
this.apiKeyErrorAnnounced = false;
|
|
3818
|
+
this.turnEnded = false;
|
|
3819
|
+
this.assistantTextByMessageId.clear();
|
|
3820
|
+
const cliPath = resolveBundledPiCliPath();
|
|
3821
|
+
if (!cliPath) throw new Error("Bundled Pi CLI not found in @earendil-works/pi-coding-agent");
|
|
3822
|
+
const unsupportedMessage = unsupportedPiVersionMessage(readBundledPiVersion(cliPath));
|
|
3823
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
3824
|
+
const launch = buildPiLaunchOptions(ctx, { cliPath });
|
|
3825
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
3826
|
+
cwd: ctx.workingDirectory,
|
|
3827
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3828
|
+
env: launch.env,
|
|
3829
|
+
shell: process.platform === "win32"
|
|
3830
|
+
});
|
|
3831
|
+
proc.stdin?.end();
|
|
3832
|
+
return { process: proc };
|
|
3833
|
+
}
|
|
3834
|
+
parseLine(line) {
|
|
3835
|
+
let event;
|
|
3836
|
+
try {
|
|
3837
|
+
event = JSON.parse(line);
|
|
3838
|
+
} catch {
|
|
3839
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
3840
|
+
const message = apiKeyErrorMessage(line);
|
|
3841
|
+
if (!message) return [];
|
|
3842
|
+
this.apiKeyErrorAnnounced = true;
|
|
3843
|
+
this.turnEnded = true;
|
|
3844
|
+
return [
|
|
3845
|
+
{ kind: "error", message },
|
|
3846
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
3847
|
+
];
|
|
3848
|
+
}
|
|
3849
|
+
const events = [];
|
|
3850
|
+
if (event.type === "session" && event.id) {
|
|
3851
|
+
this.sessionId = event.id;
|
|
3852
|
+
}
|
|
3853
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
3854
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
3855
|
+
this.sessionAnnounced = true;
|
|
3856
|
+
}
|
|
3857
|
+
switch (event.type) {
|
|
3858
|
+
case "agent_start":
|
|
3859
|
+
case "turn_start":
|
|
3860
|
+
events.push({ kind: "thinking", text: "" });
|
|
3861
|
+
break;
|
|
3862
|
+
case "message_update":
|
|
3863
|
+
case "message_end":
|
|
3864
|
+
if (event.message?.role === "assistant") {
|
|
3865
|
+
const key = event.message.id || "current";
|
|
3866
|
+
const currentText = contentText(event.message.content);
|
|
3867
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
3868
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
3869
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
3870
|
+
} else if (currentText && currentText !== previousText) {
|
|
3871
|
+
events.push({ kind: "text", text: currentText });
|
|
3872
|
+
}
|
|
3873
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
3874
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
3875
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
break;
|
|
3879
|
+
case "tool_execution_start":
|
|
3880
|
+
events.push({
|
|
3881
|
+
kind: "tool_call",
|
|
3882
|
+
name: event.toolName || "unknown_tool",
|
|
3883
|
+
input: event.args
|
|
3884
|
+
});
|
|
3885
|
+
break;
|
|
3886
|
+
case "tool_execution_end":
|
|
3887
|
+
events.push({
|
|
3888
|
+
kind: "tool_output",
|
|
3889
|
+
name: event.toolName || "unknown_tool"
|
|
3890
|
+
});
|
|
3891
|
+
if (event.isError) {
|
|
3892
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
3893
|
+
}
|
|
3894
|
+
break;
|
|
3895
|
+
case "compaction_start":
|
|
3896
|
+
events.push({ kind: "compaction_started" });
|
|
3897
|
+
break;
|
|
3898
|
+
case "compaction_end":
|
|
3899
|
+
events.push({ kind: "compaction_finished" });
|
|
3900
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
3901
|
+
break;
|
|
3902
|
+
case "turn_end":
|
|
3903
|
+
case "agent_end":
|
|
3904
|
+
if (!this.turnEnded) {
|
|
3905
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
3906
|
+
this.turnEnded = true;
|
|
3907
|
+
}
|
|
3908
|
+
break;
|
|
3909
|
+
}
|
|
3910
|
+
return events;
|
|
3911
|
+
}
|
|
3912
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
3913
|
+
return null;
|
|
3914
|
+
}
|
|
3915
|
+
buildSystemPrompt(config, _agentId) {
|
|
3916
|
+
return buildPiSystemPrompt(config);
|
|
3917
|
+
}
|
|
3918
|
+
};
|
|
3919
|
+
|
|
3402
3920
|
// src/drivers/index.ts
|
|
3403
3921
|
var driverFactories = {
|
|
3404
3922
|
claude: () => new ClaudeDriver(),
|
|
@@ -3407,7 +3925,8 @@ var driverFactories = {
|
|
|
3407
3925
|
cursor: () => new CursorDriver(),
|
|
3408
3926
|
gemini: () => new GeminiDriver(),
|
|
3409
3927
|
kimi: () => new KimiDriver(),
|
|
3410
|
-
opencode: () => new OpenCodeDriver()
|
|
3928
|
+
opencode: () => new OpenCodeDriver(),
|
|
3929
|
+
pi: () => new PiDriver()
|
|
3411
3930
|
};
|
|
3412
3931
|
function getDriver(runtimeId) {
|
|
3413
3932
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -3420,7 +3939,7 @@ function getDriver(runtimeId) {
|
|
|
3420
3939
|
|
|
3421
3940
|
// src/workspaces.ts
|
|
3422
3941
|
import { readdir, rm, stat } from "fs/promises";
|
|
3423
|
-
import
|
|
3942
|
+
import path11 from "path";
|
|
3424
3943
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
3425
3944
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
3426
3945
|
}
|
|
@@ -3428,7 +3947,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
3428
3947
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
3429
3948
|
return null;
|
|
3430
3949
|
}
|
|
3431
|
-
return
|
|
3950
|
+
return path11.join(dataDir, directoryName);
|
|
3432
3951
|
}
|
|
3433
3952
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
3434
3953
|
return {
|
|
@@ -3477,7 +3996,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
3477
3996
|
return summary;
|
|
3478
3997
|
}
|
|
3479
3998
|
const childSummaries = await Promise.all(
|
|
3480
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
3999
|
+
entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
|
|
3481
4000
|
);
|
|
3482
4001
|
for (const childSummary of childSummaries) {
|
|
3483
4002
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -3496,7 +4015,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
3496
4015
|
if (!entry.isDirectory()) {
|
|
3497
4016
|
return null;
|
|
3498
4017
|
}
|
|
3499
|
-
const dirPath =
|
|
4018
|
+
const dirPath = path11.join(dataDir, entry.name);
|
|
3500
4019
|
try {
|
|
3501
4020
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
3502
4021
|
return {
|
|
@@ -3527,8 +4046,86 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
3527
4046
|
}
|
|
3528
4047
|
}
|
|
3529
4048
|
|
|
4049
|
+
// src/runtimeErrorDiagnostics.ts
|
|
4050
|
+
import { createHash } from "crypto";
|
|
4051
|
+
var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
|
|
4052
|
+
function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
4053
|
+
const rawMessage = String(message || "");
|
|
4054
|
+
const scrubbed = scrubRuntimeErrorDiagnosticText(rawMessage);
|
|
4055
|
+
const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
|
|
4056
|
+
const httpStatus = extractHttpStatus(rawMessage);
|
|
4057
|
+
const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
|
|
4058
|
+
const fingerprint = fingerprintRuntimeError(scrubbed);
|
|
4059
|
+
const spanAttrs = {
|
|
4060
|
+
runtime_error_class: runtimeErrorClass,
|
|
4061
|
+
runtime_error_fingerprint: fingerprint,
|
|
4062
|
+
runtime_error_message_present: rawMessage.length > 0,
|
|
4063
|
+
runtime_error_message_length_bucket: bucketLength(rawMessage.length),
|
|
4064
|
+
runtime_error_message_truncated: truncated
|
|
4065
|
+
};
|
|
4066
|
+
if (httpStatus !== null) {
|
|
4067
|
+
spanAttrs.runtime_error_http_status = httpStatus;
|
|
4068
|
+
}
|
|
4069
|
+
return {
|
|
4070
|
+
spanAttrs,
|
|
4071
|
+
eventAttrs: {
|
|
4072
|
+
...spanAttrs,
|
|
4073
|
+
runtime_error_message_excerpt: excerpt
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
function scrubRuntimeErrorDiagnosticText(value) {
|
|
4078
|
+
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]");
|
|
4079
|
+
}
|
|
4080
|
+
function truncateDiagnosticText(value, maxChars) {
|
|
4081
|
+
if (value.length <= maxChars) return { value, truncated: false };
|
|
4082
|
+
return { value: value.slice(0, maxChars), truncated: true };
|
|
4083
|
+
}
|
|
4084
|
+
function extractHttpStatus(message) {
|
|
4085
|
+
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);
|
|
4086
|
+
if (!match) return null;
|
|
4087
|
+
const status = Number(match[1]);
|
|
4088
|
+
return Number.isInteger(status) ? status : null;
|
|
4089
|
+
}
|
|
4090
|
+
function classifyRuntimeError(message, httpStatus) {
|
|
4091
|
+
const explicit = /\b([A-Z][A-Za-z0-9_]*(?:Error|Exception))\b/.exec(message);
|
|
4092
|
+
if (explicit) return explicit[1];
|
|
4093
|
+
if (httpStatus !== null) {
|
|
4094
|
+
if (httpStatus === 429) return "RateLimitError";
|
|
4095
|
+
if (httpStatus === 401 || httpStatus === 403) return "AuthError";
|
|
4096
|
+
if (httpStatus === 404) return "NotFoundError";
|
|
4097
|
+
if (httpStatus >= 500) return "ProviderServerError";
|
|
4098
|
+
return "ProviderApiError";
|
|
4099
|
+
}
|
|
4100
|
+
if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
|
|
4101
|
+
if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
|
|
4102
|
+
if (/\bnot found\b/i.test(message)) return "NotFoundError";
|
|
4103
|
+
return "RuntimeError";
|
|
4104
|
+
}
|
|
4105
|
+
function fingerprintRuntimeError(value) {
|
|
4106
|
+
const normalized = value.toLowerCase().replace(/[0-9a-f]{12,}/g, "<hex>").replace(/\b\d+\b/g, "<num>").replace(/\s+/g, " ").trim();
|
|
4107
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
4108
|
+
}
|
|
4109
|
+
function bucketLength(length) {
|
|
4110
|
+
if (length === 0) return "0";
|
|
4111
|
+
if (length < 1024) return "<1k";
|
|
4112
|
+
if (length < 4096) return "1k-4k";
|
|
4113
|
+
if (length < 16384) return "4k-16k";
|
|
4114
|
+
return "16k+";
|
|
4115
|
+
}
|
|
4116
|
+
function redactUrlQuery(value) {
|
|
4117
|
+
try {
|
|
4118
|
+
const url = new URL(value);
|
|
4119
|
+
if (url.search) url.search = "?[REDACTED_QUERY]";
|
|
4120
|
+
if (url.username) url.username = "[REDACTED_USER]";
|
|
4121
|
+
if (url.password) url.password = "[REDACTED_PASSWORD]";
|
|
4122
|
+
return url.toString();
|
|
4123
|
+
} catch {
|
|
4124
|
+
return value.replace(/\?.*$/, "?[REDACTED_QUERY]");
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
|
|
3530
4128
|
// src/agentProcessManager.ts
|
|
3531
|
-
var DATA_DIR = path11.join(os5.homedir(), ".slock", "agents");
|
|
3532
4129
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
3533
4130
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
3534
4131
|
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
@@ -3609,19 +4206,19 @@ function findSessionJsonl(root, predicate) {
|
|
|
3609
4206
|
if (depth < 0 || visited >= maxEntries) return null;
|
|
3610
4207
|
let entries;
|
|
3611
4208
|
try {
|
|
3612
|
-
entries =
|
|
4209
|
+
entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3613
4210
|
} catch {
|
|
3614
4211
|
return null;
|
|
3615
4212
|
}
|
|
3616
4213
|
for (const entry of entries) {
|
|
3617
4214
|
if (++visited > maxEntries) return null;
|
|
3618
4215
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
3619
|
-
return
|
|
4216
|
+
return path12.join(dir, entry.name);
|
|
3620
4217
|
}
|
|
3621
4218
|
for (const entry of entries) {
|
|
3622
4219
|
if (++visited > maxEntries) return null;
|
|
3623
4220
|
if (!entry.isDirectory()) continue;
|
|
3624
|
-
const found = visit(
|
|
4221
|
+
const found = visit(path12.join(dir, entry.name), depth - 1);
|
|
3625
4222
|
if (found) return found;
|
|
3626
4223
|
}
|
|
3627
4224
|
return null;
|
|
@@ -3634,10 +4231,10 @@ function safeSessionFilename(value) {
|
|
|
3634
4231
|
}
|
|
3635
4232
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
3636
4233
|
try {
|
|
3637
|
-
const dir =
|
|
3638
|
-
|
|
3639
|
-
const filePath =
|
|
3640
|
-
|
|
4234
|
+
const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
|
|
4235
|
+
mkdirSync5(dir, { recursive: true });
|
|
4236
|
+
const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
4237
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
3641
4238
|
type: "runtime_session_handoff",
|
|
3642
4239
|
runtime,
|
|
3643
4240
|
sessionId,
|
|
@@ -3656,7 +4253,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
3656
4253
|
}
|
|
3657
4254
|
}
|
|
3658
4255
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
3659
|
-
const directPath =
|
|
4256
|
+
const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
|
|
3660
4257
|
if (directPath) {
|
|
3661
4258
|
try {
|
|
3662
4259
|
if (statSync2(directPath).isFile()) {
|
|
@@ -3665,7 +4262,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
|
|
|
3665
4262
|
} catch {
|
|
3666
4263
|
}
|
|
3667
4264
|
}
|
|
3668
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
4265
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
3669
4266
|
if (!resolvedPath && fallbackDir) {
|
|
3670
4267
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
3671
4268
|
if (fallback) return fallback;
|
|
@@ -3857,15 +4454,27 @@ After confirming language preference, do not give a generic product introduction
|
|
|
3857
4454
|
- Use a low-pressure prompt and guide to one concrete starter action.
|
|
3858
4455
|
|
|
3859
4456
|
### Starter Plan Output
|
|
3860
|
-
A starter plan should make the next action executable, not just descriptive
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
-
|
|
3864
|
-
-
|
|
4457
|
+
A starter plan should make the next action executable, not just descriptive.
|
|
4458
|
+
For new channels, new agents, and adding members to an existing channel, post an **action card** rather than a copyable spec:
|
|
4459
|
+
|
|
4460
|
+
- 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.
|
|
4461
|
+
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
4462
|
+
- \`{type: "agent:create", name, description?, draftHint?}\`
|
|
4463
|
+
- \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty
|
|
4464
|
+
- 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.
|
|
4465
|
+
- 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).
|
|
4466
|
+
- 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.
|
|
4467
|
+
- 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".
|
|
4468
|
+
- Do not imply the resource has been created or members added until the card flips to "Done".
|
|
4469
|
+
|
|
4470
|
+
Other plan elements still apply:
|
|
4471
|
+
- suggested channel or workstream pairing
|
|
4472
|
+
- first task to send after the card is committed
|
|
4473
|
+
- who to pull in once the channel exists (often a follow-up \`channel:add_member\` card)
|
|
3865
4474
|
|
|
3866
4475
|
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
|
|
4476
|
+
If details are missing but not blocking, state reasonable defaults inside the action card payload (the owner can edit) and invite correction in chat.
|
|
4477
|
+
Only ask one blocking question first if the answer is required before any useful card can be prepared.
|
|
3869
4478
|
Do not imply you have already created agents or channels unless the action has actually happened.
|
|
3870
4479
|
|
|
3871
4480
|
### Capability Boundary Pivot
|
|
@@ -4103,16 +4712,22 @@ Do not copy these answers verbatim.
|
|
|
4103
4712
|
|
|
4104
4713
|
## FAQ 15: How do I create agents or channels?
|
|
4105
4714
|
### Answer idea
|
|
4106
|
-
-
|
|
4107
|
-
-
|
|
4108
|
-
-
|
|
4715
|
+
- 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.
|
|
4716
|
+
- v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
|
|
4717
|
+
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
4718
|
+
- \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
|
|
4719
|
+
- \`{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.
|
|
4720
|
+
|
|
4721
|
+
- **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.
|
|
4722
|
+
- 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
4723
|
|
|
4110
4724
|
### Next step
|
|
4111
|
-
-
|
|
4725
|
+
- 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
4726
|
|
|
4113
4727
|
### Guardrail
|
|
4114
|
-
- Do not imply you already created agents or channels unless
|
|
4115
|
-
-
|
|
4728
|
+
- Do not imply you already created agents or channels unless the card state is \`executed\`.
|
|
4729
|
+
- Do not prefill technical fields on \`agent:create\` (runtime / model / computer / reasoning effort). The owner picks those in the dialog.
|
|
4730
|
+
- 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
4731
|
`;
|
|
4117
4732
|
}
|
|
4118
4733
|
function buildOnboardingSeedFiles() {
|
|
@@ -4212,6 +4827,20 @@ function runtimeProfileNotificationFromMessage(message) {
|
|
|
4212
4827
|
function runtimeProfileNotificationTitle(kind) {
|
|
4213
4828
|
return kind === "migration" ? "Runtime Profile migration" : "Runtime Profile notice";
|
|
4214
4829
|
}
|
|
4830
|
+
function hashRuntimeProfileKey(key) {
|
|
4831
|
+
if (!key) return void 0;
|
|
4832
|
+
return createHash2("sha256").update(key).digest("hex").slice(0, 16);
|
|
4833
|
+
}
|
|
4834
|
+
function runtimeProfileTurnControl(kind, key, source) {
|
|
4835
|
+
return {
|
|
4836
|
+
kind,
|
|
4837
|
+
source,
|
|
4838
|
+
keyHash: hashRuntimeProfileKey(key) ?? null,
|
|
4839
|
+
keyPresent: Boolean(key),
|
|
4840
|
+
injectedAtMs: Date.now(),
|
|
4841
|
+
migrationDoneToolObserved: false
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4215
4844
|
function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
|
|
4216
4845
|
const next = [...lines];
|
|
4217
4846
|
for (const rawLine of chunk.split(/\r?\n/)) {
|
|
@@ -4400,13 +5029,14 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4400
5029
|
defaultAgentEnvVarsProvider;
|
|
4401
5030
|
tracer;
|
|
4402
5031
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
5032
|
+
processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
4403
5033
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
4404
5034
|
this.chatBridgePath = chatBridgePath;
|
|
4405
5035
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
4406
5036
|
this.sendToServer = sendToServer;
|
|
4407
5037
|
this.daemonApiKey = daemonApiKey;
|
|
4408
5038
|
this.serverUrl = opts.serverUrl;
|
|
4409
|
-
this.dataDir = opts.dataDir ||
|
|
5039
|
+
this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
|
|
4410
5040
|
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
|
|
4411
5041
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
4412
5042
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
@@ -4651,26 +5281,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4651
5281
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4652
5282
|
try {
|
|
4653
5283
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
4654
|
-
const agentDataDir =
|
|
5284
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
4655
5285
|
await mkdir(agentDataDir, { recursive: true });
|
|
4656
5286
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
4657
|
-
const memoryMdPath =
|
|
5287
|
+
const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
|
|
4658
5288
|
try {
|
|
4659
5289
|
await access(memoryMdPath);
|
|
4660
5290
|
} catch {
|
|
4661
5291
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
4662
5292
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
4663
5293
|
}
|
|
4664
|
-
const notesDir =
|
|
5294
|
+
const notesDir = path12.join(agentDataDir, "notes");
|
|
4665
5295
|
await mkdir(notesDir, { recursive: true });
|
|
4666
5296
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
4667
5297
|
const seedFiles = buildOnboardingSeedFiles();
|
|
4668
5298
|
for (const { relativePath, content } of seedFiles) {
|
|
4669
|
-
const fullPath =
|
|
5299
|
+
const fullPath = path12.join(agentDataDir, relativePath);
|
|
4670
5300
|
try {
|
|
4671
5301
|
await access(fullPath);
|
|
4672
5302
|
} catch {
|
|
4673
|
-
await mkdir(
|
|
5303
|
+
await mkdir(path12.dirname(fullPath), { recursive: true });
|
|
4674
5304
|
await writeFile(fullPath, content);
|
|
4675
5305
|
}
|
|
4676
5306
|
}
|
|
@@ -4798,6 +5428,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4798
5428
|
exitCode: null,
|
|
4799
5429
|
exitSignal: null,
|
|
4800
5430
|
expectedTerminationReason: null,
|
|
5431
|
+
runtimeProfileTurnControl: runtimeConfig.runtimeProfileControl ? runtimeProfileTurnControl(runtimeConfig.runtimeProfileControl.kind, runtimeConfig.runtimeProfileControl.key, "agent_config") : null,
|
|
4801
5432
|
pendingTrajectory: null,
|
|
4802
5433
|
gatedSteering: createGatedSteeringState()
|
|
4803
5434
|
};
|
|
@@ -4872,7 +5503,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4872
5503
|
clean_exit: code === 0,
|
|
4873
5504
|
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
4874
5505
|
inbox_count: current?.inbox.length ?? 0,
|
|
4875
|
-
pending_notification_count: current?.pendingNotificationCount ?? 0
|
|
5506
|
+
pending_notification_count: current?.pendingNotificationCount ?? 0,
|
|
5507
|
+
...this.processExitTraceAttrs.get(proc)
|
|
4876
5508
|
});
|
|
4877
5509
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
4878
5510
|
});
|
|
@@ -4900,7 +5532,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4900
5532
|
outcome: processEndedCleanly ? "process-exit" : "process-crash",
|
|
4901
5533
|
expectedTerminationReason: ap.expectedTerminationReason || void 0,
|
|
4902
5534
|
exitCode: finalCode,
|
|
4903
|
-
exitSignal: finalSignal
|
|
5535
|
+
exitSignal: finalSignal,
|
|
5536
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
|
|
4904
5537
|
});
|
|
4905
5538
|
if (processEndedCleanly) {
|
|
4906
5539
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
|
|
@@ -5063,6 +5696,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5063
5696
|
agentId,
|
|
5064
5697
|
kind,
|
|
5065
5698
|
key_present: Boolean(key),
|
|
5699
|
+
key_hash: hashRuntimeProfileKey(key),
|
|
5066
5700
|
outcome: ap.sessionId ? "queued_busy" : "queued_before_session",
|
|
5067
5701
|
runtime: ap.config.runtime,
|
|
5068
5702
|
session_id_present: Boolean(ap.sessionId),
|
|
@@ -5085,6 +5719,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5085
5719
|
agentId,
|
|
5086
5720
|
kind,
|
|
5087
5721
|
key_present: Boolean(key),
|
|
5722
|
+
key_hash: hashRuntimeProfileKey(key),
|
|
5088
5723
|
outcome: "queued_during_start",
|
|
5089
5724
|
startup_pending: true,
|
|
5090
5725
|
starting_inbox_count: pending.length,
|
|
@@ -5121,6 +5756,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5121
5756
|
}
|
|
5122
5757
|
this.clearCompactionWatchdog(ap);
|
|
5123
5758
|
this.agents.delete(agentId);
|
|
5759
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
5760
|
+
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
5761
|
+
stop_wait_requested: wait,
|
|
5762
|
+
stop_silent: silent
|
|
5763
|
+
});
|
|
5124
5764
|
ap.process.kill("SIGTERM");
|
|
5125
5765
|
if (!silent) {
|
|
5126
5766
|
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
@@ -5320,7 +5960,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5320
5960
|
return true;
|
|
5321
5961
|
}
|
|
5322
5962
|
async resetWorkspace(agentId) {
|
|
5323
|
-
const agentDataDir =
|
|
5963
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
5324
5964
|
try {
|
|
5325
5965
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
5326
5966
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -5357,7 +5997,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5357
5997
|
return result;
|
|
5358
5998
|
}
|
|
5359
5999
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
5360
|
-
const workspacePath =
|
|
6000
|
+
const workspacePath = path12.join(this.dataDir, agentId);
|
|
5361
6001
|
return {
|
|
5362
6002
|
agentId,
|
|
5363
6003
|
launchId,
|
|
@@ -5411,7 +6051,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5411
6051
|
attrs: {
|
|
5412
6052
|
agentId,
|
|
5413
6053
|
control_kind: kind,
|
|
5414
|
-
key_present: Boolean(key)
|
|
6054
|
+
key_present: Boolean(key),
|
|
6055
|
+
key_hash: hashRuntimeProfileKey(key)
|
|
5415
6056
|
}
|
|
5416
6057
|
});
|
|
5417
6058
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5444,7 +6085,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5444
6085
|
}
|
|
5445
6086
|
if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
|
|
5446
6087
|
ap.isIdle = false;
|
|
5447
|
-
this.startRuntimeTrace(agentId, ap, "runtime-profile");
|
|
6088
|
+
this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
|
|
5448
6089
|
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
|
|
5449
6090
|
span.end(written ? "ok" : "error", {
|
|
5450
6091
|
attrs: {
|
|
@@ -5539,6 +6180,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5539
6180
|
agentId,
|
|
5540
6181
|
control_kind: control.kind,
|
|
5541
6182
|
key_present: Boolean(control.key),
|
|
6183
|
+
key_hash: hashRuntimeProfileKey(control.key),
|
|
5542
6184
|
launchId: launchId || void 0,
|
|
5543
6185
|
source: "agent_config"
|
|
5544
6186
|
}
|
|
@@ -5603,7 +6245,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5603
6245
|
}
|
|
5604
6246
|
// Workspace file browsing
|
|
5605
6247
|
async getFileTree(agentId, dirPath) {
|
|
5606
|
-
const agentDir =
|
|
6248
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
5607
6249
|
try {
|
|
5608
6250
|
await stat2(agentDir);
|
|
5609
6251
|
} catch {
|
|
@@ -5611,8 +6253,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5611
6253
|
}
|
|
5612
6254
|
let targetDir = agentDir;
|
|
5613
6255
|
if (dirPath) {
|
|
5614
|
-
const resolved =
|
|
5615
|
-
if (!resolved.startsWith(agentDir +
|
|
6256
|
+
const resolved = path12.resolve(agentDir, dirPath);
|
|
6257
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
5616
6258
|
return [];
|
|
5617
6259
|
}
|
|
5618
6260
|
targetDir = resolved;
|
|
@@ -5620,14 +6262,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5620
6262
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
5621
6263
|
}
|
|
5622
6264
|
async readFile(agentId, filePath) {
|
|
5623
|
-
const agentDir =
|
|
5624
|
-
const resolved =
|
|
5625
|
-
if (!resolved.startsWith(agentDir +
|
|
6265
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
6266
|
+
const resolved = path12.resolve(agentDir, filePath);
|
|
6267
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
5626
6268
|
throw new Error("Access denied");
|
|
5627
6269
|
}
|
|
5628
6270
|
const info = await stat2(resolved);
|
|
5629
6271
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
5630
|
-
const ext =
|
|
6272
|
+
const ext = path12.extname(resolved).toLowerCase();
|
|
5631
6273
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
5632
6274
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
5633
6275
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -5662,13 +6304,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5662
6304
|
const agent = this.agents.get(agentId);
|
|
5663
6305
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
5664
6306
|
const home = os5.homedir();
|
|
5665
|
-
const workspaceDir =
|
|
6307
|
+
const workspaceDir = path12.join(this.dataDir, agentId);
|
|
5666
6308
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
5667
6309
|
const globalResults = await Promise.all(
|
|
5668
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
6310
|
+
paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
|
|
5669
6311
|
);
|
|
5670
6312
|
const workspaceResults = await Promise.all(
|
|
5671
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
6313
|
+
paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
|
|
5672
6314
|
);
|
|
5673
6315
|
const dedup = (skills) => {
|
|
5674
6316
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5697,7 +6339,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5697
6339
|
const skills = [];
|
|
5698
6340
|
for (const entry of entries) {
|
|
5699
6341
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
5700
|
-
const skillMd =
|
|
6342
|
+
const skillMd = path12.join(dir, entry.name, "SKILL.md");
|
|
5701
6343
|
try {
|
|
5702
6344
|
const content = await readFile(skillMd, "utf-8");
|
|
5703
6345
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -5708,7 +6350,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5708
6350
|
} else if (entry.name.endsWith(".md")) {
|
|
5709
6351
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
5710
6352
|
try {
|
|
5711
|
-
const content = await readFile(
|
|
6353
|
+
const content = await readFile(path12.join(dir, entry.name), "utf-8");
|
|
5712
6354
|
const skill = this.parseSkillMd(cmdName, content);
|
|
5713
6355
|
skill.sourcePath = dir;
|
|
5714
6356
|
skills.push(skill);
|
|
@@ -5903,7 +6545,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5903
6545
|
deliveryId: context.deliveryId,
|
|
5904
6546
|
delivery_correlation_id: context.deliveryId,
|
|
5905
6547
|
control_kind: notification.kind,
|
|
5906
|
-
key_present: Boolean(notification.key)
|
|
6548
|
+
key_present: Boolean(notification.key),
|
|
6549
|
+
runtime_profile_control_kind: notification.kind,
|
|
6550
|
+
runtime_profile_key_hash: hashRuntimeProfileKey(notification.key),
|
|
6551
|
+
runtime_profile_key_present: Boolean(notification.key)
|
|
5907
6552
|
};
|
|
5908
6553
|
}
|
|
5909
6554
|
return {
|
|
@@ -5914,8 +6559,63 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5914
6559
|
delivery_correlation_id: context.deliveryId ?? first.message_id
|
|
5915
6560
|
};
|
|
5916
6561
|
}
|
|
6562
|
+
runtimeProfileTurnControlTraceAttrs(control) {
|
|
6563
|
+
if (!control) return {};
|
|
6564
|
+
const pendingAgeMs = Math.max(0, Date.now() - control.injectedAtMs);
|
|
6565
|
+
return {
|
|
6566
|
+
runtime_profile_control_kind: control.kind,
|
|
6567
|
+
runtime_profile_control_source: control.source,
|
|
6568
|
+
runtime_profile_key_hash: control.keyHash || void 0,
|
|
6569
|
+
runtime_profile_key_present: control.keyPresent,
|
|
6570
|
+
runtime_profile_pending_age_ms: pendingAgeMs,
|
|
6571
|
+
runtime_profile_requires_ack: control.kind === "migration",
|
|
6572
|
+
runtime_profile_migration_done_observed: control.kind === "migration" ? control.migrationDoneToolObserved : void 0
|
|
6573
|
+
};
|
|
6574
|
+
}
|
|
6575
|
+
activateRuntimeProfileTurnControl(ap, control) {
|
|
6576
|
+
ap.runtimeProfileTurnControl = control;
|
|
6577
|
+
}
|
|
6578
|
+
runtimeProfileTurnControlFromMessages(messages, source = "message") {
|
|
6579
|
+
const notifications = messages?.map((message) => runtimeProfileNotificationFromMessage(message)).filter((candidate) => Boolean(candidate)) ?? [];
|
|
6580
|
+
const notification = notifications.find((candidate) => candidate.kind === "migration") ?? notifications[0];
|
|
6581
|
+
if (!notification) return null;
|
|
6582
|
+
return runtimeProfileTurnControl(notification.kind, notification.key, source);
|
|
6583
|
+
}
|
|
6584
|
+
noteRuntimeProfileToolCall(agentId, ap, toolName) {
|
|
6585
|
+
const control = ap.runtimeProfileTurnControl;
|
|
6586
|
+
if (!control || control.kind !== "migration") return;
|
|
6587
|
+
if (!toolName.includes("runtime_profile_migration_done")) return;
|
|
6588
|
+
control.migrationDoneToolObserved = true;
|
|
6589
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration_done_tool.observed", {
|
|
6590
|
+
...this.runtimeProfileTurnControlTraceAttrs(control),
|
|
6591
|
+
tool: toolName
|
|
6592
|
+
});
|
|
6593
|
+
}
|
|
6594
|
+
finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
|
|
6595
|
+
const control = ap.runtimeProfileTurnControl;
|
|
6596
|
+
if (!control) return {};
|
|
6597
|
+
const attrs = this.runtimeProfileTurnControlTraceAttrs(control);
|
|
6598
|
+
if (control.kind === "migration" && !control.migrationDoneToolObserved) {
|
|
6599
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration.turn_without_ack", {
|
|
6600
|
+
...attrs,
|
|
6601
|
+
terminal,
|
|
6602
|
+
inbox_count: ap.inbox.length,
|
|
6603
|
+
pending_notification_count: ap.pendingNotificationCount
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
6606
|
+
ap.runtimeProfileTurnControl = null;
|
|
6607
|
+
return {
|
|
6608
|
+
...attrs,
|
|
6609
|
+
runtime_profile_turn_terminal: terminal,
|
|
6610
|
+
runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
5917
6613
|
startRuntimeTrace(agentId, ap, reason, messages) {
|
|
5918
6614
|
if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
|
|
6615
|
+
const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
|
|
6616
|
+
if (messageControl) {
|
|
6617
|
+
this.activateRuntimeProfileTurnControl(ap, messageControl);
|
|
6618
|
+
}
|
|
5919
6619
|
const span = this.tracer.startSpan("daemon.runtime.turn", {
|
|
5920
6620
|
surface: "daemon",
|
|
5921
6621
|
kind: "internal",
|
|
@@ -5925,10 +6625,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5925
6625
|
model: ap.config.model,
|
|
5926
6626
|
reason,
|
|
5927
6627
|
hasSession: Boolean(ap.sessionId),
|
|
5928
|
-
...this.messagesTraceAttrs(messages)
|
|
6628
|
+
...this.messagesTraceAttrs(messages),
|
|
6629
|
+
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
5929
6630
|
}
|
|
5930
6631
|
});
|
|
5931
|
-
span.addEvent("daemon.turn.started", {
|
|
6632
|
+
span.addEvent("daemon.turn.started", {
|
|
6633
|
+
reason,
|
|
6634
|
+
...this.messagesTraceAttrs(messages),
|
|
6635
|
+
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
6636
|
+
});
|
|
5932
6637
|
ap.runtimeTraceSpan = span;
|
|
5933
6638
|
return span;
|
|
5934
6639
|
}
|
|
@@ -6034,7 +6739,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6034
6739
|
ageMs: staleForMs,
|
|
6035
6740
|
lastActivity: ap.lastActivity,
|
|
6036
6741
|
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6037
|
-
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail)
|
|
6742
|
+
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6743
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6038
6744
|
});
|
|
6039
6745
|
this.broadcastActivity(agentId, "error", diagnostic.detail);
|
|
6040
6746
|
return true;
|
|
@@ -6065,7 +6771,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6065
6771
|
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6066
6772
|
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6067
6773
|
pendingMessages: ap.inbox.length,
|
|
6068
|
-
recovery: "terminate_for_queued_message"
|
|
6774
|
+
recovery: "terminate_for_queued_message",
|
|
6775
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6069
6776
|
});
|
|
6070
6777
|
ap.expectedTerminationReason = "stalled_recovery";
|
|
6071
6778
|
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : ap.driver.id;
|
|
@@ -6074,6 +6781,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6074
6781
|
);
|
|
6075
6782
|
this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
|
|
6076
6783
|
try {
|
|
6784
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
6785
|
+
stop_source: "stalled_recovery",
|
|
6786
|
+
expectedTerminationReason: "stalled_recovery",
|
|
6787
|
+
queued_messages_count: ap.inbox.length
|
|
6788
|
+
});
|
|
6077
6789
|
ap.process.kill("SIGTERM");
|
|
6078
6790
|
} catch (err) {
|
|
6079
6791
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6122,6 +6834,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6122
6834
|
if (ap) {
|
|
6123
6835
|
ap.gatedSteering.outstandingToolUses++;
|
|
6124
6836
|
this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
6837
|
+
this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
|
|
6125
6838
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
6126
6839
|
this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
|
|
6127
6840
|
event: "tool_call",
|
|
@@ -6211,11 +6924,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6211
6924
|
this.broadcastActivity(agentId, "online", "Idle");
|
|
6212
6925
|
}
|
|
6213
6926
|
}
|
|
6214
|
-
this.endRuntimeTrace(ap, "ok", {
|
|
6927
|
+
this.endRuntimeTrace(ap, "ok", {
|
|
6928
|
+
outcome: "turn-completed",
|
|
6929
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
|
|
6930
|
+
});
|
|
6215
6931
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
6216
6932
|
ap.expectedTerminationReason = "turn_end";
|
|
6217
6933
|
logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
|
|
6218
6934
|
try {
|
|
6935
|
+
this.processExitTraceAttrs.set(ap.process, {
|
|
6936
|
+
stop_source: "turn_end",
|
|
6937
|
+
expectedTerminationReason: "turn_end"
|
|
6938
|
+
});
|
|
6219
6939
|
ap.process.kill("SIGTERM");
|
|
6220
6940
|
} catch (err) {
|
|
6221
6941
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6233,6 +6953,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6233
6953
|
this.flushPendingTrajectory(agentId);
|
|
6234
6954
|
if (ap) ap.lastRuntimeError = event.message;
|
|
6235
6955
|
if (ap) {
|
|
6956
|
+
const runtimeErrorDiagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
|
|
6236
6957
|
this.setGatedSteeringPhase(agentId, ap, "error", { event: "error" });
|
|
6237
6958
|
if (ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message)) {
|
|
6238
6959
|
this.requeueGatedInFlightBatch(agentId, ap, "thinking_block_mutation_error");
|
|
@@ -6246,8 +6967,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6246
6967
|
`[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
|
|
6247
6968
|
);
|
|
6248
6969
|
}
|
|
6249
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error",
|
|
6250
|
-
this.endRuntimeTrace(ap, "error", {
|
|
6970
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
|
|
6971
|
+
this.endRuntimeTrace(ap, "error", {
|
|
6972
|
+
outcome: "runtime-error",
|
|
6973
|
+
...runtimeErrorDiagnostics.spanAttrs,
|
|
6974
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
6975
|
+
});
|
|
6251
6976
|
if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
|
|
6252
6977
|
ap.isIdle = true;
|
|
6253
6978
|
ap.pendingNotificationCount = 0;
|
|
@@ -6418,8 +7143,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
6418
7143
|
const nodes = [];
|
|
6419
7144
|
for (const entry of entries) {
|
|
6420
7145
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
6421
|
-
const fullPath =
|
|
6422
|
-
const relativePath =
|
|
7146
|
+
const fullPath = path12.join(dir, entry.name);
|
|
7147
|
+
const relativePath = path12.relative(rootDir, fullPath);
|
|
6423
7148
|
let info;
|
|
6424
7149
|
try {
|
|
6425
7150
|
info = await stat2(fullPath);
|
|
@@ -6721,11 +7446,10 @@ var ReminderCache = class {
|
|
|
6721
7446
|
};
|
|
6722
7447
|
|
|
6723
7448
|
// src/machineLock.ts
|
|
6724
|
-
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
6725
|
-
import { mkdirSync as
|
|
7449
|
+
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
7450
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
6726
7451
|
import os6 from "os";
|
|
6727
|
-
import
|
|
6728
|
-
var DEFAULT_MACHINE_STATE_ROOT = path12.join(os6.homedir(), ".slock", "machines");
|
|
7452
|
+
import path13 from "path";
|
|
6729
7453
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
6730
7454
|
var DaemonMachineLockConflictError = class extends Error {
|
|
6731
7455
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -6738,13 +7462,16 @@ var DaemonMachineLockConflictError = class extends Error {
|
|
|
6738
7462
|
}
|
|
6739
7463
|
};
|
|
6740
7464
|
function apiKeyFingerprint(apiKey) {
|
|
6741
|
-
return
|
|
7465
|
+
return createHash3("sha256").update(apiKey).digest("hex");
|
|
6742
7466
|
}
|
|
6743
7467
|
function getDaemonMachineLockId(apiKey) {
|
|
6744
7468
|
return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
|
|
6745
7469
|
}
|
|
7470
|
+
function resolveDefaultMachineStateRoot() {
|
|
7471
|
+
return resolveSlockHomePath("machines");
|
|
7472
|
+
}
|
|
6746
7473
|
function ownerPath(lockDir) {
|
|
6747
|
-
return
|
|
7474
|
+
return path13.join(lockDir, "owner.json");
|
|
6748
7475
|
}
|
|
6749
7476
|
function readOwner(lockDir) {
|
|
6750
7477
|
try {
|
|
@@ -6771,16 +7498,16 @@ function isProcessAlive(pid) {
|
|
|
6771
7498
|
}
|
|
6772
7499
|
}
|
|
6773
7500
|
function acquireDaemonMachineLock(options) {
|
|
6774
|
-
const rootDir = options.rootDir ??
|
|
7501
|
+
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
6775
7502
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
6776
7503
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
6777
|
-
const machineDir =
|
|
6778
|
-
const lockDir =
|
|
7504
|
+
const machineDir = path13.join(rootDir, lockId);
|
|
7505
|
+
const lockDir = path13.join(machineDir, "daemon.lock");
|
|
6779
7506
|
const token = randomUUID2();
|
|
6780
|
-
|
|
7507
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
6781
7508
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
6782
7509
|
try {
|
|
6783
|
-
|
|
7510
|
+
mkdirSync6(lockDir);
|
|
6784
7511
|
const owner = {
|
|
6785
7512
|
pid: process.pid,
|
|
6786
7513
|
token,
|
|
@@ -6790,7 +7517,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
6790
7517
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
6791
7518
|
};
|
|
6792
7519
|
try {
|
|
6793
|
-
|
|
7520
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
6794
7521
|
`, { mode: 384 });
|
|
6795
7522
|
} catch (err) {
|
|
6796
7523
|
rmSync2(lockDir, { recursive: true, force: true });
|
|
@@ -6827,8 +7554,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
6827
7554
|
}
|
|
6828
7555
|
|
|
6829
7556
|
// src/localTraceSink.ts
|
|
6830
|
-
import { appendFileSync, mkdirSync as
|
|
6831
|
-
import
|
|
7557
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
7558
|
+
import path14 from "path";
|
|
6832
7559
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6833
7560
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
6834
7561
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -6844,6 +7571,15 @@ var DIAGNOSTIC_ID_ATTRS = /* @__PURE__ */ new Set([
|
|
|
6844
7571
|
"deliveryCorrelationId",
|
|
6845
7572
|
"delivery_correlation_id"
|
|
6846
7573
|
]);
|
|
7574
|
+
var DIAGNOSTIC_ERROR_ATTRS = /* @__PURE__ */ new Set([
|
|
7575
|
+
"runtime_error_class",
|
|
7576
|
+
"runtime_error_fingerprint",
|
|
7577
|
+
"runtime_error_http_status",
|
|
7578
|
+
"runtime_error_message_present",
|
|
7579
|
+
"runtime_error_message_length_bucket",
|
|
7580
|
+
"runtime_error_message_truncated",
|
|
7581
|
+
"runtime_error_message_excerpt"
|
|
7582
|
+
]);
|
|
6847
7583
|
var LocalRotatingTraceSink = class {
|
|
6848
7584
|
traceDir;
|
|
6849
7585
|
maxFileBytes;
|
|
@@ -6855,7 +7591,7 @@ var LocalRotatingTraceSink = class {
|
|
|
6855
7591
|
currentSize = 0;
|
|
6856
7592
|
sequence = 0;
|
|
6857
7593
|
constructor(options) {
|
|
6858
|
-
this.traceDir =
|
|
7594
|
+
this.traceDir = path14.join(options.machineDir, "traces");
|
|
6859
7595
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
6860
7596
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
6861
7597
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -6881,26 +7617,26 @@ var LocalRotatingTraceSink = class {
|
|
|
6881
7617
|
return this.currentFile;
|
|
6882
7618
|
}
|
|
6883
7619
|
ensureFile(nextBytes) {
|
|
6884
|
-
|
|
7620
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
6885
7621
|
const nowMs = this.nowMsProvider();
|
|
6886
7622
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
6887
7623
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
6888
|
-
this.currentFile =
|
|
7624
|
+
this.currentFile = path14.join(
|
|
6889
7625
|
this.traceDir,
|
|
6890
7626
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
6891
7627
|
);
|
|
6892
|
-
|
|
7628
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
6893
7629
|
this.currentSize = statSync4(this.currentFile).size;
|
|
6894
7630
|
this.currentFileOpenedAtMs = nowMs;
|
|
6895
7631
|
this.pruneOldFiles();
|
|
6896
7632
|
}
|
|
6897
7633
|
}
|
|
6898
7634
|
pruneOldFiles() {
|
|
6899
|
-
const files =
|
|
7635
|
+
const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
|
|
6900
7636
|
const excess = files.length - this.maxFiles;
|
|
6901
7637
|
if (excess <= 0) return;
|
|
6902
7638
|
for (const file of files.slice(0, excess)) {
|
|
6903
|
-
rmSync3(
|
|
7639
|
+
rmSync3(path14.join(this.traceDir, file), { force: true });
|
|
6904
7640
|
}
|
|
6905
7641
|
}
|
|
6906
7642
|
};
|
|
@@ -6941,6 +7677,11 @@ function sanitizeAttrs(attrs) {
|
|
|
6941
7677
|
sanitized[key] = sanitizeValue(value);
|
|
6942
7678
|
continue;
|
|
6943
7679
|
}
|
|
7680
|
+
if (isDiagnosticErrorAttr(key)) {
|
|
7681
|
+
if (value === null || value === void 0 || value === "") continue;
|
|
7682
|
+
sanitized[key] = sanitizeValue(value);
|
|
7683
|
+
continue;
|
|
7684
|
+
}
|
|
6944
7685
|
if (shouldDropAttr(key)) continue;
|
|
6945
7686
|
sanitized[key] = sanitizeValue(value);
|
|
6946
7687
|
}
|
|
@@ -6974,16 +7715,19 @@ function shouldDropAttr(key) {
|
|
|
6974
7715
|
function isDiagnosticIdAttr(key) {
|
|
6975
7716
|
return DIAGNOSTIC_ID_ATTRS.has(key);
|
|
6976
7717
|
}
|
|
7718
|
+
function isDiagnosticErrorAttr(key) {
|
|
7719
|
+
return DIAGNOSTIC_ERROR_ATTRS.has(key);
|
|
7720
|
+
}
|
|
6977
7721
|
|
|
6978
7722
|
// src/traceBundleUpload.ts
|
|
6979
|
-
import { createHash as
|
|
7723
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
6980
7724
|
import { gzipSync } from "zlib";
|
|
6981
7725
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
6982
|
-
import
|
|
7726
|
+
import path15 from "path";
|
|
6983
7727
|
|
|
6984
7728
|
// src/directUploadCapability.ts
|
|
6985
|
-
function joinUrl(base,
|
|
6986
|
-
return `${base.replace(/\/+$/, "")}${
|
|
7729
|
+
function joinUrl(base, path17) {
|
|
7730
|
+
return `${base.replace(/\/+$/, "")}${path17}`;
|
|
6987
7731
|
}
|
|
6988
7732
|
function jsonHeaders(apiKey) {
|
|
6989
7733
|
return {
|
|
@@ -7106,12 +7850,12 @@ async function uploadWithSignedCapability({
|
|
|
7106
7850
|
}
|
|
7107
7851
|
|
|
7108
7852
|
// src/traceJitter.ts
|
|
7109
|
-
import { createHash as
|
|
7853
|
+
import { createHash as createHash4 } from "crypto";
|
|
7110
7854
|
var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
|
|
7111
7855
|
var UPLOAD_INTERVAL_JITTER_SPAN_MS = 6e4;
|
|
7112
7856
|
var MAX_FILE_AGE_JITTER_SPAN_MS = 6e4;
|
|
7113
7857
|
function computeTraceJitter(lockId) {
|
|
7114
|
-
const seed =
|
|
7858
|
+
const seed = createHash4("sha256").update(lockId).digest();
|
|
7115
7859
|
return {
|
|
7116
7860
|
initialUploadDelayMs: seed.readUInt32BE(0) % INITIAL_UPLOAD_DELAY_SPAN_MS,
|
|
7117
7861
|
uploadIntervalJitterMs: seed.readUInt32BE(4) % UPLOAD_INTERVAL_JITTER_SPAN_MS,
|
|
@@ -7202,7 +7946,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7202
7946
|
}, nextMs);
|
|
7203
7947
|
}
|
|
7204
7948
|
async findUploadCandidates() {
|
|
7205
|
-
const traceDir =
|
|
7949
|
+
const traceDir = path15.join(this.options.machineDir, "traces");
|
|
7206
7950
|
let names;
|
|
7207
7951
|
try {
|
|
7208
7952
|
names = await readdir3(traceDir);
|
|
@@ -7214,8 +7958,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
7214
7958
|
const currentFile = this.options.currentFileProvider?.();
|
|
7215
7959
|
const candidates = [];
|
|
7216
7960
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
7217
|
-
const file =
|
|
7218
|
-
if (currentFile &&
|
|
7961
|
+
const file = path15.join(traceDir, name);
|
|
7962
|
+
if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
|
|
7219
7963
|
if (await this.isUploaded(file)) continue;
|
|
7220
7964
|
try {
|
|
7221
7965
|
const info = await stat3(file);
|
|
@@ -7289,8 +8033,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
7289
8033
|
}
|
|
7290
8034
|
}
|
|
7291
8035
|
uploadStatePath(file) {
|
|
7292
|
-
const stateDir =
|
|
7293
|
-
return
|
|
8036
|
+
const stateDir = path15.join(this.options.machineDir, "trace-uploads");
|
|
8037
|
+
return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
|
|
7294
8038
|
}
|
|
7295
8039
|
async isUploaded(file) {
|
|
7296
8040
|
try {
|
|
@@ -7302,9 +8046,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
7302
8046
|
}
|
|
7303
8047
|
async markUploaded(file, metadata) {
|
|
7304
8048
|
const stateFile = this.uploadStatePath(file);
|
|
7305
|
-
await mkdir2(
|
|
8049
|
+
await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
|
|
7306
8050
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
7307
|
-
file:
|
|
8051
|
+
file: path15.basename(file),
|
|
7308
8052
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7309
8053
|
...metadata
|
|
7310
8054
|
}, null, 2)}
|
|
@@ -7312,7 +8056,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7312
8056
|
}
|
|
7313
8057
|
};
|
|
7314
8058
|
function sha256Hex(body) {
|
|
7315
|
-
return
|
|
8059
|
+
return createHash5("sha256").update(body).digest("hex");
|
|
7316
8060
|
}
|
|
7317
8061
|
function readPositiveIntegerEnv2(name, fallback) {
|
|
7318
8062
|
const value = process.env[name];
|
|
@@ -7343,23 +8087,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
7343
8087
|
}
|
|
7344
8088
|
}
|
|
7345
8089
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
7346
|
-
const dirname =
|
|
7347
|
-
const jsPath =
|
|
8090
|
+
const dirname = path16.dirname(fileURLToPath2(moduleUrl));
|
|
8091
|
+
const jsPath = path16.resolve(dirname, "chat-bridge.js");
|
|
7348
8092
|
try {
|
|
7349
8093
|
accessSync(jsPath);
|
|
7350
8094
|
return jsPath;
|
|
7351
8095
|
} catch {
|
|
7352
|
-
return
|
|
8096
|
+
return path16.resolve(dirname, "chat-bridge.ts");
|
|
7353
8097
|
}
|
|
7354
8098
|
}
|
|
7355
8099
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
7356
|
-
const thisDir =
|
|
7357
|
-
const bundledDistPath =
|
|
8100
|
+
const thisDir = path16.dirname(fileURLToPath2(moduleUrl));
|
|
8101
|
+
const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
|
|
7358
8102
|
try {
|
|
7359
8103
|
accessSync(bundledDistPath);
|
|
7360
8104
|
return bundledDistPath;
|
|
7361
8105
|
} catch {
|
|
7362
|
-
const workspaceDistPath =
|
|
8106
|
+
const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
7363
8107
|
accessSync(workspaceDistPath);
|
|
7364
8108
|
return workspaceDistPath;
|
|
7365
8109
|
}
|
|
@@ -7491,6 +8235,7 @@ var DaemonCore = class {
|
|
|
7491
8235
|
daemonVersion;
|
|
7492
8236
|
chatBridgePath;
|
|
7493
8237
|
slockCliPath;
|
|
8238
|
+
slockHome;
|
|
7494
8239
|
runtimeDetector;
|
|
7495
8240
|
agentManager;
|
|
7496
8241
|
connection;
|
|
@@ -7505,6 +8250,8 @@ var DaemonCore = class {
|
|
|
7505
8250
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
7506
8251
|
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
7507
8252
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
8253
|
+
this.slockHome = resolveSlockHome();
|
|
8254
|
+
process.env[SLOCK_HOME_ENV] = this.slockHome;
|
|
7508
8255
|
this.injectedTracer = Boolean(options.tracer);
|
|
7509
8256
|
this.tracer = options.tracer ?? noopTracer;
|
|
7510
8257
|
this.runtimeDetector = options.runtimeDetector ?? (() => detectRuntimes(this.tracer));
|
|
@@ -7514,7 +8261,7 @@ var DaemonCore = class {
|
|
|
7514
8261
|
});
|
|
7515
8262
|
let connection;
|
|
7516
8263
|
const agentManagerOptions = {
|
|
7517
|
-
dataDir: options.dataDir,
|
|
8264
|
+
dataDir: options.dataDir ?? resolveSlockHomePath("agents", this.slockHome),
|
|
7518
8265
|
serverUrl: options.serverUrl,
|
|
7519
8266
|
defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
|
|
7520
8267
|
slockCliPath: this.slockCliPath,
|
|
@@ -7535,8 +8282,8 @@ var DaemonCore = class {
|
|
|
7535
8282
|
}
|
|
7536
8283
|
resolveMachineStateRoot() {
|
|
7537
8284
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
7538
|
-
if (this.options.dataDir) return
|
|
7539
|
-
return
|
|
8285
|
+
if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
|
|
8286
|
+
return resolveDefaultMachineStateRoot();
|
|
7540
8287
|
}
|
|
7541
8288
|
shouldEnableLocalTrace() {
|
|
7542
8289
|
if (this.injectedTracer) return false;
|
|
@@ -7580,6 +8327,12 @@ var DaemonCore = class {
|
|
|
7580
8327
|
}
|
|
7581
8328
|
start() {
|
|
7582
8329
|
logger.info("[Slock Daemon] Starting...");
|
|
8330
|
+
logger.info(`[Slock Daemon] ${SLOCK_HOME_ENV}=${this.slockHome}`);
|
|
8331
|
+
for (const legacy of listLegacySlockStatePaths(this.slockHome)) {
|
|
8332
|
+
logger.warn(
|
|
8333
|
+
`[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.`
|
|
8334
|
+
);
|
|
8335
|
+
}
|
|
7583
8336
|
if (!this.machineLock) {
|
|
7584
8337
|
this.machineLock = acquireDaemonMachineLock({
|
|
7585
8338
|
apiKey: this.options.apiKey,
|