@slock-ai/daemon 0.52.1 → 0.52.2-play.20260522165849
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-WGO5H7XX.js → chunk-AGSYS44K.js} +807 -135
- package/dist/cli/index.js +166 -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
|
|
@@ -823,6 +825,7 @@ function buildPrompt(config, variant, opts) {
|
|
|
823
825
|
"- Always communicate through `slock` CLI commands. This is your only output channel.",
|
|
824
826
|
...opts.extraCriticalRules,
|
|
825
827
|
"- Use only the provided `slock` CLI commands for messaging.",
|
|
828
|
+
"- Do not combine multiple `slock` CLI commands in one shell command. Run one `slock` command per tool call, read its output, then decide the next command.",
|
|
826
829
|
"- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
|
|
827
830
|
] : [
|
|
828
831
|
`- Always communicate through ${sendCmd}. This is your only output channel.`,
|
|
@@ -871,13 +874,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
871
874
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
872
875
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
873
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.
|
|
874
|
-
20. **\`slock
|
|
875
|
-
21. **\`slock
|
|
876
|
-
22. **\`slock reminder
|
|
877
|
-
23. **\`slock reminder
|
|
878
|
-
24. **\`slock reminder
|
|
879
|
-
25. **\`slock reminder
|
|
880
|
-
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\`).
|
|
881
886
|
|
|
882
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:
|
|
883
888
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -912,20 +917,23 @@ Use reminders for follow-up that depends on future state you cannot resolve now,
|
|
|
912
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.
|
|
913
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.
|
|
914
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";
|
|
915
921
|
const sendingMessagesSection = isCli ? `### Sending messages
|
|
916
922
|
|
|
917
|
-
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'
|
|
918
|
-
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'
|
|
919
|
-
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'
|
|
920
|
-
- **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}\`
|
|
921
927
|
|
|
922
928
|
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
923
929
|
\`\`\`bash
|
|
924
|
-
slock message send --target "#channel-name" <<'
|
|
930
|
+
slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
|
|
925
931
|
Long message with "quotes", $vars, \`backticks\`, and code blocks.
|
|
926
|
-
|
|
932
|
+
${messageHeredocDelimiter}
|
|
927
933
|
\`\`\`
|
|
928
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
|
+
|
|
929
937
|
If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
930
938
|
- To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
|
|
931
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.
|
|
@@ -944,7 +952,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
944
952
|
|
|
945
953
|
- **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
|
|
946
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.
|
|
947
|
-
- **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.
|
|
948
956
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
949
957
|
- You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
|
|
950
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.
|
|
@@ -978,6 +986,11 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
978
986
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
979
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.
|
|
980
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. 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
|
|
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.`;
|
|
981
994
|
const readingHistorySection = isCli ? `### Reading history
|
|
982
995
|
|
|
983
996
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1014,7 +1027,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
|
|
|
1014
1027
|
**Workflow:**
|
|
1015
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)
|
|
1016
1029
|
2. If the claim fails, someone else is working on it \u2014 move on to another task
|
|
1017
|
-
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}\`
|
|
1018
1031
|
4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
|
|
1019
1032
|
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
1020
1033
|
|
|
@@ -1149,6 +1162,8 @@ ${discoverySection}
|
|
|
1149
1162
|
|
|
1150
1163
|
${channelAwarenessSection}
|
|
1151
1164
|
|
|
1165
|
+
${thirdPartyIntegrationsSection}
|
|
1166
|
+
|
|
1152
1167
|
${readingHistorySection}
|
|
1153
1168
|
|
|
1154
1169
|
${historicalReferenceSection}
|
|
@@ -1180,6 +1195,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1180
1195
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1181
1196
|
- When done, summarize the result.
|
|
1182
1197
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1198
|
+
- 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:
|
|
1199
|
+
|
|
1200
|
+
\`\`\`html
|
|
1201
|
+
<details>
|
|
1202
|
+
<summary>Evidence, logs, or edge cases</summary>
|
|
1203
|
+
|
|
1204
|
+
Detailed notes go here.
|
|
1205
|
+
</details>
|
|
1206
|
+
\`\`\`
|
|
1207
|
+
|
|
1208
|
+
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1183
1209
|
|
|
1184
1210
|
### Conversation etiquette
|
|
1185
1211
|
|
|
@@ -1348,6 +1374,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1348
1374
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1349
1375
|
}
|
|
1350
1376
|
|
|
1377
|
+
// src/authEnv.ts
|
|
1378
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1379
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1380
|
+
function scrubDaemonAuthEnv(env) {
|
|
1381
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1382
|
+
return env;
|
|
1383
|
+
}
|
|
1384
|
+
function scrubDaemonChildEnv(env) {
|
|
1385
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1386
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1387
|
+
return env;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1351
1390
|
// src/agentCredentialProxy.ts
|
|
1352
1391
|
import { randomBytes } from "crypto";
|
|
1353
1392
|
import http from "http";
|
|
@@ -1810,6 +1849,18 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
|
|
|
1810
1849
|
});
|
|
1811
1850
|
return;
|
|
1812
1851
|
}
|
|
1852
|
+
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "sent") {
|
|
1853
|
+
const messageSeq2 = typeof parsed.messageSeq === "number" && Number.isFinite(parsed.messageSeq) ? Math.floor(parsed.messageSeq) : void 0;
|
|
1854
|
+
if (sendTarget && messageSeq2 && messageSeq2 > 0) {
|
|
1855
|
+
coordinator.consumeVisibleMessages({
|
|
1856
|
+
target: sendTarget,
|
|
1857
|
+
messages: normalizeVisibleMessages([{ seq: messageSeq2, id: parsed.messageId }], sendTarget),
|
|
1858
|
+
boundarySeq: messageSeq2,
|
|
1859
|
+
source: "agent_api_send_commit"
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1813
1864
|
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
1814
1865
|
const messages = normalizeVisibleMessages(parsed.events);
|
|
1815
1866
|
coordinator.consumeVisibleMessages({ messages, source: "agent_api_events" });
|
|
@@ -1859,8 +1910,51 @@ function unregisterAgentCredentialProxyForLaunch(input) {
|
|
|
1859
1910
|
|
|
1860
1911
|
// src/drivers/cliTransport.ts
|
|
1861
1912
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1913
|
+
var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
1862
1914
|
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1863
1915
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1916
|
+
var cachedOpencliBinPath;
|
|
1917
|
+
function resolveOpencliBinPath() {
|
|
1918
|
+
if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
|
|
1919
|
+
try {
|
|
1920
|
+
const require2 = createRequire(import.meta.url);
|
|
1921
|
+
const mainPath = require2.resolve("@jackwener/opencli");
|
|
1922
|
+
let dir = path2.dirname(mainPath);
|
|
1923
|
+
const root = path2.parse(dir).root;
|
|
1924
|
+
while (dir && dir !== root) {
|
|
1925
|
+
const candidate = path2.join(dir, "package.json");
|
|
1926
|
+
try {
|
|
1927
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
1928
|
+
if (pkg.name === "@jackwener/opencli") {
|
|
1929
|
+
const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencli;
|
|
1930
|
+
if (!binEntry) {
|
|
1931
|
+
cachedOpencliBinPath = null;
|
|
1932
|
+
return null;
|
|
1933
|
+
}
|
|
1934
|
+
cachedOpencliBinPath = path2.resolve(dir, binEntry);
|
|
1935
|
+
return cachedOpencliBinPath;
|
|
1936
|
+
}
|
|
1937
|
+
} catch {
|
|
1938
|
+
}
|
|
1939
|
+
const parent = path2.dirname(dir);
|
|
1940
|
+
if (parent === dir) break;
|
|
1941
|
+
dir = parent;
|
|
1942
|
+
}
|
|
1943
|
+
cachedOpencliBinPath = null;
|
|
1944
|
+
return null;
|
|
1945
|
+
} catch {
|
|
1946
|
+
cachedOpencliBinPath = null;
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
function windowsUtf8Env() {
|
|
1951
|
+
return {
|
|
1952
|
+
PYTHONIOENCODING: "utf-8",
|
|
1953
|
+
PYTHONUTF8: "1",
|
|
1954
|
+
LANG: "C.UTF-8",
|
|
1955
|
+
LC_ALL: "C.UTF-8"
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1864
1958
|
function runtimeContextEnv(config) {
|
|
1865
1959
|
const ctx = config.runtimeContext;
|
|
1866
1960
|
if (!ctx) return {};
|
|
@@ -1919,10 +2013,70 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
|
|
|
1919
2013
|
set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
|
|
1920
2014
|
set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
1921
2015
|
` : "";
|
|
1922
|
-
const cmdBody =
|
|
1923
|
-
|
|
1924
|
-
|
|
2016
|
+
const cmdBody = [
|
|
2017
|
+
"@echo off",
|
|
2018
|
+
"set PYTHONIOENCODING=utf-8",
|
|
2019
|
+
"set PYTHONUTF8=1",
|
|
2020
|
+
"set LANG=C.UTF-8",
|
|
2021
|
+
"set LC_ALL=C.UTF-8",
|
|
2022
|
+
"chcp 65001 >NUL 2>NUL",
|
|
2023
|
+
cmdCredentialLine.trimEnd(),
|
|
2024
|
+
`"${process.execPath}" "${ctx.slockCliPath}" %*`,
|
|
2025
|
+
""
|
|
2026
|
+
].filter((line) => line.length > 0).join("\r\n") + "\r\n";
|
|
1925
2027
|
writeFileSync(cmdWrapper, cmdBody);
|
|
2028
|
+
const psWrapper = path2.join(slockDir, "slock.ps1");
|
|
2029
|
+
const psCredentialLines = agentCredentialProxy ? [
|
|
2030
|
+
`$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
|
|
2031
|
+
`$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
|
|
2032
|
+
`$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
|
|
2033
|
+
] : [];
|
|
2034
|
+
const psBody = [
|
|
2035
|
+
"$ErrorActionPreference = 'Stop'",
|
|
2036
|
+
"$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
|
|
2037
|
+
"[Console]::OutputEncoding = $utf8NoBom",
|
|
2038
|
+
"$OutputEncoding = $utf8NoBom",
|
|
2039
|
+
"$env:PYTHONIOENCODING = 'utf-8'",
|
|
2040
|
+
"$env:PYTHONUTF8 = '1'",
|
|
2041
|
+
"$env:LANG = 'C.UTF-8'",
|
|
2042
|
+
"$env:LC_ALL = 'C.UTF-8'",
|
|
2043
|
+
...psCredentialLines,
|
|
2044
|
+
`$node = ${powershellSingleQuote(process.execPath)}`,
|
|
2045
|
+
`$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
|
|
2046
|
+
"if ($MyInvocation.ExpectingInput) {",
|
|
2047
|
+
" $input | & $node $cli @args",
|
|
2048
|
+
"} else {",
|
|
2049
|
+
" & $node $cli @args",
|
|
2050
|
+
"}",
|
|
2051
|
+
"exit $LASTEXITCODE",
|
|
2052
|
+
""
|
|
2053
|
+
].join("\r\n");
|
|
2054
|
+
writeFileSync(psWrapper, psBody);
|
|
2055
|
+
}
|
|
2056
|
+
const opencliBinPath = resolveOpencliBinPath();
|
|
2057
|
+
if (opencliBinPath) {
|
|
2058
|
+
const opencliPosixWrapper = path2.join(slockDir, "opencli");
|
|
2059
|
+
writeFileSync(
|
|
2060
|
+
opencliPosixWrapper,
|
|
2061
|
+
`#!/usr/bin/env bash
|
|
2062
|
+
exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "$@"
|
|
2063
|
+
`,
|
|
2064
|
+
{ mode: 493 }
|
|
2065
|
+
);
|
|
2066
|
+
if (platform === "win32") {
|
|
2067
|
+
const opencliCmdWrapper = path2.join(slockDir, "opencli.cmd");
|
|
2068
|
+
const opencliCmdBody = [
|
|
2069
|
+
"@echo off",
|
|
2070
|
+
"set PYTHONIOENCODING=utf-8",
|
|
2071
|
+
"set PYTHONUTF8=1",
|
|
2072
|
+
"set LANG=C.UTF-8",
|
|
2073
|
+
"set LC_ALL=C.UTF-8",
|
|
2074
|
+
"chcp 65001 >NUL 2>NUL",
|
|
2075
|
+
`"${process.execPath}" "${opencliBinPath}" %*`,
|
|
2076
|
+
""
|
|
2077
|
+
].join("\r\n") + "\r\n";
|
|
2078
|
+
writeFileSync(opencliCmdWrapper, opencliCmdBody);
|
|
2079
|
+
}
|
|
1926
2080
|
}
|
|
1927
2081
|
const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
|
|
1928
2082
|
const spawnEnv = {
|
|
@@ -1930,6 +2084,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1930
2084
|
FORCE_COLOR: "0",
|
|
1931
2085
|
...ctx.config.envVars || {},
|
|
1932
2086
|
...extraEnv,
|
|
2087
|
+
...platform === "win32" ? windowsUtf8Env() : {},
|
|
1933
2088
|
...runtimeContextEnv(ctx.config),
|
|
1934
2089
|
[SLOCK_HOME_ENV]: slockHome,
|
|
1935
2090
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
@@ -1938,7 +2093,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1938
2093
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1939
2094
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1940
2095
|
};
|
|
1941
|
-
|
|
2096
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
1942
2097
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1943
2098
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1944
2099
|
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
@@ -1985,7 +2140,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1985
2140
|
}
|
|
1986
2141
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1987
2142
|
const platform = deps.platform ?? process.platform;
|
|
1988
|
-
const env = deps.env ?? process.env;
|
|
2143
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1989
2144
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1990
2145
|
if (platform === "win32") {
|
|
1991
2146
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -2010,7 +2165,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
2010
2165
|
return null;
|
|
2011
2166
|
}
|
|
2012
2167
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
2013
|
-
const env = deps.env ?? process.env;
|
|
2168
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
2014
2169
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2015
2170
|
try {
|
|
2016
2171
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -2072,7 +2227,7 @@ function expandClaudeMcpConfigVariables(raw, vars) {
|
|
|
2072
2227
|
function readClaudeMcpServers(configPath, vars = {}) {
|
|
2073
2228
|
try {
|
|
2074
2229
|
const parsed = JSON.parse(
|
|
2075
|
-
expandClaudeMcpConfigVariables(
|
|
2230
|
+
expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
|
|
2076
2231
|
);
|
|
2077
2232
|
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
|
|
2078
2233
|
return parsed.mcpServers;
|
|
@@ -2374,7 +2529,7 @@ var ClaudeDriver = class {
|
|
|
2374
2529
|
|
|
2375
2530
|
// src/drivers/codex.ts
|
|
2376
2531
|
import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
|
|
2377
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2532
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2378
2533
|
import os3 from "os";
|
|
2379
2534
|
import path5 from "path";
|
|
2380
2535
|
function getCodexNotificationErrorMessage(params) {
|
|
@@ -2912,7 +3067,7 @@ function detectCodexModels(home = os3.homedir()) {
|
|
|
2912
3067
|
const configPath = path5.join(home, ".codex", "config.toml");
|
|
2913
3068
|
let models = [];
|
|
2914
3069
|
try {
|
|
2915
|
-
const raw =
|
|
3070
|
+
const raw = readFileSync3(cachePath, "utf8");
|
|
2916
3071
|
const parsed = JSON.parse(raw);
|
|
2917
3072
|
const entries = Array.isArray(parsed?.models) ? parsed.models : [];
|
|
2918
3073
|
for (const entry of entries) {
|
|
@@ -2929,7 +3084,7 @@ function detectCodexModels(home = os3.homedir()) {
|
|
|
2929
3084
|
if (models.length === 0) return null;
|
|
2930
3085
|
let defaultModel;
|
|
2931
3086
|
try {
|
|
2932
|
-
const raw =
|
|
3087
|
+
const raw = readFileSync3(configPath, "utf8");
|
|
2933
3088
|
const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
|
|
2934
3089
|
if (match) defaultModel = match[1];
|
|
2935
3090
|
} catch {
|
|
@@ -3306,7 +3461,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
3306
3461
|
}
|
|
3307
3462
|
function runCursorModelsCommand() {
|
|
3308
3463
|
return spawnSync("cursor-agent", ["models"], {
|
|
3309
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3464
|
+
env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3310
3465
|
encoding: "utf8",
|
|
3311
3466
|
timeout: 5e3
|
|
3312
3467
|
});
|
|
@@ -3353,7 +3508,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
3353
3508
|
}
|
|
3354
3509
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
3355
3510
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
3356
|
-
const env = deps.env ?? process.env;
|
|
3511
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
3357
3512
|
const winPath = path8.win32;
|
|
3358
3513
|
let geminiEntry = null;
|
|
3359
3514
|
try {
|
|
@@ -3527,13 +3682,16 @@ var GeminiDriver = class {
|
|
|
3527
3682
|
// src/drivers/kimi.ts
|
|
3528
3683
|
import { randomUUID } from "crypto";
|
|
3529
3684
|
import { spawn as spawn6 } from "child_process";
|
|
3530
|
-
import { existsSync as existsSync7, readFileSync as
|
|
3685
|
+
import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3531
3686
|
import os4 from "os";
|
|
3532
3687
|
import path9 from "path";
|
|
3533
3688
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
3534
3689
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
3535
3690
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
3536
3691
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3692
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3693
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3694
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
3537
3695
|
function parseToolArguments(raw) {
|
|
3538
3696
|
if (typeof raw !== "string") return raw;
|
|
3539
3697
|
try {
|
|
@@ -3542,6 +3700,73 @@ function parseToolArguments(raw) {
|
|
|
3542
3700
|
return raw;
|
|
3543
3701
|
}
|
|
3544
3702
|
}
|
|
3703
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3704
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3705
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
3706
|
+
return {
|
|
3707
|
+
raw: inlineConfig,
|
|
3708
|
+
explicitPath: null,
|
|
3709
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3713
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3714
|
+
try {
|
|
3715
|
+
return {
|
|
3716
|
+
raw: readFileSync4(configPath, "utf8"),
|
|
3717
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3718
|
+
sourcePath: configPath
|
|
3719
|
+
};
|
|
3720
|
+
} catch {
|
|
3721
|
+
return {
|
|
3722
|
+
raw: null,
|
|
3723
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3724
|
+
sourcePath: configPath
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
3729
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3730
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3731
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3732
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
3733
|
+
}
|
|
3734
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3735
|
+
return {
|
|
3736
|
+
...process.env,
|
|
3737
|
+
...ctx.config.envVars || {},
|
|
3738
|
+
...overrideEnv || {}
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3742
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3743
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3744
|
+
const args = [];
|
|
3745
|
+
let configFilePath = null;
|
|
3746
|
+
let configContent = null;
|
|
3747
|
+
if (source.explicitPath) {
|
|
3748
|
+
configFilePath = source.explicitPath;
|
|
3749
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3750
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3751
|
+
configContent = source.raw;
|
|
3752
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
3753
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3754
|
+
chmodSync(configFilePath, 384);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
if (configFilePath) {
|
|
3758
|
+
args.push("--config-file", configFilePath);
|
|
3759
|
+
}
|
|
3760
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3761
|
+
args.push("--model", ctx.config.model);
|
|
3762
|
+
}
|
|
3763
|
+
return {
|
|
3764
|
+
args,
|
|
3765
|
+
env: buildKimiSpawnEnv(env),
|
|
3766
|
+
configFilePath,
|
|
3767
|
+
configContent
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3545
3770
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
3546
3771
|
return {
|
|
3547
3772
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3565,7 +3790,25 @@ var KimiDriver = class {
|
|
|
3565
3790
|
};
|
|
3566
3791
|
model = {
|
|
3567
3792
|
detectedModelsVerifiedAs: "launchable",
|
|
3568
|
-
toLaunchSpec: (modelId) =>
|
|
3793
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3794
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3795
|
+
const launchCtx = {
|
|
3796
|
+
...ctx,
|
|
3797
|
+
config: {
|
|
3798
|
+
...ctx.config,
|
|
3799
|
+
model: modelId
|
|
3800
|
+
}
|
|
3801
|
+
};
|
|
3802
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3803
|
+
home: opts?.home,
|
|
3804
|
+
writeGeneratedConfig: false
|
|
3805
|
+
});
|
|
3806
|
+
return {
|
|
3807
|
+
args: launch.args,
|
|
3808
|
+
env: launch.env,
|
|
3809
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3569
3812
|
};
|
|
3570
3813
|
supportsStdinNotification = true;
|
|
3571
3814
|
mcpToolPrefix = "";
|
|
@@ -3619,6 +3862,7 @@ var KimiDriver = class {
|
|
|
3619
3862
|
}
|
|
3620
3863
|
}
|
|
3621
3864
|
}), "utf8");
|
|
3865
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
3622
3866
|
const args = [
|
|
3623
3867
|
"--wire",
|
|
3624
3868
|
"--yolo",
|
|
@@ -3627,14 +3871,15 @@ var KimiDriver = class {
|
|
|
3627
3871
|
"--mcp-config-file",
|
|
3628
3872
|
mcpConfigPath,
|
|
3629
3873
|
"--session",
|
|
3630
|
-
this.sessionId
|
|
3874
|
+
this.sessionId,
|
|
3875
|
+
...launch.args
|
|
3631
3876
|
];
|
|
3632
3877
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3633
3878
|
args.push("--model", ctx.config.model);
|
|
3634
3879
|
}
|
|
3635
3880
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3636
|
-
const
|
|
3637
|
-
const proc = spawn6(
|
|
3881
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
3882
|
+
const proc = spawn6(spawnTarget.command, spawnTarget.args, {
|
|
3638
3883
|
cwd: ctx.workingDirectory,
|
|
3639
3884
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3640
3885
|
env: spawnEnv,
|
|
@@ -3642,7 +3887,7 @@ var KimiDriver = class {
|
|
|
3642
3887
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3643
3888
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3644
3889
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3645
|
-
shell:
|
|
3890
|
+
shell: spawnTarget.shell
|
|
3646
3891
|
});
|
|
3647
3892
|
proc.stdin?.write(JSON.stringify({
|
|
3648
3893
|
jsonrpc: "2.0",
|
|
@@ -3758,14 +4003,9 @@ var KimiDriver = class {
|
|
|
3758
4003
|
return detectKimiModels();
|
|
3759
4004
|
}
|
|
3760
4005
|
};
|
|
3761
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
try {
|
|
3765
|
-
raw = readFileSync3(configPath, "utf8");
|
|
3766
|
-
} catch {
|
|
3767
|
-
return null;
|
|
3768
|
-
}
|
|
4006
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
4007
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
4008
|
+
if (raw === null) return null;
|
|
3769
4009
|
const models = [];
|
|
3770
4010
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3771
4011
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3786,7 +4026,7 @@ function detectKimiModels(home = os4.homedir()) {
|
|
|
3786
4026
|
|
|
3787
4027
|
// src/drivers/opencode.ts
|
|
3788
4028
|
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
3789
|
-
import { readFileSync as
|
|
4029
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
3790
4030
|
import os5 from "os";
|
|
3791
4031
|
import path10 from "path";
|
|
3792
4032
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
@@ -3838,7 +4078,7 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
3838
4078
|
function readLocalOpenCodeConfig(home = os5.homedir()) {
|
|
3839
4079
|
const configPath = path10.join(home, ".config", "opencode", "opencode.json");
|
|
3840
4080
|
try {
|
|
3841
|
-
return parseOpenCodeConfigContent(
|
|
4081
|
+
return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
|
|
3842
4082
|
} catch {
|
|
3843
4083
|
}
|
|
3844
4084
|
return {};
|
|
@@ -4028,7 +4268,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
4028
4268
|
const platform = deps.platform ?? process.platform;
|
|
4029
4269
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
4030
4270
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
4031
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
4271
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
4032
4272
|
encoding: "utf8",
|
|
4033
4273
|
timeout: 5e3,
|
|
4034
4274
|
shell: platform === "win32"
|
|
@@ -4039,6 +4279,102 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
4039
4279
|
error: result.error
|
|
4040
4280
|
};
|
|
4041
4281
|
}
|
|
4282
|
+
function isWindowsCommandShim(commandPath) {
|
|
4283
|
+
const ext = path10.win32.extname(commandPath).toLowerCase();
|
|
4284
|
+
return ext === ".cmd" || ext === ".bat";
|
|
4285
|
+
}
|
|
4286
|
+
function opencodePackageEntryCandidates(packageRoot) {
|
|
4287
|
+
const winPath = path10.win32;
|
|
4288
|
+
return [
|
|
4289
|
+
winPath.join(packageRoot, "bin", "opencode.exe"),
|
|
4290
|
+
winPath.join(packageRoot, "bin", "opencode.js"),
|
|
4291
|
+
winPath.join(packageRoot, "bin", "opencode.mjs"),
|
|
4292
|
+
winPath.join(packageRoot, "dist", "index.js")
|
|
4293
|
+
];
|
|
4294
|
+
}
|
|
4295
|
+
function openCodeSpecForEntry(entry, commandArgs) {
|
|
4296
|
+
if (path10.win32.extname(entry).toLowerCase() === ".exe") {
|
|
4297
|
+
return { command: entry, args: commandArgs, shell: false };
|
|
4298
|
+
}
|
|
4299
|
+
return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
|
|
4300
|
+
}
|
|
4301
|
+
function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
4302
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync8;
|
|
4303
|
+
const execFileSyncFn = deps.execFileSyncFn;
|
|
4304
|
+
const env = deps.env ?? process.env;
|
|
4305
|
+
const winPath = path10.win32;
|
|
4306
|
+
const candidates = [];
|
|
4307
|
+
if (execFileSyncFn) {
|
|
4308
|
+
try {
|
|
4309
|
+
const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
|
|
4310
|
+
encoding: "utf8",
|
|
4311
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4312
|
+
env
|
|
4313
|
+
})).trim();
|
|
4314
|
+
if (globalRoot) {
|
|
4315
|
+
candidates.push(...opencodePackageEntryCandidates(winPath.join(globalRoot, "opencode-ai")));
|
|
4316
|
+
}
|
|
4317
|
+
} catch {
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
if (commandPath) {
|
|
4321
|
+
const commandDir = winPath.dirname(commandPath);
|
|
4322
|
+
candidates.push(...opencodePackageEntryCandidates(winPath.join(commandDir, "node_modules", "opencode-ai")));
|
|
4323
|
+
candidates.push(...extractWindowsShimTargets(commandPath, deps));
|
|
4324
|
+
}
|
|
4325
|
+
for (const candidate of candidates) {
|
|
4326
|
+
if (existsSyncFn(candidate)) return candidate;
|
|
4327
|
+
}
|
|
4328
|
+
return null;
|
|
4329
|
+
}
|
|
4330
|
+
function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
4331
|
+
if (!isWindowsCommandShim(commandPath)) return [];
|
|
4332
|
+
const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
|
|
4333
|
+
const commandDir = path10.win32.dirname(commandPath);
|
|
4334
|
+
let raw;
|
|
4335
|
+
try {
|
|
4336
|
+
raw = String(readFileSyncFn(commandPath, "utf8"));
|
|
4337
|
+
} catch {
|
|
4338
|
+
return [];
|
|
4339
|
+
}
|
|
4340
|
+
const candidates = [];
|
|
4341
|
+
const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
|
|
4342
|
+
for (const match of raw.matchAll(dp0Pattern)) {
|
|
4343
|
+
const relative = match[1]?.replace(/^\\+/, "");
|
|
4344
|
+
if (relative) candidates.push(path10.win32.normalize(path10.win32.join(commandDir, relative)));
|
|
4345
|
+
}
|
|
4346
|
+
return candidates;
|
|
4347
|
+
}
|
|
4348
|
+
function resolveOpenCodeSpawn(commandArgs, deps = {}) {
|
|
4349
|
+
const platform = deps.platform ?? process.platform;
|
|
4350
|
+
if (platform !== "win32") {
|
|
4351
|
+
return {
|
|
4352
|
+
command: resolveCommandOnPath("opencode", deps) ?? "opencode",
|
|
4353
|
+
args: commandArgs,
|
|
4354
|
+
shell: false
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
const command = resolveCommandOnPath("opencode", deps);
|
|
4358
|
+
if (command && path10.win32.extname(command).toLowerCase() === ".exe") {
|
|
4359
|
+
return { command, args: commandArgs, shell: false };
|
|
4360
|
+
}
|
|
4361
|
+
const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
|
|
4362
|
+
if (packageEntry) return openCodeSpecForEntry(packageEntry, commandArgs);
|
|
4363
|
+
if (command && !isWindowsCommandShim(command)) {
|
|
4364
|
+
return { command, args: commandArgs, shell: false };
|
|
4365
|
+
}
|
|
4366
|
+
throw new Error(
|
|
4367
|
+
"Cannot resolve OpenCode CLI entry point on Windows without cmd.exe. Install the native OpenCode executable or install opencode-ai globally so Slock can launch node_modules/opencode-ai/bin/opencode.exe directly."
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4370
|
+
function readOpenCodeVersion(deps = {}) {
|
|
4371
|
+
try {
|
|
4372
|
+
const launch = resolveOpenCodeSpawn([], deps);
|
|
4373
|
+
return readCommandVersion(launch.command, launch.args, deps);
|
|
4374
|
+
} catch {
|
|
4375
|
+
return null;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4042
4378
|
function isSystemFirstMessageTask(message) {
|
|
4043
4379
|
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
|
|
4044
4380
|
}
|
|
@@ -4081,7 +4417,7 @@ var OpenCodeDriver = class {
|
|
|
4081
4417
|
model: modelId
|
|
4082
4418
|
}
|
|
4083
4419
|
};
|
|
4084
|
-
const version =
|
|
4420
|
+
const version = readOpenCodeVersion();
|
|
4085
4421
|
const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
|
|
4086
4422
|
return {
|
|
4087
4423
|
args: launch.args,
|
|
@@ -4102,8 +4438,13 @@ var OpenCodeDriver = class {
|
|
|
4102
4438
|
sessionId = null;
|
|
4103
4439
|
sessionAnnounced = false;
|
|
4104
4440
|
probe() {
|
|
4105
|
-
|
|
4106
|
-
|
|
4441
|
+
let version;
|
|
4442
|
+
try {
|
|
4443
|
+
const launch = resolveOpenCodeSpawn([]);
|
|
4444
|
+
version = readCommandVersion(launch.command, launch.args);
|
|
4445
|
+
} catch {
|
|
4446
|
+
return { available: false };
|
|
4447
|
+
}
|
|
4107
4448
|
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
4108
4449
|
if (unsupportedMessage) {
|
|
4109
4450
|
return {
|
|
@@ -4111,7 +4452,7 @@ var OpenCodeDriver = class {
|
|
|
4111
4452
|
version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
|
|
4112
4453
|
};
|
|
4113
4454
|
}
|
|
4114
|
-
return { available: true, version };
|
|
4455
|
+
return { available: true, version: version ?? void 0 };
|
|
4115
4456
|
}
|
|
4116
4457
|
async detectModels() {
|
|
4117
4458
|
return detectOpenCodeModels();
|
|
@@ -4119,17 +4460,18 @@ var OpenCodeDriver = class {
|
|
|
4119
4460
|
spawn(ctx) {
|
|
4120
4461
|
this.sessionId = ctx.config.sessionId || null;
|
|
4121
4462
|
this.sessionAnnounced = false;
|
|
4122
|
-
const version =
|
|
4463
|
+
const version = readOpenCodeVersion();
|
|
4123
4464
|
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
4124
4465
|
if (unsupportedMessage) {
|
|
4125
4466
|
throw new Error(unsupportedMessage);
|
|
4126
4467
|
}
|
|
4127
4468
|
const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
|
|
4128
|
-
const
|
|
4469
|
+
const spawnSpec = resolveOpenCodeSpawn(launch.args);
|
|
4470
|
+
const proc = spawn7(spawnSpec.command, spawnSpec.args, {
|
|
4129
4471
|
cwd: ctx.workingDirectory,
|
|
4130
4472
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4131
4473
|
env: launch.env,
|
|
4132
|
-
shell:
|
|
4474
|
+
shell: spawnSpec.shell
|
|
4133
4475
|
});
|
|
4134
4476
|
proc.stdin?.end();
|
|
4135
4477
|
return { process: proc };
|
|
@@ -4187,6 +4529,297 @@ var OpenCodeDriver = class {
|
|
|
4187
4529
|
}
|
|
4188
4530
|
};
|
|
4189
4531
|
|
|
4532
|
+
// src/drivers/pi.ts
|
|
4533
|
+
import { spawn as spawn8 } from "child_process";
|
|
4534
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4535
|
+
import path11 from "path";
|
|
4536
|
+
import { fileURLToPath } from "url";
|
|
4537
|
+
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
4538
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
4539
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
4540
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
4541
|
+
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
4542
|
+
function parseSemver2(version) {
|
|
4543
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
4544
|
+
if (!match) return null;
|
|
4545
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4546
|
+
}
|
|
4547
|
+
function isSupportedPiVersion(version) {
|
|
4548
|
+
if (!version) return true;
|
|
4549
|
+
const actual = parseSemver2(version);
|
|
4550
|
+
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
4551
|
+
if (!actual || !minimum) return true;
|
|
4552
|
+
for (let i = 0; i < 3; i += 1) {
|
|
4553
|
+
if (actual[i] > minimum[i]) return true;
|
|
4554
|
+
if (actual[i] < minimum[i]) return false;
|
|
4555
|
+
}
|
|
4556
|
+
return true;
|
|
4557
|
+
}
|
|
4558
|
+
function unsupportedPiVersionMessage(version) {
|
|
4559
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
4560
|
+
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.`;
|
|
4561
|
+
}
|
|
4562
|
+
function probePi(version = PI_SDK_VERSION) {
|
|
4563
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
4564
|
+
if (unsupportedMessage) {
|
|
4565
|
+
return {
|
|
4566
|
+
available: false,
|
|
4567
|
+
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
return { available: true, version };
|
|
4571
|
+
}
|
|
4572
|
+
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
4573
|
+
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
4574
|
+
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
4575
|
+
if (existsSync9(sourceSibling)) return sourceSibling;
|
|
4576
|
+
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
4577
|
+
if (existsSync9(bundledEntry)) return bundledEntry;
|
|
4578
|
+
return path11.join(moduleDir, "piSdkRunner.js");
|
|
4579
|
+
}
|
|
4580
|
+
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
4581
|
+
if (runnerPath.endsWith(".ts")) {
|
|
4582
|
+
return [...process.execArgv, runnerPath];
|
|
4583
|
+
}
|
|
4584
|
+
return [runnerPath];
|
|
4585
|
+
}
|
|
4586
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
4587
|
+
const command = opts.command ?? process.execPath;
|
|
4588
|
+
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
4589
|
+
const sessionDir = path11.join(piDir, "sessions");
|
|
4590
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
4591
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4592
|
+
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
4593
|
+
const agentDir = opts.agentDir ?? getAgentDir();
|
|
4594
|
+
const runnerConfigPath = path11.join(
|
|
4595
|
+
piDir,
|
|
4596
|
+
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
4597
|
+
);
|
|
4598
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
4599
|
+
const runnerConfig = {
|
|
4600
|
+
cwd: ctx.workingDirectory,
|
|
4601
|
+
agentDir,
|
|
4602
|
+
sessionDir,
|
|
4603
|
+
sessionId: ctx.config.sessionId || null,
|
|
4604
|
+
standingPrompt: ctx.standingPrompt,
|
|
4605
|
+
prompt: turnPrompt,
|
|
4606
|
+
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
4607
|
+
};
|
|
4608
|
+
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
4609
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4610
|
+
const args = [
|
|
4611
|
+
...buildPiSdkNodeArgs(runnerPath),
|
|
4612
|
+
"--config",
|
|
4613
|
+
runnerConfigPath
|
|
4614
|
+
];
|
|
4615
|
+
return {
|
|
4616
|
+
command,
|
|
4617
|
+
args,
|
|
4618
|
+
env: slock.spawnEnv,
|
|
4619
|
+
sessionDir,
|
|
4620
|
+
agentDir,
|
|
4621
|
+
runnerConfigPath,
|
|
4622
|
+
sdkVersion: PI_SDK_VERSION
|
|
4623
|
+
};
|
|
4624
|
+
}
|
|
4625
|
+
function isSystemFirstMessageTask2(message) {
|
|
4626
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
4627
|
+
}
|
|
4628
|
+
function buildPiSystemPrompt(config) {
|
|
4629
|
+
return buildCliTransportSystemPrompt(config, {
|
|
4630
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
4631
|
+
extraCriticalRules: [
|
|
4632
|
+
"- 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."
|
|
4633
|
+
],
|
|
4634
|
+
postStartupNotes: [
|
|
4635
|
+
"**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."
|
|
4636
|
+
],
|
|
4637
|
+
includeStdinNotificationSection: false,
|
|
4638
|
+
messageNotificationStyle: "poll"
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
function contentText(content) {
|
|
4642
|
+
if (!content) return "";
|
|
4643
|
+
const chunks = [];
|
|
4644
|
+
for (const item of content) {
|
|
4645
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
4646
|
+
chunks.push(item.text);
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
return chunks.join("");
|
|
4650
|
+
}
|
|
4651
|
+
function apiKeyErrorMessage(line) {
|
|
4652
|
+
const trimmed = line.trim();
|
|
4653
|
+
if (!trimmed) return null;
|
|
4654
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
4655
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
4656
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
4657
|
+
return null;
|
|
4658
|
+
}
|
|
4659
|
+
var PiDriver = class {
|
|
4660
|
+
id = "pi";
|
|
4661
|
+
lifecycle = {
|
|
4662
|
+
kind: "per_turn",
|
|
4663
|
+
start: "defer_until_concrete_message",
|
|
4664
|
+
exit: "terminate_on_turn_end",
|
|
4665
|
+
inFlightWake: "coalesce_into_pending"
|
|
4666
|
+
};
|
|
4667
|
+
communication = {
|
|
4668
|
+
chat: "slock_cli",
|
|
4669
|
+
runtimeControl: "none"
|
|
4670
|
+
};
|
|
4671
|
+
session = {
|
|
4672
|
+
recovery: "resume_or_fresh"
|
|
4673
|
+
};
|
|
4674
|
+
model = {
|
|
4675
|
+
detectedModelsVerifiedAs: "launchable",
|
|
4676
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
4677
|
+
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
4678
|
+
const launchCtx = {
|
|
4679
|
+
...ctx,
|
|
4680
|
+
config: {
|
|
4681
|
+
...ctx.config,
|
|
4682
|
+
model: modelId
|
|
4683
|
+
}
|
|
4684
|
+
};
|
|
4685
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
4686
|
+
return {
|
|
4687
|
+
args: launch.args,
|
|
4688
|
+
env: launch.env,
|
|
4689
|
+
configFiles: [launch.runnerConfigPath],
|
|
4690
|
+
params: {
|
|
4691
|
+
agentDir: launch.agentDir,
|
|
4692
|
+
sessionDir: launch.sessionDir,
|
|
4693
|
+
sdkVersion: launch.sdkVersion,
|
|
4694
|
+
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
4695
|
+
}
|
|
4696
|
+
};
|
|
4697
|
+
}
|
|
4698
|
+
};
|
|
4699
|
+
supportsStdinNotification = false;
|
|
4700
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
4701
|
+
busyDeliveryMode = "none";
|
|
4702
|
+
terminateProcessOnTurnEnd = true;
|
|
4703
|
+
deferSpawnUntilMessage = true;
|
|
4704
|
+
usesSlockCliForCommunication = true;
|
|
4705
|
+
sessionId = null;
|
|
4706
|
+
sessionAnnounced = false;
|
|
4707
|
+
apiKeyErrorAnnounced = false;
|
|
4708
|
+
turnEnded = false;
|
|
4709
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
4710
|
+
shouldDeferWakeMessage(message) {
|
|
4711
|
+
return isSystemFirstMessageTask2(message);
|
|
4712
|
+
}
|
|
4713
|
+
probe() {
|
|
4714
|
+
return probePi();
|
|
4715
|
+
}
|
|
4716
|
+
async detectModels() {
|
|
4717
|
+
return null;
|
|
4718
|
+
}
|
|
4719
|
+
spawn(ctx) {
|
|
4720
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
4721
|
+
this.sessionAnnounced = false;
|
|
4722
|
+
this.apiKeyErrorAnnounced = false;
|
|
4723
|
+
this.turnEnded = false;
|
|
4724
|
+
this.assistantTextByMessageId.clear();
|
|
4725
|
+
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
4726
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
4727
|
+
const launch = buildPiLaunchOptions(ctx);
|
|
4728
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
4729
|
+
cwd: ctx.workingDirectory,
|
|
4730
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4731
|
+
env: launch.env,
|
|
4732
|
+
shell: false
|
|
4733
|
+
});
|
|
4734
|
+
proc.stdin?.end();
|
|
4735
|
+
return { process: proc };
|
|
4736
|
+
}
|
|
4737
|
+
parseLine(line) {
|
|
4738
|
+
let event;
|
|
4739
|
+
try {
|
|
4740
|
+
event = JSON.parse(line);
|
|
4741
|
+
} catch {
|
|
4742
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
4743
|
+
const message = apiKeyErrorMessage(line);
|
|
4744
|
+
if (!message) return [];
|
|
4745
|
+
this.apiKeyErrorAnnounced = true;
|
|
4746
|
+
this.turnEnded = true;
|
|
4747
|
+
return [
|
|
4748
|
+
{ kind: "error", message },
|
|
4749
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
4750
|
+
];
|
|
4751
|
+
}
|
|
4752
|
+
const events = [];
|
|
4753
|
+
if (event.type === "session" && event.id) {
|
|
4754
|
+
this.sessionId = event.id;
|
|
4755
|
+
}
|
|
4756
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
4757
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
4758
|
+
this.sessionAnnounced = true;
|
|
4759
|
+
}
|
|
4760
|
+
switch (event.type) {
|
|
4761
|
+
case "agent_start":
|
|
4762
|
+
case "turn_start":
|
|
4763
|
+
events.push({ kind: "thinking", text: "" });
|
|
4764
|
+
break;
|
|
4765
|
+
case "message_update":
|
|
4766
|
+
case "message_end":
|
|
4767
|
+
if (event.message?.role === "assistant") {
|
|
4768
|
+
const key = event.message.id || "current";
|
|
4769
|
+
const currentText = contentText(event.message.content);
|
|
4770
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
4771
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
4772
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
4773
|
+
} else if (currentText && currentText !== previousText) {
|
|
4774
|
+
events.push({ kind: "text", text: currentText });
|
|
4775
|
+
}
|
|
4776
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
4777
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
4778
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
break;
|
|
4782
|
+
case "tool_execution_start":
|
|
4783
|
+
events.push({
|
|
4784
|
+
kind: "tool_call",
|
|
4785
|
+
name: event.toolName || "unknown_tool",
|
|
4786
|
+
input: event.args
|
|
4787
|
+
});
|
|
4788
|
+
break;
|
|
4789
|
+
case "tool_execution_end":
|
|
4790
|
+
events.push({
|
|
4791
|
+
kind: "tool_output",
|
|
4792
|
+
name: event.toolName || "unknown_tool"
|
|
4793
|
+
});
|
|
4794
|
+
if (event.isError) {
|
|
4795
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4796
|
+
}
|
|
4797
|
+
break;
|
|
4798
|
+
case "compaction_start":
|
|
4799
|
+
events.push({ kind: "compaction_started" });
|
|
4800
|
+
break;
|
|
4801
|
+
case "compaction_end":
|
|
4802
|
+
events.push({ kind: "compaction_finished" });
|
|
4803
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4804
|
+
break;
|
|
4805
|
+
case "turn_end":
|
|
4806
|
+
case "agent_end":
|
|
4807
|
+
if (!this.turnEnded) {
|
|
4808
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4809
|
+
this.turnEnded = true;
|
|
4810
|
+
}
|
|
4811
|
+
break;
|
|
4812
|
+
}
|
|
4813
|
+
return events;
|
|
4814
|
+
}
|
|
4815
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4816
|
+
return null;
|
|
4817
|
+
}
|
|
4818
|
+
buildSystemPrompt(config, _agentId) {
|
|
4819
|
+
return buildPiSystemPrompt(config);
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
|
|
4190
4823
|
// src/drivers/index.ts
|
|
4191
4824
|
var driverFactories = {
|
|
4192
4825
|
claude: () => new ClaudeDriver(),
|
|
@@ -4195,7 +4828,8 @@ var driverFactories = {
|
|
|
4195
4828
|
cursor: () => new CursorDriver(),
|
|
4196
4829
|
gemini: () => new GeminiDriver(),
|
|
4197
4830
|
kimi: () => new KimiDriver(),
|
|
4198
|
-
opencode: () => new OpenCodeDriver()
|
|
4831
|
+
opencode: () => new OpenCodeDriver(),
|
|
4832
|
+
pi: () => new PiDriver()
|
|
4199
4833
|
};
|
|
4200
4834
|
function getDriver(runtimeId) {
|
|
4201
4835
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -4208,7 +4842,7 @@ function getDriver(runtimeId) {
|
|
|
4208
4842
|
|
|
4209
4843
|
// src/workspaces.ts
|
|
4210
4844
|
import { readdir, rm, stat } from "fs/promises";
|
|
4211
|
-
import
|
|
4845
|
+
import path12 from "path";
|
|
4212
4846
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
4213
4847
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
4214
4848
|
}
|
|
@@ -4216,7 +4850,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
4216
4850
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
4217
4851
|
return null;
|
|
4218
4852
|
}
|
|
4219
|
-
return
|
|
4853
|
+
return path12.join(dataDir, directoryName);
|
|
4220
4854
|
}
|
|
4221
4855
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
4222
4856
|
return {
|
|
@@ -4265,7 +4899,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
4265
4899
|
return summary;
|
|
4266
4900
|
}
|
|
4267
4901
|
const childSummaries = await Promise.all(
|
|
4268
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4902
|
+
entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
|
|
4269
4903
|
);
|
|
4270
4904
|
for (const childSummary of childSummaries) {
|
|
4271
4905
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -4284,7 +4918,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
4284
4918
|
if (!entry.isDirectory()) {
|
|
4285
4919
|
return null;
|
|
4286
4920
|
}
|
|
4287
|
-
const dirPath =
|
|
4921
|
+
const dirPath = path12.join(dataDir, entry.name);
|
|
4288
4922
|
try {
|
|
4289
4923
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
4290
4924
|
return {
|
|
@@ -4367,6 +5001,7 @@ function classifyRuntimeError(message, httpStatus) {
|
|
|
4367
5001
|
return "ProviderApiError";
|
|
4368
5002
|
}
|
|
4369
5003
|
if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
|
|
5004
|
+
if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
|
|
4370
5005
|
if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
|
|
4371
5006
|
if (/\bnot found\b/i.test(message)) return "NotFoundError";
|
|
4372
5007
|
return "RuntimeError";
|
|
@@ -4541,12 +5176,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
4541
5176
|
for (const entry of entries) {
|
|
4542
5177
|
if (++visited > maxEntries) return null;
|
|
4543
5178
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
4544
|
-
return
|
|
5179
|
+
return path13.join(dir, entry.name);
|
|
4545
5180
|
}
|
|
4546
5181
|
for (const entry of entries) {
|
|
4547
5182
|
if (++visited > maxEntries) return null;
|
|
4548
5183
|
if (!entry.isDirectory()) continue;
|
|
4549
|
-
const found = visit(
|
|
5184
|
+
const found = visit(path13.join(dir, entry.name), depth - 1);
|
|
4550
5185
|
if (found) return found;
|
|
4551
5186
|
}
|
|
4552
5187
|
return null;
|
|
@@ -4559,10 +5194,10 @@ function safeSessionFilename(value) {
|
|
|
4559
5194
|
}
|
|
4560
5195
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
4561
5196
|
try {
|
|
4562
|
-
const dir =
|
|
4563
|
-
|
|
4564
|
-
const filePath =
|
|
4565
|
-
|
|
5197
|
+
const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
|
|
5198
|
+
mkdirSync5(dir, { recursive: true });
|
|
5199
|
+
const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
5200
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4566
5201
|
type: "runtime_session_handoff",
|
|
4567
5202
|
runtime,
|
|
4568
5203
|
sessionId,
|
|
@@ -4581,7 +5216,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
4581
5216
|
}
|
|
4582
5217
|
}
|
|
4583
5218
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
4584
|
-
const directPath =
|
|
5219
|
+
const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
|
|
4585
5220
|
if (directPath) {
|
|
4586
5221
|
try {
|
|
4587
5222
|
if (statSync2(directPath).isFile()) {
|
|
@@ -4590,7 +5225,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
4590
5225
|
} catch {
|
|
4591
5226
|
}
|
|
4592
5227
|
}
|
|
4593
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
5228
|
+
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;
|
|
4594
5229
|
if (!resolvedPath && fallbackDir) {
|
|
4595
5230
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
4596
5231
|
if (fallback) return fallback;
|
|
@@ -5042,7 +5677,7 @@ Do not copy these answers verbatim.
|
|
|
5042
5677
|
## FAQ 15: How do I create agents or channels?
|
|
5043
5678
|
### Answer idea
|
|
5044
5679
|
- 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.
|
|
5045
|
-
- v1 supports three action types via \`slock action prepare --target '<channel>' <<
|
|
5680
|
+
- v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
|
|
5046
5681
|
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
5047
5682
|
- \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
|
|
5048
5683
|
- \`{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.
|
|
@@ -5241,12 +5876,21 @@ function classifyTerminalFailure(ap) {
|
|
|
5241
5876
|
].filter((value) => !!value);
|
|
5242
5877
|
for (const text of candidates) {
|
|
5243
5878
|
const lower = text.toLowerCase();
|
|
5244
|
-
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found")) {
|
|
5879
|
+
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || isProviderStreamFailureText(text)) {
|
|
5245
5880
|
return text;
|
|
5246
5881
|
}
|
|
5247
5882
|
}
|
|
5248
5883
|
return null;
|
|
5249
5884
|
}
|
|
5885
|
+
function isProviderStreamFailureText(text) {
|
|
5886
|
+
return /stream closed before response\.completed|error decoding response body/i.test(text);
|
|
5887
|
+
}
|
|
5888
|
+
function isCodexProviderReconnectLog(text) {
|
|
5889
|
+
return /Reconnecting\.\.\.\s*\d+\s*\/\s*\d+/i.test(text);
|
|
5890
|
+
}
|
|
5891
|
+
function isCodexBenignTransportLog(text) {
|
|
5892
|
+
return /Falling back from WebSockets/i.test(text);
|
|
5893
|
+
}
|
|
5250
5894
|
function hasDirectStdinRecoveryEvidence(ap) {
|
|
5251
5895
|
const candidates = [
|
|
5252
5896
|
ap.lastRuntimeError,
|
|
@@ -5890,26 +6534,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5890
6534
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
5891
6535
|
try {
|
|
5892
6536
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
5893
|
-
const agentDataDir =
|
|
6537
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
5894
6538
|
await mkdir(agentDataDir, { recursive: true });
|
|
5895
6539
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
5896
|
-
const memoryMdPath =
|
|
6540
|
+
const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
|
|
5897
6541
|
try {
|
|
5898
6542
|
await access(memoryMdPath);
|
|
5899
6543
|
} catch {
|
|
5900
6544
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
5901
6545
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
5902
6546
|
}
|
|
5903
|
-
const notesDir =
|
|
6547
|
+
const notesDir = path13.join(agentDataDir, "notes");
|
|
5904
6548
|
await mkdir(notesDir, { recursive: true });
|
|
5905
6549
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
5906
6550
|
const seedFiles = buildOnboardingSeedFiles();
|
|
5907
6551
|
for (const { relativePath, content } of seedFiles) {
|
|
5908
|
-
const fullPath =
|
|
6552
|
+
const fullPath = path13.join(agentDataDir, relativePath);
|
|
5909
6553
|
try {
|
|
5910
6554
|
await access(fullPath);
|
|
5911
6555
|
} catch {
|
|
5912
|
-
await mkdir(
|
|
6556
|
+
await mkdir(path13.dirname(fullPath), { recursive: true });
|
|
5913
6557
|
await writeFile(fullPath, content);
|
|
5914
6558
|
}
|
|
5915
6559
|
}
|
|
@@ -6098,8 +6742,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6098
6742
|
proc.stderr?.on("data", (chunk) => {
|
|
6099
6743
|
const text = chunk.toString().trim();
|
|
6100
6744
|
if (!text) return;
|
|
6101
|
-
if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
|
|
6102
6745
|
const current = this.agents.get(agentId);
|
|
6746
|
+
if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
|
|
6747
|
+
if (current) {
|
|
6748
|
+
current.recentStderr = pushRecentStderr(current.recentStderr, text);
|
|
6749
|
+
}
|
|
6750
|
+
this.recordDaemonTrace("daemon.agent.provider_reconnect", {
|
|
6751
|
+
agentId,
|
|
6752
|
+
launchId: current?.launchId || void 0,
|
|
6753
|
+
runtime: config.runtime,
|
|
6754
|
+
model: config.model
|
|
6755
|
+
});
|
|
6756
|
+
this.broadcastActivity(agentId, "working", "Codex reconnecting to provider\u2026", [
|
|
6757
|
+
{ kind: "text", text }
|
|
6758
|
+
]);
|
|
6759
|
+
logger.info(`[Agent ${agentId} stderr]: ${text}`);
|
|
6760
|
+
return;
|
|
6761
|
+
}
|
|
6762
|
+
if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
|
|
6103
6763
|
if (current) {
|
|
6104
6764
|
current.recentStderr = pushRecentStderr(current.recentStderr, text);
|
|
6105
6765
|
}
|
|
@@ -6243,10 +6903,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6243
6903
|
}
|
|
6244
6904
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
6245
6905
|
} else {
|
|
6246
|
-
this.idleAgentConfigs.delete(agentId);
|
|
6247
6906
|
const reason = formatCrashReason(finalCode, finalSignal, ap);
|
|
6248
|
-
|
|
6249
|
-
|
|
6907
|
+
if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail)) {
|
|
6908
|
+
this.idleAgentConfigs.set(agentId, {
|
|
6909
|
+
config: { ...ap.config, sessionId: ap.sessionId },
|
|
6910
|
+
sessionId: ap.sessionId,
|
|
6911
|
+
launchId: ap.launchId
|
|
6912
|
+
});
|
|
6913
|
+
logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
|
|
6914
|
+
this.sendAgentStatus(agentId, "active", ap.launchId);
|
|
6915
|
+
} else {
|
|
6916
|
+
this.idleAgentConfigs.delete(agentId);
|
|
6917
|
+
logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
|
|
6918
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
6919
|
+
}
|
|
6250
6920
|
if (terminalFailureDetail) {
|
|
6251
6921
|
this.broadcastActivity(
|
|
6252
6922
|
agentId,
|
|
@@ -6760,7 +7430,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6760
7430
|
return true;
|
|
6761
7431
|
}
|
|
6762
7432
|
async resetWorkspace(agentId) {
|
|
6763
|
-
const agentDataDir =
|
|
7433
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
6764
7434
|
try {
|
|
6765
7435
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
6766
7436
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -6797,7 +7467,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6797
7467
|
return result;
|
|
6798
7468
|
}
|
|
6799
7469
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
6800
|
-
const workspacePath =
|
|
7470
|
+
const workspacePath = path13.join(this.dataDir, agentId);
|
|
6801
7471
|
return {
|
|
6802
7472
|
agentId,
|
|
6803
7473
|
launchId,
|
|
@@ -7054,7 +7724,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7054
7724
|
}
|
|
7055
7725
|
// Workspace file browsing
|
|
7056
7726
|
async getFileTree(agentId, dirPath) {
|
|
7057
|
-
const agentDir =
|
|
7727
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7058
7728
|
try {
|
|
7059
7729
|
await stat2(agentDir);
|
|
7060
7730
|
} catch {
|
|
@@ -7062,8 +7732,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7062
7732
|
}
|
|
7063
7733
|
let targetDir = agentDir;
|
|
7064
7734
|
if (dirPath) {
|
|
7065
|
-
const resolved =
|
|
7066
|
-
if (!resolved.startsWith(agentDir +
|
|
7735
|
+
const resolved = path13.resolve(agentDir, dirPath);
|
|
7736
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
7067
7737
|
return [];
|
|
7068
7738
|
}
|
|
7069
7739
|
targetDir = resolved;
|
|
@@ -7071,14 +7741,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7071
7741
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
7072
7742
|
}
|
|
7073
7743
|
async readFile(agentId, filePath) {
|
|
7074
|
-
const agentDir =
|
|
7075
|
-
const resolved =
|
|
7076
|
-
if (!resolved.startsWith(agentDir +
|
|
7744
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7745
|
+
const resolved = path13.resolve(agentDir, filePath);
|
|
7746
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
7077
7747
|
throw new Error("Access denied");
|
|
7078
7748
|
}
|
|
7079
7749
|
const info = await stat2(resolved);
|
|
7080
7750
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
7081
|
-
const ext =
|
|
7751
|
+
const ext = path13.extname(resolved).toLowerCase();
|
|
7082
7752
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
7083
7753
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
7084
7754
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -7113,13 +7783,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7113
7783
|
const agent = this.agents.get(agentId);
|
|
7114
7784
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
7115
7785
|
const home = os6.homedir();
|
|
7116
|
-
const workspaceDir =
|
|
7786
|
+
const workspaceDir = path13.join(this.dataDir, agentId);
|
|
7117
7787
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
7118
7788
|
const globalResults = await Promise.all(
|
|
7119
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
7789
|
+
paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
|
|
7120
7790
|
);
|
|
7121
7791
|
const workspaceResults = await Promise.all(
|
|
7122
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
7792
|
+
paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
|
|
7123
7793
|
);
|
|
7124
7794
|
const dedup = (skills) => {
|
|
7125
7795
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7148,7 +7818,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7148
7818
|
const skills = [];
|
|
7149
7819
|
for (const entry of entries) {
|
|
7150
7820
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
7151
|
-
const skillMd =
|
|
7821
|
+
const skillMd = path13.join(dir, entry.name, "SKILL.md");
|
|
7152
7822
|
try {
|
|
7153
7823
|
const content = await readFile(skillMd, "utf-8");
|
|
7154
7824
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -7159,7 +7829,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7159
7829
|
} else if (entry.name.endsWith(".md")) {
|
|
7160
7830
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
7161
7831
|
try {
|
|
7162
|
-
const content = await readFile(
|
|
7832
|
+
const content = await readFile(path13.join(dir, entry.name), "utf-8");
|
|
7163
7833
|
const skill = this.parseSkillMd(cmdName, content);
|
|
7164
7834
|
skill.sourcePath = dir;
|
|
7165
7835
|
skills.push(skill);
|
|
@@ -8125,8 +8795,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
8125
8795
|
const nodes = [];
|
|
8126
8796
|
for (const entry of entries) {
|
|
8127
8797
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8128
|
-
const fullPath =
|
|
8129
|
-
const relativePath =
|
|
8798
|
+
const fullPath = path13.join(dir, entry.name);
|
|
8799
|
+
const relativePath = path13.relative(rootDir, fullPath);
|
|
8130
8800
|
let info;
|
|
8131
8801
|
try {
|
|
8132
8802
|
info = await stat2(fullPath);
|
|
@@ -8431,9 +9101,9 @@ var ReminderCache = class {
|
|
|
8431
9101
|
|
|
8432
9102
|
// src/machineLock.ts
|
|
8433
9103
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
8434
|
-
import { mkdirSync as
|
|
9104
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
8435
9105
|
import os7 from "os";
|
|
8436
|
-
import
|
|
9106
|
+
import path14 from "path";
|
|
8437
9107
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
8438
9108
|
var DaemonMachineLockConflictError = class extends Error {
|
|
8439
9109
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -8455,11 +9125,11 @@ function resolveDefaultMachineStateRoot() {
|
|
|
8455
9125
|
return resolveSlockHomePath("machines");
|
|
8456
9126
|
}
|
|
8457
9127
|
function ownerPath(lockDir) {
|
|
8458
|
-
return
|
|
9128
|
+
return path14.join(lockDir, "owner.json");
|
|
8459
9129
|
}
|
|
8460
9130
|
function readOwner(lockDir) {
|
|
8461
9131
|
try {
|
|
8462
|
-
return JSON.parse(
|
|
9132
|
+
return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
|
|
8463
9133
|
} catch {
|
|
8464
9134
|
return null;
|
|
8465
9135
|
}
|
|
@@ -8485,13 +9155,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
8485
9155
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
8486
9156
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
8487
9157
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
8488
|
-
const machineDir =
|
|
8489
|
-
const lockDir =
|
|
9158
|
+
const machineDir = path14.join(rootDir, lockId);
|
|
9159
|
+
const lockDir = path14.join(machineDir, "daemon.lock");
|
|
8490
9160
|
const token = randomUUID2();
|
|
8491
|
-
|
|
9161
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
8492
9162
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
8493
9163
|
try {
|
|
8494
|
-
|
|
9164
|
+
mkdirSync6(lockDir);
|
|
8495
9165
|
const owner = {
|
|
8496
9166
|
pid: process.pid,
|
|
8497
9167
|
token,
|
|
@@ -8501,7 +9171,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
8501
9171
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
8502
9172
|
};
|
|
8503
9173
|
try {
|
|
8504
|
-
|
|
9174
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
8505
9175
|
`, { mode: 384 });
|
|
8506
9176
|
} catch (err) {
|
|
8507
9177
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -8538,8 +9208,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
8538
9208
|
}
|
|
8539
9209
|
|
|
8540
9210
|
// src/localTraceSink.ts
|
|
8541
|
-
import { appendFileSync, mkdirSync as
|
|
8542
|
-
import
|
|
9211
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
9212
|
+
import path15 from "path";
|
|
8543
9213
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8544
9214
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
8545
9215
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -8575,7 +9245,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8575
9245
|
currentSize = 0;
|
|
8576
9246
|
sequence = 0;
|
|
8577
9247
|
constructor(options) {
|
|
8578
|
-
this.traceDir =
|
|
9248
|
+
this.traceDir = path15.join(options.machineDir, "traces");
|
|
8579
9249
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
8580
9250
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
8581
9251
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -8601,15 +9271,15 @@ var LocalRotatingTraceSink = class {
|
|
|
8601
9271
|
return this.currentFile;
|
|
8602
9272
|
}
|
|
8603
9273
|
ensureFile(nextBytes) {
|
|
8604
|
-
|
|
9274
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
8605
9275
|
const nowMs = this.nowMsProvider();
|
|
8606
9276
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
8607
9277
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
8608
|
-
this.currentFile =
|
|
9278
|
+
this.currentFile = path15.join(
|
|
8609
9279
|
this.traceDir,
|
|
8610
9280
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
8611
9281
|
);
|
|
8612
|
-
|
|
9282
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
8613
9283
|
this.currentSize = statSync4(this.currentFile).size;
|
|
8614
9284
|
this.currentFileOpenedAtMs = nowMs;
|
|
8615
9285
|
this.pruneOldFiles();
|
|
@@ -8620,7 +9290,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8620
9290
|
const excess = files.length - this.maxFiles;
|
|
8621
9291
|
if (excess <= 0) return;
|
|
8622
9292
|
for (const file of files.slice(0, excess)) {
|
|
8623
|
-
rmSync4(
|
|
9293
|
+
rmSync4(path15.join(this.traceDir, file), { force: true });
|
|
8624
9294
|
}
|
|
8625
9295
|
}
|
|
8626
9296
|
};
|
|
@@ -8707,11 +9377,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
8707
9377
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
8708
9378
|
import { gzipSync } from "zlib";
|
|
8709
9379
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8710
|
-
import
|
|
9380
|
+
import path16 from "path";
|
|
8711
9381
|
|
|
8712
9382
|
// src/directUploadCapability.ts
|
|
8713
|
-
function joinUrl(base,
|
|
8714
|
-
return `${base.replace(/\/+$/, "")}${
|
|
9383
|
+
function joinUrl(base, path18) {
|
|
9384
|
+
return `${base.replace(/\/+$/, "")}${path18}`;
|
|
8715
9385
|
}
|
|
8716
9386
|
function jsonHeaders(apiKey) {
|
|
8717
9387
|
return {
|
|
@@ -8930,7 +9600,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
8930
9600
|
}, nextMs);
|
|
8931
9601
|
}
|
|
8932
9602
|
async findUploadCandidates() {
|
|
8933
|
-
const traceDir =
|
|
9603
|
+
const traceDir = path16.join(this.options.machineDir, "traces");
|
|
8934
9604
|
let names;
|
|
8935
9605
|
try {
|
|
8936
9606
|
names = await readdir3(traceDir);
|
|
@@ -8942,8 +9612,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8942
9612
|
const currentFile = this.options.currentFileProvider?.();
|
|
8943
9613
|
const candidates = [];
|
|
8944
9614
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
8945
|
-
const file =
|
|
8946
|
-
if (currentFile &&
|
|
9615
|
+
const file = path16.join(traceDir, name);
|
|
9616
|
+
if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
|
|
8947
9617
|
if (await this.isUploaded(file)) continue;
|
|
8948
9618
|
try {
|
|
8949
9619
|
const info = await stat3(file);
|
|
@@ -9017,8 +9687,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
9017
9687
|
}
|
|
9018
9688
|
}
|
|
9019
9689
|
uploadStatePath(file) {
|
|
9020
|
-
const stateDir =
|
|
9021
|
-
return
|
|
9690
|
+
const stateDir = path16.join(this.options.machineDir, "trace-uploads");
|
|
9691
|
+
return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
|
|
9022
9692
|
}
|
|
9023
9693
|
async isUploaded(file) {
|
|
9024
9694
|
try {
|
|
@@ -9030,9 +9700,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
9030
9700
|
}
|
|
9031
9701
|
async markUploaded(file, metadata) {
|
|
9032
9702
|
const stateFile = this.uploadStatePath(file);
|
|
9033
|
-
await mkdir2(
|
|
9703
|
+
await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
|
|
9034
9704
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
9035
|
-
file:
|
|
9705
|
+
file: path16.basename(file),
|
|
9036
9706
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9037
9707
|
...metadata
|
|
9038
9708
|
}, null, 2)}
|
|
@@ -9054,7 +9724,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
9054
9724
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
9055
9725
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
9056
9726
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
9057
|
-
var DAEMON_CLI_USAGE =
|
|
9727
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
9058
9728
|
var RunnerCredentialMintError2 = class extends Error {
|
|
9059
9729
|
code;
|
|
9060
9730
|
retryable;
|
|
@@ -9090,9 +9760,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
9090
9760
|
async function waitForRunnerCredentialRetry2() {
|
|
9091
9761
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
9092
9762
|
}
|
|
9093
|
-
function parseDaemonCliArgs(args) {
|
|
9763
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
9094
9764
|
let serverUrl = "";
|
|
9095
|
-
let apiKey = "";
|
|
9765
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
9096
9766
|
for (let i = 0; i < args.length; i++) {
|
|
9097
9767
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
9098
9768
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -9102,30 +9772,30 @@ function parseDaemonCliArgs(args) {
|
|
|
9102
9772
|
}
|
|
9103
9773
|
function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
9104
9774
|
try {
|
|
9105
|
-
const require2 =
|
|
9775
|
+
const require2 = createRequire2(moduleUrl);
|
|
9106
9776
|
return require2("../package.json").version;
|
|
9107
9777
|
} catch {
|
|
9108
9778
|
return "0.0.0-dev";
|
|
9109
9779
|
}
|
|
9110
9780
|
}
|
|
9111
9781
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
9112
|
-
const dirname =
|
|
9113
|
-
const jsPath =
|
|
9782
|
+
const dirname = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9783
|
+
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
9114
9784
|
try {
|
|
9115
9785
|
accessSync(jsPath);
|
|
9116
9786
|
return jsPath;
|
|
9117
9787
|
} catch {
|
|
9118
|
-
return
|
|
9788
|
+
return path17.resolve(dirname, "chat-bridge.ts");
|
|
9119
9789
|
}
|
|
9120
9790
|
}
|
|
9121
9791
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
9122
|
-
const thisDir =
|
|
9123
|
-
const bundledDistPath =
|
|
9792
|
+
const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9793
|
+
const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
|
|
9124
9794
|
try {
|
|
9125
9795
|
accessSync(bundledDistPath);
|
|
9126
9796
|
return bundledDistPath;
|
|
9127
9797
|
} catch {
|
|
9128
|
-
const workspaceDistPath =
|
|
9798
|
+
const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
9129
9799
|
accessSync(workspaceDistPath);
|
|
9130
9800
|
return workspaceDistPath;
|
|
9131
9801
|
}
|
|
@@ -9304,7 +9974,7 @@ var DaemonCore = class {
|
|
|
9304
9974
|
}
|
|
9305
9975
|
resolveMachineStateRoot() {
|
|
9306
9976
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
9307
|
-
if (this.options.dataDir) return
|
|
9977
|
+
if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
|
|
9308
9978
|
return resolveDefaultMachineStateRoot();
|
|
9309
9979
|
}
|
|
9310
9980
|
shouldEnableLocalTrace() {
|
|
@@ -9806,6 +10476,8 @@ var DaemonCore = class {
|
|
|
9806
10476
|
};
|
|
9807
10477
|
|
|
9808
10478
|
export {
|
|
10479
|
+
DAEMON_API_KEY_ENV,
|
|
10480
|
+
scrubDaemonAuthEnv,
|
|
9809
10481
|
resolveWorkspaceDirectoryPath,
|
|
9810
10482
|
scanWorkspaceDirectories,
|
|
9811
10483
|
deleteWorkspaceDirectory,
|