@slock-ai/daemon 0.53.2 → 0.54.0
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-UIJF67BT.js → chunk-JXS4CW3D.js} +202 -45
- package/dist/cli/index.js +497 -56
- package/dist/cli/package.json +5 -0
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -609,13 +609,16 @@ var agentCreateOperationSchema = z.object({
|
|
|
609
609
|
name: z.string().trim().min(1).max(60),
|
|
610
610
|
description: z.string().trim().max(500).optional(),
|
|
611
611
|
/**
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
612
|
+
* Optional computer placement contract. Agents may only set this when the
|
|
613
|
+
* human request is explicitly computer-bound; server prepare resolves the
|
|
614
|
+
* name/UUID and stores the UUID-only form. `suggestedComputer` preselects
|
|
615
|
+
* the dialog when available; `requiredComputer` prevents silent fallback to
|
|
616
|
+
* any other computer.
|
|
617
|
+
*
|
|
618
|
+
* Runtime / model / reasoning effort remain human-picked technical fields.
|
|
618
619
|
*/
|
|
620
|
+
suggestedComputer: idOrHandleSchema.optional(),
|
|
621
|
+
requiredComputer: idOrHandleSchema.optional(),
|
|
619
622
|
draftHint: draftHintSchema
|
|
620
623
|
});
|
|
621
624
|
var channelAddMemberOperationSchema = z.object({
|
|
@@ -1942,6 +1945,9 @@ var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
|
1942
1945
|
var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
1943
1946
|
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1944
1947
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1948
|
+
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
1949
|
+
"SLOCK_AGENT_CREDENTIAL_KEY"
|
|
1950
|
+
];
|
|
1945
1951
|
var cachedOpencliBinPath;
|
|
1946
1952
|
function resolveOpencliBinPath() {
|
|
1947
1953
|
if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
|
|
@@ -2123,8 +2129,9 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
|
|
|
2123
2129
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
2124
2130
|
};
|
|
2125
2131
|
delete spawnEnv.SLOCK_AGENT_TOKEN;
|
|
2126
|
-
|
|
2127
|
-
|
|
2132
|
+
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
2133
|
+
delete spawnEnv[key];
|
|
2134
|
+
}
|
|
2128
2135
|
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
2129
2136
|
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
|
|
2130
2137
|
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
|
|
@@ -2148,6 +2155,118 @@ import path3 from "path";
|
|
|
2148
2155
|
function normalizeExecOutput(raw) {
|
|
2149
2156
|
return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
|
|
2150
2157
|
}
|
|
2158
|
+
var WINDOWS_ENVIRONMENT_SCRIPT = [
|
|
2159
|
+
"& {",
|
|
2160
|
+
" $result = [ordered]@{}",
|
|
2161
|
+
" foreach ($scope in @('Machine', 'User')) {",
|
|
2162
|
+
" $scopeEnv = [Environment]::GetEnvironmentVariables($scope)",
|
|
2163
|
+
" $scopeObj = [ordered]@{}",
|
|
2164
|
+
" foreach ($key in $scopeEnv.Keys) {",
|
|
2165
|
+
" $value = $scopeEnv[$key]",
|
|
2166
|
+
" if ($null -ne $value) { $scopeObj[$key] = [string]$value }",
|
|
2167
|
+
" }",
|
|
2168
|
+
" $result[$scope] = $scopeObj",
|
|
2169
|
+
" }",
|
|
2170
|
+
" $result | ConvertTo-Json -Compress -Depth 3",
|
|
2171
|
+
"}"
|
|
2172
|
+
].join(" ");
|
|
2173
|
+
function normalizeProcessEnv(value) {
|
|
2174
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2175
|
+
const env = {};
|
|
2176
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
2177
|
+
if (rawValue !== void 0 && rawValue !== null) {
|
|
2178
|
+
env[key] = String(rawValue);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return env;
|
|
2182
|
+
}
|
|
2183
|
+
function readWindowsMachineUserEnvironment(env, execFileSyncFn) {
|
|
2184
|
+
try {
|
|
2185
|
+
const output = normalizeExecOutput(execFileSyncFn("powershell.exe", [
|
|
2186
|
+
"-NoProfile",
|
|
2187
|
+
"-NonInteractive",
|
|
2188
|
+
"-Command",
|
|
2189
|
+
WINDOWS_ENVIRONMENT_SCRIPT
|
|
2190
|
+
], {
|
|
2191
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2192
|
+
env,
|
|
2193
|
+
timeout: 5e3
|
|
2194
|
+
}));
|
|
2195
|
+
const parsed = JSON.parse(output || "{}");
|
|
2196
|
+
return {
|
|
2197
|
+
machine: normalizeProcessEnv(parsed.Machine),
|
|
2198
|
+
user: normalizeProcessEnv(parsed.User)
|
|
2199
|
+
};
|
|
2200
|
+
} catch {
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function findEnvKey(env, name) {
|
|
2205
|
+
if (!env) return null;
|
|
2206
|
+
const lowerName = name.toLowerCase();
|
|
2207
|
+
const keys = Object.keys(env);
|
|
2208
|
+
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
2209
|
+
const key = keys[index];
|
|
2210
|
+
if (key.toLowerCase() === lowerName) return key;
|
|
2211
|
+
}
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
function getEnvValue(env, name) {
|
|
2215
|
+
const key = findEnvKey(env, name);
|
|
2216
|
+
return key ? env?.[key] : void 0;
|
|
2217
|
+
}
|
|
2218
|
+
function setEnvValue(env, key, value) {
|
|
2219
|
+
const existingKey = findEnvKey(env, key);
|
|
2220
|
+
if (existingKey && existingKey !== key) {
|
|
2221
|
+
delete env[existingKey];
|
|
2222
|
+
}
|
|
2223
|
+
env[key] = value;
|
|
2224
|
+
}
|
|
2225
|
+
function mergeWindowsPathSegments(values) {
|
|
2226
|
+
const segments = [];
|
|
2227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2228
|
+
for (const value of values) {
|
|
2229
|
+
if (!value) continue;
|
|
2230
|
+
for (const rawSegment of value.split(";")) {
|
|
2231
|
+
const segment = rawSegment.trim();
|
|
2232
|
+
if (!segment) continue;
|
|
2233
|
+
const key = segment.toLowerCase();
|
|
2234
|
+
if (seen.has(key)) continue;
|
|
2235
|
+
seen.add(key);
|
|
2236
|
+
segments.push(segment);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return segments.length > 0 ? segments.join(";") : void 0;
|
|
2240
|
+
}
|
|
2241
|
+
function mergeWindowsEnvironmentScopes(baseEnv, scopes) {
|
|
2242
|
+
const merged = {};
|
|
2243
|
+
const layers = [scopes.machine ?? {}, scopes.user ?? {}, baseEnv];
|
|
2244
|
+
for (const layer of layers) {
|
|
2245
|
+
for (const [key, value] of Object.entries(layer)) {
|
|
2246
|
+
if (value === void 0 || key.toLowerCase() === "path") continue;
|
|
2247
|
+
setEnvValue(merged, key, value);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
const pathKey = findEnvKey(baseEnv, "Path") ?? findEnvKey(scopes.machine, "Path") ?? findEnvKey(scopes.user, "Path") ?? "Path";
|
|
2251
|
+
const pathValue = mergeWindowsPathSegments([
|
|
2252
|
+
getEnvValue(baseEnv, "Path"),
|
|
2253
|
+
getEnvValue(scopes.machine, "Path"),
|
|
2254
|
+
getEnvValue(scopes.user, "Path")
|
|
2255
|
+
]);
|
|
2256
|
+
if (pathValue) {
|
|
2257
|
+
merged[pathKey] = pathValue;
|
|
2258
|
+
}
|
|
2259
|
+
return merged;
|
|
2260
|
+
}
|
|
2261
|
+
function withWindowsUserEnvironment(env, deps = {}) {
|
|
2262
|
+
const platform = deps.platform ?? process.platform;
|
|
2263
|
+
if (platform !== "win32") return env;
|
|
2264
|
+
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2265
|
+
const reader = deps.windowsEnvironmentReaderFn ?? readWindowsMachineUserEnvironment;
|
|
2266
|
+
const scopes = reader(env, execFileSyncFn);
|
|
2267
|
+
if (!scopes) return env;
|
|
2268
|
+
return mergeWindowsEnvironmentScopes(env, scopes);
|
|
2269
|
+
}
|
|
2151
2270
|
function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
|
|
2152
2271
|
const script = "& {$cmd = Get-Command -Name $args[0] -ErrorAction Stop | Select-Object -First 1; if ($cmd.Path) { $cmd.Path } elseif ($cmd.Source) { $cmd.Source } elseif ($cmd.Definition) { $cmd.Definition } }";
|
|
2153
2272
|
try {
|
|
@@ -2185,7 +2304,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
|
|
|
2185
2304
|
}
|
|
2186
2305
|
function resolveCommandOnPath(command, deps = {}) {
|
|
2187
2306
|
const platform = deps.platform ?? process.platform;
|
|
2188
|
-
const env = deps.env ?? process.env;
|
|
2307
|
+
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
2189
2308
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2190
2309
|
const existsSyncFn = deps.existsSyncFn ?? existsSync2;
|
|
2191
2310
|
if (platform === "win32") {
|
|
@@ -2211,7 +2330,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
2211
2330
|
return null;
|
|
2212
2331
|
}
|
|
2213
2332
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
2214
|
-
const env = deps.env ?? process.env;
|
|
2333
|
+
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
2215
2334
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
2216
2335
|
try {
|
|
2217
2336
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -2488,6 +2607,7 @@ var ClaudeDriver = class {
|
|
|
2488
2607
|
const detail = parts.join(" | ") || fallback;
|
|
2489
2608
|
events.push({ kind: "error", message: detail });
|
|
2490
2609
|
};
|
|
2610
|
+
const isProviderApiFailureText = (value, hasToolUse) => !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) || /\b5\d{2}\b/.test(value));
|
|
2491
2611
|
switch (event.type) {
|
|
2492
2612
|
case "system":
|
|
2493
2613
|
if (event.subtype === "init" && event.session_id) {
|
|
@@ -2503,11 +2623,16 @@ var ClaudeDriver = class {
|
|
|
2503
2623
|
case "assistant": {
|
|
2504
2624
|
const content = event.message?.content;
|
|
2505
2625
|
if (Array.isArray(content)) {
|
|
2626
|
+
const hasToolUse = content.some((block) => block?.type === "tool_use");
|
|
2506
2627
|
for (const block of content) {
|
|
2507
2628
|
if (block.type === "thinking" && block.thinking) {
|
|
2508
2629
|
events.push({ kind: "thinking", text: block.thinking });
|
|
2509
2630
|
} else if (block.type === "text" && block.text) {
|
|
2510
|
-
|
|
2631
|
+
if (isProviderApiFailureText(block.text, hasToolUse)) {
|
|
2632
|
+
events.push({ kind: "error", message: block.text });
|
|
2633
|
+
} else {
|
|
2634
|
+
events.push({ kind: "text", text: block.text });
|
|
2635
|
+
}
|
|
2511
2636
|
} else if (block.type === "tool_use") {
|
|
2512
2637
|
events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
|
|
2513
2638
|
}
|
|
@@ -2821,6 +2946,11 @@ var CodexDriver = class {
|
|
|
2821
2946
|
sessionAnnounced = false;
|
|
2822
2947
|
streamedAgentMessageIds = /* @__PURE__ */ new Set();
|
|
2823
2948
|
streamedReasoningIds = /* @__PURE__ */ new Set();
|
|
2949
|
+
/**
|
|
2950
|
+
* Post-tool window where the app-server may not yet accept stdin steering.
|
|
2951
|
+
* Gate busy-mode delivery until turn/completed or next progress.
|
|
2952
|
+
*/
|
|
2953
|
+
steeringGateActive = false;
|
|
2824
2954
|
async spawn(ctx) {
|
|
2825
2955
|
ensureGitRepoForCodex(ctx.workingDirectory);
|
|
2826
2956
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
@@ -2835,6 +2965,7 @@ var CodexDriver = class {
|
|
|
2835
2965
|
this.sessionAnnounced = false;
|
|
2836
2966
|
this.streamedAgentMessageIds.clear();
|
|
2837
2967
|
this.streamedReasoningIds.clear();
|
|
2968
|
+
this.steeringGateActive = false;
|
|
2838
2969
|
const args = ["app-server", "--listen", "stdio://"];
|
|
2839
2970
|
args.push(...this.buildRuntimeActionsConfigArgs(ctx));
|
|
2840
2971
|
const { command, args: spawnArgs } = resolveCodexSpawn(args);
|
|
@@ -2908,6 +3039,7 @@ var CodexDriver = class {
|
|
|
2908
3039
|
if (typeof turnId === "string") {
|
|
2909
3040
|
this.activeTurnId = turnId;
|
|
2910
3041
|
}
|
|
3042
|
+
this.steeringGateActive = false;
|
|
2911
3043
|
events.push({ kind: "thinking", text: "" });
|
|
2912
3044
|
break;
|
|
2913
3045
|
}
|
|
@@ -2918,6 +3050,7 @@ var CodexDriver = class {
|
|
|
2918
3050
|
this.streamedAgentMessageIds.add(itemId);
|
|
2919
3051
|
}
|
|
2920
3052
|
if (typeof delta === "string" && delta.length > 0) {
|
|
3053
|
+
this.steeringGateActive = false;
|
|
2921
3054
|
events.push({ kind: "text", text: delta });
|
|
2922
3055
|
}
|
|
2923
3056
|
break;
|
|
@@ -2930,6 +3063,7 @@ var CodexDriver = class {
|
|
|
2930
3063
|
this.streamedReasoningIds.add(itemId);
|
|
2931
3064
|
}
|
|
2932
3065
|
if (typeof delta === "string" && delta.length > 0) {
|
|
3066
|
+
this.steeringGateActive = false;
|
|
2933
3067
|
events.push({ kind: "thinking", text: delta });
|
|
2934
3068
|
}
|
|
2935
3069
|
break;
|
|
@@ -2946,6 +3080,7 @@ var CodexDriver = class {
|
|
|
2946
3080
|
if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
|
|
2947
3081
|
const text = joinReasoningText(item);
|
|
2948
3082
|
if (text) {
|
|
3083
|
+
this.steeringGateActive = false;
|
|
2949
3084
|
events.push({ kind: "thinking", text });
|
|
2950
3085
|
}
|
|
2951
3086
|
}
|
|
@@ -2955,6 +3090,7 @@ var CodexDriver = class {
|
|
|
2955
3090
|
break;
|
|
2956
3091
|
case "agentMessage":
|
|
2957
3092
|
if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
|
|
3093
|
+
this.steeringGateActive = false;
|
|
2958
3094
|
events.push({ kind: "text", text: item.text });
|
|
2959
3095
|
}
|
|
2960
3096
|
if (isCompleted && typeof item.id === "string") {
|
|
@@ -2967,6 +3103,7 @@ var CodexDriver = class {
|
|
|
2967
3103
|
}
|
|
2968
3104
|
if (isCompleted) {
|
|
2969
3105
|
events.push({ kind: "tool_output", name: "shell" });
|
|
3106
|
+
this.steeringGateActive = true;
|
|
2970
3107
|
}
|
|
2971
3108
|
break;
|
|
2972
3109
|
case "contextCompaction":
|
|
@@ -2996,17 +3133,24 @@ var CodexDriver = class {
|
|
|
2996
3133
|
if (isCompleted) {
|
|
2997
3134
|
const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
|
|
2998
3135
|
events.push({ kind: "tool_output", name: toolName });
|
|
3136
|
+
this.steeringGateActive = true;
|
|
2999
3137
|
}
|
|
3000
3138
|
break;
|
|
3001
3139
|
case "collabAgentToolCall":
|
|
3002
3140
|
if (isStarted) {
|
|
3003
3141
|
events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
|
|
3004
3142
|
}
|
|
3143
|
+
if (isCompleted) {
|
|
3144
|
+
this.steeringGateActive = true;
|
|
3145
|
+
}
|
|
3005
3146
|
break;
|
|
3006
3147
|
case "webSearch":
|
|
3007
3148
|
if (isStarted) {
|
|
3008
3149
|
events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
|
|
3009
3150
|
}
|
|
3151
|
+
if (isCompleted) {
|
|
3152
|
+
this.steeringGateActive = true;
|
|
3153
|
+
}
|
|
3010
3154
|
break;
|
|
3011
3155
|
}
|
|
3012
3156
|
break;
|
|
@@ -3019,6 +3163,7 @@ var CodexDriver = class {
|
|
|
3019
3163
|
this.activeTurnId = null;
|
|
3020
3164
|
this.streamedAgentMessageIds.clear();
|
|
3021
3165
|
this.streamedReasoningIds.clear();
|
|
3166
|
+
this.steeringGateActive = false;
|
|
3022
3167
|
events.push({ kind: "turn_end", sessionId: this.threadId || void 0 });
|
|
3023
3168
|
break;
|
|
3024
3169
|
}
|
|
@@ -3038,7 +3183,7 @@ var CodexDriver = class {
|
|
|
3038
3183
|
if (!this.threadId) return null;
|
|
3039
3184
|
const mode = opts?.mode || "busy";
|
|
3040
3185
|
if (mode === "busy") {
|
|
3041
|
-
if (!this.activeTurnId) return null;
|
|
3186
|
+
if (!this.activeTurnId || this.steeringGateActive) return null;
|
|
3042
3187
|
return JSON.stringify({
|
|
3043
3188
|
jsonrpc: "2.0",
|
|
3044
3189
|
id: this.nextRequestId(),
|
|
@@ -3452,8 +3597,9 @@ var CopilotDriver = class {
|
|
|
3452
3597
|
import { spawn as spawn5, spawnSync } from "child_process";
|
|
3453
3598
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
|
|
3454
3599
|
import path7 from "path";
|
|
3455
|
-
async function buildCursorSpawnEnv(ctx) {
|
|
3456
|
-
|
|
3600
|
+
async function buildCursorSpawnEnv(ctx, deps = {}) {
|
|
3601
|
+
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
3602
|
+
return withWindowsUserEnvironment(spawnEnv, deps);
|
|
3457
3603
|
}
|
|
3458
3604
|
var CursorDriver = class {
|
|
3459
3605
|
id = "cursor";
|
|
@@ -3637,9 +3783,16 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
3637
3783
|
if (result.error || result.status !== 0) return null;
|
|
3638
3784
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
3639
3785
|
}
|
|
3786
|
+
function buildCursorModelProbeEnv(deps = {}) {
|
|
3787
|
+
return withWindowsUserEnvironment({
|
|
3788
|
+
...deps.env ?? process.env,
|
|
3789
|
+
FORCE_COLOR: "0",
|
|
3790
|
+
NO_COLOR: "1"
|
|
3791
|
+
}, deps);
|
|
3792
|
+
}
|
|
3640
3793
|
function runCursorModelsCommand() {
|
|
3641
3794
|
return spawnSync("cursor-agent", ["models"], {
|
|
3642
|
-
env:
|
|
3795
|
+
env: buildCursorModelProbeEnv(),
|
|
3643
3796
|
encoding: "utf8",
|
|
3644
3797
|
timeout: 5e3
|
|
3645
3798
|
});
|
|
@@ -4777,9 +4930,13 @@ function buildRuntimeErrorDiagnosticEnvelope(message) {
|
|
|
4777
4930
|
const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
|
|
4778
4931
|
const httpStatus = extractHttpStatus(rawMessage);
|
|
4779
4932
|
const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
|
|
4933
|
+
const runtimeErrorReason = classifyRuntimeErrorReason(runtimeErrorClass);
|
|
4780
4934
|
const runtimeErrorAction = classifyRuntimeErrorAction(rawMessage, runtimeErrorClass);
|
|
4781
4935
|
const fingerprint = fingerprintRuntimeError(scrubbed);
|
|
4782
4936
|
const spanAttrs = {
|
|
4937
|
+
turn_outcome: "failed",
|
|
4938
|
+
turn_subtype: "runtime_error",
|
|
4939
|
+
turn_reason: runtimeErrorReason,
|
|
4783
4940
|
runtime_error_class: runtimeErrorClass,
|
|
4784
4941
|
runtime_error_action: runtimeErrorAction,
|
|
4785
4942
|
runtime_error_action_required: runtimeErrorAction !== "none",
|
|
@@ -4831,7 +4988,10 @@ function classifyRuntimeError(message, httpStatus) {
|
|
|
4831
4988
|
return "ProviderApiError";
|
|
4832
4989
|
}
|
|
4833
4990
|
if (isRuntimeAuthActionRequiredText(message)) return "AuthError";
|
|
4834
|
-
if (/\
|
|
4991
|
+
if (/\b(?:ETIMEDOUT|timeout|timed out)\b/i.test(message)) return "TimeoutError";
|
|
4992
|
+
if (/\b(?:ECONNRESET|EPIPE|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(message) || /\bUnable to connect to API\b/i.test(message)) {
|
|
4993
|
+
return "ProviderConnectionError";
|
|
4994
|
+
}
|
|
4835
4995
|
if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
|
|
4836
4996
|
if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
|
|
4837
4997
|
if (/\bnot found\b/i.test(message)) return "NotFoundError";
|
|
@@ -4846,6 +5006,28 @@ function classifyRuntimeErrorAction(message, runtimeErrorClass) {
|
|
|
4846
5006
|
function isRuntimeAuthActionRequiredText(text) {
|
|
4847
5007
|
return RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
|
|
4848
5008
|
}
|
|
5009
|
+
function classifyRuntimeErrorReason(runtimeErrorClass) {
|
|
5010
|
+
switch (runtimeErrorClass) {
|
|
5011
|
+
case "ProviderConnectionError":
|
|
5012
|
+
return "provider_connection_error";
|
|
5013
|
+
case "TimeoutError":
|
|
5014
|
+
return "provider_timeout";
|
|
5015
|
+
case "ProviderStreamError":
|
|
5016
|
+
return "provider_stream_error";
|
|
5017
|
+
case "RateLimitError":
|
|
5018
|
+
return "rate_limited";
|
|
5019
|
+
case "AuthError":
|
|
5020
|
+
return "auth_failed";
|
|
5021
|
+
case "NotFoundError":
|
|
5022
|
+
return "not_found";
|
|
5023
|
+
case "ProviderServerError":
|
|
5024
|
+
return "provider_server_error";
|
|
5025
|
+
case "ProviderApiError":
|
|
5026
|
+
return "provider_api_error";
|
|
5027
|
+
default:
|
|
5028
|
+
return "unclassified_runtime_error";
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
4849
5031
|
function runtimeDisplayName(runtimeId) {
|
|
4850
5032
|
switch (runtimeId) {
|
|
4851
5033
|
case "antigravity":
|
|
@@ -5272,10 +5454,10 @@ For new channels, new agents, and adding members to an existing channel, post an
|
|
|
5272
5454
|
|
|
5273
5455
|
- Use \`slock action prepare --target <onboarding-channel>\` and pipe an \`ActionCardAction\` JSON. Identity references are handles (\`@alice\` / \`@scout\` / \`#general\` \u2014 bare names work too), never UUIDs. Server resolves at prepare time.
|
|
5274
5456
|
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
5275
|
-
- \`{type: "agent:create", name, description?, draftHint?}\`
|
|
5457
|
+
- \`{type: "agent:create", name, description?, suggestedComputer?, requiredComputer?, draftHint?}\`
|
|
5276
5458
|
- \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty
|
|
5277
5459
|
- The owner clicks the button on the card; the matching dialog opens **prefilled with your values** (editable, deselectable for add_member). They review, adjust, and submit; the action is committed under their identity.
|
|
5278
|
-
-
|
|
5460
|
+
- Runtime / model / reasoning effort are NOT yours to prefill on \`agent:create\`. If the human request explicitly binds the agent to a computer, use a structured \`requiredComputer\` (or \`suggestedComputer\` for a soft preference) instead of burying the constraint in \`draftHint\`; otherwise stay on semantic intent (name + description).
|
|
5279
5461
|
- For \`channel:add_member\`, only suggest people who are actually likely candidates (already in the server, relevant to the channel's topic). The owner will deselect anyone they don't want \u2014 make their default-yes list useful, not exhaustive.
|
|
5280
5462
|
- Do not just describe or list copyable specs once action cards are available \u2014 the human input cost should land at "click the card, review, submit", not "copy this name into the dialog yourself".
|
|
5281
5463
|
- Do not imply the resource has been created or members added until the card flips to "Done".
|
|
@@ -5528,7 +5710,7 @@ Do not copy these answers verbatim.
|
|
|
5528
5710
|
- 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.
|
|
5529
5711
|
- v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
|
|
5530
5712
|
- \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
|
|
5531
|
-
- \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model /
|
|
5713
|
+
- \`{type: "agent:create", name, description?, suggestedComputer?, requiredComputer?, draftHint?}\` \u2014 runtime / model / reasoning effort are the owner's call. Use \`requiredComputer\` only when the owner explicitly says the new agent must run on that computer; use \`suggestedComputer\` for a soft preference.
|
|
5532
5714
|
- \`{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.
|
|
5533
5715
|
|
|
5534
5716
|
- **Identity references are handles, not UUIDs.** Use \`@alice\` / \`@scout\` / \`#general\` (or bare \`alice\` / \`scout\` / \`general\`). The server resolves to UUIDs at prepare time. If a handle doesn't match a real human / agent / channel in this server you get a 422 INVALID_HANDLE error pointing at the field \u2014 fix the handle and retry. You should never see or write UUIDs in action card payloads.
|
|
@@ -5539,7 +5721,7 @@ Do not copy these answers verbatim.
|
|
|
5539
5721
|
|
|
5540
5722
|
### Guardrail
|
|
5541
5723
|
- Do not imply you already created agents or channels unless the card state is \`executed\`.
|
|
5542
|
-
- Do not prefill
|
|
5724
|
+
- Do not prefill runtime / model / reasoning effort on \`agent:create\`. Computer placement is only allowed as the structured \`suggestedComputer\` / \`requiredComputer\` field when the owner's request includes that placement; never rely on \`draftHint\` for a computer constraint.
|
|
5543
5725
|
- If the action type the user wants is not yet supported (e.g. \`channel:add_member\`), say so plainly and offer the manual UI path; do not invent action types the schema does not accept.
|
|
5544
5726
|
`;
|
|
5545
5727
|
}
|
|
@@ -7065,14 +7247,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7065
7247
|
});
|
|
7066
7248
|
logger.info(`[Agent ${agentId}] Queued runtime profile ${kind} ${key} during startup`);
|
|
7067
7249
|
}
|
|
7068
|
-
splitRuntimeProfileControlBatch(messages) {
|
|
7069
|
-
const controlMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message));
|
|
7070
|
-
if (controlMessages.length === 0 || controlMessages.length === messages.length) {
|
|
7071
|
-
return { nextMessages: messages, deferredMessages: [] };
|
|
7072
|
-
}
|
|
7073
|
-
const deferredMessages = messages.filter((message) => !runtimeProfileNotificationFromMessage(message));
|
|
7074
|
-
return { nextMessages: controlMessages, deferredMessages };
|
|
7075
|
-
}
|
|
7076
7250
|
containsOrdinaryInboxMessage(messages) {
|
|
7077
7251
|
return messages.some((message) => !runtimeProfileNotificationFromMessage(message));
|
|
7078
7252
|
}
|
|
@@ -8602,7 +8776,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8602
8776
|
...runtimeTraceCounterAttrs(ap)
|
|
8603
8777
|
});
|
|
8604
8778
|
this.endRuntimeTrace(ap, "error", {
|
|
8605
|
-
outcome: "runtime-error",
|
|
8606
8779
|
...runtimeErrorDiagnostics.spanAttrs,
|
|
8607
8780
|
...runtimeTraceCounterAttrs(ap),
|
|
8608
8781
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
@@ -8775,22 +8948,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8775
8948
|
return true;
|
|
8776
8949
|
}
|
|
8777
8950
|
}
|
|
8778
|
-
const split = this.splitRuntimeProfileControlBatch(messages);
|
|
8779
|
-
if (split.deferredMessages.length > 0) {
|
|
8780
|
-
ap.inbox.unshift(...split.deferredMessages);
|
|
8781
|
-
ap.pendingNotificationCount += split.deferredMessages.length;
|
|
8782
|
-
messages = split.nextMessages;
|
|
8783
|
-
this.recordDaemonTrace("daemon.agent.runtime_profile.split_batch", {
|
|
8784
|
-
agentId,
|
|
8785
|
-
launchId: ap.launchId || void 0,
|
|
8786
|
-
runtime: ap.config.runtime,
|
|
8787
|
-
mode,
|
|
8788
|
-
delivered_control_messages_count: messages.length,
|
|
8789
|
-
deferred_messages_count: split.deferredMessages.length,
|
|
8790
|
-
inbox_count: ap.inbox.length,
|
|
8791
|
-
pending_notification_count: ap.pendingNotificationCount
|
|
8792
|
-
});
|
|
8793
|
-
}
|
|
8794
8951
|
const traceAttrs = {
|
|
8795
8952
|
agentId,
|
|
8796
8953
|
launchId: ap.launchId || void 0,
|
package/dist/cli/index.js
CHANGED
|
@@ -19,13 +19,47 @@ var CliExit = class extends Error {
|
|
|
19
19
|
function emit(payload) {
|
|
20
20
|
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
21
21
|
}
|
|
22
|
-
function fail(code, message,
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
function fail(code, message, options) {
|
|
23
|
+
const body = { ok: false, code, message };
|
|
24
|
+
if (options?.suggestedNextAction) {
|
|
25
|
+
body.suggested_next_action = options.suggestedNextAction;
|
|
26
|
+
}
|
|
27
|
+
process.stderr.write(JSON.stringify(body) + "\n");
|
|
28
|
+
throw new CliExit(options?.exitCode ?? 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/version.ts
|
|
32
|
+
import { readFileSync } from "fs";
|
|
33
|
+
var FALLBACK_VERSION = "0.0.0";
|
|
34
|
+
function readVersionFrom(candidate) {
|
|
35
|
+
try {
|
|
36
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
37
|
+
return typeof pkg.version === "string" && pkg.version ? pkg.version : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function readCliVersion(baseUrl = import.meta.url) {
|
|
43
|
+
return (
|
|
44
|
+
// Built package and daemon-bundled CLI: dist/package.json travels with dist/index.js.
|
|
45
|
+
readVersionFrom(new URL("./package.json", baseUrl)) ?? readVersionFrom(new URL("../package.json", baseUrl)) ?? FALLBACK_VERSION
|
|
46
|
+
);
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
// src/auth/env.ts
|
|
28
50
|
import fs from "fs";
|
|
51
|
+
import os from "os";
|
|
52
|
+
import path from "path";
|
|
53
|
+
var RAW_AGENT_ENV_KEYS = [
|
|
54
|
+
"SLOCK_AGENT_ID",
|
|
55
|
+
"SLOCK_SERVER_URL",
|
|
56
|
+
"SLOCK_SERVER_ID",
|
|
57
|
+
"SLOCK_AGENT_PROXY_URL",
|
|
58
|
+
"SLOCK_AGENT_PROXY_TOKEN",
|
|
59
|
+
"SLOCK_AGENT_PROXY_TOKEN_FILE",
|
|
60
|
+
"SLOCK_AGENT_TOKEN_FILE",
|
|
61
|
+
"SLOCK_AGENT_TOKEN"
|
|
62
|
+
];
|
|
29
63
|
var AgentBootstrapError = class extends Error {
|
|
30
64
|
constructor(code, message) {
|
|
31
65
|
super(message);
|
|
@@ -33,6 +67,47 @@ var AgentBootstrapError = class extends Error {
|
|
|
33
67
|
this.name = "AgentBootstrapError";
|
|
34
68
|
}
|
|
35
69
|
};
|
|
70
|
+
function resolveProfileDir(slug, env = process.env) {
|
|
71
|
+
if (env.SLOCK_PROFILE_DIR) {
|
|
72
|
+
return env.SLOCK_PROFILE_DIR;
|
|
73
|
+
}
|
|
74
|
+
if (env.SLOCK_HOME) {
|
|
75
|
+
return path.join(env.SLOCK_HOME, "profiles", slug);
|
|
76
|
+
}
|
|
77
|
+
const home = env.HOME ?? os.homedir();
|
|
78
|
+
return path.join(home, ".slock", "profiles", slug);
|
|
79
|
+
}
|
|
80
|
+
function resolveProfileCredentialPath(slug, env) {
|
|
81
|
+
return path.join(resolveProfileDir(slug, env), "credential.json");
|
|
82
|
+
}
|
|
83
|
+
function readProfileCredential(slug, env) {
|
|
84
|
+
const filePath = resolveProfileCredentialPath(slug, env);
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = fs.readFileSync(filePath, "utf-8");
|
|
88
|
+
} catch (err) {
|
|
89
|
+
throw new AgentBootstrapError(
|
|
90
|
+
"PROFILE_FILE_UNREADABLE",
|
|
91
|
+
`SLOCK_PROFILE=${slug} resolved to ${filePath}, which could not be read: ${err.message}. Run \`slock agent login\` to mint a credential.`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(raw);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
throw new AgentBootstrapError(
|
|
99
|
+
"PROFILE_FILE_INVALID",
|
|
100
|
+
`${filePath} is not valid JSON: ${err.message}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.apiKey !== "string" || typeof parsed.agentId !== "string" || typeof parsed.serverUrl !== "string") {
|
|
104
|
+
throw new AgentBootstrapError(
|
|
105
|
+
"PROFILE_FILE_INVALID",
|
|
106
|
+
`${filePath} is missing required fields (apiKey, agentId, serverUrl).`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return { filePath, data: parsed };
|
|
110
|
+
}
|
|
36
111
|
function readTokenFromFile(filePath) {
|
|
37
112
|
let raw;
|
|
38
113
|
try {
|
|
@@ -53,10 +128,32 @@ function readTokenFromFile(filePath) {
|
|
|
53
128
|
return token;
|
|
54
129
|
}
|
|
55
130
|
function loadAgentContext(env = process.env) {
|
|
131
|
+
const activeCapabilities = env.SLOCK_AGENT_ACTIVE_CAPABILITIES ? env.SLOCK_AGENT_ACTIVE_CAPABILITIES.split(",").map((cap) => cap.trim()).filter(Boolean) : null;
|
|
132
|
+
const profileSlug = env.SLOCK_PROFILE;
|
|
133
|
+
if (profileSlug) {
|
|
134
|
+
const shadowed = RAW_AGENT_ENV_KEYS.filter((k) => env[k]);
|
|
135
|
+
if (shadowed.length > 0) {
|
|
136
|
+
process.stderr.write(
|
|
137
|
+
`slock: SLOCK_PROFILE=${profileSlug} active; ignoring ${shadowed.join(", ")} from env.
|
|
138
|
+
`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const { filePath, data } = readProfileCredential(profileSlug, env);
|
|
142
|
+
return {
|
|
143
|
+
agentId: data.agentId,
|
|
144
|
+
serverUrl: data.serverUrl,
|
|
145
|
+
serverId: data.serverId ?? null,
|
|
146
|
+
token: data.apiKey,
|
|
147
|
+
clientMode: "self-hosted-runner",
|
|
148
|
+
secretSource: "profile-credential-file",
|
|
149
|
+
activeCapabilities,
|
|
150
|
+
profileSlug,
|
|
151
|
+
profileCredentialPath: filePath
|
|
152
|
+
};
|
|
153
|
+
}
|
|
56
154
|
const agentId = env.SLOCK_AGENT_ID;
|
|
57
155
|
const serverUrl = env.SLOCK_SERVER_URL;
|
|
58
156
|
const serverId = env.SLOCK_SERVER_ID ?? null;
|
|
59
|
-
const activeCapabilities = env.SLOCK_AGENT_ACTIVE_CAPABILITIES ? env.SLOCK_AGENT_ACTIVE_CAPABILITIES.split(",").map((cap) => cap.trim()).filter(Boolean) : null;
|
|
60
157
|
if (!agentId) throw new AgentBootstrapError("MISSING_AGENT_ID", "SLOCK_AGENT_ID is required");
|
|
61
158
|
if (!serverUrl) throw new AgentBootstrapError("MISSING_SERVER_URL", "SLOCK_SERVER_URL is required");
|
|
62
159
|
const agentProxyUrl = env.SLOCK_AGENT_PROXY_URL;
|
|
@@ -89,18 +186,6 @@ function loadAgentContext(env = process.env) {
|
|
|
89
186
|
activeCapabilities
|
|
90
187
|
};
|
|
91
188
|
}
|
|
92
|
-
const agentCredentialFile = env.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
93
|
-
if (agentCredentialFile) {
|
|
94
|
-
return {
|
|
95
|
-
agentId,
|
|
96
|
-
serverUrl,
|
|
97
|
-
serverId,
|
|
98
|
-
token: readTokenFromFile(agentCredentialFile),
|
|
99
|
-
clientMode: "self-hosted-runner",
|
|
100
|
-
secretSource: "agent-credential-file",
|
|
101
|
-
activeCapabilities
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
189
|
const tokenFile = env.SLOCK_AGENT_TOKEN_FILE;
|
|
105
190
|
if (tokenFile) {
|
|
106
191
|
return {
|
|
@@ -127,7 +212,7 @@ function loadAgentContext(env = process.env) {
|
|
|
127
212
|
}
|
|
128
213
|
throw new AgentBootstrapError(
|
|
129
214
|
"MISSING_TOKEN",
|
|
130
|
-
"Neither SLOCK_AGENT_PROXY_TOKEN_FILE, SLOCK_AGENT_PROXY_TOKEN,
|
|
215
|
+
"Neither SLOCK_AGENT_PROXY_TOKEN_FILE, SLOCK_AGENT_PROXY_TOKEN, SLOCK_AGENT_TOKEN_FILE nor SLOCK_AGENT_TOKEN is set. The daemon should inject one of these when spawning the agent process."
|
|
131
216
|
);
|
|
132
217
|
}
|
|
133
218
|
|
|
@@ -148,12 +233,348 @@ function registerWhoamiCommand(parent) {
|
|
|
148
233
|
serverUrl: ctx.serverUrl,
|
|
149
234
|
serverId: ctx.serverId,
|
|
150
235
|
clientMode: ctx.clientMode,
|
|
151
|
-
secretSource: ctx.secretSource
|
|
236
|
+
secretSource: ctx.secretSource,
|
|
237
|
+
...ctx.profileSlug ? { profileSlug: ctx.profileSlug } : {},
|
|
238
|
+
...ctx.profileCredentialPath ? { profileCredentialPath: ctx.profileCredentialPath } : {}
|
|
152
239
|
}
|
|
153
240
|
});
|
|
154
241
|
});
|
|
155
242
|
}
|
|
156
243
|
|
|
244
|
+
// src/commands/agent/list.ts
|
|
245
|
+
import { fetch as undiciFetch } from "undici";
|
|
246
|
+
|
|
247
|
+
// src/agentLogin/deviceAuthClient.ts
|
|
248
|
+
import { fetch as fetch2 } from "undici";
|
|
249
|
+
var DeviceCodeLoginError = class extends Error {
|
|
250
|
+
constructor(code, message) {
|
|
251
|
+
super(message);
|
|
252
|
+
this.code = code;
|
|
253
|
+
this.name = "DeviceCodeLoginError";
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var ACTIONABLE_ERROR_MESSAGES = {
|
|
257
|
+
device_login_disabled: "Device login is not enabled on this Slock server. Ask an admin to set SLOCK_DEVICE_LOGIN_ENABLED=true.",
|
|
258
|
+
device_code_required: "Internal CLI bug: device_code was missing from the poll request.",
|
|
259
|
+
user_code_required: "Internal CLI bug: user_code was missing from the approve request.",
|
|
260
|
+
authorization_pending: "Still waiting for you to approve the login on the web page.",
|
|
261
|
+
expired_token: "The login code expired. Run `slock agent login` again to start a new flow.",
|
|
262
|
+
access_denied: "You denied the login request in the web approval page.",
|
|
263
|
+
device_code_consumed: "This login code has already been used. Run `slock agent login` again.",
|
|
264
|
+
device_code_invalid: "Unknown / malformed device code. Run `slock agent login` again to start a fresh flow."
|
|
265
|
+
};
|
|
266
|
+
function describeDeviceCodeLoginError(code) {
|
|
267
|
+
return ACTIONABLE_ERROR_MESSAGES[code] ?? `Device login failed (code: ${code}).`;
|
|
268
|
+
}
|
|
269
|
+
async function runDeviceCodeLogin(options) {
|
|
270
|
+
const httpFetch = options.fetchImpl ?? fetch2;
|
|
271
|
+
const base = options.serverUrl.replace(/\/+$/, "");
|
|
272
|
+
const authorizeRes = await httpFetch(`${base}/api/auth/device/authorize`, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { "content-type": "application/json" },
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
...options.clientName ? { clientName: options.clientName } : {}
|
|
277
|
+
})
|
|
278
|
+
});
|
|
279
|
+
if (!authorizeRes.ok) {
|
|
280
|
+
const payload = await safeJson(authorizeRes);
|
|
281
|
+
throw new DeviceCodeLoginError(
|
|
282
|
+
typeof payload?.code === "string" ? payload.code : "authorize_failed",
|
|
283
|
+
describeDeviceCodeLoginError(typeof payload?.code === "string" ? payload.code : "authorize_failed")
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const authorizeBody = await authorizeRes.json();
|
|
287
|
+
if (!authorizeBody.deviceCode || !authorizeBody.userCode || !authorizeBody.verificationUri) {
|
|
288
|
+
throw new DeviceCodeLoginError(
|
|
289
|
+
"authorize_response_invalid",
|
|
290
|
+
"Server's authorize response was missing deviceCode / userCode / verificationUri."
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const verificationUri = authorizeBody.verificationUri.startsWith("http") ? authorizeBody.verificationUri : `${base}${authorizeBody.verificationUri}`;
|
|
294
|
+
await options.onUserAction({
|
|
295
|
+
verificationUri,
|
|
296
|
+
userCode: authorizeBody.userCode,
|
|
297
|
+
expiresInSeconds: authorizeBody.expiresIn ?? 0
|
|
298
|
+
});
|
|
299
|
+
const serverIntervalMs = (authorizeBody.interval ?? 5) * 1e3;
|
|
300
|
+
const pollIntervalMs = options.pollIntervalOverrideMs ?? serverIntervalMs;
|
|
301
|
+
const deadlineMs = Date.now() + Math.max(1, authorizeBody.expiresIn ?? 600) * 1e3;
|
|
302
|
+
while (Date.now() < deadlineMs) {
|
|
303
|
+
const tokenRes = await httpFetch(`${base}/api/auth/device/token`, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: { "content-type": "application/json" },
|
|
306
|
+
body: JSON.stringify({ deviceCode: authorizeBody.deviceCode })
|
|
307
|
+
});
|
|
308
|
+
if (tokenRes.ok) {
|
|
309
|
+
const tokenBody = await tokenRes.json();
|
|
310
|
+
if (!tokenBody.accessToken || !tokenBody.refreshToken || !tokenBody.userId) {
|
|
311
|
+
throw new DeviceCodeLoginError(
|
|
312
|
+
"token_response_invalid",
|
|
313
|
+
"Server's token response was missing accessToken / refreshToken / userId."
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
accessToken: tokenBody.accessToken,
|
|
318
|
+
refreshToken: tokenBody.refreshToken,
|
|
319
|
+
userId: tokenBody.userId
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const tokenError = await safeJson(tokenRes);
|
|
323
|
+
const code = typeof tokenError?.code === "string" ? tokenError.code : "token_failed";
|
|
324
|
+
if (code === "authorization_pending") {
|
|
325
|
+
await delay(pollIntervalMs);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
throw new DeviceCodeLoginError(code, describeDeviceCodeLoginError(code));
|
|
329
|
+
}
|
|
330
|
+
throw new DeviceCodeLoginError(
|
|
331
|
+
"expired_token",
|
|
332
|
+
describeDeviceCodeLoginError("expired_token")
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
async function safeJson(res) {
|
|
336
|
+
try {
|
|
337
|
+
return await res.json();
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function delay(ms) {
|
|
343
|
+
if (ms <= 0) return Promise.resolve();
|
|
344
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/commands/agent/list.ts
|
|
348
|
+
function describeListResult(reason, serverUrl) {
|
|
349
|
+
switch (reason) {
|
|
350
|
+
case "ok":
|
|
351
|
+
return `Ask the user which agent to bind to this machine, then run \`slock agent login --server ${serverUrl} --agent <id>\` with the selected agent id.`;
|
|
352
|
+
case "no_manageable_server":
|
|
353
|
+
return "You are logged in but don't have `manageAgents` on any server you're a member of. Ask a server owner or admin to grant the capability, then rerun `slock agent list`.";
|
|
354
|
+
case "no_agents_on_manageable_servers":
|
|
355
|
+
return "You have `manageAgents` on at least one server, but no agents exist on those servers yet. Ask the user to create an agent first (via web UI), then rerun `slock agent list`.";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function registerAgentListCommand(parent) {
|
|
359
|
+
parent.command("list").description(
|
|
360
|
+
"List Slock agents the user can mint credentials for (after a device-code login)."
|
|
361
|
+
).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").option("--client-name <label>", "Human-readable label shown on the web approval page").action(async (options) => {
|
|
362
|
+
let userSession;
|
|
363
|
+
try {
|
|
364
|
+
userSession = await runDeviceCodeLogin({
|
|
365
|
+
serverUrl: options.server,
|
|
366
|
+
...options.clientName ? { clientName: options.clientName } : {},
|
|
367
|
+
onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
|
|
368
|
+
process.stderr.write(
|
|
369
|
+
`Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
|
|
370
|
+
`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
} catch (err) {
|
|
375
|
+
if (err instanceof DeviceCodeLoginError) {
|
|
376
|
+
fail(err.code, err.message);
|
|
377
|
+
}
|
|
378
|
+
throw err;
|
|
379
|
+
}
|
|
380
|
+
const res = await undiciFetch(
|
|
381
|
+
`${options.server.replace(/\/+$/, "")}/api/agents/manageable`,
|
|
382
|
+
{
|
|
383
|
+
method: "GET",
|
|
384
|
+
headers: {
|
|
385
|
+
"content-type": "application/json",
|
|
386
|
+
authorization: `Bearer ${userSession.accessToken}`
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
if (!res.ok) {
|
|
391
|
+
let body = null;
|
|
392
|
+
try {
|
|
393
|
+
body = await res.json();
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
fail(
|
|
397
|
+
body?.code ?? `list_failed_${res.status}`,
|
|
398
|
+
body?.error ?? `Failed to list manageable agents (status ${res.status}).`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const payload = await res.json();
|
|
402
|
+
const agents = payload.data?.agents ?? [];
|
|
403
|
+
const reason = payload.data?.reason ?? (agents.length > 0 ? "ok" : "no_agents_on_manageable_servers");
|
|
404
|
+
const suggestedNextAction = describeListResult(reason, options.server);
|
|
405
|
+
emit({
|
|
406
|
+
ok: true,
|
|
407
|
+
data: {
|
|
408
|
+
agents,
|
|
409
|
+
reason,
|
|
410
|
+
...typeof payload.data?.manageable_server_count === "number" ? { manageable_server_count: payload.data.manageable_server_count } : {},
|
|
411
|
+
suggested_next_action: suggestedNextAction
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/commands/agent/login.ts
|
|
418
|
+
import { mkdir, stat, writeFile } from "fs/promises";
|
|
419
|
+
import path2 from "path";
|
|
420
|
+
import { fetch as undiciFetch2 } from "undici";
|
|
421
|
+
function registerAgentLoginCommand(parent) {
|
|
422
|
+
parent.command("login").description(
|
|
423
|
+
"Sign this CLI in as a specific Slock agent via the device-code login grant."
|
|
424
|
+
).requiredOption("--server <url>", "Slock server base URL, e.g. https://slock.example.com").requiredOption("--agent <agentId>", "Agent id to log in as").option("--client-name <label>", "Human-readable label shown on the web approval page").option("--profile-slug <slug>", "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use.").option("--profile-dir <path>", "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)").action(async (options) => {
|
|
425
|
+
const invalidShape = describeInvalidAgentIdShape(options.agent);
|
|
426
|
+
if (invalidShape) {
|
|
427
|
+
fail("INVALID_AGENT_ID", invalidShape, {
|
|
428
|
+
suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const profileSlug = options.profileSlug ?? options.agent;
|
|
432
|
+
const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
|
|
433
|
+
const credentialPath = path2.join(profileDir, "credential.json");
|
|
434
|
+
if (await profileFileExists(credentialPath)) {
|
|
435
|
+
fail(
|
|
436
|
+
"PROFILE_ALREADY_EXISTS",
|
|
437
|
+
`Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
|
|
438
|
+
{
|
|
439
|
+
suggestedNextAction: `Use a different \`--profile-slug <slug>\` to mint a coexistent credential, OR manually delete ${credentialPath} and rerun login. Note: the existing sk_agent_* on the server is NOT revoked by deleting the local file \u2014 it remains valid until it expires or is explicitly revoked from the agent settings UI.`
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
let userSession;
|
|
444
|
+
try {
|
|
445
|
+
userSession = await runDeviceCodeLogin({
|
|
446
|
+
serverUrl: options.server,
|
|
447
|
+
...options.clientName ? { clientName: options.clientName } : {},
|
|
448
|
+
onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
|
|
449
|
+
process.stderr.write(
|
|
450
|
+
`Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
|
|
451
|
+
`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
} catch (err) {
|
|
456
|
+
if (err instanceof DeviceCodeLoginError) {
|
|
457
|
+
fail(err.code, err.message);
|
|
458
|
+
}
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
461
|
+
const mintRes = await undiciFetch2(
|
|
462
|
+
`${options.server.replace(/\/+$/, "")}/api/agents/${encodeURIComponent(options.agent)}/credentials`,
|
|
463
|
+
{
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: {
|
|
466
|
+
"content-type": "application/json",
|
|
467
|
+
authorization: `Bearer ${userSession.accessToken}`
|
|
468
|
+
},
|
|
469
|
+
body: JSON.stringify({})
|
|
470
|
+
}
|
|
471
|
+
);
|
|
472
|
+
if (!mintRes.ok) {
|
|
473
|
+
const body = await safeJson2(mintRes);
|
|
474
|
+
const code = body?.code ?? `mint_failed_${mintRes.status}`;
|
|
475
|
+
const detail = describeMintError(code, options.server);
|
|
476
|
+
fail(
|
|
477
|
+
code,
|
|
478
|
+
detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
|
|
479
|
+
detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
const minted = await mintRes.json();
|
|
483
|
+
if (!minted.apiKey || !minted.agentId || !minted.serverId) {
|
|
484
|
+
fail(
|
|
485
|
+
"mint_response_invalid",
|
|
486
|
+
"Server mint response was missing apiKey / agentId / serverId."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
await mkdir(profileDir, { recursive: true, mode: 448 });
|
|
490
|
+
await writeFile(
|
|
491
|
+
credentialPath,
|
|
492
|
+
JSON.stringify(
|
|
493
|
+
{
|
|
494
|
+
schemaVersion: 1,
|
|
495
|
+
serverUrl: options.server,
|
|
496
|
+
agentId: minted.agentId,
|
|
497
|
+
agentName: minted.agentName,
|
|
498
|
+
serverId: minted.serverId,
|
|
499
|
+
credentialId: minted.credentialId,
|
|
500
|
+
scopes: minted.scopes,
|
|
501
|
+
apiKey: minted.apiKey,
|
|
502
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
503
|
+
},
|
|
504
|
+
null,
|
|
505
|
+
2
|
|
506
|
+
) + "\n",
|
|
507
|
+
{ mode: 384 }
|
|
508
|
+
);
|
|
509
|
+
emit({
|
|
510
|
+
ok: true,
|
|
511
|
+
data: {
|
|
512
|
+
agentId: minted.agentId,
|
|
513
|
+
agentName: minted.agentName,
|
|
514
|
+
serverId: minted.serverId,
|
|
515
|
+
credentialId: minted.credentialId,
|
|
516
|
+
scopes: minted.scopes,
|
|
517
|
+
profileSlug,
|
|
518
|
+
credentialPath
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async function safeJson2(res) {
|
|
524
|
+
try {
|
|
525
|
+
return await res.json();
|
|
526
|
+
} catch {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function profileFileExists(filePath) {
|
|
531
|
+
try {
|
|
532
|
+
await stat(filePath);
|
|
533
|
+
return true;
|
|
534
|
+
} catch {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function describeInvalidAgentIdShape(input) {
|
|
539
|
+
const trimmed = input.trim();
|
|
540
|
+
if (trimmed.length === 0) {
|
|
541
|
+
return "--agent must not be empty.";
|
|
542
|
+
}
|
|
543
|
+
if (trimmed.startsWith("@")) {
|
|
544
|
+
return "--agent expects an agent id (an opaque server-issued identifier), not an @handle.";
|
|
545
|
+
}
|
|
546
|
+
if (trimmed.startsWith("#")) {
|
|
547
|
+
return "--agent expects an agent id, not a #channel name.";
|
|
548
|
+
}
|
|
549
|
+
if (/^https?:\/\//i.test(trimmed) || trimmed.includes("/")) {
|
|
550
|
+
return "--agent expects an agent id, not a URL or path.";
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
function describeMintError(code, serverUrl) {
|
|
555
|
+
switch (code) {
|
|
556
|
+
case "device_login_disabled":
|
|
557
|
+
return { message: describeDeviceCodeLoginError(code) ?? code };
|
|
558
|
+
case "agent_missing":
|
|
559
|
+
return {
|
|
560
|
+
message: "Agent id is not known on this server, or the user you approved with isn't a member of the agent's server.",
|
|
561
|
+
suggestedNextAction: `Run \`slock agent list --server ${serverUrl}\` to see manageable agents, then rerun login with --agent <id>.`
|
|
562
|
+
};
|
|
563
|
+
case "insufficient_role":
|
|
564
|
+
return {
|
|
565
|
+
message: "The user you approved with isn't a server owner or admin on the agent's server, so they can't mint agent credentials.",
|
|
566
|
+
suggestedNextAction: "Ask a server owner or admin to grant you the `manageAgents` capability on this server, then rerun login."
|
|
567
|
+
};
|
|
568
|
+
case "scopes_invalid":
|
|
569
|
+
case "scopes_empty":
|
|
570
|
+
case "name_invalid":
|
|
571
|
+
return {
|
|
572
|
+
message: "Invalid request body for the agent credential mint. Re-run with default flags."
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
return void 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
157
578
|
// ../shared/src/tracing/index.ts
|
|
158
579
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
159
580
|
var TRACE_ID_HEX_LENGTH = 32;
|
|
@@ -1007,10 +1428,10 @@ function mergeDefs(...defs) {
|
|
|
1007
1428
|
function cloneDef(schema) {
|
|
1008
1429
|
return mergeDefs(schema._zod.def);
|
|
1009
1430
|
}
|
|
1010
|
-
function getElementAtPath(obj,
|
|
1011
|
-
if (!
|
|
1431
|
+
function getElementAtPath(obj, path4) {
|
|
1432
|
+
if (!path4)
|
|
1012
1433
|
return obj;
|
|
1013
|
-
return
|
|
1434
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
1014
1435
|
}
|
|
1015
1436
|
function promiseAllObject(promisesObj) {
|
|
1016
1437
|
const keys = Object.keys(promisesObj);
|
|
@@ -1393,11 +1814,11 @@ function aborted(x, startIndex = 0) {
|
|
|
1393
1814
|
}
|
|
1394
1815
|
return false;
|
|
1395
1816
|
}
|
|
1396
|
-
function prefixIssues(
|
|
1817
|
+
function prefixIssues(path4, issues) {
|
|
1397
1818
|
return issues.map((iss) => {
|
|
1398
1819
|
var _a2;
|
|
1399
1820
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
1400
|
-
iss.path.unshift(
|
|
1821
|
+
iss.path.unshift(path4);
|
|
1401
1822
|
return iss;
|
|
1402
1823
|
});
|
|
1403
1824
|
}
|
|
@@ -1580,7 +2001,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1580
2001
|
}
|
|
1581
2002
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
1582
2003
|
const result = { errors: [] };
|
|
1583
|
-
const processError = (error49,
|
|
2004
|
+
const processError = (error49, path4 = []) => {
|
|
1584
2005
|
var _a2, _b;
|
|
1585
2006
|
for (const issue2 of error49.issues) {
|
|
1586
2007
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -1590,7 +2011,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1590
2011
|
} else if (issue2.code === "invalid_element") {
|
|
1591
2012
|
processError({ issues: issue2.issues }, issue2.path);
|
|
1592
2013
|
} else {
|
|
1593
|
-
const fullpath = [...
|
|
2014
|
+
const fullpath = [...path4, ...issue2.path];
|
|
1594
2015
|
if (fullpath.length === 0) {
|
|
1595
2016
|
result.errors.push(mapper(issue2));
|
|
1596
2017
|
continue;
|
|
@@ -1622,8 +2043,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
1622
2043
|
}
|
|
1623
2044
|
function toDotPath(_path) {
|
|
1624
2045
|
const segs = [];
|
|
1625
|
-
const
|
|
1626
|
-
for (const seg of
|
|
2046
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2047
|
+
for (const seg of path4) {
|
|
1627
2048
|
if (typeof seg === "number")
|
|
1628
2049
|
segs.push(`[${seg}]`);
|
|
1629
2050
|
else if (typeof seg === "symbol")
|
|
@@ -13600,13 +14021,13 @@ function resolveRef(ref, ctx) {
|
|
|
13600
14021
|
if (!ref.startsWith("#")) {
|
|
13601
14022
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
13602
14023
|
}
|
|
13603
|
-
const
|
|
13604
|
-
if (
|
|
14024
|
+
const path4 = ref.slice(1).split("/").filter(Boolean);
|
|
14025
|
+
if (path4.length === 0) {
|
|
13605
14026
|
return ctx.rootSchema;
|
|
13606
14027
|
}
|
|
13607
14028
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
13608
|
-
if (
|
|
13609
|
-
const key =
|
|
14029
|
+
if (path4[0] === defsKey) {
|
|
14030
|
+
const key = path4[1];
|
|
13610
14031
|
if (!key || !ctx.defs[key]) {
|
|
13611
14032
|
throw new Error(`Reference not found: ${ref}`);
|
|
13612
14033
|
}
|
|
@@ -14038,13 +14459,16 @@ var agentCreateOperationSchema = external_exports.object({
|
|
|
14038
14459
|
name: external_exports.string().trim().min(1).max(60),
|
|
14039
14460
|
description: external_exports.string().trim().max(500).optional(),
|
|
14040
14461
|
/**
|
|
14041
|
-
*
|
|
14042
|
-
*
|
|
14043
|
-
*
|
|
14044
|
-
*
|
|
14045
|
-
*
|
|
14046
|
-
*
|
|
14462
|
+
* Optional computer placement contract. Agents may only set this when the
|
|
14463
|
+
* human request is explicitly computer-bound; server prepare resolves the
|
|
14464
|
+
* name/UUID and stores the UUID-only form. `suggestedComputer` preselects
|
|
14465
|
+
* the dialog when available; `requiredComputer` prevents silent fallback to
|
|
14466
|
+
* any other computer.
|
|
14467
|
+
*
|
|
14468
|
+
* Runtime / model / reasoning effort remain human-picked technical fields.
|
|
14047
14469
|
*/
|
|
14470
|
+
suggestedComputer: idOrHandleSchema.optional(),
|
|
14471
|
+
requiredComputer: idOrHandleSchema.optional(),
|
|
14048
14472
|
draftHint: draftHintSchema
|
|
14049
14473
|
});
|
|
14050
14474
|
var channelAddMemberOperationSchema = external_exports.object({
|
|
@@ -14067,6 +14491,11 @@ var actionCardActionSchema = external_exports.discriminatedUnion("type", [
|
|
|
14067
14491
|
channelAddMemberOperationSchema
|
|
14068
14492
|
]);
|
|
14069
14493
|
function validateActionCardAction(action) {
|
|
14494
|
+
if (action.type === "agent:create") {
|
|
14495
|
+
if (action.suggestedComputer && action.requiredComputer) {
|
|
14496
|
+
return "agent:create must include only one of suggestedComputer or requiredComputer";
|
|
14497
|
+
}
|
|
14498
|
+
}
|
|
14070
14499
|
if (action.type === "channel:add_member") {
|
|
14071
14500
|
const total = (action.humans?.length ?? 0) + (action.agents?.length ?? 0);
|
|
14072
14501
|
if (total === 0) {
|
|
@@ -15004,11 +15433,11 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
|
|
|
15004
15433
|
|
|
15005
15434
|
// src/commands/message/_continueDraftState.ts
|
|
15006
15435
|
import fs2 from "fs";
|
|
15007
|
-
import
|
|
15008
|
-
import
|
|
15436
|
+
import os2 from "os";
|
|
15437
|
+
import path3 from "path";
|
|
15009
15438
|
var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
|
|
15010
15439
|
function stateFilePath(agentId) {
|
|
15011
|
-
return
|
|
15440
|
+
return path3.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
|
|
15012
15441
|
}
|
|
15013
15442
|
function readState(agentId) {
|
|
15014
15443
|
const filePath = stateFilePath(agentId);
|
|
@@ -15022,7 +15451,7 @@ function readState(agentId) {
|
|
|
15022
15451
|
}
|
|
15023
15452
|
function writeState(agentId, state) {
|
|
15024
15453
|
const filePath = stateFilePath(agentId);
|
|
15025
|
-
fs2.mkdirSync(
|
|
15454
|
+
fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
15026
15455
|
fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
|
|
15027
15456
|
}
|
|
15028
15457
|
function getSavedDraft(agentId, target) {
|
|
@@ -15305,8 +15734,8 @@ async function drainInbox(ctx, opts) {
|
|
|
15305
15734
|
const query = [];
|
|
15306
15735
|
if (opts.block) query.push("block=true");
|
|
15307
15736
|
if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
|
|
15308
|
-
const
|
|
15309
|
-
const res = await client.request("GET",
|
|
15737
|
+
const path4 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
|
|
15738
|
+
const res = await client.request("GET", path4);
|
|
15310
15739
|
if (!res.ok) {
|
|
15311
15740
|
const code = res.status >= 500 ? "SERVER_5XX" : failCode;
|
|
15312
15741
|
fail(code, res.error ?? `HTTP ${res.status}`);
|
|
@@ -15481,7 +15910,7 @@ function registerReactCommand(parent) {
|
|
|
15481
15910
|
}
|
|
15482
15911
|
|
|
15483
15912
|
// src/commands/attachment/upload.ts
|
|
15484
|
-
import { existsSync, statSync, readFileSync } from "fs";
|
|
15913
|
+
import { existsSync, statSync, readFileSync as readFileSync2 } from "fs";
|
|
15485
15914
|
import { basename } from "path";
|
|
15486
15915
|
var MAX_ATTACHMENT_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
15487
15916
|
var MAX_ATTACHMENT_UPLOAD_LABEL = "50MB";
|
|
@@ -15581,12 +16010,12 @@ function registerAttachmentUploadCommand(parent) {
|
|
|
15581
16010
|
if (!existsSync(opts.path)) {
|
|
15582
16011
|
fail("INVALID_ARG", `--path does not exist: ${opts.path}`);
|
|
15583
16012
|
}
|
|
15584
|
-
const
|
|
15585
|
-
if (!
|
|
16013
|
+
const stat2 = statSync(opts.path);
|
|
16014
|
+
if (!stat2.isFile()) {
|
|
15586
16015
|
fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
|
|
15587
16016
|
}
|
|
15588
16017
|
try {
|
|
15589
|
-
validateUploadFileSize(
|
|
16018
|
+
validateUploadFileSize(stat2.size);
|
|
15590
16019
|
} catch (err) {
|
|
15591
16020
|
if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
|
|
15592
16021
|
throw err;
|
|
@@ -15609,7 +16038,7 @@ function registerAttachmentUploadCommand(parent) {
|
|
|
15609
16038
|
fail(code, resolved.error ?? `Could not resolve channel: ${opts.channel}`);
|
|
15610
16039
|
}
|
|
15611
16040
|
const channelId = resolved.data.channelId;
|
|
15612
|
-
const buffer =
|
|
16041
|
+
const buffer = readFileSync2(opts.path);
|
|
15613
16042
|
const filename = basename(opts.path);
|
|
15614
16043
|
let explicitMimeType;
|
|
15615
16044
|
try {
|
|
@@ -16011,7 +16440,7 @@ function registerProfileShowCommand(parent) {
|
|
|
16011
16440
|
|
|
16012
16441
|
// src/commands/profile/update.ts
|
|
16013
16442
|
import { basename as basename2 } from "path";
|
|
16014
|
-
import { existsSync as existsSync2, readFileSync as
|
|
16443
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
16015
16444
|
var MAX_PROFILE_AVATAR_BYTES = 2 * 1024 * 1024;
|
|
16016
16445
|
var PROFILE_AVATAR_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
16017
16446
|
"image/jpeg",
|
|
@@ -16050,17 +16479,17 @@ function readAvatarFile(avatarFile) {
|
|
|
16050
16479
|
if (!existsSync2(avatarFile)) {
|
|
16051
16480
|
fail("PROFILE_FILE_NOT_FOUND", `Avatar file does not exist: ${avatarFile}`);
|
|
16052
16481
|
}
|
|
16053
|
-
const
|
|
16054
|
-
if (!
|
|
16482
|
+
const stat2 = statSync2(avatarFile);
|
|
16483
|
+
if (!stat2.isFile()) {
|
|
16055
16484
|
fail("PROFILE_FILE_NOT_FOUND", `Avatar file is not a regular file: ${avatarFile}`);
|
|
16056
16485
|
}
|
|
16057
|
-
if (
|
|
16486
|
+
if (stat2.size > MAX_PROFILE_AVATAR_BYTES) {
|
|
16058
16487
|
fail(
|
|
16059
16488
|
"PROFILE_AVATAR_TOO_LARGE",
|
|
16060
|
-
`Avatar file is ${
|
|
16489
|
+
`Avatar file is ${stat2.size} bytes; max size is ${MAX_PROFILE_AVATAR_BYTES} bytes`
|
|
16061
16490
|
);
|
|
16062
16491
|
}
|
|
16063
|
-
const buffer =
|
|
16492
|
+
const buffer = readFileSync3(avatarFile);
|
|
16064
16493
|
const filename = basename2(avatarFile);
|
|
16065
16494
|
const mimeType = inferImageMimeType(filename, buffer);
|
|
16066
16495
|
if (!mimeType || !PROFILE_AVATAR_MIME_TYPES.has(mimeType)) {
|
|
@@ -16683,10 +17112,22 @@ function registerReminderLogCommand(parent) {
|
|
|
16683
17112
|
// src/index.ts
|
|
16684
17113
|
var program = new Command();
|
|
16685
17114
|
program.name("slock").description(
|
|
16686
|
-
"Agent-facing
|
|
16687
|
-
).
|
|
17115
|
+
"Agent-facing CLI for Slock. Two entry shapes: (A) self-managed agent via `slock agent login --profile-slug <slug>` to create a profile, then `slock --profile <slug>` (or SLOCK_PROFILE=<slug>) to use it; (B) daemon-injected runner, where the local `slock` wrapper sets the SLOCK_AGENT_* env vars for you."
|
|
17116
|
+
).option(
|
|
17117
|
+
"-p, --profile <slug>",
|
|
17118
|
+
"Use the credential at $SLOCK_HOME/profiles/<slug>/credential.json (or ~/.slock/profiles/<slug>/credential.json when SLOCK_HOME is unset). Equivalent to setting SLOCK_PROFILE=<slug>. To create a new profile, use `slock agent login --profile-slug <slug>`."
|
|
17119
|
+
).version(readCliVersion());
|
|
17120
|
+
program.hook("preAction", () => {
|
|
17121
|
+
const opts = program.opts();
|
|
17122
|
+
if (opts.profile) {
|
|
17123
|
+
process.env.SLOCK_PROFILE = opts.profile;
|
|
17124
|
+
}
|
|
17125
|
+
});
|
|
16688
17126
|
var authCmd = program.command("auth").description("Auth introspection");
|
|
16689
17127
|
registerWhoamiCommand(authCmd);
|
|
17128
|
+
var agentCmd = program.command("agent").description("Self-managed agent onboarding (device-code login \u2192 sk_agent_* mint \u2192 ~/.slock/profiles/<slug>/credential.json)");
|
|
17129
|
+
registerAgentLoginCommand(agentCmd);
|
|
17130
|
+
registerAgentListCommand(agentCmd);
|
|
16690
17131
|
var channelCmd = program.command("channel").description("Channel membership operations");
|
|
16691
17132
|
registerChannelMembersCommand(channelCmd);
|
|
16692
17133
|
registerChannelJoinCommand(channelCmd);
|
package/dist/core.js
CHANGED
package/dist/index.js
CHANGED