@slock-ai/daemon 0.55.3 → 0.55.4
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-DLB2KVD7.js → chunk-S5AEBH3V.js} +599 -121
- package/dist/cli/index.js +7 -4
- package/dist/cli/package.json +1 -1
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-M2KQBJR3.js";
|
|
9
9
|
|
|
10
10
|
// src/core.ts
|
|
11
|
-
import
|
|
11
|
+
import path17 from "path";
|
|
12
12
|
import os7 from "os";
|
|
13
13
|
import { createRequire as createRequire2 } from "module";
|
|
14
14
|
import { accessSync } from "fs";
|
|
@@ -995,13 +995,19 @@ var RUNTIMES = [
|
|
|
995
995
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
996
996
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
997
997
|
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
|
|
998
|
-
{ id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
|
|
998
|
+
{ id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true },
|
|
999
|
+
{ id: "pi", displayName: "Pi CLI", binary: "pi", supported: true }
|
|
999
1000
|
];
|
|
1000
1001
|
var RUNTIME_MODELS = {
|
|
1001
1002
|
claude: [
|
|
1002
|
-
{ id: "opus", label: "Opus" },
|
|
1003
|
-
{ id: "
|
|
1004
|
-
{ id: "
|
|
1003
|
+
{ id: "opus", label: "Claude Opus" },
|
|
1004
|
+
{ id: "claude-opus-4-8", label: "Claude Opus 4.8" },
|
|
1005
|
+
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
|
|
1006
|
+
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
|
1007
|
+
{ id: "sonnet", label: "Claude Sonnet" },
|
|
1008
|
+
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
|
1009
|
+
{ id: "haiku", label: "Claude Haiku" },
|
|
1010
|
+
{ id: "claude-haiku-4-5", label: "Claude Haiku 4.5" }
|
|
1005
1011
|
],
|
|
1006
1012
|
codex: [
|
|
1007
1013
|
{ id: "gpt-5.5", label: "GPT-5.5" },
|
|
@@ -1042,6 +1048,11 @@ var RUNTIME_MODELS = {
|
|
|
1042
1048
|
{ id: "openrouter/anthropic/claude-opus-4.5", label: "Claude Opus 4.5 via OpenRouter", verified: "suggestion_only" },
|
|
1043
1049
|
{ id: "fusecode/opus[1m]", label: "Opus 1M via FuseCode", verified: "suggestion_only" }
|
|
1044
1050
|
],
|
|
1051
|
+
pi: [
|
|
1052
|
+
{ id: "default", label: "Configured Default / Auto", verified: "suggestion_only" },
|
|
1053
|
+
{ id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro (Pi)", verified: "suggestion_only" },
|
|
1054
|
+
{ id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash (Pi)", verified: "suggestion_only" }
|
|
1055
|
+
],
|
|
1045
1056
|
// Kimi CLI resolves model keys from each user's local config, so the safest
|
|
1046
1057
|
// built-in option is to defer to whatever default model the CLI already uses.
|
|
1047
1058
|
kimi: [
|
|
@@ -1086,22 +1097,28 @@ function isPresetRuntimeModel(runtime, model) {
|
|
|
1086
1097
|
function modelConfigFromLegacy(runtime, model) {
|
|
1087
1098
|
return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
|
|
1088
1099
|
}
|
|
1089
|
-
function parseProviderConfig(runtime, value) {
|
|
1100
|
+
function parseProviderConfig(runtime, value, legacyApiUrl, legacyApiKey) {
|
|
1090
1101
|
if (runtime !== "claude") return void 0;
|
|
1091
1102
|
if (!isPlainRecord(value)) return { kind: "default" };
|
|
1092
1103
|
if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
|
|
1093
1104
|
return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
|
|
1094
1105
|
}
|
|
1106
|
+
if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
|
|
1107
|
+
return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
|
|
1108
|
+
}
|
|
1095
1109
|
return { kind: "default" };
|
|
1096
1110
|
}
|
|
1097
|
-
function parseModelConfig(runtime, value, fallback) {
|
|
1111
|
+
function parseModelConfig(runtime, value, fallback, provider, legacyCustomModel) {
|
|
1098
1112
|
if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
|
|
1099
1113
|
if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
|
|
1100
1114
|
return { kind: "custom", name: value.name.trim() };
|
|
1101
1115
|
}
|
|
1102
|
-
if (value.kind === "preset" && typeof value.id === "string" && value.id.trim()) {
|
|
1116
|
+
if (value.kind === "preset" && typeof value.id === "string" && value.id.trim() && isPresetRuntimeModel(runtime, value.id.trim())) {
|
|
1103
1117
|
return { kind: "preset", id: value.id.trim() };
|
|
1104
1118
|
}
|
|
1119
|
+
if (provider?.kind === "custom" && runtime === "claude" && legacyCustomModel?.trim()) {
|
|
1120
|
+
return { kind: "custom", name: legacyCustomModel.trim() };
|
|
1121
|
+
}
|
|
1105
1122
|
return modelConfigFromLegacy(runtime, fallback);
|
|
1106
1123
|
}
|
|
1107
1124
|
function parseModeConfig(value) {
|
|
@@ -1117,12 +1134,13 @@ function hydrateRuntimeConfig(input) {
|
|
|
1117
1134
|
const storedEnvVars = normalizeEnvVars(stored?.envVars);
|
|
1118
1135
|
const legacyClaudeApiUrl = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_BASE_URL : void 0;
|
|
1119
1136
|
const legacyClaudeApiKey = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_API_KEY : void 0;
|
|
1120
|
-
const
|
|
1137
|
+
const legacyClaudeCustomModel = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_CUSTOM_MODEL_OPTION : void 0;
|
|
1138
|
+
const provider = stored ? parseProviderConfig(runtime, stored.provider, legacyClaudeApiUrl, legacyClaudeApiKey) : runtime === "claude" && legacyClaudeApiUrl && legacyClaudeApiKey ? { kind: "custom", apiUrl: legacyClaudeApiUrl, apiKey: legacyClaudeApiKey } : runtime === "claude" ? { kind: "default" } : void 0;
|
|
1121
1139
|
return {
|
|
1122
1140
|
version: RUNTIME_CONFIG_VERSION,
|
|
1123
1141
|
runtime,
|
|
1124
1142
|
...provider ? { provider } : {},
|
|
1125
|
-
model: stored ? parseModelConfig(runtime, stored.model, fallbackModel) : modelConfigFromLegacy(runtime, fallbackModel),
|
|
1143
|
+
model: stored ? parseModelConfig(runtime, stored.model, fallbackModel, provider, legacyClaudeCustomModel) : modelConfigFromLegacy(runtime, fallbackModel),
|
|
1126
1144
|
mode: stored ? parseModeConfig(stored.mode) : { kind: "default" },
|
|
1127
1145
|
reasoningEffort: stored?.reasoningEffort ?? input.reasoningEffort ?? null,
|
|
1128
1146
|
envVars: stripControlledRuntimeEnvVars(runtime, stored ? storedEnvVars : legacyEnvVars)
|
|
@@ -1190,10 +1208,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
1190
1208
|
};
|
|
1191
1209
|
|
|
1192
1210
|
// src/agentProcessManager.ts
|
|
1193
|
-
import { mkdirSync as
|
|
1211
|
+
import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
|
|
1194
1212
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
1195
1213
|
import { createHash as createHash3 } from "crypto";
|
|
1196
|
-
import
|
|
1214
|
+
import path13 from "path";
|
|
1197
1215
|
import os5 from "os";
|
|
1198
1216
|
|
|
1199
1217
|
// src/drivers/claude.ts
|
|
@@ -1374,19 +1392,19 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
|
1374
1392
|
|
|
1375
1393
|
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1376
1394
|
|
|
1377
|
-
- **Thread targets** have a colon and short ID suffix: \`#general:
|
|
1395
|
+
- **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
|
|
1378
1396
|
- 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.
|
|
1379
|
-
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=
|
|
1397
|
+
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`slock message send --target "#general:00000000" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
|
|
1380
1398
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1381
|
-
- You can read thread history: \`slock message read --channel "#general:
|
|
1382
|
-
- You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:
|
|
1399
|
+
- You can read thread history: \`slock message read --channel "#general:00000000"\`
|
|
1400
|
+
- You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:00000000"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
|
|
1383
1401
|
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
|
|
1384
1402
|
|
|
1385
1403
|
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1386
1404
|
|
|
1387
|
-
- **Thread targets** have a colon and short ID suffix: \`#general:
|
|
1405
|
+
- **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
|
|
1388
1406
|
- 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.
|
|
1389
|
-
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=
|
|
1407
|
+
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, call \`${t("send_message")}(target="#general:00000000", content="...")\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
|
|
1390
1408
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1391
1409
|
- You can read thread history via ${readCmd} with the same thread target.
|
|
1392
1410
|
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
|
|
@@ -1552,13 +1570,18 @@ ${opts.postStartupNotes.join("\n")}`;
|
|
|
1552
1570
|
Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
|
|
1553
1571
|
|
|
1554
1572
|
\`\`\`
|
|
1555
|
-
[target=#general msg=
|
|
1556
|
-
[target=#general msg=
|
|
1557
|
-
[target=dm:@richard msg=
|
|
1558
|
-
[target=#general:
|
|
1559
|
-
[target=dm:@richard:
|
|
1573
|
+
[target=#general msg=00000000 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
|
|
1574
|
+
[target=#general msg=11111111 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
|
|
1575
|
+
[target=dm:@richard msg=22222222 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
|
|
1576
|
+
[target=#general:00000000 msg=33333333 time=2026-03-15T01:00:03 type=human] @richard: thread reply
|
|
1577
|
+
[target=dm:@richard:22222222 msg=44444444 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
|
|
1560
1578
|
\`\`\`
|
|
1561
1579
|
|
|
1580
|
+
Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
|
|
1581
|
+
and \`22222222\`. They show the shape of a real message ID but are not actual
|
|
1582
|
+
messages. Do not cite them as evidence; use only IDs from messages you actually
|
|
1583
|
+
received or read.
|
|
1584
|
+
|
|
1562
1585
|
Header fields:
|
|
1563
1586
|
- \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
|
|
1564
1587
|
- \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
|
|
@@ -1630,9 +1653,11 @@ Slock auto-renders these inline tokens as interactive links whenever they appear
|
|
|
1630
1653
|
|
|
1631
1654
|
Write them inline as plain words in your sentence \u2014 the same way you'd type any other word \u2014 and Slock turns them into clickable references.
|
|
1632
1655
|
|
|
1656
|
+
Markdown markup expresses presentation semantics; do not mix markup delimiters into literal payloads. Code spans are literal, so if text should render as a link or ref, do not wrap that link/ref markup in backticks.
|
|
1657
|
+
|
|
1633
1658
|
### Formatting \u2014 URLs in non-English text
|
|
1634
1659
|
|
|
1635
|
-
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.),
|
|
1660
|
+
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.) and the URL should render as a link, wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
|
|
1636
1661
|
|
|
1637
1662
|
- **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
|
|
1638
1663
|
- **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
|
|
@@ -1712,12 +1737,12 @@ How to handle these:
|
|
|
1712
1737
|
- Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
|
|
1713
1738
|
- If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
|
|
1714
1739
|
} else {
|
|
1715
|
-
const notifyExample = isCli ? `\`[
|
|
1740
|
+
const notifyExample = isCli ? `\`[Slock inbox notice: You have N pending inbox message(s). Call slock message check to read them when you're ready.]\`` : `\`[Slock inbox notice: You have N pending inbox message(s). Call ${t("check_messages")} to read them when you're ready.]\``;
|
|
1716
1741
|
prompt += `
|
|
1717
1742
|
|
|
1718
1743
|
## Message Notifications
|
|
1719
1744
|
|
|
1720
|
-
While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens,
|
|
1745
|
+
While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, the daemon may write a Slock inbox notice like:
|
|
1721
1746
|
|
|
1722
1747
|
${notifyExample}
|
|
1723
1748
|
|
|
@@ -3081,6 +3106,125 @@ function collectResultErrorDetail(message, fallback) {
|
|
|
3081
3106
|
function isProviderApiFailureText(value, hasToolUse) {
|
|
3082
3107
|
return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b4\d{2}\b/.test(value) || /\b5\d{2}\b/.test(value));
|
|
3083
3108
|
}
|
|
3109
|
+
function finiteNumber(value) {
|
|
3110
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3111
|
+
}
|
|
3112
|
+
function finiteString(value) {
|
|
3113
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3114
|
+
}
|
|
3115
|
+
function withDefined(attrs) {
|
|
3116
|
+
return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
|
|
3117
|
+
}
|
|
3118
|
+
function collectNumericFields(value, fields) {
|
|
3119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
3120
|
+
const attrs = {};
|
|
3121
|
+
for (const [sourceKey, attrKey] of Object.entries(fields)) {
|
|
3122
|
+
const numberValue = finiteNumber(value[sourceKey]);
|
|
3123
|
+
if (numberValue !== void 0) attrs[attrKey] = numberValue;
|
|
3124
|
+
}
|
|
3125
|
+
return attrs;
|
|
3126
|
+
}
|
|
3127
|
+
function collectModelUsageAttrs(value) {
|
|
3128
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
3129
|
+
const sanitized = {};
|
|
3130
|
+
const aggregate = {};
|
|
3131
|
+
const knownNumericFields = [
|
|
3132
|
+
"inputTokens",
|
|
3133
|
+
"outputTokens",
|
|
3134
|
+
"cacheReadInputTokens",
|
|
3135
|
+
"cacheCreationInputTokens",
|
|
3136
|
+
"webSearchRequests",
|
|
3137
|
+
"costUSD",
|
|
3138
|
+
"contextWindow",
|
|
3139
|
+
"maxOutputTokens"
|
|
3140
|
+
];
|
|
3141
|
+
for (const [modelName, rawUsage] of Object.entries(value)) {
|
|
3142
|
+
if (!rawUsage || typeof rawUsage !== "object" || Array.isArray(rawUsage)) continue;
|
|
3143
|
+
const modelUsage = {};
|
|
3144
|
+
for (const field of knownNumericFields) {
|
|
3145
|
+
const numberValue = finiteNumber(rawUsage[field]);
|
|
3146
|
+
if (numberValue === void 0) continue;
|
|
3147
|
+
modelUsage[field] = numberValue;
|
|
3148
|
+
const aggregateKey = field === "costUSD" ? "costUsd" : field;
|
|
3149
|
+
if (field === "contextWindow" || field === "maxOutputTokens") {
|
|
3150
|
+
aggregate[aggregateKey] = Math.max(aggregate[aggregateKey] ?? 0, numberValue);
|
|
3151
|
+
} else {
|
|
3152
|
+
aggregate[aggregateKey] = (aggregate[aggregateKey] ?? 0) + numberValue;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
if (Object.keys(modelUsage).length > 0) sanitized[modelName] = modelUsage;
|
|
3156
|
+
}
|
|
3157
|
+
const modelNames = Object.keys(sanitized).sort();
|
|
3158
|
+
if (modelNames.length === 0) return {};
|
|
3159
|
+
const orderedSanitized = Object.fromEntries(modelNames.map((modelName) => [modelName, sanitized[modelName]]));
|
|
3160
|
+
return withDefined({
|
|
3161
|
+
modelUsageModelCount: modelNames.length,
|
|
3162
|
+
modelUsageModels: modelNames.join(","),
|
|
3163
|
+
modelUsageJson: JSON.stringify(orderedSanitized),
|
|
3164
|
+
modelUsageInputTokens: aggregate.inputTokens,
|
|
3165
|
+
modelUsageOutputTokens: aggregate.outputTokens,
|
|
3166
|
+
modelUsageCachedInputTokens: aggregate.cacheReadInputTokens,
|
|
3167
|
+
modelUsageCacheCreationInputTokens: aggregate.cacheCreationInputTokens,
|
|
3168
|
+
modelUsageWebSearchRequests: aggregate.webSearchRequests,
|
|
3169
|
+
modelUsageCostUsd: aggregate.costUsd,
|
|
3170
|
+
modelUsageMaxContextWindow: aggregate.contextWindow,
|
|
3171
|
+
modelUsageMaxOutputTokens: aggregate.maxOutputTokens
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
function parseClaudeResultUsageTelemetry(event) {
|
|
3175
|
+
const usage = event.usage && typeof event.usage === "object" ? event.usage : void 0;
|
|
3176
|
+
const totalCostUsd = finiteNumber(event.total_cost_usd);
|
|
3177
|
+
const modelUsageAttrs = collectModelUsageAttrs(event.modelUsage);
|
|
3178
|
+
const hasTelemetrySource = usage !== void 0 || totalCostUsd !== void 0 || Object.keys(modelUsageAttrs).length > 0;
|
|
3179
|
+
if (!hasTelemetrySource) return null;
|
|
3180
|
+
const inputTokens = finiteNumber(usage?.input_tokens);
|
|
3181
|
+
const outputTokens = finiteNumber(usage?.output_tokens);
|
|
3182
|
+
const cachedInputTokens = finiteNumber(usage?.cache_read_input_tokens);
|
|
3183
|
+
const cacheCreationInputTokens = finiteNumber(usage?.cache_creation_input_tokens);
|
|
3184
|
+
const attrs = {
|
|
3185
|
+
...withDefined({
|
|
3186
|
+
totalCostUsd,
|
|
3187
|
+
durationMs: finiteNumber(event.duration_ms),
|
|
3188
|
+
durationApiMs: finiteNumber(event.duration_api_ms),
|
|
3189
|
+
numTurns: finiteNumber(event.num_turns),
|
|
3190
|
+
resultSubtype: finiteString(event.subtype),
|
|
3191
|
+
stopReason: finiteString(event.stop_reason),
|
|
3192
|
+
resultIsError: typeof event.is_error === "boolean" ? event.is_error : void 0,
|
|
3193
|
+
fastModeState: finiteString(event.fast_mode_state),
|
|
3194
|
+
permissionDenialsCount: Array.isArray(event.permission_denials) ? event.permission_denials.length : void 0,
|
|
3195
|
+
serviceTier: finiteString(usage?.service_tier),
|
|
3196
|
+
inferenceGeo: finiteString(usage?.inference_geo),
|
|
3197
|
+
usageSpeed: finiteString(usage?.speed),
|
|
3198
|
+
usageIterationsCount: Array.isArray(usage?.iterations) ? usage.iterations.length : void 0
|
|
3199
|
+
}),
|
|
3200
|
+
...collectNumericFields(usage?.server_tool_use, {
|
|
3201
|
+
web_search_requests: "serverToolUseWebSearchRequests",
|
|
3202
|
+
web_fetch_requests: "serverToolUseWebFetchRequests"
|
|
3203
|
+
}),
|
|
3204
|
+
...collectNumericFields(usage?.cache_creation, {
|
|
3205
|
+
ephemeral_1h_input_tokens: "cacheCreationEphemeral1hInputTokens",
|
|
3206
|
+
ephemeral_5m_input_tokens: "cacheCreationEphemeral5mInputTokens"
|
|
3207
|
+
}),
|
|
3208
|
+
...modelUsageAttrs
|
|
3209
|
+
};
|
|
3210
|
+
if (inputTokens !== void 0) attrs.inputTokens = inputTokens;
|
|
3211
|
+
if (outputTokens !== void 0) attrs.outputTokens = outputTokens;
|
|
3212
|
+
if (cachedInputTokens !== void 0) attrs.cachedInputTokens = cachedInputTokens;
|
|
3213
|
+
if (cacheCreationInputTokens !== void 0) attrs.cacheCreationInputTokens = cacheCreationInputTokens;
|
|
3214
|
+
const tokenComponents = [inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens];
|
|
3215
|
+
const totalTokens = tokenComponents.reduce((sum, value) => sum + (value ?? 0), 0);
|
|
3216
|
+
if (tokenComponents.some((value) => value !== void 0)) attrs.totalTokens = totalTokens;
|
|
3217
|
+
if (Object.keys(attrs).length === 0) return null;
|
|
3218
|
+
return {
|
|
3219
|
+
kind: "telemetry",
|
|
3220
|
+
name: "token_usage",
|
|
3221
|
+
source: "claude_result_usage",
|
|
3222
|
+
usageKind: "per_turn",
|
|
3223
|
+
...typeof event.session_id === "string" && event.session_id ? { sessionId: event.session_id } : {},
|
|
3224
|
+
...typeof event.uuid === "string" && event.uuid ? { runtimeResultId: event.uuid } : {},
|
|
3225
|
+
attrs
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3084
3228
|
var ClaudeEventNormalizer = class {
|
|
3085
3229
|
normalizeLine(line) {
|
|
3086
3230
|
let event;
|
|
@@ -3155,6 +3299,7 @@ var ClaudeEventNormalizer = class {
|
|
|
3155
3299
|
case "result": {
|
|
3156
3300
|
const subtype = typeof event.subtype === "string" ? event.subtype : "success";
|
|
3157
3301
|
const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
|
|
3302
|
+
const usageTelemetry = parseClaudeResultUsageTelemetry(event);
|
|
3158
3303
|
switch (subtype) {
|
|
3159
3304
|
case "success":
|
|
3160
3305
|
if (event.is_error && stopReason !== "max_tokens") {
|
|
@@ -3176,6 +3321,7 @@ var ClaudeEventNormalizer = class {
|
|
|
3176
3321
|
pushResultError(event, "Structured output retries exceeded");
|
|
3177
3322
|
break;
|
|
3178
3323
|
}
|
|
3324
|
+
if (usageTelemetry) events.push(usageTelemetry);
|
|
3179
3325
|
events.push({ kind: "turn_end", sessionId: event.session_id });
|
|
3180
3326
|
break;
|
|
3181
3327
|
}
|
|
@@ -3441,7 +3587,7 @@ function buildClaudeArgs(config, opts) {
|
|
|
3441
3587
|
args.push("--effort", launchRuntimeFields.reasoningEffort);
|
|
3442
3588
|
}
|
|
3443
3589
|
if (launchRuntimeFields.mode.kind === "fast") {
|
|
3444
|
-
args.push("--
|
|
3590
|
+
args.push("--settings", JSON.stringify({ fastMode: true }));
|
|
3445
3591
|
}
|
|
3446
3592
|
if (config.sessionId) {
|
|
3447
3593
|
args.push("--resume", config.sessionId);
|
|
@@ -3599,53 +3745,64 @@ var RuntimeTurnState = class {
|
|
|
3599
3745
|
};
|
|
3600
3746
|
|
|
3601
3747
|
// src/drivers/codexTelemetrySidecar.ts
|
|
3602
|
-
function
|
|
3748
|
+
function finiteNumber2(value) {
|
|
3603
3749
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3604
3750
|
}
|
|
3605
|
-
function
|
|
3751
|
+
function finiteString2(value) {
|
|
3606
3752
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3607
3753
|
}
|
|
3608
3754
|
function ratio(numerator, denominator) {
|
|
3609
3755
|
if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
|
|
3610
3756
|
return Number((numerator / denominator).toFixed(6));
|
|
3611
3757
|
}
|
|
3612
|
-
function
|
|
3758
|
+
function withDefined2(attrs) {
|
|
3613
3759
|
return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
|
|
3614
3760
|
}
|
|
3615
3761
|
function parseTokenUsageTelemetry(message) {
|
|
3616
3762
|
const usage = message.params?.tokenUsage;
|
|
3617
3763
|
const total = usage?.total;
|
|
3618
3764
|
if (!total || typeof total !== "object") return null;
|
|
3619
|
-
const inputTokens =
|
|
3620
|
-
const cachedInputTokens =
|
|
3621
|
-
const totalTokens =
|
|
3622
|
-
const modelContextWindow =
|
|
3623
|
-
const attrs =
|
|
3765
|
+
const inputTokens = finiteNumber2(total.inputTokens);
|
|
3766
|
+
const cachedInputTokens = finiteNumber2(total.cachedInputTokens);
|
|
3767
|
+
const totalTokens = finiteNumber2(total.totalTokens);
|
|
3768
|
+
const modelContextWindow = finiteNumber2(usage.modelContextWindow);
|
|
3769
|
+
const attrs = withDefined2({
|
|
3624
3770
|
totalTokens,
|
|
3625
3771
|
inputTokens,
|
|
3626
3772
|
cachedInputTokens,
|
|
3627
|
-
outputTokens:
|
|
3628
|
-
reasoningOutputTokens:
|
|
3773
|
+
outputTokens: finiteNumber2(total.outputTokens),
|
|
3774
|
+
reasoningOutputTokens: finiteNumber2(total.reasoningOutputTokens),
|
|
3629
3775
|
modelContextWindow,
|
|
3630
3776
|
cachedInputRatio: ratio(cachedInputTokens, inputTokens),
|
|
3631
3777
|
contextUtilization: ratio(totalTokens, modelContextWindow)
|
|
3632
3778
|
});
|
|
3633
3779
|
if (Object.keys(attrs).length === 0) return null;
|
|
3634
|
-
return {
|
|
3780
|
+
return {
|
|
3781
|
+
kind: "telemetry",
|
|
3782
|
+
name: "token_usage",
|
|
3783
|
+
source: "codex_thread_token_usage_updated",
|
|
3784
|
+
usageKind: "cumulative_session",
|
|
3785
|
+
attrs
|
|
3786
|
+
};
|
|
3635
3787
|
}
|
|
3636
3788
|
function parseRateLimitTelemetry(message) {
|
|
3637
3789
|
const rateLimits = message.params?.rateLimits;
|
|
3638
3790
|
const primary = rateLimits?.primary;
|
|
3639
3791
|
if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
|
|
3640
|
-
const attrs =
|
|
3641
|
-
limitId:
|
|
3642
|
-
planType:
|
|
3643
|
-
usedPercent:
|
|
3644
|
-
windowDurationMins:
|
|
3645
|
-
resetsAt:
|
|
3792
|
+
const attrs = withDefined2({
|
|
3793
|
+
limitId: finiteString2(rateLimits.limitId),
|
|
3794
|
+
planType: finiteString2(rateLimits.planType),
|
|
3795
|
+
usedPercent: finiteNumber2(primary.usedPercent),
|
|
3796
|
+
windowDurationMins: finiteNumber2(primary.windowDurationMins),
|
|
3797
|
+
resetsAt: finiteNumber2(primary.resetsAt)
|
|
3646
3798
|
});
|
|
3647
3799
|
if (Object.keys(attrs).length === 0) return null;
|
|
3648
|
-
return {
|
|
3800
|
+
return {
|
|
3801
|
+
kind: "telemetry",
|
|
3802
|
+
name: "rate_limits",
|
|
3803
|
+
source: "codex_account_rate_limits_updated",
|
|
3804
|
+
attrs
|
|
3805
|
+
};
|
|
3649
3806
|
}
|
|
3650
3807
|
function parseCodexTelemetryEvent(message) {
|
|
3651
3808
|
switch (message.method) {
|
|
@@ -3752,7 +3909,11 @@ var CodexEventNormalizer = class {
|
|
|
3752
3909
|
}
|
|
3753
3910
|
const telemetry = parseCodexTelemetryEvent(message);
|
|
3754
3911
|
if (telemetry) {
|
|
3755
|
-
events.push(
|
|
3912
|
+
events.push({
|
|
3913
|
+
...telemetry,
|
|
3914
|
+
...this.currentThreadId ? { sessionId: this.currentThreadId } : {},
|
|
3915
|
+
...this.turnState.activeTurnId ? { turnId: this.turnState.activeTurnId } : {}
|
|
3916
|
+
});
|
|
3756
3917
|
return { events };
|
|
3757
3918
|
}
|
|
3758
3919
|
const rawProgress = rawResponseItemProgressEvent(message);
|
|
@@ -5794,6 +5955,277 @@ var OpenCodeDriver = class {
|
|
|
5794
5955
|
}
|
|
5795
5956
|
};
|
|
5796
5957
|
|
|
5958
|
+
// src/drivers/pi.ts
|
|
5959
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5960
|
+
import { spawn as spawn9, spawnSync as spawnSync3 } from "child_process";
|
|
5961
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
5962
|
+
import path11 from "path";
|
|
5963
|
+
var PI_SESSION_DIR = ".pi-sessions";
|
|
5964
|
+
var PI_PROVIDER_LABELS = {
|
|
5965
|
+
deepseek: "DeepSeek",
|
|
5966
|
+
google: "Google",
|
|
5967
|
+
openai: "OpenAI",
|
|
5968
|
+
openrouter: "OpenRouter"
|
|
5969
|
+
};
|
|
5970
|
+
function buildPiSessionDir(workingDirectory) {
|
|
5971
|
+
return path11.join(workingDirectory, PI_SESSION_DIR);
|
|
5972
|
+
}
|
|
5973
|
+
function buildPiRpcArgs(ctx, sessionId) {
|
|
5974
|
+
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
5975
|
+
const args = [
|
|
5976
|
+
"--mode",
|
|
5977
|
+
"rpc",
|
|
5978
|
+
"--session-dir",
|
|
5979
|
+
buildPiSessionDir(ctx.workingDirectory),
|
|
5980
|
+
"--system-prompt",
|
|
5981
|
+
ctx.standingPrompt
|
|
5982
|
+
];
|
|
5983
|
+
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
5984
|
+
args.push("--model", launchRuntimeFields.model);
|
|
5985
|
+
}
|
|
5986
|
+
if (launchRuntimeFields.reasoningEffort) {
|
|
5987
|
+
args.push("--thinking", launchRuntimeFields.reasoningEffort);
|
|
5988
|
+
}
|
|
5989
|
+
if (sessionId) {
|
|
5990
|
+
args.push("--session-id", sessionId);
|
|
5991
|
+
}
|
|
5992
|
+
return args;
|
|
5993
|
+
}
|
|
5994
|
+
async function buildPiSpawnEnv(ctx) {
|
|
5995
|
+
return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
5996
|
+
}
|
|
5997
|
+
function parsePiModelsOutput(output) {
|
|
5998
|
+
const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
5999
|
+
const models = [];
|
|
6000
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6001
|
+
for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
|
|
6002
|
+
const line = rawLine.trim();
|
|
6003
|
+
if (!line || /^provider\s+model\s+/i.test(line) || /^[-\s]+$/.test(line)) continue;
|
|
6004
|
+
const columns = line.split(/\s+/);
|
|
6005
|
+
const provider = columns[0];
|
|
6006
|
+
const model = columns[1];
|
|
6007
|
+
if (!provider || !model || provider.startsWith("-") || model.startsWith("-")) continue;
|
|
6008
|
+
if (/^(yes|no)$/i.test(model)) continue;
|
|
6009
|
+
const id = `${provider}/${model}`;
|
|
6010
|
+
if (seen.has(id)) continue;
|
|
6011
|
+
seen.add(id);
|
|
6012
|
+
models.push({
|
|
6013
|
+
id,
|
|
6014
|
+
label: `${humanizePiSegment(model)} \xB7 ${PI_PROVIDER_LABELS[provider] || humanizePiSegment(provider)}`,
|
|
6015
|
+
verified: "launchable"
|
|
6016
|
+
});
|
|
6017
|
+
}
|
|
6018
|
+
return models.length > 0 ? { models } : null;
|
|
6019
|
+
}
|
|
6020
|
+
function detectPiModels(runCommand = runPiModelsCommand) {
|
|
6021
|
+
const result = runCommand();
|
|
6022
|
+
if (result.error || result.status !== 0) return null;
|
|
6023
|
+
return parsePiModelsOutput(result.stdout);
|
|
6024
|
+
}
|
|
6025
|
+
function runPiModelsCommand() {
|
|
6026
|
+
const result = spawnSync3("pi", ["--list-models"], {
|
|
6027
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
6028
|
+
encoding: "utf8",
|
|
6029
|
+
timeout: 5e3
|
|
6030
|
+
});
|
|
6031
|
+
return {
|
|
6032
|
+
status: result.status,
|
|
6033
|
+
stdout: String(result.stdout || ""),
|
|
6034
|
+
error: result.error
|
|
6035
|
+
};
|
|
6036
|
+
}
|
|
6037
|
+
function humanizePiSegment(value) {
|
|
6038
|
+
return value.split(/[-_/]/).filter(Boolean).map(formatPiLabelToken).join(" ");
|
|
6039
|
+
}
|
|
6040
|
+
function formatPiLabelToken(token) {
|
|
6041
|
+
const normalized = token.toLowerCase();
|
|
6042
|
+
const specialCases = {
|
|
6043
|
+
ai: "AI",
|
|
6044
|
+
api: "API",
|
|
6045
|
+
deepseek: "DeepSeek",
|
|
6046
|
+
flash: "Flash",
|
|
6047
|
+
gpt: "GPT",
|
|
6048
|
+
pro: "Pro",
|
|
6049
|
+
v4: "V4"
|
|
6050
|
+
};
|
|
6051
|
+
if (specialCases[normalized]) return specialCases[normalized];
|
|
6052
|
+
if (/^v\d+(\.\d+)?$/.test(normalized)) return normalized.toUpperCase();
|
|
6053
|
+
if (/^\d+[bk]$/i.test(token)) return token.toUpperCase();
|
|
6054
|
+
if (/^\d/.test(token)) return token;
|
|
6055
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
6056
|
+
}
|
|
6057
|
+
function piErrorMessage(error) {
|
|
6058
|
+
if (typeof error === "string" && error.trim()) return error.trim();
|
|
6059
|
+
if (error && typeof error === "object") {
|
|
6060
|
+
const record = error;
|
|
6061
|
+
if (typeof record.message === "string" && record.message.trim()) return record.message.trim();
|
|
6062
|
+
try {
|
|
6063
|
+
return JSON.stringify(error);
|
|
6064
|
+
} catch {
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
return "Unknown Pi error";
|
|
6068
|
+
}
|
|
6069
|
+
var PiDriver = class {
|
|
6070
|
+
id = "pi";
|
|
6071
|
+
supportsNativeStandingPrompt = true;
|
|
6072
|
+
lifecycle = {
|
|
6073
|
+
kind: "persistent",
|
|
6074
|
+
stdin: "direct",
|
|
6075
|
+
inFlightWake: "steer"
|
|
6076
|
+
};
|
|
6077
|
+
communication = {
|
|
6078
|
+
chat: "slock_cli",
|
|
6079
|
+
runtimeControl: "none"
|
|
6080
|
+
};
|
|
6081
|
+
session = {
|
|
6082
|
+
recovery: "resume_or_fresh"
|
|
6083
|
+
};
|
|
6084
|
+
model = {
|
|
6085
|
+
detectedModelsVerifiedAs: "launchable",
|
|
6086
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
6087
|
+
};
|
|
6088
|
+
supportsStdinNotification = true;
|
|
6089
|
+
mcpToolPrefix = "";
|
|
6090
|
+
busyDeliveryMode = "direct";
|
|
6091
|
+
usesSlockCliForCommunication = true;
|
|
6092
|
+
sessionId = null;
|
|
6093
|
+
sessionAnnounced = false;
|
|
6094
|
+
sawTextDelta = false;
|
|
6095
|
+
requestId = 0;
|
|
6096
|
+
process = null;
|
|
6097
|
+
probe() {
|
|
6098
|
+
const command = resolveCommandOnPath("pi");
|
|
6099
|
+
if (!command) return { available: false };
|
|
6100
|
+
return {
|
|
6101
|
+
available: true,
|
|
6102
|
+
version: readCommandVersion(command) ?? void 0
|
|
6103
|
+
};
|
|
6104
|
+
}
|
|
6105
|
+
async detectModels() {
|
|
6106
|
+
return detectPiModels();
|
|
6107
|
+
}
|
|
6108
|
+
async spawn(ctx) {
|
|
6109
|
+
this.sessionId = ctx.config.sessionId || randomUUID3();
|
|
6110
|
+
this.sessionAnnounced = false;
|
|
6111
|
+
this.sawTextDelta = false;
|
|
6112
|
+
this.requestId = 0;
|
|
6113
|
+
mkdirSync4(buildPiSessionDir(ctx.workingDirectory), { recursive: true });
|
|
6114
|
+
const proc = spawn9(resolveCommandOnPath("pi") ?? "pi", buildPiRpcArgs(ctx, this.sessionId), {
|
|
6115
|
+
cwd: ctx.workingDirectory,
|
|
6116
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
6117
|
+
env: await buildPiSpawnEnv(ctx),
|
|
6118
|
+
shell: process.platform === "win32"
|
|
6119
|
+
});
|
|
6120
|
+
this.process = proc;
|
|
6121
|
+
this.sendRpcCommand("prompt", { message: ctx.prompt });
|
|
6122
|
+
return { process: proc };
|
|
6123
|
+
}
|
|
6124
|
+
parseLine(line) {
|
|
6125
|
+
let event;
|
|
6126
|
+
try {
|
|
6127
|
+
event = JSON.parse(line);
|
|
6128
|
+
} catch {
|
|
6129
|
+
return [];
|
|
6130
|
+
}
|
|
6131
|
+
const events = [];
|
|
6132
|
+
if (event.type === "session" && event.id) {
|
|
6133
|
+
this.sessionId = event.id;
|
|
6134
|
+
if (!this.sessionAnnounced) {
|
|
6135
|
+
this.sessionAnnounced = true;
|
|
6136
|
+
events.push({ kind: "session_init", sessionId: event.id });
|
|
6137
|
+
}
|
|
6138
|
+
return events;
|
|
6139
|
+
}
|
|
6140
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
6141
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
6142
|
+
this.sessionAnnounced = true;
|
|
6143
|
+
}
|
|
6144
|
+
if (event.type === "response") {
|
|
6145
|
+
if (event.data?.sessionId && event.data.sessionId !== this.sessionId) {
|
|
6146
|
+
this.sessionId = event.data.sessionId;
|
|
6147
|
+
}
|
|
6148
|
+
if (event.success === false) {
|
|
6149
|
+
events.push({ kind: "error", message: piErrorMessage(event.error) });
|
|
6150
|
+
}
|
|
6151
|
+
return events;
|
|
6152
|
+
}
|
|
6153
|
+
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
6154
|
+
this.sawTextDelta = false;
|
|
6155
|
+
return events;
|
|
6156
|
+
}
|
|
6157
|
+
const assistantEvent = event.assistantMessageEvent;
|
|
6158
|
+
if (event.type === "message_update" && assistantEvent) {
|
|
6159
|
+
switch (assistantEvent.type) {
|
|
6160
|
+
case "thinking_delta":
|
|
6161
|
+
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
6162
|
+
events.push({ kind: "thinking", text: assistantEvent.delta });
|
|
6163
|
+
}
|
|
6164
|
+
break;
|
|
6165
|
+
case "text_delta":
|
|
6166
|
+
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
6167
|
+
this.sawTextDelta = true;
|
|
6168
|
+
events.push({ kind: "text", text: assistantEvent.delta });
|
|
6169
|
+
}
|
|
6170
|
+
break;
|
|
6171
|
+
case "thinking_start":
|
|
6172
|
+
case "text_start":
|
|
6173
|
+
break;
|
|
6174
|
+
case "tool_use":
|
|
6175
|
+
case "tool_call":
|
|
6176
|
+
case "tool_start":
|
|
6177
|
+
events.push({
|
|
6178
|
+
kind: "tool_call",
|
|
6179
|
+
name: assistantEvent.name || assistantEvent.toolName || "unknown_tool",
|
|
6180
|
+
input: assistantEvent.input ?? assistantEvent.parameters ?? {}
|
|
6181
|
+
});
|
|
6182
|
+
break;
|
|
6183
|
+
case "text_end":
|
|
6184
|
+
if (!this.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0) {
|
|
6185
|
+
events.push({ kind: "text", text: assistantEvent.content });
|
|
6186
|
+
}
|
|
6187
|
+
break;
|
|
6188
|
+
}
|
|
6189
|
+
return events;
|
|
6190
|
+
}
|
|
6191
|
+
if (event.type === "agent_end") {
|
|
6192
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
6193
|
+
} else if (event.type === "error") {
|
|
6194
|
+
events.push({ kind: "error", message: piErrorMessage(event.error ?? event.message) });
|
|
6195
|
+
}
|
|
6196
|
+
return events;
|
|
6197
|
+
}
|
|
6198
|
+
encodeStdinMessage(text, _sessionId, opts) {
|
|
6199
|
+
return JSON.stringify({
|
|
6200
|
+
id: this.nextRequestId(),
|
|
6201
|
+
type: opts?.mode === "idle" ? "prompt" : "steer",
|
|
6202
|
+
message: text
|
|
6203
|
+
});
|
|
6204
|
+
}
|
|
6205
|
+
buildSystemPrompt(config, _agentId) {
|
|
6206
|
+
return buildCliTransportSystemPrompt(config, {
|
|
6207
|
+
toolPrefix: "",
|
|
6208
|
+
extraCriticalRules: [],
|
|
6209
|
+
postStartupNotes: [
|
|
6210
|
+
"**Pi runtime note:** Slock keeps Pi running in RPC mode. While you are working, Slock may send inbox-count notifications into the current turn; call `slock message check` at natural breakpoints."
|
|
6211
|
+
],
|
|
6212
|
+
includeStdinNotificationSection: true,
|
|
6213
|
+
messageNotificationStyle: "direct"
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6216
|
+
nextRequestId() {
|
|
6217
|
+
this.requestId += 1;
|
|
6218
|
+
return String(this.requestId);
|
|
6219
|
+
}
|
|
6220
|
+
sendRpcCommand(type, params) {
|
|
6221
|
+
this.process?.stdin?.write(JSON.stringify({
|
|
6222
|
+
id: this.nextRequestId(),
|
|
6223
|
+
type,
|
|
6224
|
+
...params
|
|
6225
|
+
}) + "\n");
|
|
6226
|
+
}
|
|
6227
|
+
};
|
|
6228
|
+
|
|
5797
6229
|
// src/drivers/index.ts
|
|
5798
6230
|
var driverFactories = {
|
|
5799
6231
|
claude: () => new ClaudeDriver(),
|
|
@@ -5803,7 +6235,8 @@ var driverFactories = {
|
|
|
5803
6235
|
cursor: () => new CursorDriver(),
|
|
5804
6236
|
gemini: () => new GeminiDriver(),
|
|
5805
6237
|
kimi: () => new KimiDriver(),
|
|
5806
|
-
opencode: () => new OpenCodeDriver()
|
|
6238
|
+
opencode: () => new OpenCodeDriver(),
|
|
6239
|
+
pi: () => new PiDriver()
|
|
5807
6240
|
};
|
|
5808
6241
|
function getDriver(runtimeId) {
|
|
5809
6242
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -5816,7 +6249,7 @@ function getDriver(runtimeId) {
|
|
|
5816
6249
|
|
|
5817
6250
|
// src/workspaces.ts
|
|
5818
6251
|
import { readdir, rm, stat } from "fs/promises";
|
|
5819
|
-
import
|
|
6252
|
+
import path12 from "path";
|
|
5820
6253
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
5821
6254
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
5822
6255
|
}
|
|
@@ -5824,7 +6257,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
5824
6257
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
5825
6258
|
return null;
|
|
5826
6259
|
}
|
|
5827
|
-
return
|
|
6260
|
+
return path12.join(dataDir, directoryName);
|
|
5828
6261
|
}
|
|
5829
6262
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
5830
6263
|
return {
|
|
@@ -5873,7 +6306,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
5873
6306
|
return summary;
|
|
5874
6307
|
}
|
|
5875
6308
|
const childSummaries = await Promise.all(
|
|
5876
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
6309
|
+
entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
|
|
5877
6310
|
);
|
|
5878
6311
|
for (const childSummary of childSummaries) {
|
|
5879
6312
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -5892,7 +6325,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
5892
6325
|
if (!entry.isDirectory()) {
|
|
5893
6326
|
return null;
|
|
5894
6327
|
}
|
|
5895
|
-
const dirPath =
|
|
6328
|
+
const dirPath = path12.join(dataDir, entry.name);
|
|
5896
6329
|
try {
|
|
5897
6330
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
5898
6331
|
return {
|
|
@@ -6068,6 +6501,8 @@ function runtimeDisplayName(runtimeId) {
|
|
|
6068
6501
|
return "Kimi CLI";
|
|
6069
6502
|
case "opencode":
|
|
6070
6503
|
return "OpenCode";
|
|
6504
|
+
case "pi":
|
|
6505
|
+
return "Pi CLI";
|
|
6071
6506
|
default:
|
|
6072
6507
|
return runtimeId || "This runtime";
|
|
6073
6508
|
}
|
|
@@ -6332,12 +6767,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
6332
6767
|
for (const entry of entries) {
|
|
6333
6768
|
if (++visited > maxEntries) return null;
|
|
6334
6769
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
6335
|
-
return
|
|
6770
|
+
return path13.join(dir, entry.name);
|
|
6336
6771
|
}
|
|
6337
6772
|
for (const entry of entries) {
|
|
6338
6773
|
if (++visited > maxEntries) return null;
|
|
6339
6774
|
if (!entry.isDirectory()) continue;
|
|
6340
|
-
const found = visit(
|
|
6775
|
+
const found = visit(path13.join(dir, entry.name), depth - 1);
|
|
6341
6776
|
if (found) return found;
|
|
6342
6777
|
}
|
|
6343
6778
|
return null;
|
|
@@ -6350,9 +6785,9 @@ function safeSessionFilename(value) {
|
|
|
6350
6785
|
}
|
|
6351
6786
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
6352
6787
|
try {
|
|
6353
|
-
const dir =
|
|
6354
|
-
|
|
6355
|
-
const filePath =
|
|
6788
|
+
const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
|
|
6789
|
+
mkdirSync5(dir, { recursive: true });
|
|
6790
|
+
const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
6356
6791
|
writeFileSync7(filePath, JSON.stringify({
|
|
6357
6792
|
type: "runtime_session_handoff",
|
|
6358
6793
|
runtime,
|
|
@@ -6372,7 +6807,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
6372
6807
|
}
|
|
6373
6808
|
}
|
|
6374
6809
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
6375
|
-
const directPath =
|
|
6810
|
+
const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
|
|
6376
6811
|
if (directPath) {
|
|
6377
6812
|
try {
|
|
6378
6813
|
if (statSync(directPath).isFile()) {
|
|
@@ -6381,7 +6816,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
|
|
|
6381
6816
|
} catch {
|
|
6382
6817
|
}
|
|
6383
6818
|
}
|
|
6384
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
6819
|
+
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;
|
|
6385
6820
|
if (!resolvedPath && fallbackDir) {
|
|
6386
6821
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
6387
6822
|
if (fallback) return fallback;
|
|
@@ -6470,7 +6905,7 @@ function dynamicClaimInstruction(driver) {
|
|
|
6470
6905
|
}
|
|
6471
6906
|
function formatIncomingMessage(message, driver) {
|
|
6472
6907
|
const threadJoinPrefix = message.thread_join_context ? [
|
|
6473
|
-
`[
|
|
6908
|
+
`[Slock thread context: you were added to a new thread via @mention.]`,
|
|
6474
6909
|
`parent: ${message.thread_join_context.parent_target}`,
|
|
6475
6910
|
`thread: ${message.thread_join_context.thread_target}`,
|
|
6476
6911
|
`suggested next step: ${driver?.usesSlockCliForCommunication ? `slock message read --channel "${message.thread_join_context.suggested_read_history_target}"` : `${communicationCommand(driver, "read_history")}(channel="${message.thread_join_context.suggested_read_history_target}")`}`,
|
|
@@ -7296,7 +7731,7 @@ function getBusyDeliveryNote(driver) {
|
|
|
7296
7731
|
if (driver.busyDeliveryMode === "gated") {
|
|
7297
7732
|
return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn at runtime-observed safe boundaries. Call check_messages to read the pending messages before context-sensitive side effects.";
|
|
7298
7733
|
}
|
|
7299
|
-
return "\n\nNote: While you are busy, you may receive [
|
|
7734
|
+
return "\n\nNote: While you are busy, you may receive [Slock inbox notice: ...] messages. Finish your current step, then call check_messages to check for messages.";
|
|
7300
7735
|
}
|
|
7301
7736
|
var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
|
|
7302
7737
|
// Claude Code 2.1.114 treats "follow your system prompt" style user turns as
|
|
@@ -7805,7 +8240,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
7805
8240
|
);
|
|
7806
8241
|
wakeMessage = void 0;
|
|
7807
8242
|
}
|
|
7808
|
-
const agentDataDir =
|
|
8243
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
7809
8244
|
await mkdir(agentDataDir, { recursive: true });
|
|
7810
8245
|
let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
7811
8246
|
const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
|
|
@@ -7819,23 +8254,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
7819
8254
|
);
|
|
7820
8255
|
runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
|
|
7821
8256
|
}
|
|
7822
|
-
const memoryMdPath =
|
|
8257
|
+
const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
|
|
7823
8258
|
try {
|
|
7824
8259
|
await access(memoryMdPath);
|
|
7825
8260
|
} catch {
|
|
7826
8261
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
7827
8262
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
7828
8263
|
}
|
|
7829
|
-
const notesDir =
|
|
8264
|
+
const notesDir = path13.join(agentDataDir, "notes");
|
|
7830
8265
|
await mkdir(notesDir, { recursive: true });
|
|
7831
8266
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
7832
8267
|
const seedFiles = buildOnboardingSeedFiles();
|
|
7833
8268
|
for (const { relativePath, content } of seedFiles) {
|
|
7834
|
-
const fullPath =
|
|
8269
|
+
const fullPath = path13.join(agentDataDir, relativePath);
|
|
7835
8270
|
try {
|
|
7836
8271
|
await access(fullPath);
|
|
7837
8272
|
} catch {
|
|
7838
|
-
await mkdir(
|
|
8273
|
+
await mkdir(path13.dirname(fullPath), { recursive: true });
|
|
7839
8274
|
await writeFile(fullPath, content);
|
|
7840
8275
|
}
|
|
7841
8276
|
}
|
|
@@ -8707,7 +9142,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8707
9142
|
return true;
|
|
8708
9143
|
}
|
|
8709
9144
|
async resetWorkspace(agentId) {
|
|
8710
|
-
const agentDataDir =
|
|
9145
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
8711
9146
|
try {
|
|
8712
9147
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
8713
9148
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -8768,7 +9203,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8768
9203
|
return result;
|
|
8769
9204
|
}
|
|
8770
9205
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
8771
|
-
const workspacePath =
|
|
9206
|
+
const workspacePath = path13.join(this.dataDir, agentId);
|
|
8772
9207
|
return {
|
|
8773
9208
|
agentId,
|
|
8774
9209
|
launchId,
|
|
@@ -9025,7 +9460,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9025
9460
|
}
|
|
9026
9461
|
// Workspace file browsing
|
|
9027
9462
|
async getFileTree(agentId, dirPath) {
|
|
9028
|
-
const agentDir =
|
|
9463
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
9029
9464
|
try {
|
|
9030
9465
|
await stat2(agentDir);
|
|
9031
9466
|
} catch {
|
|
@@ -9033,8 +9468,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9033
9468
|
}
|
|
9034
9469
|
let targetDir = agentDir;
|
|
9035
9470
|
if (dirPath) {
|
|
9036
|
-
const resolved =
|
|
9037
|
-
if (!resolved.startsWith(agentDir +
|
|
9471
|
+
const resolved = path13.resolve(agentDir, dirPath);
|
|
9472
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
9038
9473
|
return [];
|
|
9039
9474
|
}
|
|
9040
9475
|
targetDir = resolved;
|
|
@@ -9042,14 +9477,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9042
9477
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
9043
9478
|
}
|
|
9044
9479
|
async readFile(agentId, filePath) {
|
|
9045
|
-
const agentDir =
|
|
9046
|
-
const resolved =
|
|
9047
|
-
if (!resolved.startsWith(agentDir +
|
|
9480
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
9481
|
+
const resolved = path13.resolve(agentDir, filePath);
|
|
9482
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
9048
9483
|
throw new Error("Access denied");
|
|
9049
9484
|
}
|
|
9050
9485
|
const info = await stat2(resolved);
|
|
9051
9486
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
9052
|
-
const ext =
|
|
9487
|
+
const ext = path13.extname(resolved).toLowerCase();
|
|
9053
9488
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
9054
9489
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
9055
9490
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -9084,13 +9519,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9084
9519
|
const agent = this.agents.get(agentId);
|
|
9085
9520
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
9086
9521
|
const home = os5.homedir();
|
|
9087
|
-
const workspaceDir =
|
|
9522
|
+
const workspaceDir = path13.join(this.dataDir, agentId);
|
|
9088
9523
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
9089
9524
|
const globalResults = await Promise.all(
|
|
9090
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
9525
|
+
paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
|
|
9091
9526
|
);
|
|
9092
9527
|
const workspaceResults = await Promise.all(
|
|
9093
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
9528
|
+
paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
|
|
9094
9529
|
);
|
|
9095
9530
|
const dedup = (skills) => {
|
|
9096
9531
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9119,7 +9554,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9119
9554
|
const skills = [];
|
|
9120
9555
|
for (const entry of entries) {
|
|
9121
9556
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
9122
|
-
const skillMd =
|
|
9557
|
+
const skillMd = path13.join(dir, entry.name, "SKILL.md");
|
|
9123
9558
|
try {
|
|
9124
9559
|
const content = await readFile(skillMd, "utf-8");
|
|
9125
9560
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -9130,7 +9565,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9130
9565
|
} else if (entry.name.endsWith(".md")) {
|
|
9131
9566
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
9132
9567
|
try {
|
|
9133
|
-
const content = await readFile(
|
|
9568
|
+
const content = await readFile(path13.join(dir, entry.name), "utf-8");
|
|
9134
9569
|
const skill = this.parseSkillMd(cmdName, content);
|
|
9135
9570
|
skill.sourcePath = dir;
|
|
9136
9571
|
skills.push(skill);
|
|
@@ -10019,15 +10454,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10019
10454
|
}
|
|
10020
10455
|
}
|
|
10021
10456
|
recordRuntimeTelemetry(agentId, ap, event) {
|
|
10457
|
+
const telemetryAttrs = {
|
|
10458
|
+
...event.attrs,
|
|
10459
|
+
...event.source ? { source: event.source } : {},
|
|
10460
|
+
...event.usageKind ? { usageKind: event.usageKind } : {},
|
|
10461
|
+
...event.sessionId ? { sessionId: event.sessionId } : {},
|
|
10462
|
+
...event.turnId ? { turnId: event.turnId } : {},
|
|
10463
|
+
...event.runtimeResultId ? { runtimeResultId: event.runtimeResultId } : {}
|
|
10464
|
+
};
|
|
10022
10465
|
const attrs = {
|
|
10023
10466
|
agentId,
|
|
10024
10467
|
launchId: ap.launchId || void 0,
|
|
10025
10468
|
runtime: ap.config.runtime,
|
|
10026
10469
|
model: ap.config.model,
|
|
10027
10470
|
telemetry_name: event.name,
|
|
10028
|
-
...
|
|
10471
|
+
...telemetryAttrs
|
|
10029
10472
|
};
|
|
10030
|
-
ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`,
|
|
10473
|
+
ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
|
|
10031
10474
|
this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
|
|
10032
10475
|
}
|
|
10033
10476
|
sendAgentStatus(agentId, status, launchId) {
|
|
@@ -10101,7 +10544,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10101
10544
|
}
|
|
10102
10545
|
const inboxCount = ap.inbox.length;
|
|
10103
10546
|
if (inboxCount === 0) return false;
|
|
10104
|
-
const notification = `[
|
|
10547
|
+
const notification = `[Slock inbox notice: You have ${inboxCount} pending inbox message${inboxCount > 1 ? "s" : ""}. Call check_messages to read ${inboxCount > 1 ? "them" : "it"} when you're ready.]`;
|
|
10105
10548
|
logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
|
|
10106
10549
|
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
|
|
10107
10550
|
if (encoded) {
|
|
@@ -10248,8 +10691,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
10248
10691
|
const nodes = [];
|
|
10249
10692
|
for (const entry of entries) {
|
|
10250
10693
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
10251
|
-
const fullPath =
|
|
10252
|
-
const relativePath =
|
|
10694
|
+
const fullPath = path13.join(dir, entry.name);
|
|
10695
|
+
const relativePath = path13.relative(rootDir, fullPath);
|
|
10253
10696
|
let info;
|
|
10254
10697
|
try {
|
|
10255
10698
|
info = await stat2(fullPath);
|
|
@@ -10553,10 +10996,10 @@ var ReminderCache = class {
|
|
|
10553
10996
|
};
|
|
10554
10997
|
|
|
10555
10998
|
// src/machineLock.ts
|
|
10556
|
-
import { createHash as createHash4, randomUUID as
|
|
10557
|
-
import { mkdirSync as
|
|
10999
|
+
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
11000
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
10558
11001
|
import os6 from "os";
|
|
10559
|
-
import
|
|
11002
|
+
import path14 from "path";
|
|
10560
11003
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
10561
11004
|
var DaemonMachineLockConflictError = class extends Error {
|
|
10562
11005
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -10578,7 +11021,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
10578
11021
|
return resolveSlockHomePath("machines");
|
|
10579
11022
|
}
|
|
10580
11023
|
function ownerPath(lockDir) {
|
|
10581
|
-
return
|
|
11024
|
+
return path14.join(lockDir, "owner.json");
|
|
10582
11025
|
}
|
|
10583
11026
|
function readOwner(lockDir) {
|
|
10584
11027
|
try {
|
|
@@ -10608,13 +11051,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
10608
11051
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
10609
11052
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
10610
11053
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
10611
|
-
const machineDir =
|
|
10612
|
-
const lockDir =
|
|
10613
|
-
const token =
|
|
10614
|
-
|
|
11054
|
+
const machineDir = path14.join(rootDir, lockId);
|
|
11055
|
+
const lockDir = path14.join(machineDir, "daemon.lock");
|
|
11056
|
+
const token = randomUUID4();
|
|
11057
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
10615
11058
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
10616
11059
|
try {
|
|
10617
|
-
|
|
11060
|
+
mkdirSync6(lockDir);
|
|
10618
11061
|
const owner = {
|
|
10619
11062
|
pid: process.pid,
|
|
10620
11063
|
token,
|
|
@@ -10637,7 +11080,15 @@ function acquireDaemonMachineLock(options) {
|
|
|
10637
11080
|
release: () => {
|
|
10638
11081
|
const currentOwner = readOwner(lockDir);
|
|
10639
11082
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
10640
|
-
|
|
11083
|
+
const released = { ...currentOwner, pid: 0 };
|
|
11084
|
+
try {
|
|
11085
|
+
writeFileSync8(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
|
|
11086
|
+
`, {
|
|
11087
|
+
mode: 384
|
|
11088
|
+
});
|
|
11089
|
+
} catch {
|
|
11090
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
11091
|
+
}
|
|
10641
11092
|
}
|
|
10642
11093
|
}
|
|
10643
11094
|
};
|
|
@@ -10661,8 +11112,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
10661
11112
|
}
|
|
10662
11113
|
|
|
10663
11114
|
// src/localTraceSink.ts
|
|
10664
|
-
import { appendFileSync, mkdirSync as
|
|
10665
|
-
import
|
|
11115
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
11116
|
+
import path15 from "path";
|
|
10666
11117
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
10667
11118
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
10668
11119
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -10699,7 +11150,7 @@ var LocalRotatingTraceSink = class {
|
|
|
10699
11150
|
currentSize = 0;
|
|
10700
11151
|
sequence = 0;
|
|
10701
11152
|
constructor(options) {
|
|
10702
|
-
this.traceDir =
|
|
11153
|
+
this.traceDir = path15.join(options.machineDir, "traces");
|
|
10703
11154
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
10704
11155
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
10705
11156
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -10725,11 +11176,11 @@ var LocalRotatingTraceSink = class {
|
|
|
10725
11176
|
return this.currentFile;
|
|
10726
11177
|
}
|
|
10727
11178
|
ensureFile(nextBytes) {
|
|
10728
|
-
|
|
11179
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
10729
11180
|
const nowMs = this.nowMsProvider();
|
|
10730
11181
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
10731
11182
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
10732
|
-
this.currentFile =
|
|
11183
|
+
this.currentFile = path15.join(
|
|
10733
11184
|
this.traceDir,
|
|
10734
11185
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
10735
11186
|
);
|
|
@@ -10744,7 +11195,7 @@ var LocalRotatingTraceSink = class {
|
|
|
10744
11195
|
const excess = files.length - this.maxFiles;
|
|
10745
11196
|
if (excess <= 0) return;
|
|
10746
11197
|
for (const file of files.slice(0, excess)) {
|
|
10747
|
-
rmSync4(
|
|
11198
|
+
rmSync4(path15.join(this.traceDir, file), { force: true });
|
|
10748
11199
|
}
|
|
10749
11200
|
}
|
|
10750
11201
|
};
|
|
@@ -10832,14 +11283,14 @@ function isDiagnosticErrorAttr(key) {
|
|
|
10832
11283
|
}
|
|
10833
11284
|
|
|
10834
11285
|
// src/traceBundleUpload.ts
|
|
10835
|
-
import { createHash as createHash6, randomUUID as
|
|
11286
|
+
import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
|
|
10836
11287
|
import { gzipSync } from "zlib";
|
|
10837
11288
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
10838
|
-
import
|
|
11289
|
+
import path16 from "path";
|
|
10839
11290
|
|
|
10840
11291
|
// src/directUploadCapability.ts
|
|
10841
|
-
function joinUrl(base,
|
|
10842
|
-
return `${base.replace(/\/+$/, "")}${
|
|
11292
|
+
function joinUrl(base, path18) {
|
|
11293
|
+
return `${base.replace(/\/+$/, "")}${path18}`;
|
|
10843
11294
|
}
|
|
10844
11295
|
function jsonHeaders(apiKey) {
|
|
10845
11296
|
return {
|
|
@@ -11058,7 +11509,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
11058
11509
|
}, nextMs);
|
|
11059
11510
|
}
|
|
11060
11511
|
async findUploadCandidates() {
|
|
11061
|
-
const traceDir =
|
|
11512
|
+
const traceDir = path16.join(this.options.machineDir, "traces");
|
|
11062
11513
|
let names;
|
|
11063
11514
|
try {
|
|
11064
11515
|
names = await readdir3(traceDir);
|
|
@@ -11070,8 +11521,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
11070
11521
|
const currentFile = this.options.currentFileProvider?.();
|
|
11071
11522
|
const candidates = [];
|
|
11072
11523
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
11073
|
-
const file =
|
|
11074
|
-
if (currentFile &&
|
|
11524
|
+
const file = path16.join(traceDir, name);
|
|
11525
|
+
if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
|
|
11075
11526
|
if (await this.isUploaded(file)) continue;
|
|
11076
11527
|
try {
|
|
11077
11528
|
const info = await stat3(file);
|
|
@@ -11103,7 +11554,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
11103
11554
|
}
|
|
11104
11555
|
const gzipped = gzipSync(raw);
|
|
11105
11556
|
const bundleSha256 = sha256Hex(gzipped);
|
|
11106
|
-
const bundleId =
|
|
11557
|
+
const bundleId = randomUUID5();
|
|
11107
11558
|
await uploadWithSignedCapability({
|
|
11108
11559
|
serverUrl: this.options.serverUrl,
|
|
11109
11560
|
apiKey: this.options.apiKey,
|
|
@@ -11145,8 +11596,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
11145
11596
|
}
|
|
11146
11597
|
}
|
|
11147
11598
|
uploadStatePath(file) {
|
|
11148
|
-
const stateDir =
|
|
11149
|
-
return
|
|
11599
|
+
const stateDir = path16.join(this.options.machineDir, "trace-uploads");
|
|
11600
|
+
return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
|
|
11150
11601
|
}
|
|
11151
11602
|
async isUploaded(file) {
|
|
11152
11603
|
try {
|
|
@@ -11158,9 +11609,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
11158
11609
|
}
|
|
11159
11610
|
async markUploaded(file, metadata) {
|
|
11160
11611
|
const stateFile = this.uploadStatePath(file);
|
|
11161
|
-
await mkdir2(
|
|
11612
|
+
await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
|
|
11162
11613
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
11163
|
-
file:
|
|
11614
|
+
file: path16.basename(file),
|
|
11164
11615
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11165
11616
|
...metadata
|
|
11166
11617
|
}, null, 2)}
|
|
@@ -11237,23 +11688,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
11237
11688
|
}
|
|
11238
11689
|
}
|
|
11239
11690
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
11240
|
-
const dirname =
|
|
11241
|
-
const jsPath =
|
|
11691
|
+
const dirname = path17.dirname(fileURLToPath(moduleUrl));
|
|
11692
|
+
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
11242
11693
|
try {
|
|
11243
11694
|
accessSync(jsPath);
|
|
11244
11695
|
return jsPath;
|
|
11245
11696
|
} catch {
|
|
11246
|
-
return
|
|
11697
|
+
return path17.resolve(dirname, "chat-bridge.ts");
|
|
11247
11698
|
}
|
|
11248
11699
|
}
|
|
11249
11700
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
11250
|
-
const thisDir =
|
|
11251
|
-
const bundledDistPath =
|
|
11701
|
+
const thisDir = path17.dirname(fileURLToPath(moduleUrl));
|
|
11702
|
+
const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
|
|
11252
11703
|
try {
|
|
11253
11704
|
accessSync(bundledDistPath);
|
|
11254
11705
|
return bundledDistPath;
|
|
11255
11706
|
} catch {
|
|
11256
|
-
const workspaceDistPath =
|
|
11707
|
+
const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
11257
11708
|
accessSync(workspaceDistPath);
|
|
11258
11709
|
return workspaceDistPath;
|
|
11259
11710
|
}
|
|
@@ -11383,6 +11834,11 @@ function summarizeIncomingMessage(msg) {
|
|
|
11383
11834
|
var DaemonCore = class {
|
|
11384
11835
|
options;
|
|
11385
11836
|
daemonVersion;
|
|
11837
|
+
// When this runner is launched by a managed Computer service, the
|
|
11838
|
+
// service exports SLOCK_COMPUTER_VERSION (the `@slock-ai/computer`
|
|
11839
|
+
// bundle version). Reported in `ready` so the server can surface the
|
|
11840
|
+
// Computer's own version (distinct from the underlying daemonVersion).
|
|
11841
|
+
computerVersion;
|
|
11386
11842
|
chatBridgePath;
|
|
11387
11843
|
slockCliPath;
|
|
11388
11844
|
slockHome;
|
|
@@ -11398,6 +11854,7 @@ var DaemonCore = class {
|
|
|
11398
11854
|
constructor(options) {
|
|
11399
11855
|
this.options = options;
|
|
11400
11856
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
11857
|
+
this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
|
|
11401
11858
|
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
11402
11859
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
11403
11860
|
this.slockHome = resolveSlockHome();
|
|
@@ -11432,7 +11889,7 @@ var DaemonCore = class {
|
|
|
11432
11889
|
}
|
|
11433
11890
|
resolveMachineStateRoot() {
|
|
11434
11891
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
11435
|
-
if (this.options.dataDir) return
|
|
11892
|
+
if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
|
|
11436
11893
|
return resolveDefaultMachineStateRoot();
|
|
11437
11894
|
}
|
|
11438
11895
|
shouldEnableLocalTrace() {
|
|
@@ -11458,7 +11915,7 @@ var DaemonCore = class {
|
|
|
11458
11915
|
sink: this.localTraceSink
|
|
11459
11916
|
});
|
|
11460
11917
|
this.agentManager.setTracer(this.tracer);
|
|
11461
|
-
this.agentManager.setCliTransportTraceDir(
|
|
11918
|
+
this.agentManager.setCliTransportTraceDir(path17.join(machineDir, "traces"));
|
|
11462
11919
|
}
|
|
11463
11920
|
installTraceBundleUploader(machineDir) {
|
|
11464
11921
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -11857,6 +12314,26 @@ var DaemonCore = class {
|
|
|
11857
12314
|
case "ping":
|
|
11858
12315
|
this.connection.send({ type: "pong" });
|
|
11859
12316
|
break;
|
|
12317
|
+
case "computer:restart":
|
|
12318
|
+
case "computer:upgrade": {
|
|
12319
|
+
const action = msg.type === "computer:restart" ? "restart" : "upgrade";
|
|
12320
|
+
this.recordDaemonTrace("daemon.computer_control.received", {
|
|
12321
|
+
action,
|
|
12322
|
+
handled: Boolean(this.options.onComputerControl)
|
|
12323
|
+
});
|
|
12324
|
+
if (this.options.onComputerControl) {
|
|
12325
|
+
try {
|
|
12326
|
+
this.options.onComputerControl(action);
|
|
12327
|
+
} catch (err) {
|
|
12328
|
+
logger.error(
|
|
12329
|
+
`[Daemon] computer:${action} control handler failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12330
|
+
);
|
|
12331
|
+
}
|
|
12332
|
+
} else {
|
|
12333
|
+
logger.info(`[Daemon] Ignoring computer:${action} \u2014 not launched by a Computer service.`);
|
|
12334
|
+
}
|
|
12335
|
+
break;
|
|
12336
|
+
}
|
|
11860
12337
|
}
|
|
11861
12338
|
}
|
|
11862
12339
|
onReminderFire(job) {
|
|
@@ -11883,7 +12360,8 @@ var DaemonCore = class {
|
|
|
11883
12360
|
runningAgents: runningAgentIds,
|
|
11884
12361
|
hostname: this.options.hostname ?? os7.hostname(),
|
|
11885
12362
|
os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
|
|
11886
|
-
daemonVersion: this.daemonVersion
|
|
12363
|
+
daemonVersion: this.daemonVersion,
|
|
12364
|
+
...this.computerVersion ? { computerVersion: this.computerVersion } : {}
|
|
11887
12365
|
});
|
|
11888
12366
|
this.recordDaemonTrace("daemon.ready.sent", {
|
|
11889
12367
|
runtimes_count: runtimes.length,
|