@slock-ai/daemon 0.52.2 → 0.53.0-play.20260524064439
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-HSBOURQE.js → chunk-AB3IDL5P.js} +634 -124
- package/dist/cli/index.js +167 -13
- package/dist/core.js +6 -2
- package/dist/drivers/piSdkRunner.js +96 -0
- package/dist/index.js +5 -3
- package/package.json +3 -1
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from "./chunk-KNMCE6WB.js";
|
|
8
8
|
|
|
9
9
|
// src/core.ts
|
|
10
|
-
import
|
|
10
|
+
import path17 from "path";
|
|
11
11
|
import os8 from "os";
|
|
12
|
-
import { createRequire } from "module";
|
|
12
|
+
import { createRequire as createRequire2 } from "module";
|
|
13
13
|
import { accessSync } from "fs";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
14
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15
15
|
|
|
16
16
|
// ../shared/src/tracing/index.ts
|
|
17
17
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -723,6 +723,7 @@ var SERVER_CAPABILITY_MATRIX = {
|
|
|
723
723
|
var RUNTIMES = [
|
|
724
724
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
725
725
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
726
|
+
{ id: "pi", displayName: "Pi", binary: "pi", supported: true },
|
|
726
727
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
727
728
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
728
729
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -764,20 +765,21 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
764
765
|
};
|
|
765
766
|
|
|
766
767
|
// src/agentProcessManager.ts
|
|
767
|
-
import { mkdirSync as
|
|
768
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
768
769
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
769
770
|
import { createHash as createHash2 } from "crypto";
|
|
770
|
-
import
|
|
771
|
+
import path13 from "path";
|
|
771
772
|
import os6 from "os";
|
|
772
773
|
|
|
773
774
|
// src/drivers/claude.ts
|
|
774
775
|
import { spawn } from "child_process";
|
|
775
|
-
import { existsSync as existsSync3, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
776
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
776
777
|
import os2 from "os";
|
|
777
778
|
import path4 from "path";
|
|
778
779
|
|
|
779
780
|
// src/drivers/cliTransport.ts
|
|
780
|
-
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
781
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
782
|
+
import { createRequire } from "module";
|
|
781
783
|
import path2 from "path";
|
|
782
784
|
|
|
783
785
|
// src/drivers/systemPrompt.ts
|
|
@@ -872,13 +874,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
872
874
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
873
875
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
874
876
|
19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
875
|
-
20. **\`slock
|
|
876
|
-
21. **\`slock
|
|
877
|
-
22. **\`slock reminder
|
|
878
|
-
23. **\`slock reminder
|
|
879
|
-
24. **\`slock reminder
|
|
880
|
-
25. **\`slock reminder
|
|
881
|
-
26. **\`slock
|
|
877
|
+
20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
|
|
878
|
+
21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
|
|
879
|
+
22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
880
|
+
23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
881
|
+
24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
882
|
+
25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
883
|
+
26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
884
|
+
27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
885
|
+
28. **\`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\`).
|
|
882
886
|
|
|
883
887
|
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:
|
|
884
888
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -913,20 +917,23 @@ Use reminders for follow-up that depends on future state you cannot resolve now,
|
|
|
913
917
|
When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
|
|
914
918
|
Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
|
|
915
919
|
Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
|
|
920
|
+
const messageHeredocDelimiter = "SLOCKMSG";
|
|
916
921
|
const sendingMessagesSection = isCli ? `### Sending messages
|
|
917
922
|
|
|
918
|
-
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'
|
|
919
|
-
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'
|
|
920
|
-
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'
|
|
921
|
-
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'
|
|
923
|
+
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
924
|
+
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
925
|
+
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
926
|
+
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
922
927
|
|
|
923
928
|
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
924
929
|
\`\`\`bash
|
|
925
|
-
slock message send --target "#channel-name" <<'
|
|
930
|
+
slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
|
|
926
931
|
Long message with "quotes", $vars, \`backticks\`, and code blocks.
|
|
927
|
-
|
|
932
|
+
${messageHeredocDelimiter}
|
|
928
933
|
\`\`\`
|
|
929
934
|
|
|
935
|
+
Use a delimiter that is unlikely to appear in the message body; the examples use \`${messageHeredocDelimiter}\` instead of \`EOF\` so shell snippets and recovery drafts are less likely to leak delimiter text into sent messages.
|
|
936
|
+
|
|
930
937
|
If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
931
938
|
- To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
|
|
932
939
|
- To send the current draft unchanged, use \`slock message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
|
|
@@ -945,7 +952,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
945
952
|
|
|
946
953
|
- **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
|
|
947
954
|
- When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
|
|
948
|
-
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`slock message send --target "#general:a1b2c3d4" <<'
|
|
955
|
+
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`slock message send --target "#general:a1b2c3d4" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet.
|
|
949
956
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
950
957
|
- You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
|
|
951
958
|
- You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:a1b2c3d4"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
|
|
@@ -979,6 +986,11 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
979
986
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
980
987
|
- **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.
|
|
981
988
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
989
|
+
const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
|
|
990
|
+
|
|
991
|
+
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. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. 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, call internal Slock integration endpoints directly, 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
|
|
992
|
+
|
|
993
|
+
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.`;
|
|
982
994
|
const readingHistorySection = isCli ? `### Reading history
|
|
983
995
|
|
|
984
996
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1015,7 +1027,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
|
|
|
1015
1027
|
**Workflow:**
|
|
1016
1028
|
1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
|
|
1017
1029
|
2. If the claim fails, someone else is working on it \u2014 move on to another task
|
|
1018
|
-
3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'
|
|
1030
|
+
3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1019
1031
|
4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
|
|
1020
1032
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
1021
1033
|
|
|
@@ -1150,6 +1162,8 @@ ${discoverySection}
|
|
|
1150
1162
|
|
|
1151
1163
|
${channelAwarenessSection}
|
|
1152
1164
|
|
|
1165
|
+
${thirdPartyIntegrationsSection}
|
|
1166
|
+
|
|
1153
1167
|
${readingHistorySection}
|
|
1154
1168
|
|
|
1155
1169
|
${historicalReferenceSection}
|
|
@@ -1349,6 +1363,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1349
1363
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1350
1364
|
}
|
|
1351
1365
|
|
|
1366
|
+
// src/authEnv.ts
|
|
1367
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1368
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1369
|
+
function scrubDaemonAuthEnv(env) {
|
|
1370
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1371
|
+
return env;
|
|
1372
|
+
}
|
|
1373
|
+
function scrubDaemonChildEnv(env) {
|
|
1374
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1375
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1376
|
+
return env;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1352
1379
|
// src/agentCredentialProxy.ts
|
|
1353
1380
|
import { randomBytes } from "crypto";
|
|
1354
1381
|
import http from "http";
|
|
@@ -1872,8 +1899,51 @@ function unregisterAgentCredentialProxyForLaunch(input) {
|
|
|
1872
1899
|
|
|
1873
1900
|
// src/drivers/cliTransport.ts
|
|
1874
1901
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1902
|
+
var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
1875
1903
|
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1876
1904
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1905
|
+
var cachedOpencliBinPath;
|
|
1906
|
+
function resolveOpencliBinPath() {
|
|
1907
|
+
if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
|
|
1908
|
+
try {
|
|
1909
|
+
const require2 = createRequire(import.meta.url);
|
|
1910
|
+
const mainPath = require2.resolve("@jackwener/opencli");
|
|
1911
|
+
let dir = path2.dirname(mainPath);
|
|
1912
|
+
const root = path2.parse(dir).root;
|
|
1913
|
+
while (dir && dir !== root) {
|
|
1914
|
+
const candidate = path2.join(dir, "package.json");
|
|
1915
|
+
try {
|
|
1916
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
1917
|
+
if (pkg.name === "@jackwener/opencli") {
|
|
1918
|
+
const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencli;
|
|
1919
|
+
if (!binEntry) {
|
|
1920
|
+
cachedOpencliBinPath = null;
|
|
1921
|
+
return null;
|
|
1922
|
+
}
|
|
1923
|
+
cachedOpencliBinPath = path2.resolve(dir, binEntry);
|
|
1924
|
+
return cachedOpencliBinPath;
|
|
1925
|
+
}
|
|
1926
|
+
} catch {
|
|
1927
|
+
}
|
|
1928
|
+
const parent = path2.dirname(dir);
|
|
1929
|
+
if (parent === dir) break;
|
|
1930
|
+
dir = parent;
|
|
1931
|
+
}
|
|
1932
|
+
cachedOpencliBinPath = null;
|
|
1933
|
+
return null;
|
|
1934
|
+
} catch {
|
|
1935
|
+
cachedOpencliBinPath = null;
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function windowsUtf8Env() {
|
|
1940
|
+
return {
|
|
1941
|
+
PYTHONIOENCODING: "utf-8",
|
|
1942
|
+
PYTHONUTF8: "1",
|
|
1943
|
+
LANG: "C.UTF-8",
|
|
1944
|
+
LC_ALL: "C.UTF-8"
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1877
1947
|
function runtimeContextEnv(config) {
|
|
1878
1948
|
const ctx = config.runtimeContext;
|
|
1879
1949
|
if (!ctx) return {};
|
|
@@ -1932,10 +2002,70 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
|
|
|
1932
2002
|
set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
|
|
1933
2003
|
set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
1934
2004
|
` : "";
|
|
1935
|
-
const cmdBody =
|
|
1936
|
-
|
|
1937
|
-
|
|
2005
|
+
const cmdBody = [
|
|
2006
|
+
"@echo off",
|
|
2007
|
+
"set PYTHONIOENCODING=utf-8",
|
|
2008
|
+
"set PYTHONUTF8=1",
|
|
2009
|
+
"set LANG=C.UTF-8",
|
|
2010
|
+
"set LC_ALL=C.UTF-8",
|
|
2011
|
+
"chcp 65001 >NUL 2>NUL",
|
|
2012
|
+
cmdCredentialLine.trimEnd(),
|
|
2013
|
+
`"${process.execPath}" "${ctx.slockCliPath}" %*`,
|
|
2014
|
+
""
|
|
2015
|
+
].filter((line) => line.length > 0).join("\r\n") + "\r\n";
|
|
1938
2016
|
writeFileSync(cmdWrapper, cmdBody);
|
|
2017
|
+
const psWrapper = path2.join(slockDir, "slock.ps1");
|
|
2018
|
+
const psCredentialLines = agentCredentialProxy ? [
|
|
2019
|
+
`$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
|
|
2020
|
+
`$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
|
|
2021
|
+
`$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
|
|
2022
|
+
] : [];
|
|
2023
|
+
const psBody = [
|
|
2024
|
+
"$ErrorActionPreference = 'Stop'",
|
|
2025
|
+
"$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
|
|
2026
|
+
"[Console]::OutputEncoding = $utf8NoBom",
|
|
2027
|
+
"$OutputEncoding = $utf8NoBom",
|
|
2028
|
+
"$env:PYTHONIOENCODING = 'utf-8'",
|
|
2029
|
+
"$env:PYTHONUTF8 = '1'",
|
|
2030
|
+
"$env:LANG = 'C.UTF-8'",
|
|
2031
|
+
"$env:LC_ALL = 'C.UTF-8'",
|
|
2032
|
+
...psCredentialLines,
|
|
2033
|
+
`$node = ${powershellSingleQuote(process.execPath)}`,
|
|
2034
|
+
`$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
|
|
2035
|
+
"if ($MyInvocation.ExpectingInput) {",
|
|
2036
|
+
" $input | & $node $cli @args",
|
|
2037
|
+
"} else {",
|
|
2038
|
+
" & $node $cli @args",
|
|
2039
|
+
"}",
|
|
2040
|
+
"exit $LASTEXITCODE",
|
|
2041
|
+
""
|
|
2042
|
+
].join("\r\n");
|
|
2043
|
+
writeFileSync(psWrapper, psBody);
|
|
2044
|
+
}
|
|
2045
|
+
const opencliBinPath = resolveOpencliBinPath();
|
|
2046
|
+
if (opencliBinPath) {
|
|
2047
|
+
const opencliPosixWrapper = path2.join(slockDir, "opencli");
|
|
2048
|
+
writeFileSync(
|
|
2049
|
+
opencliPosixWrapper,
|
|
2050
|
+
`#!/usr/bin/env bash
|
|
2051
|
+
exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "$@"
|
|
2052
|
+
`,
|
|
2053
|
+
{ mode: 493 }
|
|
2054
|
+
);
|
|
2055
|
+
if (platform === "win32") {
|
|
2056
|
+
const opencliCmdWrapper = path2.join(slockDir, "opencli.cmd");
|
|
2057
|
+
const opencliCmdBody = [
|
|
2058
|
+
"@echo off",
|
|
2059
|
+
"set PYTHONIOENCODING=utf-8",
|
|
2060
|
+
"set PYTHONUTF8=1",
|
|
2061
|
+
"set LANG=C.UTF-8",
|
|
2062
|
+
"set LC_ALL=C.UTF-8",
|
|
2063
|
+
"chcp 65001 >NUL 2>NUL",
|
|
2064
|
+
`"${process.execPath}" "${opencliBinPath}" %*`,
|
|
2065
|
+
""
|
|
2066
|
+
].join("\r\n") + "\r\n";
|
|
2067
|
+
writeFileSync(opencliCmdWrapper, opencliCmdBody);
|
|
2068
|
+
}
|
|
1939
2069
|
}
|
|
1940
2070
|
const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
|
|
1941
2071
|
const spawnEnv = {
|
|
@@ -1943,6 +2073,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1943
2073
|
FORCE_COLOR: "0",
|
|
1944
2074
|
...ctx.config.envVars || {},
|
|
1945
2075
|
...extraEnv,
|
|
2076
|
+
...platform === "win32" ? windowsUtf8Env() : {},
|
|
1946
2077
|
...runtimeContextEnv(ctx.config),
|
|
1947
2078
|
[SLOCK_HOME_ENV]: slockHome,
|
|
1948
2079
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
@@ -1951,7 +2082,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1951
2082
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1952
2083
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1953
2084
|
};
|
|
1954
|
-
|
|
2085
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
1955
2086
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1956
2087
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1957
2088
|
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
@@ -1998,7 +2129,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1998
2129
|
}
|
|
1999
2130
|
function resolveCommandOnPath(command, deps = {}) {
|
|
2000
2131
|
const platform = deps.platform ?? process.platform;
|
|
2001
|
-
const env = deps.env ?? process.env;
|
|
2132
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
2002
2133
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2003
2134
|
if (platform === "win32") {
|
|
2004
2135
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -2023,7 +2154,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
2023
2154
|
return null;
|
|
2024
2155
|
}
|
|
2025
2156
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
2026
|
-
const env = deps.env ?? process.env;
|
|
2157
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
2027
2158
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2028
2159
|
try {
|
|
2029
2160
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -2085,7 +2216,7 @@ function expandClaudeMcpConfigVariables(raw, vars) {
|
|
|
2085
2216
|
function readClaudeMcpServers(configPath, vars = {}) {
|
|
2086
2217
|
try {
|
|
2087
2218
|
const parsed = JSON.parse(
|
|
2088
|
-
expandClaudeMcpConfigVariables(
|
|
2219
|
+
expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
|
|
2089
2220
|
);
|
|
2090
2221
|
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
|
|
2091
2222
|
return parsed.mcpServers;
|
|
@@ -2387,7 +2518,7 @@ var ClaudeDriver = class {
|
|
|
2387
2518
|
|
|
2388
2519
|
// src/drivers/codex.ts
|
|
2389
2520
|
import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
|
|
2390
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2521
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2391
2522
|
import os3 from "os";
|
|
2392
2523
|
import path5 from "path";
|
|
2393
2524
|
function getCodexNotificationErrorMessage(params) {
|
|
@@ -2925,7 +3056,7 @@ function detectCodexModels(home = os3.homedir()) {
|
|
|
2925
3056
|
const configPath = path5.join(home, ".codex", "config.toml");
|
|
2926
3057
|
let models = [];
|
|
2927
3058
|
try {
|
|
2928
|
-
const raw =
|
|
3059
|
+
const raw = readFileSync3(cachePath, "utf8");
|
|
2929
3060
|
const parsed = JSON.parse(raw);
|
|
2930
3061
|
const entries = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
2931
3062
|
for (const entry of entries) {
|
|
@@ -2942,7 +3073,7 @@ function detectCodexModels(home = os3.homedir()) {
|
|
|
2942
3073
|
if (models.length === 0) return null;
|
|
2943
3074
|
let defaultModel;
|
|
2944
3075
|
try {
|
|
2945
|
-
const raw =
|
|
3076
|
+
const raw = readFileSync3(configPath, "utf8");
|
|
2946
3077
|
const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
2947
3078
|
if (match) defaultModel = match[1];
|
|
2948
3079
|
} catch {
|
|
@@ -3319,7 +3450,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
3319
3450
|
}
|
|
3320
3451
|
function runCursorModelsCommand() {
|
|
3321
3452
|
return spawnSync("cursor-agent", ["models"], {
|
|
3322
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3453
|
+
env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3323
3454
|
encoding: "utf8",
|
|
3324
3455
|
timeout: 5e3
|
|
3325
3456
|
});
|
|
@@ -3366,7 +3497,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
3366
3497
|
}
|
|
3367
3498
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
3368
3499
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
3369
|
-
const env = deps.env ?? process.env;
|
|
3500
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
3370
3501
|
const winPath = path8.win32;
|
|
3371
3502
|
let geminiEntry = null;
|
|
3372
3503
|
try {
|
|
@@ -3540,13 +3671,16 @@ var GeminiDriver = class {
|
|
|
3540
3671
|
// src/drivers/kimi.ts
|
|
3541
3672
|
import { randomUUID } from "crypto";
|
|
3542
3673
|
import { spawn as spawn6 } from "child_process";
|
|
3543
|
-
import { existsSync as existsSync7, readFileSync as
|
|
3674
|
+
import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3544
3675
|
import os4 from "os";
|
|
3545
3676
|
import path9 from "path";
|
|
3546
3677
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
3547
3678
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
3548
3679
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
3549
3680
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3681
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3682
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3683
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
3550
3684
|
function parseToolArguments(raw) {
|
|
3551
3685
|
if (typeof raw !== "string") return raw;
|
|
3552
3686
|
try {
|
|
@@ -3555,6 +3689,73 @@ function parseToolArguments(raw) {
|
|
|
3555
3689
|
return raw;
|
|
3556
3690
|
}
|
|
3557
3691
|
}
|
|
3692
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3693
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3694
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
3695
|
+
return {
|
|
3696
|
+
raw: inlineConfig,
|
|
3697
|
+
explicitPath: null,
|
|
3698
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3702
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3703
|
+
try {
|
|
3704
|
+
return {
|
|
3705
|
+
raw: readFileSync4(configPath, "utf8"),
|
|
3706
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3707
|
+
sourcePath: configPath
|
|
3708
|
+
};
|
|
3709
|
+
} catch {
|
|
3710
|
+
return {
|
|
3711
|
+
raw: null,
|
|
3712
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3713
|
+
sourcePath: configPath
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
3718
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3719
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3720
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3721
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
3722
|
+
}
|
|
3723
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3724
|
+
return {
|
|
3725
|
+
...process.env,
|
|
3726
|
+
...ctx.config.envVars || {},
|
|
3727
|
+
...overrideEnv || {}
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3731
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3732
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3733
|
+
const args = [];
|
|
3734
|
+
let configFilePath = null;
|
|
3735
|
+
let configContent = null;
|
|
3736
|
+
if (source.explicitPath) {
|
|
3737
|
+
configFilePath = source.explicitPath;
|
|
3738
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3739
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3740
|
+
configContent = source.raw;
|
|
3741
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
3742
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3743
|
+
chmodSync(configFilePath, 384);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
if (configFilePath) {
|
|
3747
|
+
args.push("--config-file", configFilePath);
|
|
3748
|
+
}
|
|
3749
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3750
|
+
args.push("--model", ctx.config.model);
|
|
3751
|
+
}
|
|
3752
|
+
return {
|
|
3753
|
+
args,
|
|
3754
|
+
env: buildKimiSpawnEnv(env),
|
|
3755
|
+
configFilePath,
|
|
3756
|
+
configContent
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3558
3759
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
3559
3760
|
return {
|
|
3560
3761
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3578,7 +3779,25 @@ var KimiDriver = class {
|
|
|
3578
3779
|
};
|
|
3579
3780
|
model = {
|
|
3580
3781
|
detectedModelsVerifiedAs: "launchable",
|
|
3581
|
-
toLaunchSpec: (modelId) =>
|
|
3782
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3783
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3784
|
+
const launchCtx = {
|
|
3785
|
+
...ctx,
|
|
3786
|
+
config: {
|
|
3787
|
+
...ctx.config,
|
|
3788
|
+
model: modelId
|
|
3789
|
+
}
|
|
3790
|
+
};
|
|
3791
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3792
|
+
home: opts?.home,
|
|
3793
|
+
writeGeneratedConfig: false
|
|
3794
|
+
});
|
|
3795
|
+
return {
|
|
3796
|
+
args: launch.args,
|
|
3797
|
+
env: launch.env,
|
|
3798
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3582
3801
|
};
|
|
3583
3802
|
supportsStdinNotification = true;
|
|
3584
3803
|
mcpToolPrefix = "";
|
|
@@ -3632,6 +3851,7 @@ var KimiDriver = class {
|
|
|
3632
3851
|
}
|
|
3633
3852
|
}
|
|
3634
3853
|
}), "utf8");
|
|
3854
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
3635
3855
|
const args = [
|
|
3636
3856
|
"--wire",
|
|
3637
3857
|
"--yolo",
|
|
@@ -3640,14 +3860,15 @@ var KimiDriver = class {
|
|
|
3640
3860
|
"--mcp-config-file",
|
|
3641
3861
|
mcpConfigPath,
|
|
3642
3862
|
"--session",
|
|
3643
|
-
this.sessionId
|
|
3863
|
+
this.sessionId,
|
|
3864
|
+
...launch.args
|
|
3644
3865
|
];
|
|
3645
3866
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3646
3867
|
args.push("--model", ctx.config.model);
|
|
3647
3868
|
}
|
|
3648
3869
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3649
|
-
const
|
|
3650
|
-
const proc = spawn6(
|
|
3870
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
3871
|
+
const proc = spawn6(spawnTarget.command, spawnTarget.args, {
|
|
3651
3872
|
cwd: ctx.workingDirectory,
|
|
3652
3873
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3653
3874
|
env: spawnEnv,
|
|
@@ -3655,7 +3876,7 @@ var KimiDriver = class {
|
|
|
3655
3876
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3656
3877
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3657
3878
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3658
|
-
shell:
|
|
3879
|
+
shell: spawnTarget.shell
|
|
3659
3880
|
});
|
|
3660
3881
|
proc.stdin?.write(JSON.stringify({
|
|
3661
3882
|
jsonrpc: "2.0",
|
|
@@ -3771,14 +3992,9 @@ var KimiDriver = class {
|
|
|
3771
3992
|
return detectKimiModels();
|
|
3772
3993
|
}
|
|
3773
3994
|
};
|
|
3774
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
try {
|
|
3778
|
-
raw = readFileSync3(configPath, "utf8");
|
|
3779
|
-
} catch {
|
|
3780
|
-
return null;
|
|
3781
|
-
}
|
|
3995
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
3996
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3997
|
+
if (raw === null) return null;
|
|
3782
3998
|
const models = [];
|
|
3783
3999
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3784
4000
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3799,7 +4015,7 @@ function detectKimiModels(home = os4.homedir()) {
|
|
|
3799
4015
|
|
|
3800
4016
|
// src/drivers/opencode.ts
|
|
3801
4017
|
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
3802
|
-
import { existsSync as existsSync8, readFileSync as
|
|
4018
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
3803
4019
|
import os5 from "os";
|
|
3804
4020
|
import path10 from "path";
|
|
3805
4021
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
@@ -3851,7 +4067,7 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
3851
4067
|
function readLocalOpenCodeConfig(home = os5.homedir()) {
|
|
3852
4068
|
const configPath = path10.join(home, ".config", "opencode", "opencode.json");
|
|
3853
4069
|
try {
|
|
3854
|
-
return parseOpenCodeConfigContent(
|
|
4070
|
+
return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
|
|
3855
4071
|
} catch {
|
|
3856
4072
|
}
|
|
3857
4073
|
return {};
|
|
@@ -4041,7 +4257,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
4041
4257
|
const platform = deps.platform ?? process.platform;
|
|
4042
4258
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
4043
4259
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
4044
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
4260
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
4045
4261
|
encoding: "utf8",
|
|
4046
4262
|
timeout: 5e3,
|
|
4047
4263
|
shell: platform === "win32"
|
|
@@ -4102,7 +4318,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
|
4102
4318
|
}
|
|
4103
4319
|
function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
4104
4320
|
if (!isWindowsCommandShim(commandPath)) return [];
|
|
4105
|
-
const readFileSyncFn = deps.readFileSyncFn ??
|
|
4321
|
+
const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
|
|
4106
4322
|
const commandDir = path10.win32.dirname(commandPath);
|
|
4107
4323
|
let raw;
|
|
4108
4324
|
try {
|
|
@@ -4302,6 +4518,297 @@ var OpenCodeDriver = class {
|
|
|
4302
4518
|
}
|
|
4303
4519
|
};
|
|
4304
4520
|
|
|
4521
|
+
// src/drivers/pi.ts
|
|
4522
|
+
import { spawn as spawn8 } from "child_process";
|
|
4523
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4524
|
+
import path11 from "path";
|
|
4525
|
+
import { fileURLToPath } from "url";
|
|
4526
|
+
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
4527
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
4528
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
4529
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
4530
|
+
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
4531
|
+
function parseSemver2(version) {
|
|
4532
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
4533
|
+
if (!match) return null;
|
|
4534
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4535
|
+
}
|
|
4536
|
+
function isSupportedPiVersion(version) {
|
|
4537
|
+
if (!version) return true;
|
|
4538
|
+
const actual = parseSemver2(version);
|
|
4539
|
+
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
4540
|
+
if (!actual || !minimum) return true;
|
|
4541
|
+
for (let i = 0; i < 3; i += 1) {
|
|
4542
|
+
if (actual[i] > minimum[i]) return true;
|
|
4543
|
+
if (actual[i] < minimum[i]) return false;
|
|
4544
|
+
}
|
|
4545
|
+
return true;
|
|
4546
|
+
}
|
|
4547
|
+
function unsupportedPiVersionMessage(version) {
|
|
4548
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
4549
|
+
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.`;
|
|
4550
|
+
}
|
|
4551
|
+
function probePi(version = PI_SDK_VERSION) {
|
|
4552
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
4553
|
+
if (unsupportedMessage) {
|
|
4554
|
+
return {
|
|
4555
|
+
available: false,
|
|
4556
|
+
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
4557
|
+
};
|
|
4558
|
+
}
|
|
4559
|
+
return { available: true, version };
|
|
4560
|
+
}
|
|
4561
|
+
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
4562
|
+
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
4563
|
+
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
4564
|
+
if (existsSync9(sourceSibling)) return sourceSibling;
|
|
4565
|
+
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
4566
|
+
if (existsSync9(bundledEntry)) return bundledEntry;
|
|
4567
|
+
return path11.join(moduleDir, "piSdkRunner.js");
|
|
4568
|
+
}
|
|
4569
|
+
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
4570
|
+
if (runnerPath.endsWith(".ts")) {
|
|
4571
|
+
return [...process.execArgv, runnerPath];
|
|
4572
|
+
}
|
|
4573
|
+
return [runnerPath];
|
|
4574
|
+
}
|
|
4575
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
4576
|
+
const command = opts.command ?? process.execPath;
|
|
4577
|
+
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
4578
|
+
const sessionDir = path11.join(piDir, "sessions");
|
|
4579
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
4580
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4581
|
+
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
4582
|
+
const agentDir = opts.agentDir ?? getAgentDir();
|
|
4583
|
+
const runnerConfigPath = path11.join(
|
|
4584
|
+
piDir,
|
|
4585
|
+
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
4586
|
+
);
|
|
4587
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
4588
|
+
const runnerConfig = {
|
|
4589
|
+
cwd: ctx.workingDirectory,
|
|
4590
|
+
agentDir,
|
|
4591
|
+
sessionDir,
|
|
4592
|
+
sessionId: ctx.config.sessionId || null,
|
|
4593
|
+
standingPrompt: ctx.standingPrompt,
|
|
4594
|
+
prompt: turnPrompt,
|
|
4595
|
+
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
4596
|
+
};
|
|
4597
|
+
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
4598
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4599
|
+
const args = [
|
|
4600
|
+
...buildPiSdkNodeArgs(runnerPath),
|
|
4601
|
+
"--config",
|
|
4602
|
+
runnerConfigPath
|
|
4603
|
+
];
|
|
4604
|
+
return {
|
|
4605
|
+
command,
|
|
4606
|
+
args,
|
|
4607
|
+
env: slock.spawnEnv,
|
|
4608
|
+
sessionDir,
|
|
4609
|
+
agentDir,
|
|
4610
|
+
runnerConfigPath,
|
|
4611
|
+
sdkVersion: PI_SDK_VERSION
|
|
4612
|
+
};
|
|
4613
|
+
}
|
|
4614
|
+
function isSystemFirstMessageTask2(message) {
|
|
4615
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
4616
|
+
}
|
|
4617
|
+
function buildPiSystemPrompt(config) {
|
|
4618
|
+
return buildCliTransportSystemPrompt(config, {
|
|
4619
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
4620
|
+
extraCriticalRules: [
|
|
4621
|
+
"- 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."
|
|
4622
|
+
],
|
|
4623
|
+
postStartupNotes: [
|
|
4624
|
+
"**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."
|
|
4625
|
+
],
|
|
4626
|
+
includeStdinNotificationSection: false,
|
|
4627
|
+
messageNotificationStyle: "poll"
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
function contentText(content) {
|
|
4631
|
+
if (!content) return "";
|
|
4632
|
+
const chunks = [];
|
|
4633
|
+
for (const item of content) {
|
|
4634
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
4635
|
+
chunks.push(item.text);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
return chunks.join("");
|
|
4639
|
+
}
|
|
4640
|
+
function apiKeyErrorMessage(line) {
|
|
4641
|
+
const trimmed = line.trim();
|
|
4642
|
+
if (!trimmed) return null;
|
|
4643
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
4644
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
4645
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
4646
|
+
return null;
|
|
4647
|
+
}
|
|
4648
|
+
var PiDriver = class {
|
|
4649
|
+
id = "pi";
|
|
4650
|
+
lifecycle = {
|
|
4651
|
+
kind: "per_turn",
|
|
4652
|
+
start: "defer_until_concrete_message",
|
|
4653
|
+
exit: "terminate_on_turn_end",
|
|
4654
|
+
inFlightWake: "coalesce_into_pending"
|
|
4655
|
+
};
|
|
4656
|
+
communication = {
|
|
4657
|
+
chat: "slock_cli",
|
|
4658
|
+
runtimeControl: "none"
|
|
4659
|
+
};
|
|
4660
|
+
session = {
|
|
4661
|
+
recovery: "resume_or_fresh"
|
|
4662
|
+
};
|
|
4663
|
+
model = {
|
|
4664
|
+
detectedModelsVerifiedAs: "launchable",
|
|
4665
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
4666
|
+
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
4667
|
+
const launchCtx = {
|
|
4668
|
+
...ctx,
|
|
4669
|
+
config: {
|
|
4670
|
+
...ctx.config,
|
|
4671
|
+
model: modelId
|
|
4672
|
+
}
|
|
4673
|
+
};
|
|
4674
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
4675
|
+
return {
|
|
4676
|
+
args: launch.args,
|
|
4677
|
+
env: launch.env,
|
|
4678
|
+
configFiles: [launch.runnerConfigPath],
|
|
4679
|
+
params: {
|
|
4680
|
+
agentDir: launch.agentDir,
|
|
4681
|
+
sessionDir: launch.sessionDir,
|
|
4682
|
+
sdkVersion: launch.sdkVersion,
|
|
4683
|
+
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
4684
|
+
}
|
|
4685
|
+
};
|
|
4686
|
+
}
|
|
4687
|
+
};
|
|
4688
|
+
supportsStdinNotification = false;
|
|
4689
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
4690
|
+
busyDeliveryMode = "none";
|
|
4691
|
+
terminateProcessOnTurnEnd = true;
|
|
4692
|
+
deferSpawnUntilMessage = true;
|
|
4693
|
+
usesSlockCliForCommunication = true;
|
|
4694
|
+
sessionId = null;
|
|
4695
|
+
sessionAnnounced = false;
|
|
4696
|
+
apiKeyErrorAnnounced = false;
|
|
4697
|
+
turnEnded = false;
|
|
4698
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
4699
|
+
shouldDeferWakeMessage(message) {
|
|
4700
|
+
return isSystemFirstMessageTask2(message);
|
|
4701
|
+
}
|
|
4702
|
+
probe() {
|
|
4703
|
+
return probePi();
|
|
4704
|
+
}
|
|
4705
|
+
async detectModels() {
|
|
4706
|
+
return null;
|
|
4707
|
+
}
|
|
4708
|
+
spawn(ctx) {
|
|
4709
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
4710
|
+
this.sessionAnnounced = false;
|
|
4711
|
+
this.apiKeyErrorAnnounced = false;
|
|
4712
|
+
this.turnEnded = false;
|
|
4713
|
+
this.assistantTextByMessageId.clear();
|
|
4714
|
+
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
4715
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
4716
|
+
const launch = buildPiLaunchOptions(ctx);
|
|
4717
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
4718
|
+
cwd: ctx.workingDirectory,
|
|
4719
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4720
|
+
env: launch.env,
|
|
4721
|
+
shell: false
|
|
4722
|
+
});
|
|
4723
|
+
proc.stdin?.end();
|
|
4724
|
+
return { process: proc };
|
|
4725
|
+
}
|
|
4726
|
+
parseLine(line) {
|
|
4727
|
+
let event;
|
|
4728
|
+
try {
|
|
4729
|
+
event = JSON.parse(line);
|
|
4730
|
+
} catch {
|
|
4731
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
4732
|
+
const message = apiKeyErrorMessage(line);
|
|
4733
|
+
if (!message) return [];
|
|
4734
|
+
this.apiKeyErrorAnnounced = true;
|
|
4735
|
+
this.turnEnded = true;
|
|
4736
|
+
return [
|
|
4737
|
+
{ kind: "error", message },
|
|
4738
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
4739
|
+
];
|
|
4740
|
+
}
|
|
4741
|
+
const events = [];
|
|
4742
|
+
if (event.type === "session" && event.id) {
|
|
4743
|
+
this.sessionId = event.id;
|
|
4744
|
+
}
|
|
4745
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
4746
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
4747
|
+
this.sessionAnnounced = true;
|
|
4748
|
+
}
|
|
4749
|
+
switch (event.type) {
|
|
4750
|
+
case "agent_start":
|
|
4751
|
+
case "turn_start":
|
|
4752
|
+
events.push({ kind: "thinking", text: "" });
|
|
4753
|
+
break;
|
|
4754
|
+
case "message_update":
|
|
4755
|
+
case "message_end":
|
|
4756
|
+
if (event.message?.role === "assistant") {
|
|
4757
|
+
const key = event.message.id || "current";
|
|
4758
|
+
const currentText = contentText(event.message.content);
|
|
4759
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
4760
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
4761
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
4762
|
+
} else if (currentText && currentText !== previousText) {
|
|
4763
|
+
events.push({ kind: "text", text: currentText });
|
|
4764
|
+
}
|
|
4765
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
4766
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
4767
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
break;
|
|
4771
|
+
case "tool_execution_start":
|
|
4772
|
+
events.push({
|
|
4773
|
+
kind: "tool_call",
|
|
4774
|
+
name: event.toolName || "unknown_tool",
|
|
4775
|
+
input: event.args
|
|
4776
|
+
});
|
|
4777
|
+
break;
|
|
4778
|
+
case "tool_execution_end":
|
|
4779
|
+
events.push({
|
|
4780
|
+
kind: "tool_output",
|
|
4781
|
+
name: event.toolName || "unknown_tool"
|
|
4782
|
+
});
|
|
4783
|
+
if (event.isError) {
|
|
4784
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4785
|
+
}
|
|
4786
|
+
break;
|
|
4787
|
+
case "compaction_start":
|
|
4788
|
+
events.push({ kind: "compaction_started" });
|
|
4789
|
+
break;
|
|
4790
|
+
case "compaction_end":
|
|
4791
|
+
events.push({ kind: "compaction_finished" });
|
|
4792
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4793
|
+
break;
|
|
4794
|
+
case "turn_end":
|
|
4795
|
+
case "agent_end":
|
|
4796
|
+
if (!this.turnEnded) {
|
|
4797
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4798
|
+
this.turnEnded = true;
|
|
4799
|
+
}
|
|
4800
|
+
break;
|
|
4801
|
+
}
|
|
4802
|
+
return events;
|
|
4803
|
+
}
|
|
4804
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4805
|
+
return null;
|
|
4806
|
+
}
|
|
4807
|
+
buildSystemPrompt(config, _agentId) {
|
|
4808
|
+
return buildPiSystemPrompt(config);
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
|
|
4305
4812
|
// src/drivers/index.ts
|
|
4306
4813
|
var driverFactories = {
|
|
4307
4814
|
claude: () => new ClaudeDriver(),
|
|
@@ -4310,7 +4817,8 @@ var driverFactories = {
|
|
|
4310
4817
|
cursor: () => new CursorDriver(),
|
|
4311
4818
|
gemini: () => new GeminiDriver(),
|
|
4312
4819
|
kimi: () => new KimiDriver(),
|
|
4313
|
-
opencode: () => new OpenCodeDriver()
|
|
4820
|
+
opencode: () => new OpenCodeDriver(),
|
|
4821
|
+
pi: () => new PiDriver()
|
|
4314
4822
|
};
|
|
4315
4823
|
function getDriver(runtimeId) {
|
|
4316
4824
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -4323,7 +4831,7 @@ function getDriver(runtimeId) {
|
|
|
4323
4831
|
|
|
4324
4832
|
// src/workspaces.ts
|
|
4325
4833
|
import { readdir, rm, stat } from "fs/promises";
|
|
4326
|
-
import
|
|
4834
|
+
import path12 from "path";
|
|
4327
4835
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
4328
4836
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
4329
4837
|
}
|
|
@@ -4331,7 +4839,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
4331
4839
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
4332
4840
|
return null;
|
|
4333
4841
|
}
|
|
4334
|
-
return
|
|
4842
|
+
return path12.join(dataDir, directoryName);
|
|
4335
4843
|
}
|
|
4336
4844
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
4337
4845
|
return {
|
|
@@ -4380,7 +4888,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
4380
4888
|
return summary;
|
|
4381
4889
|
}
|
|
4382
4890
|
const childSummaries = await Promise.all(
|
|
4383
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4891
|
+
entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
|
|
4384
4892
|
);
|
|
4385
4893
|
for (const childSummary of childSummaries) {
|
|
4386
4894
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -4399,7 +4907,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
4399
4907
|
if (!entry.isDirectory()) {
|
|
4400
4908
|
return null;
|
|
4401
4909
|
}
|
|
4402
|
-
const dirPath =
|
|
4910
|
+
const dirPath = path12.join(dataDir, entry.name);
|
|
4403
4911
|
try {
|
|
4404
4912
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
4405
4913
|
return {
|
|
@@ -4657,12 +5165,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
4657
5165
|
for (const entry of entries) {
|
|
4658
5166
|
if (++visited > maxEntries) return null;
|
|
4659
5167
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
4660
|
-
return
|
|
5168
|
+
return path13.join(dir, entry.name);
|
|
4661
5169
|
}
|
|
4662
5170
|
for (const entry of entries) {
|
|
4663
5171
|
if (++visited > maxEntries) return null;
|
|
4664
5172
|
if (!entry.isDirectory()) continue;
|
|
4665
|
-
const found = visit(
|
|
5173
|
+
const found = visit(path13.join(dir, entry.name), depth - 1);
|
|
4666
5174
|
if (found) return found;
|
|
4667
5175
|
}
|
|
4668
5176
|
return null;
|
|
@@ -4675,10 +5183,10 @@ function safeSessionFilename(value) {
|
|
|
4675
5183
|
}
|
|
4676
5184
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
4677
5185
|
try {
|
|
4678
|
-
const dir =
|
|
4679
|
-
|
|
4680
|
-
const filePath =
|
|
4681
|
-
|
|
5186
|
+
const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
|
|
5187
|
+
mkdirSync5(dir, { recursive: true });
|
|
5188
|
+
const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
5189
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4682
5190
|
type: "runtime_session_handoff",
|
|
4683
5191
|
runtime,
|
|
4684
5192
|
sessionId,
|
|
@@ -4697,7 +5205,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
4697
5205
|
}
|
|
4698
5206
|
}
|
|
4699
5207
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
4700
|
-
const directPath =
|
|
5208
|
+
const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
|
|
4701
5209
|
if (directPath) {
|
|
4702
5210
|
try {
|
|
4703
5211
|
if (statSync2(directPath).isFile()) {
|
|
@@ -4706,7 +5214,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
4706
5214
|
} catch {
|
|
4707
5215
|
}
|
|
4708
5216
|
}
|
|
4709
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
5217
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
4710
5218
|
if (!resolvedPath && fallbackDir) {
|
|
4711
5219
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
4712
5220
|
if (fallback) return fallback;
|
|
@@ -5158,7 +5666,7 @@ Do not copy these answers verbatim.
|
|
|
5158
5666
|
## FAQ 15: How do I create agents or channels?
|
|
5159
5667
|
### Answer idea
|
|
5160
5668
|
- 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.
|
|
5161
|
-
- v1 supports three action types via \`slock action prepare --target '<channel>' <<
|
|
5669
|
+
- v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
|
|
5162
5670
|
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
5163
5671
|
- \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
|
|
5164
5672
|
- \`{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.
|
|
@@ -6015,26 +6523,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
6015
6523
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
6016
6524
|
try {
|
|
6017
6525
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
6018
|
-
const agentDataDir =
|
|
6526
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
6019
6527
|
await mkdir(agentDataDir, { recursive: true });
|
|
6020
6528
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
6021
|
-
const memoryMdPath =
|
|
6529
|
+
const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
|
|
6022
6530
|
try {
|
|
6023
6531
|
await access(memoryMdPath);
|
|
6024
6532
|
} catch {
|
|
6025
6533
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
6026
6534
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
6027
6535
|
}
|
|
6028
|
-
const notesDir =
|
|
6536
|
+
const notesDir = path13.join(agentDataDir, "notes");
|
|
6029
6537
|
await mkdir(notesDir, { recursive: true });
|
|
6030
6538
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
6031
6539
|
const seedFiles = buildOnboardingSeedFiles();
|
|
6032
6540
|
for (const { relativePath, content } of seedFiles) {
|
|
6033
|
-
const fullPath =
|
|
6541
|
+
const fullPath = path13.join(agentDataDir, relativePath);
|
|
6034
6542
|
try {
|
|
6035
6543
|
await access(fullPath);
|
|
6036
6544
|
} catch {
|
|
6037
|
-
await mkdir(
|
|
6545
|
+
await mkdir(path13.dirname(fullPath), { recursive: true });
|
|
6038
6546
|
await writeFile(fullPath, content);
|
|
6039
6547
|
}
|
|
6040
6548
|
}
|
|
@@ -6911,7 +7419,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6911
7419
|
return true;
|
|
6912
7420
|
}
|
|
6913
7421
|
async resetWorkspace(agentId) {
|
|
6914
|
-
const agentDataDir =
|
|
7422
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
6915
7423
|
try {
|
|
6916
7424
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
6917
7425
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -6948,7 +7456,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6948
7456
|
return result;
|
|
6949
7457
|
}
|
|
6950
7458
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
6951
|
-
const workspacePath =
|
|
7459
|
+
const workspacePath = path13.join(this.dataDir, agentId);
|
|
6952
7460
|
return {
|
|
6953
7461
|
agentId,
|
|
6954
7462
|
launchId,
|
|
@@ -7205,7 +7713,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7205
7713
|
}
|
|
7206
7714
|
// Workspace file browsing
|
|
7207
7715
|
async getFileTree(agentId, dirPath) {
|
|
7208
|
-
const agentDir =
|
|
7716
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7209
7717
|
try {
|
|
7210
7718
|
await stat2(agentDir);
|
|
7211
7719
|
} catch {
|
|
@@ -7213,8 +7721,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7213
7721
|
}
|
|
7214
7722
|
let targetDir = agentDir;
|
|
7215
7723
|
if (dirPath) {
|
|
7216
|
-
const resolved =
|
|
7217
|
-
if (!resolved.startsWith(agentDir +
|
|
7724
|
+
const resolved = path13.resolve(agentDir, dirPath);
|
|
7725
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
7218
7726
|
return [];
|
|
7219
7727
|
}
|
|
7220
7728
|
targetDir = resolved;
|
|
@@ -7222,14 +7730,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7222
7730
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
7223
7731
|
}
|
|
7224
7732
|
async readFile(agentId, filePath) {
|
|
7225
|
-
const agentDir =
|
|
7226
|
-
const resolved =
|
|
7227
|
-
if (!resolved.startsWith(agentDir +
|
|
7733
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7734
|
+
const resolved = path13.resolve(agentDir, filePath);
|
|
7735
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
7228
7736
|
throw new Error("Access denied");
|
|
7229
7737
|
}
|
|
7230
7738
|
const info = await stat2(resolved);
|
|
7231
7739
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
7232
|
-
const ext =
|
|
7740
|
+
const ext = path13.extname(resolved).toLowerCase();
|
|
7233
7741
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
7234
7742
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
7235
7743
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -7264,13 +7772,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7264
7772
|
const agent = this.agents.get(agentId);
|
|
7265
7773
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
7266
7774
|
const home = os6.homedir();
|
|
7267
|
-
const workspaceDir =
|
|
7775
|
+
const workspaceDir = path13.join(this.dataDir, agentId);
|
|
7268
7776
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
7269
7777
|
const globalResults = await Promise.all(
|
|
7270
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
7778
|
+
paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
|
|
7271
7779
|
);
|
|
7272
7780
|
const workspaceResults = await Promise.all(
|
|
7273
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
7781
|
+
paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
|
|
7274
7782
|
);
|
|
7275
7783
|
const dedup = (skills) => {
|
|
7276
7784
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7299,7 +7807,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7299
7807
|
const skills = [];
|
|
7300
7808
|
for (const entry of entries) {
|
|
7301
7809
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
7302
|
-
const skillMd =
|
|
7810
|
+
const skillMd = path13.join(dir, entry.name, "SKILL.md");
|
|
7303
7811
|
try {
|
|
7304
7812
|
const content = await readFile(skillMd, "utf-8");
|
|
7305
7813
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -7310,7 +7818,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7310
7818
|
} else if (entry.name.endsWith(".md")) {
|
|
7311
7819
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
7312
7820
|
try {
|
|
7313
|
-
const content = await readFile(
|
|
7821
|
+
const content = await readFile(path13.join(dir, entry.name), "utf-8");
|
|
7314
7822
|
const skill = this.parseSkillMd(cmdName, content);
|
|
7315
7823
|
skill.sourcePath = dir;
|
|
7316
7824
|
skills.push(skill);
|
|
@@ -8276,8 +8784,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
8276
8784
|
const nodes = [];
|
|
8277
8785
|
for (const entry of entries) {
|
|
8278
8786
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8279
|
-
const fullPath =
|
|
8280
|
-
const relativePath =
|
|
8787
|
+
const fullPath = path13.join(dir, entry.name);
|
|
8788
|
+
const relativePath = path13.relative(rootDir, fullPath);
|
|
8281
8789
|
let info;
|
|
8282
8790
|
try {
|
|
8283
8791
|
info = await stat2(fullPath);
|
|
@@ -8582,9 +9090,9 @@ var ReminderCache = class {
|
|
|
8582
9090
|
|
|
8583
9091
|
// src/machineLock.ts
|
|
8584
9092
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
8585
|
-
import { mkdirSync as
|
|
9093
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
8586
9094
|
import os7 from "os";
|
|
8587
|
-
import
|
|
9095
|
+
import path14 from "path";
|
|
8588
9096
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
8589
9097
|
var DaemonMachineLockConflictError = class extends Error {
|
|
8590
9098
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -8606,11 +9114,11 @@ function resolveDefaultMachineStateRoot() {
|
|
|
8606
9114
|
return resolveSlockHomePath("machines");
|
|
8607
9115
|
}
|
|
8608
9116
|
function ownerPath(lockDir) {
|
|
8609
|
-
return
|
|
9117
|
+
return path14.join(lockDir, "owner.json");
|
|
8610
9118
|
}
|
|
8611
9119
|
function readOwner(lockDir) {
|
|
8612
9120
|
try {
|
|
8613
|
-
return JSON.parse(
|
|
9121
|
+
return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
|
|
8614
9122
|
} catch {
|
|
8615
9123
|
return null;
|
|
8616
9124
|
}
|
|
@@ -8636,13 +9144,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
8636
9144
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
8637
9145
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
8638
9146
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
8639
|
-
const machineDir =
|
|
8640
|
-
const lockDir =
|
|
9147
|
+
const machineDir = path14.join(rootDir, lockId);
|
|
9148
|
+
const lockDir = path14.join(machineDir, "daemon.lock");
|
|
8641
9149
|
const token = randomUUID2();
|
|
8642
|
-
|
|
9150
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
8643
9151
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
8644
9152
|
try {
|
|
8645
|
-
|
|
9153
|
+
mkdirSync6(lockDir);
|
|
8646
9154
|
const owner = {
|
|
8647
9155
|
pid: process.pid,
|
|
8648
9156
|
token,
|
|
@@ -8652,7 +9160,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
8652
9160
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
8653
9161
|
};
|
|
8654
9162
|
try {
|
|
8655
|
-
|
|
9163
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
8656
9164
|
`, { mode: 384 });
|
|
8657
9165
|
} catch (err) {
|
|
8658
9166
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -8689,8 +9197,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
8689
9197
|
}
|
|
8690
9198
|
|
|
8691
9199
|
// src/localTraceSink.ts
|
|
8692
|
-
import { appendFileSync, mkdirSync as
|
|
8693
|
-
import
|
|
9200
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
9201
|
+
import path15 from "path";
|
|
8694
9202
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8695
9203
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
8696
9204
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -8726,7 +9234,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8726
9234
|
currentSize = 0;
|
|
8727
9235
|
sequence = 0;
|
|
8728
9236
|
constructor(options) {
|
|
8729
|
-
this.traceDir =
|
|
9237
|
+
this.traceDir = path15.join(options.machineDir, "traces");
|
|
8730
9238
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
8731
9239
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
8732
9240
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -8752,15 +9260,15 @@ var LocalRotatingTraceSink = class {
|
|
|
8752
9260
|
return this.currentFile;
|
|
8753
9261
|
}
|
|
8754
9262
|
ensureFile(nextBytes) {
|
|
8755
|
-
|
|
9263
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
8756
9264
|
const nowMs = this.nowMsProvider();
|
|
8757
9265
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
8758
9266
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
8759
|
-
this.currentFile =
|
|
9267
|
+
this.currentFile = path15.join(
|
|
8760
9268
|
this.traceDir,
|
|
8761
9269
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
8762
9270
|
);
|
|
8763
|
-
|
|
9271
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
8764
9272
|
this.currentSize = statSync4(this.currentFile).size;
|
|
8765
9273
|
this.currentFileOpenedAtMs = nowMs;
|
|
8766
9274
|
this.pruneOldFiles();
|
|
@@ -8771,7 +9279,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8771
9279
|
const excess = files.length - this.maxFiles;
|
|
8772
9280
|
if (excess <= 0) return;
|
|
8773
9281
|
for (const file of files.slice(0, excess)) {
|
|
8774
|
-
rmSync4(
|
|
9282
|
+
rmSync4(path15.join(this.traceDir, file), { force: true });
|
|
8775
9283
|
}
|
|
8776
9284
|
}
|
|
8777
9285
|
};
|
|
@@ -8858,11 +9366,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
8858
9366
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
8859
9367
|
import { gzipSync } from "zlib";
|
|
8860
9368
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8861
|
-
import
|
|
9369
|
+
import path16 from "path";
|
|
8862
9370
|
|
|
8863
9371
|
// src/directUploadCapability.ts
|
|
8864
|
-
function joinUrl(base,
|
|
8865
|
-
return `${base.replace(/\/+$/, "")}${
|
|
9372
|
+
function joinUrl(base, path18) {
|
|
9373
|
+
return `${base.replace(/\/+$/, "")}${path18}`;
|
|
8866
9374
|
}
|
|
8867
9375
|
function jsonHeaders(apiKey) {
|
|
8868
9376
|
return {
|
|
@@ -9081,7 +9589,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
9081
9589
|
}, nextMs);
|
|
9082
9590
|
}
|
|
9083
9591
|
async findUploadCandidates() {
|
|
9084
|
-
const traceDir =
|
|
9592
|
+
const traceDir = path16.join(this.options.machineDir, "traces");
|
|
9085
9593
|
let names;
|
|
9086
9594
|
try {
|
|
9087
9595
|
names = await readdir3(traceDir);
|
|
@@ -9093,8 +9601,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
9093
9601
|
const currentFile = this.options.currentFileProvider?.();
|
|
9094
9602
|
const candidates = [];
|
|
9095
9603
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
9096
|
-
const file =
|
|
9097
|
-
if (currentFile &&
|
|
9604
|
+
const file = path16.join(traceDir, name);
|
|
9605
|
+
if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
|
|
9098
9606
|
if (await this.isUploaded(file)) continue;
|
|
9099
9607
|
try {
|
|
9100
9608
|
const info = await stat3(file);
|
|
@@ -9168,8 +9676,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
9168
9676
|
}
|
|
9169
9677
|
}
|
|
9170
9678
|
uploadStatePath(file) {
|
|
9171
|
-
const stateDir =
|
|
9172
|
-
return
|
|
9679
|
+
const stateDir = path16.join(this.options.machineDir, "trace-uploads");
|
|
9680
|
+
return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
|
|
9173
9681
|
}
|
|
9174
9682
|
async isUploaded(file) {
|
|
9175
9683
|
try {
|
|
@@ -9181,9 +9689,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
9181
9689
|
}
|
|
9182
9690
|
async markUploaded(file, metadata) {
|
|
9183
9691
|
const stateFile = this.uploadStatePath(file);
|
|
9184
|
-
await mkdir2(
|
|
9692
|
+
await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
|
|
9185
9693
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
9186
|
-
file:
|
|
9694
|
+
file: path16.basename(file),
|
|
9187
9695
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9188
9696
|
...metadata
|
|
9189
9697
|
}, null, 2)}
|
|
@@ -9205,7 +9713,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
9205
9713
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
9206
9714
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
9207
9715
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
9208
|
-
var DAEMON_CLI_USAGE =
|
|
9716
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
9209
9717
|
var RunnerCredentialMintError2 = class extends Error {
|
|
9210
9718
|
code;
|
|
9211
9719
|
retryable;
|
|
@@ -9241,9 +9749,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
9241
9749
|
async function waitForRunnerCredentialRetry2() {
|
|
9242
9750
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
9243
9751
|
}
|
|
9244
|
-
function parseDaemonCliArgs(args) {
|
|
9752
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
9245
9753
|
let serverUrl = "";
|
|
9246
|
-
let apiKey = "";
|
|
9754
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
9247
9755
|
for (let i = 0; i < args.length; i++) {
|
|
9248
9756
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
9249
9757
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -9253,30 +9761,30 @@ function parseDaemonCliArgs(args) {
|
|
|
9253
9761
|
}
|
|
9254
9762
|
function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
9255
9763
|
try {
|
|
9256
|
-
const require2 =
|
|
9764
|
+
const require2 = createRequire2(moduleUrl);
|
|
9257
9765
|
return require2("../package.json").version;
|
|
9258
9766
|
} catch {
|
|
9259
9767
|
return "0.0.0-dev";
|
|
9260
9768
|
}
|
|
9261
9769
|
}
|
|
9262
9770
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
9263
|
-
const dirname =
|
|
9264
|
-
const jsPath =
|
|
9771
|
+
const dirname = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9772
|
+
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
9265
9773
|
try {
|
|
9266
9774
|
accessSync(jsPath);
|
|
9267
9775
|
return jsPath;
|
|
9268
9776
|
} catch {
|
|
9269
|
-
return
|
|
9777
|
+
return path17.resolve(dirname, "chat-bridge.ts");
|
|
9270
9778
|
}
|
|
9271
9779
|
}
|
|
9272
9780
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
9273
|
-
const thisDir =
|
|
9274
|
-
const bundledDistPath =
|
|
9781
|
+
const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9782
|
+
const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
|
|
9275
9783
|
try {
|
|
9276
9784
|
accessSync(bundledDistPath);
|
|
9277
9785
|
return bundledDistPath;
|
|
9278
9786
|
} catch {
|
|
9279
|
-
const workspaceDistPath =
|
|
9787
|
+
const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
9280
9788
|
accessSync(workspaceDistPath);
|
|
9281
9789
|
return workspaceDistPath;
|
|
9282
9790
|
}
|
|
@@ -9455,7 +9963,7 @@ var DaemonCore = class {
|
|
|
9455
9963
|
}
|
|
9456
9964
|
resolveMachineStateRoot() {
|
|
9457
9965
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
9458
|
-
if (this.options.dataDir) return
|
|
9966
|
+
if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
|
|
9459
9967
|
return resolveDefaultMachineStateRoot();
|
|
9460
9968
|
}
|
|
9461
9969
|
shouldEnableLocalTrace() {
|
|
@@ -9957,6 +10465,8 @@ var DaemonCore = class {
|
|
|
9957
10465
|
};
|
|
9958
10466
|
|
|
9959
10467
|
export {
|
|
10468
|
+
DAEMON_API_KEY_ENV,
|
|
10469
|
+
scrubDaemonAuthEnv,
|
|
9960
10470
|
resolveWorkspaceDirectoryPath,
|
|
9961
10471
|
scanWorkspaceDirectories,
|
|
9962
10472
|
deleteWorkspaceDirectory,
|