@slock-ai/daemon 0.47.0 → 0.48.0-play.20260515194406
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/{chunk-77TJIZSE.js → chunk-54NE5A4K.js} +765 -128
- package/dist/cli/index.js +220 -2
- package/dist/core.js +6 -2
- package/dist/drivers/piSdkRunner.js +96 -0
- package/dist/index.js +5 -3
- package/package.json +2 -1
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
} from "./chunk-B7XIMLOT.js";
|
|
12
12
|
|
|
13
13
|
// src/core.ts
|
|
14
|
-
import
|
|
14
|
+
import path16 from "path";
|
|
15
15
|
import os7 from "os";
|
|
16
16
|
import { createRequire } from "module";
|
|
17
17
|
import { accessSync } from "fs";
|
|
18
|
-
import { fileURLToPath } from "url";
|
|
18
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/tracing/index.ts
|
|
21
21
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -640,6 +640,22 @@ var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
|
640
640
|
channelAddMemberOperationSchema
|
|
641
641
|
]);
|
|
642
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
|
+
|
|
643
659
|
// ../shared/src/testing/failpoints.ts
|
|
644
660
|
var NoopFailpointRegistry = class {
|
|
645
661
|
get enabled() {
|
|
@@ -709,6 +725,7 @@ var SERVER_CAPABILITY_MATRIX = {
|
|
|
709
725
|
var RUNTIMES = [
|
|
710
726
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
711
727
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
728
|
+
{ id: "pi", displayName: "Pi", binary: "pi", supported: true },
|
|
712
729
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
713
730
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
714
731
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -750,10 +767,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
750
767
|
};
|
|
751
768
|
|
|
752
769
|
// src/agentProcessManager.ts
|
|
753
|
-
import { mkdirSync as
|
|
770
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
754
771
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
755
772
|
import { createHash as createHash2 } from "crypto";
|
|
756
|
-
import
|
|
773
|
+
import path12 from "path";
|
|
757
774
|
import os5 from "os";
|
|
758
775
|
|
|
759
776
|
// src/drivers/claude.ts
|
|
@@ -846,22 +863,25 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
846
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.
|
|
847
864
|
7. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
|
|
848
865
|
8. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
849
|
-
9. **\`slock
|
|
850
|
-
10. **\`slock task
|
|
851
|
-
11. **\`slock task
|
|
852
|
-
12. **\`slock task
|
|
853
|
-
13. **\`slock task
|
|
854
|
-
14. **\`slock
|
|
855
|
-
15. **\`slock attachment
|
|
856
|
-
16. **\`slock
|
|
857
|
-
17. **\`slock profile
|
|
858
|
-
18. **\`slock
|
|
859
|
-
19. **\`slock
|
|
860
|
-
20. **\`slock
|
|
861
|
-
21. **\`slock reminder
|
|
862
|
-
22. **\`slock reminder
|
|
863
|
-
23. **\`slock reminder
|
|
864
|
-
24. **\`slock
|
|
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 integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
|
|
877
|
+
20. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
|
|
878
|
+
21. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
879
|
+
22. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
880
|
+
23. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
881
|
+
24. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
882
|
+
25. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
883
|
+
26. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
884
|
+
27. **\`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\`).
|
|
865
885
|
|
|
866
886
|
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:
|
|
867
887
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -957,6 +977,11 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
957
977
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
958
978
|
- **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
|
|
959
979
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
980
|
+
const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
|
|
981
|
+
|
|
982
|
+
If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
|
|
983
|
+
|
|
984
|
+
If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
|
|
960
985
|
const readingHistorySection = isCli ? `### Reading history
|
|
961
986
|
|
|
962
987
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1128,6 +1153,8 @@ ${discoverySection}
|
|
|
1128
1153
|
|
|
1129
1154
|
${channelAwarenessSection}
|
|
1130
1155
|
|
|
1156
|
+
${thirdPartyIntegrationsSection}
|
|
1157
|
+
|
|
1131
1158
|
${readingHistorySection}
|
|
1132
1159
|
|
|
1133
1160
|
${historicalReferenceSection}
|
|
@@ -1159,6 +1186,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1159
1186
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1160
1187
|
- When done, summarize the result.
|
|
1161
1188
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1189
|
+
- 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:
|
|
1190
|
+
|
|
1191
|
+
\`\`\`html
|
|
1192
|
+
<details>
|
|
1193
|
+
<summary>Evidence, logs, or edge cases</summary>
|
|
1194
|
+
|
|
1195
|
+
Detailed notes go here.
|
|
1196
|
+
</details>
|
|
1197
|
+
\`\`\`
|
|
1198
|
+
|
|
1199
|
+
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1162
1200
|
|
|
1163
1201
|
### Conversation etiquette
|
|
1164
1202
|
|
|
@@ -1292,6 +1330,19 @@ function buildMcpSystemPrompt(config, opts) {
|
|
|
1292
1330
|
return buildPrompt(config, "mcp", opts);
|
|
1293
1331
|
}
|
|
1294
1332
|
|
|
1333
|
+
// src/authEnv.ts
|
|
1334
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1335
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1336
|
+
function scrubDaemonAuthEnv(env) {
|
|
1337
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1338
|
+
return env;
|
|
1339
|
+
}
|
|
1340
|
+
function scrubDaemonChildEnv(env) {
|
|
1341
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1342
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1343
|
+
return env;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1295
1346
|
// src/drivers/cliTransport.ts
|
|
1296
1347
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1297
1348
|
function runtimeContextEnv(config) {
|
|
@@ -1345,7 +1396,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1345
1396
|
SLOCK_AGENT_TOKEN_FILE: tokenFile,
|
|
1346
1397
|
PATH: `${slockDir}${path.delimiter}${process.env.PATH ?? ""}`
|
|
1347
1398
|
};
|
|
1348
|
-
|
|
1399
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
1349
1400
|
return {
|
|
1350
1401
|
slockDir,
|
|
1351
1402
|
tokenFile,
|
|
@@ -1382,7 +1433,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1382
1433
|
}
|
|
1383
1434
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1384
1435
|
const platform = deps.platform ?? process.platform;
|
|
1385
|
-
const env = deps.env ?? process.env;
|
|
1436
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1386
1437
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1387
1438
|
if (platform === "win32") {
|
|
1388
1439
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -1407,7 +1458,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
1407
1458
|
return null;
|
|
1408
1459
|
}
|
|
1409
1460
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
1410
|
-
const env = deps.env ?? process.env;
|
|
1461
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1411
1462
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1412
1463
|
try {
|
|
1413
1464
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -1772,7 +1823,7 @@ var ClaudeDriver = class {
|
|
|
1772
1823
|
|
|
1773
1824
|
// src/drivers/codex.ts
|
|
1774
1825
|
import { spawn as spawn2, execSync } from "child_process";
|
|
1775
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
1826
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1776
1827
|
import os2 from "os";
|
|
1777
1828
|
import path4 from "path";
|
|
1778
1829
|
function getCodexNotificationErrorMessage(params) {
|
|
@@ -1801,11 +1852,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
|
1801
1852
|
);
|
|
1802
1853
|
}
|
|
1803
1854
|
var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
|
|
1855
|
+
function resolveWindowsSandboxRunner(deps = {}) {
|
|
1856
|
+
const homeDir = deps.homeDir ?? os2.homedir();
|
|
1857
|
+
const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
|
|
1858
|
+
if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
|
|
1859
|
+
try {
|
|
1860
|
+
const files = readdirSync2(sandboxBinDir);
|
|
1861
|
+
const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
|
|
1862
|
+
if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1804
1867
|
function resolveCodexCommand(deps = {}) {
|
|
1805
1868
|
const pathCommand = resolveCommandOnPath("codex", deps);
|
|
1806
1869
|
if (pathCommand) return pathCommand;
|
|
1807
|
-
|
|
1808
|
-
|
|
1870
|
+
const platform = deps.platform ?? process.platform;
|
|
1871
|
+
if (platform === "darwin") {
|
|
1872
|
+
return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
|
|
1873
|
+
}
|
|
1874
|
+
if (platform === "win32") {
|
|
1875
|
+
return resolveWindowsSandboxRunner(deps);
|
|
1876
|
+
}
|
|
1877
|
+
return null;
|
|
1809
1878
|
}
|
|
1810
1879
|
function probeCodex(deps = {}) {
|
|
1811
1880
|
if ((deps.platform ?? process.platform) === "win32") {
|
|
@@ -1830,22 +1899,32 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1830
1899
|
if ((deps.platform ?? process.platform) !== "win32") {
|
|
1831
1900
|
return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs };
|
|
1832
1901
|
}
|
|
1902
|
+
const execSyncFn = deps.execSyncFn ?? execSync;
|
|
1903
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync3;
|
|
1904
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1833
1905
|
let codexEntry = null;
|
|
1834
1906
|
try {
|
|
1835
|
-
const globalRoot =
|
|
1907
|
+
const globalRoot = execSyncFn("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim();
|
|
1836
1908
|
const candidate = path4.join(globalRoot, "@openai", "codex", "bin", "codex.js");
|
|
1837
|
-
if (
|
|
1909
|
+
if (existsSyncFn(candidate)) codexEntry = candidate;
|
|
1838
1910
|
} catch {
|
|
1839
1911
|
}
|
|
1840
1912
|
if (!codexEntry) {
|
|
1841
1913
|
try {
|
|
1842
|
-
const cmdPath =
|
|
1914
|
+
const cmdPath = execSyncFn("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim().split(/\r?\n/)[0];
|
|
1843
1915
|
const candidate = path4.join(path4.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
1844
|
-
if (
|
|
1916
|
+
if (existsSyncFn(candidate)) codexEntry = candidate;
|
|
1845
1917
|
} catch {
|
|
1846
1918
|
}
|
|
1847
1919
|
}
|
|
1848
1920
|
if (!codexEntry) {
|
|
1921
|
+
const sandboxRunner = resolveWindowsSandboxRunner(deps);
|
|
1922
|
+
if (sandboxRunner) {
|
|
1923
|
+
return {
|
|
1924
|
+
command: sandboxRunner,
|
|
1925
|
+
args: commandArgs
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1849
1928
|
throw new Error(
|
|
1850
1929
|
"Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
|
|
1851
1930
|
);
|
|
@@ -2296,13 +2375,13 @@ import { spawn as spawn3 } from "child_process";
|
|
|
2296
2375
|
import path5 from "path";
|
|
2297
2376
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2298
2377
|
function buildCopilotSpawnEnv(ctx) {
|
|
2299
|
-
return {
|
|
2378
|
+
return scrubDaemonChildEnv({
|
|
2300
2379
|
...process.env,
|
|
2301
2380
|
FORCE_COLOR: "0",
|
|
2302
2381
|
NO_COLOR: "1",
|
|
2303
2382
|
...ctx.config.envVars || {},
|
|
2304
2383
|
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2305
|
-
};
|
|
2384
|
+
});
|
|
2306
2385
|
}
|
|
2307
2386
|
var CopilotDriver = class {
|
|
2308
2387
|
id = "copilot";
|
|
@@ -2460,13 +2539,13 @@ import { spawn as spawn4, spawnSync } from "child_process";
|
|
|
2460
2539
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
2461
2540
|
import path6 from "path";
|
|
2462
2541
|
function buildCursorSpawnEnv(ctx) {
|
|
2463
|
-
return {
|
|
2542
|
+
return scrubDaemonChildEnv({
|
|
2464
2543
|
...process.env,
|
|
2465
2544
|
FORCE_COLOR: "0",
|
|
2466
2545
|
NO_COLOR: "1",
|
|
2467
2546
|
...ctx.config.envVars || {},
|
|
2468
2547
|
[SLOCK_HOME_ENV]: resolveSlockHome()
|
|
2469
|
-
};
|
|
2548
|
+
});
|
|
2470
2549
|
}
|
|
2471
2550
|
var CursorDriver = class {
|
|
2472
2551
|
id = "cursor";
|
|
@@ -2636,7 +2715,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
2636
2715
|
}
|
|
2637
2716
|
function runCursorModelsCommand() {
|
|
2638
2717
|
return spawnSync("cursor-agent", ["models"], {
|
|
2639
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
2718
|
+
env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
2640
2719
|
encoding: "utf8",
|
|
2641
2720
|
timeout: 5e3
|
|
2642
2721
|
});
|
|
@@ -2683,7 +2762,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
2683
2762
|
}
|
|
2684
2763
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
|
|
2685
2764
|
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
2686
|
-
const env = deps.env ?? process.env;
|
|
2765
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
2687
2766
|
const winPath = path7.win32;
|
|
2688
2767
|
let geminiEntry = null;
|
|
2689
2768
|
try {
|
|
@@ -2857,13 +2936,16 @@ var GeminiDriver = class {
|
|
|
2857
2936
|
// src/drivers/kimi.ts
|
|
2858
2937
|
import { randomUUID } from "crypto";
|
|
2859
2938
|
import { spawn as spawn6 } from "child_process";
|
|
2860
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2939
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2861
2940
|
import os3 from "os";
|
|
2862
2941
|
import path8 from "path";
|
|
2863
2942
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
2864
2943
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
2865
2944
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
2866
2945
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
2946
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
2947
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
2948
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
2867
2949
|
function parseToolArguments(raw) {
|
|
2868
2950
|
if (typeof raw !== "string") return raw;
|
|
2869
2951
|
try {
|
|
@@ -2872,6 +2954,73 @@ function parseToolArguments(raw) {
|
|
|
2872
2954
|
return raw;
|
|
2873
2955
|
}
|
|
2874
2956
|
}
|
|
2957
|
+
function readKimiConfigSource(home = os3.homedir(), env = process.env) {
|
|
2958
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2959
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
2960
|
+
return {
|
|
2961
|
+
raw: inlineConfig,
|
|
2962
|
+
explicitPath: null,
|
|
2963
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2967
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
|
|
2968
|
+
try {
|
|
2969
|
+
return {
|
|
2970
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
2971
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2972
|
+
sourcePath: configPath
|
|
2973
|
+
};
|
|
2974
|
+
} catch {
|
|
2975
|
+
return {
|
|
2976
|
+
raw: null,
|
|
2977
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
2978
|
+
sourcePath: configPath
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
2983
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2984
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
2985
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
2986
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
2987
|
+
}
|
|
2988
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
2989
|
+
return {
|
|
2990
|
+
...process.env,
|
|
2991
|
+
...ctx.config.envVars || {},
|
|
2992
|
+
...overrideEnv || {}
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
2996
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
2997
|
+
const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
|
|
2998
|
+
const args = [];
|
|
2999
|
+
let configFilePath = null;
|
|
3000
|
+
let configContent = null;
|
|
3001
|
+
if (source.explicitPath) {
|
|
3002
|
+
configFilePath = source.explicitPath;
|
|
3003
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3004
|
+
configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3005
|
+
configContent = source.raw;
|
|
3006
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
3007
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3008
|
+
chmodSync(configFilePath, 384);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (configFilePath) {
|
|
3012
|
+
args.push("--config-file", configFilePath);
|
|
3013
|
+
}
|
|
3014
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3015
|
+
args.push("--model", ctx.config.model);
|
|
3016
|
+
}
|
|
3017
|
+
return {
|
|
3018
|
+
args,
|
|
3019
|
+
env: buildKimiSpawnEnv(env),
|
|
3020
|
+
configFilePath,
|
|
3021
|
+
configContent
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
2875
3024
|
var KimiDriver = class {
|
|
2876
3025
|
id = "kimi";
|
|
2877
3026
|
lifecycle = {
|
|
@@ -2888,7 +3037,25 @@ var KimiDriver = class {
|
|
|
2888
3037
|
};
|
|
2889
3038
|
model = {
|
|
2890
3039
|
detectedModelsVerifiedAs: "launchable",
|
|
2891
|
-
toLaunchSpec: (modelId) =>
|
|
3040
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3041
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3042
|
+
const launchCtx = {
|
|
3043
|
+
...ctx,
|
|
3044
|
+
config: {
|
|
3045
|
+
...ctx.config,
|
|
3046
|
+
model: modelId
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3050
|
+
home: opts?.home,
|
|
3051
|
+
writeGeneratedConfig: false
|
|
3052
|
+
});
|
|
3053
|
+
return {
|
|
3054
|
+
args: launch.args,
|
|
3055
|
+
env: launch.env,
|
|
3056
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
2892
3059
|
};
|
|
2893
3060
|
supportsStdinNotification = true;
|
|
2894
3061
|
mcpToolPrefix = "";
|
|
@@ -2940,6 +3107,7 @@ var KimiDriver = class {
|
|
|
2940
3107
|
}
|
|
2941
3108
|
}
|
|
2942
3109
|
}), "utf8");
|
|
3110
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
2943
3111
|
const args = [
|
|
2944
3112
|
"--wire",
|
|
2945
3113
|
"--yolo",
|
|
@@ -2948,16 +3116,13 @@ var KimiDriver = class {
|
|
|
2948
3116
|
"--mcp-config-file",
|
|
2949
3117
|
mcpConfigPath,
|
|
2950
3118
|
"--session",
|
|
2951
|
-
this.sessionId
|
|
3119
|
+
this.sessionId,
|
|
3120
|
+
...launch.args
|
|
2952
3121
|
];
|
|
2953
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2954
|
-
args.push("--model", ctx.config.model);
|
|
2955
|
-
}
|
|
2956
|
-
const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
2957
3122
|
const proc = spawn6("kimi", args, {
|
|
2958
3123
|
cwd: ctx.workingDirectory,
|
|
2959
3124
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2960
|
-
env:
|
|
3125
|
+
env: launch.env,
|
|
2961
3126
|
shell: process.platform === "win32"
|
|
2962
3127
|
});
|
|
2963
3128
|
proc.stdin?.write(JSON.stringify({
|
|
@@ -3074,14 +3239,9 @@ var KimiDriver = class {
|
|
|
3074
3239
|
return detectKimiModels();
|
|
3075
3240
|
}
|
|
3076
3241
|
};
|
|
3077
|
-
function detectKimiModels(home = os3.homedir()) {
|
|
3078
|
-
const
|
|
3079
|
-
|
|
3080
|
-
try {
|
|
3081
|
-
raw = readFileSync3(configPath, "utf8");
|
|
3082
|
-
} catch {
|
|
3083
|
-
return null;
|
|
3084
|
-
}
|
|
3242
|
+
function detectKimiModels(home = os3.homedir(), opts = {}) {
|
|
3243
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3244
|
+
if (raw === null) return null;
|
|
3085
3245
|
const models = [];
|
|
3086
3246
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3087
3247
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3328,7 +3488,7 @@ function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeMode
|
|
|
3328
3488
|
}
|
|
3329
3489
|
function runOpenCodeModelsCommand(home) {
|
|
3330
3490
|
const result = spawnSync2("opencode", ["models"], {
|
|
3331
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3491
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3332
3492
|
encoding: "utf8",
|
|
3333
3493
|
timeout: 5e3
|
|
3334
3494
|
});
|
|
@@ -3484,6 +3644,297 @@ var OpenCodeDriver = class {
|
|
|
3484
3644
|
}
|
|
3485
3645
|
};
|
|
3486
3646
|
|
|
3647
|
+
// src/drivers/pi.ts
|
|
3648
|
+
import { spawn as spawn8 } from "child_process";
|
|
3649
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3650
|
+
import path10 from "path";
|
|
3651
|
+
import { fileURLToPath } from "url";
|
|
3652
|
+
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
3653
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
3654
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
3655
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
3656
|
+
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
3657
|
+
function parseSemver2(version) {
|
|
3658
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
3659
|
+
if (!match) return null;
|
|
3660
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
3661
|
+
}
|
|
3662
|
+
function isSupportedPiVersion(version) {
|
|
3663
|
+
if (!version) return true;
|
|
3664
|
+
const actual = parseSemver2(version);
|
|
3665
|
+
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
3666
|
+
if (!actual || !minimum) return true;
|
|
3667
|
+
for (let i = 0; i < 3; i += 1) {
|
|
3668
|
+
if (actual[i] > minimum[i]) return true;
|
|
3669
|
+
if (actual[i] < minimum[i]) return false;
|
|
3670
|
+
}
|
|
3671
|
+
return true;
|
|
3672
|
+
}
|
|
3673
|
+
function unsupportedPiVersionMessage(version) {
|
|
3674
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
3675
|
+
return `Pi SDK ${version} is unsupported; requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION}. Upgrade the daemon Pi dependency before starting this runtime.`;
|
|
3676
|
+
}
|
|
3677
|
+
function probePi(version = PI_SDK_VERSION) {
|
|
3678
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
3679
|
+
if (unsupportedMessage) {
|
|
3680
|
+
return {
|
|
3681
|
+
available: false,
|
|
3682
|
+
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
return { available: true, version };
|
|
3686
|
+
}
|
|
3687
|
+
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
3688
|
+
const moduleDir = path10.dirname(fileURLToPath(moduleUrl));
|
|
3689
|
+
const sourceSibling = path10.join(moduleDir, "piSdkRunner.ts");
|
|
3690
|
+
if (existsSync7(sourceSibling)) return sourceSibling;
|
|
3691
|
+
const bundledEntry = path10.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
3692
|
+
if (existsSync7(bundledEntry)) return bundledEntry;
|
|
3693
|
+
return path10.join(moduleDir, "piSdkRunner.js");
|
|
3694
|
+
}
|
|
3695
|
+
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
3696
|
+
if (runnerPath.endsWith(".ts")) {
|
|
3697
|
+
return [...process.execArgv, runnerPath];
|
|
3698
|
+
}
|
|
3699
|
+
return [runnerPath];
|
|
3700
|
+
}
|
|
3701
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
3702
|
+
const command = opts.command ?? process.execPath;
|
|
3703
|
+
const piDir = path10.join(ctx.workingDirectory, ".slock", "pi");
|
|
3704
|
+
const sessionDir = path10.join(piDir, "sessions");
|
|
3705
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
3706
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
3707
|
+
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
3708
|
+
const agentDir = opts.agentDir ?? getAgentDir();
|
|
3709
|
+
const runnerConfigPath = path10.join(
|
|
3710
|
+
piDir,
|
|
3711
|
+
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
3712
|
+
);
|
|
3713
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
3714
|
+
const runnerConfig = {
|
|
3715
|
+
cwd: ctx.workingDirectory,
|
|
3716
|
+
agentDir,
|
|
3717
|
+
sessionDir,
|
|
3718
|
+
sessionId: ctx.config.sessionId || null,
|
|
3719
|
+
standingPrompt: ctx.standingPrompt,
|
|
3720
|
+
prompt: turnPrompt,
|
|
3721
|
+
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
3722
|
+
};
|
|
3723
|
+
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
3724
|
+
`, { encoding: "utf8", mode: 384 });
|
|
3725
|
+
const args = [
|
|
3726
|
+
...buildPiSdkNodeArgs(runnerPath),
|
|
3727
|
+
"--config",
|
|
3728
|
+
runnerConfigPath
|
|
3729
|
+
];
|
|
3730
|
+
return {
|
|
3731
|
+
command,
|
|
3732
|
+
args,
|
|
3733
|
+
env: slock.spawnEnv,
|
|
3734
|
+
sessionDir,
|
|
3735
|
+
agentDir,
|
|
3736
|
+
runnerConfigPath,
|
|
3737
|
+
sdkVersion: PI_SDK_VERSION
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
function isSystemFirstMessageTask2(message) {
|
|
3741
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
3742
|
+
}
|
|
3743
|
+
function buildPiSystemPrompt(config) {
|
|
3744
|
+
return buildCliTransportSystemPrompt(config, {
|
|
3745
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
3746
|
+
extraCriticalRules: [
|
|
3747
|
+
"- 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."
|
|
3748
|
+
],
|
|
3749
|
+
postStartupNotes: [
|
|
3750
|
+
"**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."
|
|
3751
|
+
],
|
|
3752
|
+
includeStdinNotificationSection: false,
|
|
3753
|
+
messageNotificationStyle: "poll"
|
|
3754
|
+
});
|
|
3755
|
+
}
|
|
3756
|
+
function contentText(content) {
|
|
3757
|
+
if (!content) return "";
|
|
3758
|
+
const chunks = [];
|
|
3759
|
+
for (const item of content) {
|
|
3760
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
3761
|
+
chunks.push(item.text);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
return chunks.join("");
|
|
3765
|
+
}
|
|
3766
|
+
function apiKeyErrorMessage(line) {
|
|
3767
|
+
const trimmed = line.trim();
|
|
3768
|
+
if (!trimmed) return null;
|
|
3769
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
3770
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
3771
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
3772
|
+
return null;
|
|
3773
|
+
}
|
|
3774
|
+
var PiDriver = class {
|
|
3775
|
+
id = "pi";
|
|
3776
|
+
lifecycle = {
|
|
3777
|
+
kind: "per_turn",
|
|
3778
|
+
start: "defer_until_concrete_message",
|
|
3779
|
+
exit: "terminate_on_turn_end",
|
|
3780
|
+
inFlightWake: "coalesce_into_pending"
|
|
3781
|
+
};
|
|
3782
|
+
communication = {
|
|
3783
|
+
chat: "slock_cli",
|
|
3784
|
+
runtimeControl: "none"
|
|
3785
|
+
};
|
|
3786
|
+
session = {
|
|
3787
|
+
recovery: "resume_or_fresh"
|
|
3788
|
+
};
|
|
3789
|
+
model = {
|
|
3790
|
+
detectedModelsVerifiedAs: "launchable",
|
|
3791
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
3792
|
+
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
3793
|
+
const launchCtx = {
|
|
3794
|
+
...ctx,
|
|
3795
|
+
config: {
|
|
3796
|
+
...ctx.config,
|
|
3797
|
+
model: modelId
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3800
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
3801
|
+
return {
|
|
3802
|
+
args: launch.args,
|
|
3803
|
+
env: launch.env,
|
|
3804
|
+
configFiles: [launch.runnerConfigPath],
|
|
3805
|
+
params: {
|
|
3806
|
+
agentDir: launch.agentDir,
|
|
3807
|
+
sessionDir: launch.sessionDir,
|
|
3808
|
+
sdkVersion: launch.sdkVersion,
|
|
3809
|
+
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
3810
|
+
}
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
supportsStdinNotification = false;
|
|
3815
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
3816
|
+
busyDeliveryMode = "none";
|
|
3817
|
+
terminateProcessOnTurnEnd = true;
|
|
3818
|
+
deferSpawnUntilMessage = true;
|
|
3819
|
+
usesSlockCliForCommunication = true;
|
|
3820
|
+
sessionId = null;
|
|
3821
|
+
sessionAnnounced = false;
|
|
3822
|
+
apiKeyErrorAnnounced = false;
|
|
3823
|
+
turnEnded = false;
|
|
3824
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
3825
|
+
shouldDeferWakeMessage(message) {
|
|
3826
|
+
return isSystemFirstMessageTask2(message);
|
|
3827
|
+
}
|
|
3828
|
+
probe() {
|
|
3829
|
+
return probePi();
|
|
3830
|
+
}
|
|
3831
|
+
async detectModels() {
|
|
3832
|
+
return null;
|
|
3833
|
+
}
|
|
3834
|
+
spawn(ctx) {
|
|
3835
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
3836
|
+
this.sessionAnnounced = false;
|
|
3837
|
+
this.apiKeyErrorAnnounced = false;
|
|
3838
|
+
this.turnEnded = false;
|
|
3839
|
+
this.assistantTextByMessageId.clear();
|
|
3840
|
+
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
3841
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
3842
|
+
const launch = buildPiLaunchOptions(ctx);
|
|
3843
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
3844
|
+
cwd: ctx.workingDirectory,
|
|
3845
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3846
|
+
env: launch.env,
|
|
3847
|
+
shell: false
|
|
3848
|
+
});
|
|
3849
|
+
proc.stdin?.end();
|
|
3850
|
+
return { process: proc };
|
|
3851
|
+
}
|
|
3852
|
+
parseLine(line) {
|
|
3853
|
+
let event;
|
|
3854
|
+
try {
|
|
3855
|
+
event = JSON.parse(line);
|
|
3856
|
+
} catch {
|
|
3857
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
3858
|
+
const message = apiKeyErrorMessage(line);
|
|
3859
|
+
if (!message) return [];
|
|
3860
|
+
this.apiKeyErrorAnnounced = true;
|
|
3861
|
+
this.turnEnded = true;
|
|
3862
|
+
return [
|
|
3863
|
+
{ kind: "error", message },
|
|
3864
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
3865
|
+
];
|
|
3866
|
+
}
|
|
3867
|
+
const events = [];
|
|
3868
|
+
if (event.type === "session" && event.id) {
|
|
3869
|
+
this.sessionId = event.id;
|
|
3870
|
+
}
|
|
3871
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
3872
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
3873
|
+
this.sessionAnnounced = true;
|
|
3874
|
+
}
|
|
3875
|
+
switch (event.type) {
|
|
3876
|
+
case "agent_start":
|
|
3877
|
+
case "turn_start":
|
|
3878
|
+
events.push({ kind: "thinking", text: "" });
|
|
3879
|
+
break;
|
|
3880
|
+
case "message_update":
|
|
3881
|
+
case "message_end":
|
|
3882
|
+
if (event.message?.role === "assistant") {
|
|
3883
|
+
const key = event.message.id || "current";
|
|
3884
|
+
const currentText = contentText(event.message.content);
|
|
3885
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
3886
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
3887
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
3888
|
+
} else if (currentText && currentText !== previousText) {
|
|
3889
|
+
events.push({ kind: "text", text: currentText });
|
|
3890
|
+
}
|
|
3891
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
3892
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
3893
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
break;
|
|
3897
|
+
case "tool_execution_start":
|
|
3898
|
+
events.push({
|
|
3899
|
+
kind: "tool_call",
|
|
3900
|
+
name: event.toolName || "unknown_tool",
|
|
3901
|
+
input: event.args
|
|
3902
|
+
});
|
|
3903
|
+
break;
|
|
3904
|
+
case "tool_execution_end":
|
|
3905
|
+
events.push({
|
|
3906
|
+
kind: "tool_output",
|
|
3907
|
+
name: event.toolName || "unknown_tool"
|
|
3908
|
+
});
|
|
3909
|
+
if (event.isError) {
|
|
3910
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
3911
|
+
}
|
|
3912
|
+
break;
|
|
3913
|
+
case "compaction_start":
|
|
3914
|
+
events.push({ kind: "compaction_started" });
|
|
3915
|
+
break;
|
|
3916
|
+
case "compaction_end":
|
|
3917
|
+
events.push({ kind: "compaction_finished" });
|
|
3918
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
3919
|
+
break;
|
|
3920
|
+
case "turn_end":
|
|
3921
|
+
case "agent_end":
|
|
3922
|
+
if (!this.turnEnded) {
|
|
3923
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
3924
|
+
this.turnEnded = true;
|
|
3925
|
+
}
|
|
3926
|
+
break;
|
|
3927
|
+
}
|
|
3928
|
+
return events;
|
|
3929
|
+
}
|
|
3930
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
3931
|
+
return null;
|
|
3932
|
+
}
|
|
3933
|
+
buildSystemPrompt(config, _agentId) {
|
|
3934
|
+
return buildPiSystemPrompt(config);
|
|
3935
|
+
}
|
|
3936
|
+
};
|
|
3937
|
+
|
|
3487
3938
|
// src/drivers/index.ts
|
|
3488
3939
|
var driverFactories = {
|
|
3489
3940
|
claude: () => new ClaudeDriver(),
|
|
@@ -3492,7 +3943,8 @@ var driverFactories = {
|
|
|
3492
3943
|
cursor: () => new CursorDriver(),
|
|
3493
3944
|
gemini: () => new GeminiDriver(),
|
|
3494
3945
|
kimi: () => new KimiDriver(),
|
|
3495
|
-
opencode: () => new OpenCodeDriver()
|
|
3946
|
+
opencode: () => new OpenCodeDriver(),
|
|
3947
|
+
pi: () => new PiDriver()
|
|
3496
3948
|
};
|
|
3497
3949
|
function getDriver(runtimeId) {
|
|
3498
3950
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -3505,7 +3957,7 @@ function getDriver(runtimeId) {
|
|
|
3505
3957
|
|
|
3506
3958
|
// src/workspaces.ts
|
|
3507
3959
|
import { readdir, rm, stat } from "fs/promises";
|
|
3508
|
-
import
|
|
3960
|
+
import path11 from "path";
|
|
3509
3961
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
3510
3962
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
3511
3963
|
}
|
|
@@ -3513,7 +3965,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
3513
3965
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
3514
3966
|
return null;
|
|
3515
3967
|
}
|
|
3516
|
-
return
|
|
3968
|
+
return path11.join(dataDir, directoryName);
|
|
3517
3969
|
}
|
|
3518
3970
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
3519
3971
|
return {
|
|
@@ -3562,7 +4014,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
3562
4014
|
return summary;
|
|
3563
4015
|
}
|
|
3564
4016
|
const childSummaries = await Promise.all(
|
|
3565
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4017
|
+
entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
|
|
3566
4018
|
);
|
|
3567
4019
|
for (const childSummary of childSummaries) {
|
|
3568
4020
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -3581,7 +4033,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
3581
4033
|
if (!entry.isDirectory()) {
|
|
3582
4034
|
return null;
|
|
3583
4035
|
}
|
|
3584
|
-
const dirPath =
|
|
4036
|
+
const dirPath = path11.join(dataDir, entry.name);
|
|
3585
4037
|
try {
|
|
3586
4038
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
3587
4039
|
return {
|
|
@@ -3772,19 +4224,19 @@ function findSessionJsonl(root, predicate) {
|
|
|
3772
4224
|
if (depth < 0 || visited >= maxEntries) return null;
|
|
3773
4225
|
let entries;
|
|
3774
4226
|
try {
|
|
3775
|
-
entries =
|
|
4227
|
+
entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3776
4228
|
} catch {
|
|
3777
4229
|
return null;
|
|
3778
4230
|
}
|
|
3779
4231
|
for (const entry of entries) {
|
|
3780
4232
|
if (++visited > maxEntries) return null;
|
|
3781
4233
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
3782
|
-
return
|
|
4234
|
+
return path12.join(dir, entry.name);
|
|
3783
4235
|
}
|
|
3784
4236
|
for (const entry of entries) {
|
|
3785
4237
|
if (++visited > maxEntries) return null;
|
|
3786
4238
|
if (!entry.isDirectory()) continue;
|
|
3787
|
-
const found = visit(
|
|
4239
|
+
const found = visit(path12.join(dir, entry.name), depth - 1);
|
|
3788
4240
|
if (found) return found;
|
|
3789
4241
|
}
|
|
3790
4242
|
return null;
|
|
@@ -3797,10 +4249,10 @@ function safeSessionFilename(value) {
|
|
|
3797
4249
|
}
|
|
3798
4250
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
3799
4251
|
try {
|
|
3800
|
-
const dir =
|
|
3801
|
-
|
|
3802
|
-
const filePath =
|
|
3803
|
-
|
|
4252
|
+
const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
|
|
4253
|
+
mkdirSync5(dir, { recursive: true });
|
|
4254
|
+
const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
4255
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
3804
4256
|
type: "runtime_session_handoff",
|
|
3805
4257
|
runtime,
|
|
3806
4258
|
sessionId,
|
|
@@ -3819,7 +4271,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
3819
4271
|
}
|
|
3820
4272
|
}
|
|
3821
4273
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
3822
|
-
const directPath =
|
|
4274
|
+
const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
|
|
3823
4275
|
if (directPath) {
|
|
3824
4276
|
try {
|
|
3825
4277
|
if (statSync2(directPath).isFile()) {
|
|
@@ -3828,7 +4280,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
|
|
|
3828
4280
|
} catch {
|
|
3829
4281
|
}
|
|
3830
4282
|
}
|
|
3831
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
4283
|
+
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;
|
|
3832
4284
|
if (!resolvedPath && fallbackDir) {
|
|
3833
4285
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
3834
4286
|
if (fallback) return fallback;
|
|
@@ -4368,6 +4820,17 @@ Success = user starts useful collaboration and setup progresses,
|
|
|
4368
4820
|
not finishing a long onboarding conversation in one channel.
|
|
4369
4821
|
`;
|
|
4370
4822
|
}
|
|
4823
|
+
function createRuntimeTraceCounters() {
|
|
4824
|
+
return {
|
|
4825
|
+
events: 0,
|
|
4826
|
+
toolCalls: 0,
|
|
4827
|
+
toolOutputs: 0,
|
|
4828
|
+
compactionStarts: 0,
|
|
4829
|
+
compactionFinishes: 0,
|
|
4830
|
+
textEvents: 0,
|
|
4831
|
+
thinkingEvents: 0
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4371
4834
|
function createGatedSteeringState() {
|
|
4372
4835
|
return {
|
|
4373
4836
|
phase: "idle",
|
|
@@ -4546,10 +5009,113 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
|
4546
5009
|
outstandingToolUses: ap.gatedSteering.outstandingToolUses,
|
|
4547
5010
|
compacting: ap.gatedSteering.compacting,
|
|
4548
5011
|
recentStderrCount: ap.recentStderr.length,
|
|
4549
|
-
recentStdoutCount: ap.recentStdout.length
|
|
5012
|
+
recentStdoutCount: ap.recentStdout.length,
|
|
5013
|
+
...runtimeTraceCounterAttrs(ap)
|
|
4550
5014
|
}
|
|
4551
5015
|
};
|
|
4552
5016
|
}
|
|
5017
|
+
function bucketBytes(value) {
|
|
5018
|
+
const bytes = typeof value === "string" ? Buffer.byteLength(value, "utf8") : Math.max(0, Math.floor(value ?? 0));
|
|
5019
|
+
if (bytes === 0) return "0";
|
|
5020
|
+
if (bytes < 1024) return "<1KB";
|
|
5021
|
+
if (bytes < 10 * 1024) return "1KB-10KB";
|
|
5022
|
+
if (bytes < 100 * 1024) return "10KB-100KB";
|
|
5023
|
+
if (bytes < 1024 * 1024) return "100KB-1MB";
|
|
5024
|
+
return "1MB+";
|
|
5025
|
+
}
|
|
5026
|
+
function attachmentBytesBucket(bytes, knownCount) {
|
|
5027
|
+
return knownCount > 0 ? bucketBytes(bytes) : "unknown";
|
|
5028
|
+
}
|
|
5029
|
+
function summarizeMessageInputBytes(messages) {
|
|
5030
|
+
if (!messages || messages.length === 0) {
|
|
5031
|
+
return {
|
|
5032
|
+
runtime_input_messages_count: 0,
|
|
5033
|
+
runtime_input_messages_content_bytes_bucket: "0",
|
|
5034
|
+
runtime_input_attachments_count: 0,
|
|
5035
|
+
runtime_input_image_attachments_count: 0,
|
|
5036
|
+
runtime_input_attachments_size_known_count: 0,
|
|
5037
|
+
runtime_input_attachments_bytes_bucket: "0",
|
|
5038
|
+
runtime_input_image_attachments_size_known_count: 0,
|
|
5039
|
+
runtime_input_image_attachments_bytes_bucket: "0",
|
|
5040
|
+
runtime_input_largest_attachment_bytes_bucket: "0",
|
|
5041
|
+
runtime_input_thread_context_messages_count: 0,
|
|
5042
|
+
runtime_input_thread_context_content_bytes_bucket: "0"
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
5045
|
+
let contentBytes = 0;
|
|
5046
|
+
let attachmentCount = 0;
|
|
5047
|
+
let imageAttachmentCount = 0;
|
|
5048
|
+
let attachmentSizeKnownCount = 0;
|
|
5049
|
+
let attachmentBytes = 0;
|
|
5050
|
+
let imageAttachmentSizeKnownCount = 0;
|
|
5051
|
+
let imageAttachmentBytes = 0;
|
|
5052
|
+
let largestAttachmentBytes = 0;
|
|
5053
|
+
let threadContextMessages = 0;
|
|
5054
|
+
let threadContextContentBytes = 0;
|
|
5055
|
+
for (const message of messages) {
|
|
5056
|
+
contentBytes += Buffer.byteLength(message.content || "", "utf8");
|
|
5057
|
+
for (const attachment of message.attachments || []) {
|
|
5058
|
+
attachmentCount++;
|
|
5059
|
+
if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
|
|
5060
|
+
attachmentSizeKnownCount++;
|
|
5061
|
+
attachmentBytes += attachment.sizeBytes;
|
|
5062
|
+
largestAttachmentBytes = Math.max(largestAttachmentBytes, attachment.sizeBytes);
|
|
5063
|
+
}
|
|
5064
|
+
if (attachment.mimeType?.startsWith("image/")) {
|
|
5065
|
+
imageAttachmentCount++;
|
|
5066
|
+
if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
|
|
5067
|
+
imageAttachmentSizeKnownCount++;
|
|
5068
|
+
imageAttachmentBytes += attachment.sizeBytes;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
const joinContext = message.thread_join_context;
|
|
5073
|
+
if (joinContext) {
|
|
5074
|
+
const contextMessages = [joinContext.parent_message, ...joinContext.recent_messages];
|
|
5075
|
+
threadContextMessages += contextMessages.length;
|
|
5076
|
+
for (const contextMessage of contextMessages) {
|
|
5077
|
+
threadContextContentBytes += Buffer.byteLength(contextMessage.content || "", "utf8");
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
return {
|
|
5082
|
+
runtime_input_messages_count: messages.length,
|
|
5083
|
+
runtime_input_messages_content_bytes_bucket: bucketBytes(contentBytes),
|
|
5084
|
+
runtime_input_attachments_count: attachmentCount,
|
|
5085
|
+
runtime_input_image_attachments_count: imageAttachmentCount,
|
|
5086
|
+
runtime_input_attachments_size_known_count: attachmentSizeKnownCount,
|
|
5087
|
+
runtime_input_attachments_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(attachmentBytes, attachmentSizeKnownCount) : "0",
|
|
5088
|
+
runtime_input_image_attachments_size_known_count: imageAttachmentSizeKnownCount,
|
|
5089
|
+
runtime_input_image_attachments_bytes_bucket: imageAttachmentCount > 0 ? attachmentBytesBucket(imageAttachmentBytes, imageAttachmentSizeKnownCount) : "0",
|
|
5090
|
+
runtime_input_largest_attachment_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(largestAttachmentBytes, attachmentSizeKnownCount) : "0",
|
|
5091
|
+
runtime_input_thread_context_messages_count: threadContextMessages,
|
|
5092
|
+
runtime_input_thread_context_content_bytes_bucket: bucketBytes(threadContextContentBytes)
|
|
5093
|
+
};
|
|
5094
|
+
}
|
|
5095
|
+
function buildRuntimeInputTraceAttrs(opts) {
|
|
5096
|
+
return {
|
|
5097
|
+
runtime_input_source: opts.source,
|
|
5098
|
+
runtime_input_prompt_bytes_bucket: bucketBytes(opts.prompt),
|
|
5099
|
+
runtime_input_standing_prompt_bytes_bucket: bucketBytes(opts.standingPrompt),
|
|
5100
|
+
runtime_input_resume_prompt_present: Boolean(opts.resumePrompt),
|
|
5101
|
+
runtime_input_resume_prompt_bytes_bucket: bucketBytes(opts.resumePrompt),
|
|
5102
|
+
runtime_input_session_present: opts.sessionIdPresent,
|
|
5103
|
+
runtime_input_native_standing_prompt_present: opts.nativeStandingPrompt,
|
|
5104
|
+
runtime_input_unread_channels_count: opts.unreadSummary ? Object.keys(opts.unreadSummary).length : 0,
|
|
5105
|
+
...summarizeMessageInputBytes(opts.messages)
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
function runtimeTraceCounterAttrs(ap) {
|
|
5109
|
+
return {
|
|
5110
|
+
runtime_events_count: ap.runtimeTraceCounters.events,
|
|
5111
|
+
runtime_tool_calls_count: ap.runtimeTraceCounters.toolCalls,
|
|
5112
|
+
runtime_tool_outputs_count: ap.runtimeTraceCounters.toolOutputs,
|
|
5113
|
+
runtime_compaction_starts_count: ap.runtimeTraceCounters.compactionStarts,
|
|
5114
|
+
runtime_compaction_finishes_count: ap.runtimeTraceCounters.compactionFinishes,
|
|
5115
|
+
runtime_text_events_count: ap.runtimeTraceCounters.textEvents,
|
|
5116
|
+
runtime_thinking_events_count: ap.runtimeTraceCounters.thinkingEvents
|
|
5117
|
+
};
|
|
5118
|
+
}
|
|
4553
5119
|
function getMessageDeliveryText(driver) {
|
|
4554
5120
|
return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
4555
5121
|
}
|
|
@@ -4847,26 +5413,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4847
5413
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
4848
5414
|
try {
|
|
4849
5415
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
4850
|
-
const agentDataDir =
|
|
5416
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
4851
5417
|
await mkdir(agentDataDir, { recursive: true });
|
|
4852
5418
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
4853
|
-
const memoryMdPath =
|
|
5419
|
+
const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
|
|
4854
5420
|
try {
|
|
4855
5421
|
await access(memoryMdPath);
|
|
4856
5422
|
} catch {
|
|
4857
5423
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
4858
5424
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
4859
5425
|
}
|
|
4860
|
-
const notesDir =
|
|
5426
|
+
const notesDir = path12.join(agentDataDir, "notes");
|
|
4861
5427
|
await mkdir(notesDir, { recursive: true });
|
|
4862
5428
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
4863
5429
|
const seedFiles = buildOnboardingSeedFiles();
|
|
4864
5430
|
for (const { relativePath, content } of seedFiles) {
|
|
4865
|
-
const fullPath =
|
|
5431
|
+
const fullPath = path12.join(agentDataDir, relativePath);
|
|
4866
5432
|
try {
|
|
4867
5433
|
await access(fullPath);
|
|
4868
5434
|
} catch {
|
|
4869
|
-
await mkdir(
|
|
5435
|
+
await mkdir(path12.dirname(fullPath), { recursive: true });
|
|
4870
5436
|
await writeFile(fullPath, content);
|
|
4871
5437
|
}
|
|
4872
5438
|
}
|
|
@@ -4874,17 +5440,21 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4874
5440
|
const isResume = !!runtimeConfig.sessionId;
|
|
4875
5441
|
const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
|
|
4876
5442
|
let prompt;
|
|
5443
|
+
let promptSource;
|
|
4877
5444
|
if (runtimeConfig.runtimeProfileControl && !wakeMessage) {
|
|
4878
5445
|
prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : formatRuntimeProfileControlStartupInput(runtimeConfig.runtimeProfileControl, driver);
|
|
5446
|
+
promptSource = "runtime_profile_control";
|
|
4879
5447
|
} else if (isResume && resumePrompt) {
|
|
4880
5448
|
prompt = resumePrompt;
|
|
4881
5449
|
prompt += getBusyDeliveryNote(driver);
|
|
5450
|
+
promptSource = "resume_prompt";
|
|
4882
5451
|
} else if (wakeMessage) {
|
|
4883
5452
|
const runtimeProfileControlPrompt = formatRuntimeProfileControlPrompt([wakeMessage]);
|
|
4884
5453
|
const channelLabel = formatChannelLabel(wakeMessage);
|
|
4885
5454
|
prompt = runtimeProfileControlPrompt ?? `New message received:
|
|
4886
5455
|
|
|
4887
5456
|
${formatIncomingMessage(wakeMessage, driver)}`;
|
|
5457
|
+
promptSource = runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_message";
|
|
4888
5458
|
if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
4889
5459
|
const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
|
|
4890
5460
|
if (otherUnread.length > 0) {
|
|
@@ -4918,12 +5488,25 @@ IMPORTANT: If the message requires multi-step work (e.g. research, code changes,
|
|
|
4918
5488
|
prompt += `
|
|
4919
5489
|
|
|
4920
5490
|
Use ${communicationCommand(driver, "read_history")} to catch up on the channels listed above, then stop. Read each listed channel at most once unless a read fails. Do NOT call ${communicationCommand(driver, "check_messages")} in this mode. If the history reveals a direct request, assignment, @mention, review request, or task clearly addressed to you, switch into active handling instead of stopping: ${dynamicReplyInstruction(driver)} and ${dynamicClaimInstruction(driver)} before starting work. Otherwise, do NOT send any message in this mode. ${getMessageDeliveryText(driver)}`;
|
|
5491
|
+
promptSource = "resume_unread_summary";
|
|
4921
5492
|
} else if (isResume) {
|
|
4922
5493
|
prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
|
|
4923
5494
|
prompt += getBusyDeliveryNote(driver);
|
|
5495
|
+
promptSource = "resume_empty";
|
|
4924
5496
|
} else {
|
|
4925
5497
|
prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : standingPrompt;
|
|
5498
|
+
promptSource = "cold_start";
|
|
4926
5499
|
}
|
|
5500
|
+
const runtimeInputTraceAttrs = buildRuntimeInputTraceAttrs({
|
|
5501
|
+
source: promptSource,
|
|
5502
|
+
prompt,
|
|
5503
|
+
standingPrompt,
|
|
5504
|
+
resumePrompt,
|
|
5505
|
+
messages: wakeMessage ? [wakeMessage] : void 0,
|
|
5506
|
+
unreadSummary,
|
|
5507
|
+
sessionIdPresent: isResume,
|
|
5508
|
+
nativeStandingPrompt: Boolean(driver.supportsNativeStandingPrompt)
|
|
5509
|
+
});
|
|
4927
5510
|
const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
|
|
4928
5511
|
const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
|
|
4929
5512
|
if (canDeferEmptyStart) {
|
|
@@ -4984,6 +5567,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
4984
5567
|
lastRuntimeEventAt: Date.now(),
|
|
4985
5568
|
runtimeProgressStaleSince: null,
|
|
4986
5569
|
runtimeTraceSpan: null,
|
|
5570
|
+
runtimeTraceCounters: createRuntimeTraceCounters(),
|
|
4987
5571
|
lastActivity: "",
|
|
4988
5572
|
lastActivityDetail: "",
|
|
4989
5573
|
activityClientSeq: 0,
|
|
@@ -5005,7 +5589,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5005
5589
|
sessionId: effectiveConfig.sessionId || null,
|
|
5006
5590
|
launchId: launchId || null
|
|
5007
5591
|
});
|
|
5008
|
-
this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0);
|
|
5592
|
+
this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0, runtimeInputTraceAttrs);
|
|
5009
5593
|
this.agentsStarting.delete(agentId);
|
|
5010
5594
|
if (config.runtimeProfileControl) {
|
|
5011
5595
|
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
@@ -5099,6 +5683,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5099
5683
|
expectedTerminationReason: ap.expectedTerminationReason || void 0,
|
|
5100
5684
|
exitCode: finalCode,
|
|
5101
5685
|
exitSignal: finalSignal,
|
|
5686
|
+
...runtimeTraceCounterAttrs(ap),
|
|
5102
5687
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
|
|
5103
5688
|
});
|
|
5104
5689
|
if (processEndedCleanly) {
|
|
@@ -5526,7 +6111,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5526
6111
|
return true;
|
|
5527
6112
|
}
|
|
5528
6113
|
async resetWorkspace(agentId) {
|
|
5529
|
-
const agentDataDir =
|
|
6114
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
5530
6115
|
try {
|
|
5531
6116
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
5532
6117
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -5563,7 +6148,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5563
6148
|
return result;
|
|
5564
6149
|
}
|
|
5565
6150
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
5566
|
-
const workspacePath =
|
|
6151
|
+
const workspacePath = path12.join(this.dataDir, agentId);
|
|
5567
6152
|
return {
|
|
5568
6153
|
agentId,
|
|
5569
6154
|
launchId,
|
|
@@ -5811,7 +6396,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5811
6396
|
}
|
|
5812
6397
|
// Workspace file browsing
|
|
5813
6398
|
async getFileTree(agentId, dirPath) {
|
|
5814
|
-
const agentDir =
|
|
6399
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
5815
6400
|
try {
|
|
5816
6401
|
await stat2(agentDir);
|
|
5817
6402
|
} catch {
|
|
@@ -5819,8 +6404,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5819
6404
|
}
|
|
5820
6405
|
let targetDir = agentDir;
|
|
5821
6406
|
if (dirPath) {
|
|
5822
|
-
const resolved =
|
|
5823
|
-
if (!resolved.startsWith(agentDir +
|
|
6407
|
+
const resolved = path12.resolve(agentDir, dirPath);
|
|
6408
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
5824
6409
|
return [];
|
|
5825
6410
|
}
|
|
5826
6411
|
targetDir = resolved;
|
|
@@ -5828,14 +6413,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5828
6413
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
5829
6414
|
}
|
|
5830
6415
|
async readFile(agentId, filePath) {
|
|
5831
|
-
const agentDir =
|
|
5832
|
-
const resolved =
|
|
5833
|
-
if (!resolved.startsWith(agentDir +
|
|
6416
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
6417
|
+
const resolved = path12.resolve(agentDir, filePath);
|
|
6418
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
5834
6419
|
throw new Error("Access denied");
|
|
5835
6420
|
}
|
|
5836
6421
|
const info = await stat2(resolved);
|
|
5837
6422
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
5838
|
-
const ext =
|
|
6423
|
+
const ext = path12.extname(resolved).toLowerCase();
|
|
5839
6424
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
5840
6425
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
5841
6426
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -5870,13 +6455,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5870
6455
|
const agent = this.agents.get(agentId);
|
|
5871
6456
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
5872
6457
|
const home = os5.homedir();
|
|
5873
|
-
const workspaceDir =
|
|
6458
|
+
const workspaceDir = path12.join(this.dataDir, agentId);
|
|
5874
6459
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
5875
6460
|
const globalResults = await Promise.all(
|
|
5876
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
6461
|
+
paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
|
|
5877
6462
|
);
|
|
5878
6463
|
const workspaceResults = await Promise.all(
|
|
5879
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
6464
|
+
paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
|
|
5880
6465
|
);
|
|
5881
6466
|
const dedup = (skills) => {
|
|
5882
6467
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -5905,7 +6490,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5905
6490
|
const skills = [];
|
|
5906
6491
|
for (const entry of entries) {
|
|
5907
6492
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
5908
|
-
const skillMd =
|
|
6493
|
+
const skillMd = path12.join(dir, entry.name, "SKILL.md");
|
|
5909
6494
|
try {
|
|
5910
6495
|
const content = await readFile(skillMd, "utf-8");
|
|
5911
6496
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -5916,7 +6501,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5916
6501
|
} else if (entry.name.endsWith(".md")) {
|
|
5917
6502
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
5918
6503
|
try {
|
|
5919
|
-
const content = await readFile(
|
|
6504
|
+
const content = await readFile(path12.join(dir, entry.name), "utf-8");
|
|
5920
6505
|
const skill = this.parseSkillMd(cmdName, content);
|
|
5921
6506
|
skill.sourcePath = dir;
|
|
5922
6507
|
skills.push(skill);
|
|
@@ -5951,6 +6536,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5951
6536
|
/**
|
|
5952
6537
|
* Broadcast an activity change — emits a single agent:activity event that carries
|
|
5953
6538
|
* both the status (for the dot indicator) and trajectory entries (for the activity log).
|
|
6539
|
+
*
|
|
6540
|
+
* TODO(lifecycle-v2/daemon-protocol): split this legacy frame into
|
|
6541
|
+
* structured lifecycle producers. Runtime progress, transient heartbeat,
|
|
6542
|
+
* provider/runtime error, process exit, and user-visible activity entries
|
|
6543
|
+
* should carry explicit reason/correlation/window attrs so the server no
|
|
6544
|
+
* longer infers lifecycle semantics from generic activity strings.
|
|
5954
6545
|
*/
|
|
5955
6546
|
broadcastActivity(agentId, activity, detail, extraTrajectory = [], launchIdOverride) {
|
|
5956
6547
|
const ap = this.agents.get(agentId);
|
|
@@ -6176,8 +6767,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6176
6767
|
runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
|
|
6177
6768
|
};
|
|
6178
6769
|
}
|
|
6179
|
-
startRuntimeTrace(agentId, ap, reason, messages) {
|
|
6770
|
+
startRuntimeTrace(agentId, ap, reason, messages, inputTraceAttrs = {}) {
|
|
6180
6771
|
if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
|
|
6772
|
+
ap.runtimeTraceCounters = createRuntimeTraceCounters();
|
|
6181
6773
|
const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
|
|
6182
6774
|
if (messageControl) {
|
|
6183
6775
|
this.activateRuntimeProfileTurnControl(ap, messageControl);
|
|
@@ -6192,12 +6784,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6192
6784
|
reason,
|
|
6193
6785
|
hasSession: Boolean(ap.sessionId),
|
|
6194
6786
|
...this.messagesTraceAttrs(messages),
|
|
6787
|
+
...inputTraceAttrs,
|
|
6195
6788
|
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
6196
6789
|
}
|
|
6197
6790
|
});
|
|
6198
6791
|
span.addEvent("daemon.turn.started", {
|
|
6199
6792
|
reason,
|
|
6200
6793
|
...this.messagesTraceAttrs(messages),
|
|
6794
|
+
...inputTraceAttrs,
|
|
6201
6795
|
...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
|
|
6202
6796
|
});
|
|
6203
6797
|
ap.runtimeTraceSpan = span;
|
|
@@ -6306,6 +6900,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6306
6900
|
lastActivity: ap.lastActivity,
|
|
6307
6901
|
lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
|
|
6308
6902
|
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6903
|
+
...runtimeTraceCounterAttrs(ap),
|
|
6309
6904
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6310
6905
|
});
|
|
6311
6906
|
this.broadcastActivity(agentId, "error", diagnostic.detail);
|
|
@@ -6338,6 +6933,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6338
6933
|
lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
|
|
6339
6934
|
pendingMessages: ap.inbox.length,
|
|
6340
6935
|
recovery: "terminate_for_queued_message",
|
|
6936
|
+
...runtimeTraceCounterAttrs(ap),
|
|
6341
6937
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
|
|
6342
6938
|
});
|
|
6343
6939
|
ap.expectedTerminationReason = "stalled_recovery";
|
|
@@ -6365,6 +6961,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6365
6961
|
const ap = this.agents.get(agentId);
|
|
6366
6962
|
if (ap) {
|
|
6367
6963
|
const wasStalled = Boolean(ap.runtimeProgressStaleSince);
|
|
6964
|
+
this.noteRuntimeTraceCounter(ap, event);
|
|
6368
6965
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", { kind: event.kind });
|
|
6369
6966
|
if (wasStalled) {
|
|
6370
6967
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
|
|
@@ -6492,6 +7089,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6492
7089
|
}
|
|
6493
7090
|
this.endRuntimeTrace(ap, "ok", {
|
|
6494
7091
|
outcome: "turn-completed",
|
|
7092
|
+
...runtimeTraceCounterAttrs(ap),
|
|
6495
7093
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
|
|
6496
7094
|
});
|
|
6497
7095
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
@@ -6533,10 +7131,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6533
7131
|
`[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
|
|
6534
7132
|
);
|
|
6535
7133
|
}
|
|
6536
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error",
|
|
7134
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", {
|
|
7135
|
+
...runtimeErrorDiagnostics.eventAttrs,
|
|
7136
|
+
...runtimeTraceCounterAttrs(ap)
|
|
7137
|
+
});
|
|
6537
7138
|
this.endRuntimeTrace(ap, "error", {
|
|
6538
7139
|
outcome: "runtime-error",
|
|
6539
7140
|
...runtimeErrorDiagnostics.spanAttrs,
|
|
7141
|
+
...runtimeTraceCounterAttrs(ap),
|
|
6540
7142
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
6541
7143
|
});
|
|
6542
7144
|
if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
|
|
@@ -6559,6 +7161,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6559
7161
|
sendAgentStatus(agentId, status, launchId) {
|
|
6560
7162
|
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
6561
7163
|
}
|
|
7164
|
+
noteRuntimeTraceCounter(ap, event) {
|
|
7165
|
+
ap.runtimeTraceCounters.events++;
|
|
7166
|
+
switch (event.kind) {
|
|
7167
|
+
case "tool_call":
|
|
7168
|
+
ap.runtimeTraceCounters.toolCalls++;
|
|
7169
|
+
break;
|
|
7170
|
+
case "tool_output":
|
|
7171
|
+
ap.runtimeTraceCounters.toolOutputs++;
|
|
7172
|
+
break;
|
|
7173
|
+
case "compaction_started":
|
|
7174
|
+
ap.runtimeTraceCounters.compactionStarts++;
|
|
7175
|
+
break;
|
|
7176
|
+
case "compaction_finished":
|
|
7177
|
+
ap.runtimeTraceCounters.compactionFinishes++;
|
|
7178
|
+
break;
|
|
7179
|
+
case "text":
|
|
7180
|
+
ap.runtimeTraceCounters.textEvents++;
|
|
7181
|
+
break;
|
|
7182
|
+
case "thinking":
|
|
7183
|
+
ap.runtimeTraceCounters.thinkingEvents++;
|
|
7184
|
+
break;
|
|
7185
|
+
}
|
|
7186
|
+
}
|
|
6562
7187
|
/** Send a batched notification to the agent via stdin about pending messages */
|
|
6563
7188
|
sendStdinNotification(agentId) {
|
|
6564
7189
|
const ap = this.agents.get(agentId);
|
|
@@ -6661,6 +7286,14 @@ ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n"
|
|
|
6661
7286
|
|
|
6662
7287
|
Respond as appropriate. Complete all your work before stopping.
|
|
6663
7288
|
${RESPONSE_TARGET_HINT}`);
|
|
7289
|
+
const inputTraceAttrs = buildRuntimeInputTraceAttrs({
|
|
7290
|
+
source: `stdin_${mode}_delivery`,
|
|
7291
|
+
prompt,
|
|
7292
|
+
messages,
|
|
7293
|
+
sessionIdPresent: Boolean(ap.sessionId),
|
|
7294
|
+
nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
|
|
7295
|
+
});
|
|
7296
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
|
|
6664
7297
|
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
|
|
6665
7298
|
if (!encoded) {
|
|
6666
7299
|
ap.inbox.unshift(...messages);
|
|
@@ -6672,6 +7305,7 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
6672
7305
|
);
|
|
6673
7306
|
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6674
7307
|
...traceAttrs,
|
|
7308
|
+
...inputTraceAttrs,
|
|
6675
7309
|
outcome: "encode_failed",
|
|
6676
7310
|
requeued_messages_count: messages.length
|
|
6677
7311
|
}, "error");
|
|
@@ -6688,6 +7322,7 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
6688
7322
|
this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
|
|
6689
7323
|
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
6690
7324
|
...traceAttrs,
|
|
7325
|
+
...inputTraceAttrs,
|
|
6691
7326
|
outcome: "written",
|
|
6692
7327
|
stdin_write_attempted: true
|
|
6693
7328
|
});
|
|
@@ -6709,8 +7344,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
6709
7344
|
const nodes = [];
|
|
6710
7345
|
for (const entry of entries) {
|
|
6711
7346
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
6712
|
-
const fullPath =
|
|
6713
|
-
const relativePath =
|
|
7347
|
+
const fullPath = path12.join(dir, entry.name);
|
|
7348
|
+
const relativePath = path12.relative(rootDir, fullPath);
|
|
6714
7349
|
let info;
|
|
6715
7350
|
try {
|
|
6716
7351
|
info = await stat2(fullPath);
|
|
@@ -7013,9 +7648,9 @@ var ReminderCache = class {
|
|
|
7013
7648
|
|
|
7014
7649
|
// src/machineLock.ts
|
|
7015
7650
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
7016
|
-
import { mkdirSync as
|
|
7651
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
7017
7652
|
import os6 from "os";
|
|
7018
|
-
import
|
|
7653
|
+
import path13 from "path";
|
|
7019
7654
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
7020
7655
|
var DaemonMachineLockConflictError = class extends Error {
|
|
7021
7656
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -7037,7 +7672,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
7037
7672
|
return resolveSlockHomePath("machines");
|
|
7038
7673
|
}
|
|
7039
7674
|
function ownerPath(lockDir) {
|
|
7040
|
-
return
|
|
7675
|
+
return path13.join(lockDir, "owner.json");
|
|
7041
7676
|
}
|
|
7042
7677
|
function readOwner(lockDir) {
|
|
7043
7678
|
try {
|
|
@@ -7067,13 +7702,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
7067
7702
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
7068
7703
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
7069
7704
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
7070
|
-
const machineDir =
|
|
7071
|
-
const lockDir =
|
|
7705
|
+
const machineDir = path13.join(rootDir, lockId);
|
|
7706
|
+
const lockDir = path13.join(machineDir, "daemon.lock");
|
|
7072
7707
|
const token = randomUUID2();
|
|
7073
|
-
|
|
7708
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
7074
7709
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7075
7710
|
try {
|
|
7076
|
-
|
|
7711
|
+
mkdirSync6(lockDir);
|
|
7077
7712
|
const owner = {
|
|
7078
7713
|
pid: process.pid,
|
|
7079
7714
|
token,
|
|
@@ -7083,7 +7718,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
7083
7718
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
7084
7719
|
};
|
|
7085
7720
|
try {
|
|
7086
|
-
|
|
7721
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
7087
7722
|
`, { mode: 384 });
|
|
7088
7723
|
} catch (err) {
|
|
7089
7724
|
rmSync2(lockDir, { recursive: true, force: true });
|
|
@@ -7120,8 +7755,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
7120
7755
|
}
|
|
7121
7756
|
|
|
7122
7757
|
// src/localTraceSink.ts
|
|
7123
|
-
import { appendFileSync, mkdirSync as
|
|
7124
|
-
import
|
|
7758
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
7759
|
+
import path14 from "path";
|
|
7125
7760
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
7126
7761
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
7127
7762
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -7157,7 +7792,7 @@ var LocalRotatingTraceSink = class {
|
|
|
7157
7792
|
currentSize = 0;
|
|
7158
7793
|
sequence = 0;
|
|
7159
7794
|
constructor(options) {
|
|
7160
|
-
this.traceDir =
|
|
7795
|
+
this.traceDir = path14.join(options.machineDir, "traces");
|
|
7161
7796
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
7162
7797
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
7163
7798
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -7183,26 +7818,26 @@ var LocalRotatingTraceSink = class {
|
|
|
7183
7818
|
return this.currentFile;
|
|
7184
7819
|
}
|
|
7185
7820
|
ensureFile(nextBytes) {
|
|
7186
|
-
|
|
7821
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
7187
7822
|
const nowMs = this.nowMsProvider();
|
|
7188
7823
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
7189
7824
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
7190
|
-
this.currentFile =
|
|
7825
|
+
this.currentFile = path14.join(
|
|
7191
7826
|
this.traceDir,
|
|
7192
7827
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
7193
7828
|
);
|
|
7194
|
-
|
|
7829
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
7195
7830
|
this.currentSize = statSync4(this.currentFile).size;
|
|
7196
7831
|
this.currentFileOpenedAtMs = nowMs;
|
|
7197
7832
|
this.pruneOldFiles();
|
|
7198
7833
|
}
|
|
7199
7834
|
}
|
|
7200
7835
|
pruneOldFiles() {
|
|
7201
|
-
const files =
|
|
7836
|
+
const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
|
|
7202
7837
|
const excess = files.length - this.maxFiles;
|
|
7203
7838
|
if (excess <= 0) return;
|
|
7204
7839
|
for (const file of files.slice(0, excess)) {
|
|
7205
|
-
rmSync3(
|
|
7840
|
+
rmSync3(path14.join(this.traceDir, file), { force: true });
|
|
7206
7841
|
}
|
|
7207
7842
|
}
|
|
7208
7843
|
};
|
|
@@ -7289,11 +7924,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
7289
7924
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
7290
7925
|
import { gzipSync } from "zlib";
|
|
7291
7926
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
7292
|
-
import
|
|
7927
|
+
import path15 from "path";
|
|
7293
7928
|
|
|
7294
7929
|
// src/directUploadCapability.ts
|
|
7295
|
-
function joinUrl(base,
|
|
7296
|
-
return `${base.replace(/\/+$/, "")}${
|
|
7930
|
+
function joinUrl(base, path17) {
|
|
7931
|
+
return `${base.replace(/\/+$/, "")}${path17}`;
|
|
7297
7932
|
}
|
|
7298
7933
|
function jsonHeaders(apiKey) {
|
|
7299
7934
|
return {
|
|
@@ -7512,7 +8147,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7512
8147
|
}, nextMs);
|
|
7513
8148
|
}
|
|
7514
8149
|
async findUploadCandidates() {
|
|
7515
|
-
const traceDir =
|
|
8150
|
+
const traceDir = path15.join(this.options.machineDir, "traces");
|
|
7516
8151
|
let names;
|
|
7517
8152
|
try {
|
|
7518
8153
|
names = await readdir3(traceDir);
|
|
@@ -7524,8 +8159,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
7524
8159
|
const currentFile = this.options.currentFileProvider?.();
|
|
7525
8160
|
const candidates = [];
|
|
7526
8161
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
7527
|
-
const file =
|
|
7528
|
-
if (currentFile &&
|
|
8162
|
+
const file = path15.join(traceDir, name);
|
|
8163
|
+
if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
|
|
7529
8164
|
if (await this.isUploaded(file)) continue;
|
|
7530
8165
|
try {
|
|
7531
8166
|
const info = await stat3(file);
|
|
@@ -7599,8 +8234,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
7599
8234
|
}
|
|
7600
8235
|
}
|
|
7601
8236
|
uploadStatePath(file) {
|
|
7602
|
-
const stateDir =
|
|
7603
|
-
return
|
|
8237
|
+
const stateDir = path15.join(this.options.machineDir, "trace-uploads");
|
|
8238
|
+
return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
|
|
7604
8239
|
}
|
|
7605
8240
|
async isUploaded(file) {
|
|
7606
8241
|
try {
|
|
@@ -7612,9 +8247,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
7612
8247
|
}
|
|
7613
8248
|
async markUploaded(file, metadata) {
|
|
7614
8249
|
const stateFile = this.uploadStatePath(file);
|
|
7615
|
-
await mkdir2(
|
|
8250
|
+
await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
|
|
7616
8251
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
7617
|
-
file:
|
|
8252
|
+
file: path15.basename(file),
|
|
7618
8253
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7619
8254
|
...metadata
|
|
7620
8255
|
}, null, 2)}
|
|
@@ -7633,10 +8268,10 @@ function readPositiveIntegerEnv2(name, fallback) {
|
|
|
7633
8268
|
|
|
7634
8269
|
// src/core.ts
|
|
7635
8270
|
var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
7636
|
-
var DAEMON_CLI_USAGE =
|
|
7637
|
-
function parseDaemonCliArgs(args) {
|
|
8271
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
8272
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
7638
8273
|
let serverUrl = "";
|
|
7639
|
-
let apiKey = "";
|
|
8274
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
7640
8275
|
for (let i = 0; i < args.length; i++) {
|
|
7641
8276
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
7642
8277
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -7653,23 +8288,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
7653
8288
|
}
|
|
7654
8289
|
}
|
|
7655
8290
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
7656
|
-
const dirname =
|
|
7657
|
-
const jsPath =
|
|
8291
|
+
const dirname = path16.dirname(fileURLToPath2(moduleUrl));
|
|
8292
|
+
const jsPath = path16.resolve(dirname, "chat-bridge.js");
|
|
7658
8293
|
try {
|
|
7659
8294
|
accessSync(jsPath);
|
|
7660
8295
|
return jsPath;
|
|
7661
8296
|
} catch {
|
|
7662
|
-
return
|
|
8297
|
+
return path16.resolve(dirname, "chat-bridge.ts");
|
|
7663
8298
|
}
|
|
7664
8299
|
}
|
|
7665
8300
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
7666
|
-
const thisDir =
|
|
7667
|
-
const bundledDistPath =
|
|
8301
|
+
const thisDir = path16.dirname(fileURLToPath2(moduleUrl));
|
|
8302
|
+
const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
|
|
7668
8303
|
try {
|
|
7669
8304
|
accessSync(bundledDistPath);
|
|
7670
8305
|
return bundledDistPath;
|
|
7671
8306
|
} catch {
|
|
7672
|
-
const workspaceDistPath =
|
|
8307
|
+
const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
7673
8308
|
accessSync(workspaceDistPath);
|
|
7674
8309
|
return workspaceDistPath;
|
|
7675
8310
|
}
|
|
@@ -7848,7 +8483,7 @@ var DaemonCore = class {
|
|
|
7848
8483
|
}
|
|
7849
8484
|
resolveMachineStateRoot() {
|
|
7850
8485
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
7851
|
-
if (this.options.dataDir) return
|
|
8486
|
+
if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
|
|
7852
8487
|
return resolveDefaultMachineStateRoot();
|
|
7853
8488
|
}
|
|
7854
8489
|
shouldEnableLocalTrace() {
|
|
@@ -8231,6 +8866,8 @@ var DaemonCore = class {
|
|
|
8231
8866
|
};
|
|
8232
8867
|
|
|
8233
8868
|
export {
|
|
8869
|
+
DAEMON_API_KEY_ENV,
|
|
8870
|
+
scrubDaemonAuthEnv,
|
|
8234
8871
|
resolveWorkspaceDirectoryPath,
|
|
8235
8872
|
scanWorkspaceDirectories,
|
|
8236
8873
|
deleteWorkspaceDirectory,
|