@slock-ai/daemon 0.57.3 → 0.57.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/cli/index.js CHANGED
@@ -293,7 +293,6 @@ var ApiClient = class {
293
293
  const suffix = pathname.slice(agentPrefix.length);
294
294
  if (suffix === "/server") return "/internal/agent-api/server";
295
295
  if (suffix === "/send") return "/internal/agent-api/send";
296
- if (suffix === "/inbox" || suffix.startsWith("/inbox?")) return `/internal/agent-api/inbox${suffix.slice("/inbox".length)}`;
297
296
  if (suffix.startsWith("/history")) return `/internal/agent-api/history${suffix.slice("/history".length)}`;
298
297
  if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
299
298
  const messageResolve = /^\/messages\/([^/]+)\/resolve$/.exec(suffix);
@@ -827,6 +826,28 @@ function describeDeviceCodeLoginError(code) {
827
826
  return ACTIONABLE_ERROR_MESSAGES[code] ?? `Device login failed (code: ${code}).`;
828
827
  }
829
828
  async function runDeviceCodeLogin(options) {
829
+ const authorization = await authorizeDeviceCode({
830
+ serverUrl: options.serverUrl,
831
+ ...options.clientName ? { clientName: options.clientName } : {},
832
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
833
+ });
834
+ await options.onUserAction({
835
+ verificationUri: authorization.verificationUri,
836
+ ...authorization.verificationUriComplete ? { verificationUriComplete: authorization.verificationUriComplete } : {},
837
+ userCode: authorization.userCode,
838
+ expiresInSeconds: authorization.expiresInSeconds
839
+ });
840
+ const pollIntervalMs = options.pollIntervalOverrideMs ?? authorization.intervalMs;
841
+ const deadlineMs = Date.now() + Math.max(1, authorization.expiresInSeconds) * 1e3;
842
+ return pollDeviceToken({
843
+ serverUrl: options.serverUrl,
844
+ deviceCode: authorization.deviceCode,
845
+ pollIntervalMs,
846
+ deadlineMs,
847
+ ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}
848
+ });
849
+ }
850
+ async function authorizeDeviceCode(options) {
830
851
  const httpFetch = options.fetchImpl ?? fetch2;
831
852
  const base = options.serverUrl.replace(/\/+$/, "");
832
853
  const authorizeRes = await httpFetch(`${base}/api/auth/device/authorize`, {
@@ -850,25 +871,29 @@ async function runDeviceCodeLogin(options) {
850
871
  "Server's authorize response was missing deviceCode / userCode / verificationUri."
851
872
  );
852
873
  }
853
- const verificationUri = authorizeBody.verificationUri.startsWith("http") ? authorizeBody.verificationUri : `${base}${authorizeBody.verificationUri}`;
854
- await options.onUserAction({
855
- verificationUri,
874
+ const absolutize = (uri) => uri.startsWith("http") ? uri : `${base}${uri}`;
875
+ return {
876
+ deviceCode: authorizeBody.deviceCode,
856
877
  userCode: authorizeBody.userCode,
857
- expiresInSeconds: authorizeBody.expiresIn ?? 0
858
- });
859
- const serverIntervalMs = (authorizeBody.interval ?? 5) * 1e3;
860
- const pollIntervalMs = options.pollIntervalOverrideMs ?? serverIntervalMs;
861
- const deadlineMs = Date.now() + Math.max(1, authorizeBody.expiresIn ?? 600) * 1e3;
862
- while (Date.now() < deadlineMs) {
878
+ verificationUri: absolutize(authorizeBody.verificationUri),
879
+ ...authorizeBody.verificationUriComplete ? { verificationUriComplete: absolutize(authorizeBody.verificationUriComplete) } : {},
880
+ expiresInSeconds: authorizeBody.expiresIn ?? 600,
881
+ intervalMs: (authorizeBody.interval ?? 5) * 1e3
882
+ };
883
+ }
884
+ async function pollDeviceToken(options) {
885
+ const httpFetch = options.fetchImpl ?? fetch2;
886
+ const base = options.serverUrl.replace(/\/+$/, "");
887
+ while (Date.now() < options.deadlineMs) {
863
888
  let tokenRes;
864
889
  try {
865
890
  tokenRes = await httpFetch(`${base}/api/auth/device/token`, {
866
891
  method: "POST",
867
892
  headers: { "content-type": "application/json" },
868
- body: JSON.stringify({ deviceCode: authorizeBody.deviceCode })
893
+ body: JSON.stringify({ deviceCode: options.deviceCode })
869
894
  });
870
895
  } catch {
871
- await delay(pollIntervalMs);
896
+ await delay(options.pollIntervalMs);
872
897
  continue;
873
898
  }
874
899
  if (tokenRes.ok) {
@@ -888,7 +913,7 @@ async function runDeviceCodeLogin(options) {
888
913
  const tokenError = await safeJson(tokenRes);
889
914
  const code = typeof tokenError?.code === "string" ? tokenError.code : "token_failed";
890
915
  if (code === "authorization_pending") {
891
- await delay(pollIntervalMs);
916
+ await delay(options.pollIntervalMs);
892
917
  continue;
893
918
  }
894
919
  throw new DeviceCodeLoginError(code, describeDeviceCodeLoginError(code));
@@ -993,128 +1018,332 @@ function registerAgentListCommand(parent, runtimeOptions = {}) {
993
1018
  }
994
1019
 
995
1020
  // src/commands/agent/login.ts
996
- import { mkdir, stat, writeFile } from "fs/promises";
1021
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
997
1022
  import path3 from "path";
998
1023
  import { fetch as undiciFetch2 } from "undici";
1024
+ var WAIT_MAX_POLL_MS = 15 * 60 * 1e3;
1025
+ var SERVER_OPTION = {
1026
+ flags: "--server <url>",
1027
+ description: "Slock server base URL, e.g. https://slock.example.com"
1028
+ };
1029
+ var AGENT_OPTION = {
1030
+ flags: "--agent <agentId>",
1031
+ description: "Agent id to log in as"
1032
+ };
1033
+ var CLIENT_NAME_OPTION = {
1034
+ flags: "--client-name <label>",
1035
+ description: "Human-readable label shown on the web approval page"
1036
+ };
1037
+ var PROFILE_SLUG_OPTION = {
1038
+ flags: "--profile-slug <slug>",
1039
+ description: "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use."
1040
+ };
1041
+ var PROFILE_DIR_OPTION = {
1042
+ flags: "--profile-dir <path>",
1043
+ description: "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)"
1044
+ };
1045
+ var DEVICE_CODE_OPTION = {
1046
+ flags: "--device-code <code>",
1047
+ description: "The device_code returned by `slock agent login start`"
1048
+ };
999
1049
  var agentLoginCommand = defineCommand(
1000
1050
  {
1001
1051
  name: "login",
1002
1052
  description: "Sign this CLI in as a specific Slock agent via the device-code login grant.",
1003
1053
  options: [
1004
- { flags: "--server <url>", description: "Slock server base URL, e.g. https://slock.example.com" },
1005
- { flags: "--agent <agentId>", description: "Agent id to log in as" },
1006
- { flags: "--client-name <label>", description: "Human-readable label shown on the web approval page" },
1007
- { flags: "--profile-slug <slug>", description: "Slug to save the new profile under (defaults to the agent id). Distinct from root `slock --profile`, which selects an existing profile to use." },
1008
- { flags: "--profile-dir <path>", description: "Override the profile directory root (default resolution: SLOCK_HOME/profiles/<slug> when SLOCK_HOME is set, else ~/.slock/profiles/<slug>)" }
1054
+ SERVER_OPTION,
1055
+ AGENT_OPTION,
1056
+ CLIENT_NAME_OPTION,
1057
+ PROFILE_SLUG_OPTION,
1058
+ PROFILE_DIR_OPTION
1009
1059
  ]
1010
1060
  },
1011
1061
  async (ctx, options) => {
1012
- if (!options.server?.trim()) {
1013
- throw cliError("INVALID_ARG", "--server is required");
1014
- }
1015
- if (!options.agent?.trim()) {
1016
- throw cliError("INVALID_AGENT_ID", "--agent must not be empty.");
1062
+ validateServerAgent(options);
1063
+ const paths = resolveProfilePaths(options);
1064
+ const idempotent = await handleExistingCredential(ctx, options, paths);
1065
+ if (idempotent === "handled") return;
1066
+ let userSession;
1067
+ try {
1068
+ userSession = await runDeviceCodeLogin({
1069
+ serverUrl: options.server,
1070
+ ...options.clientName ? { clientName: options.clientName } : {},
1071
+ onUserAction: (action) => {
1072
+ ctx.io.stderr.write(formatVerificationHandoff(action));
1073
+ }
1074
+ });
1075
+ } catch (err) {
1076
+ throw asCliError(err);
1017
1077
  }
1018
- const invalidShape = describeInvalidAgentIdShape(options.agent);
1019
- if (invalidShape) {
1020
- throw cliError("INVALID_AGENT_ID", invalidShape, {
1021
- suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
1078
+ await mintAndPersist(ctx, options, userSession.accessToken, paths);
1079
+ }
1080
+ );
1081
+ var agentLoginStartCommand = defineCommand(
1082
+ {
1083
+ name: "start",
1084
+ description: "Begin device-code login and print the browser handoff, then exit (does not wait for approval).",
1085
+ options: [SERVER_OPTION, AGENT_OPTION, CLIENT_NAME_OPTION, PROFILE_SLUG_OPTION, PROFILE_DIR_OPTION]
1086
+ },
1087
+ async (ctx, options) => {
1088
+ validateServerAgent(options);
1089
+ const paths = resolveProfilePaths(options);
1090
+ let authorization;
1091
+ try {
1092
+ authorization = await authorizeDeviceCode({
1093
+ serverUrl: options.server,
1094
+ ...options.clientName ? { clientName: options.clientName } : {}
1022
1095
  });
1096
+ } catch (err) {
1097
+ throw asCliError(err);
1023
1098
  }
1024
- const profileSlug = options.profileSlug ?? options.agent;
1025
- const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
1026
- const credentialPath = path3.join(profileDir, "credential.json");
1027
- if (await profileFileExists(credentialPath)) {
1028
- throw cliError(
1029
- "PROFILE_ALREADY_EXISTS",
1030
- `Profile '${profileSlug}' already has a credential at ${credentialPath}.`,
1031
- {
1032
- 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.`
1033
- }
1034
- );
1099
+ ctx.io.stdout.write(formatStartHandoff(authorization, options, paths));
1100
+ }
1101
+ );
1102
+ var agentLoginWaitCommand = defineCommand(
1103
+ {
1104
+ name: "wait",
1105
+ description: "Wait for the user to approve a `login start` request, then mint and save the credential.",
1106
+ options: [SERVER_OPTION, AGENT_OPTION, DEVICE_CODE_OPTION, PROFILE_SLUG_OPTION, PROFILE_DIR_OPTION]
1107
+ },
1108
+ async (ctx, options) => {
1109
+ validateServerAgent(options);
1110
+ if (!options.deviceCode?.trim()) {
1111
+ throw cliError("INVALID_ARG", "--device-code is required (the value printed by `slock agent login start`).");
1035
1112
  }
1113
+ const paths = resolveProfilePaths(options);
1036
1114
  let userSession;
1037
1115
  try {
1038
- userSession = await runDeviceCodeLogin({
1116
+ userSession = await pollDeviceToken({
1039
1117
  serverUrl: options.server,
1040
- ...options.clientName ? { clientName: options.clientName } : {},
1041
- onUserAction: ({ verificationUri, userCode, expiresInSeconds }) => {
1042
- ctx.io.stderr.write(
1043
- `Open ${verificationUri} in your browser, enter code ${userCode} (expires in ~${Math.max(0, Math.floor(expiresInSeconds / 60))}m).
1044
- `
1045
- );
1046
- }
1118
+ deviceCode: options.deviceCode,
1119
+ pollIntervalMs: 5e3,
1120
+ deadlineMs: Date.now() + WAIT_MAX_POLL_MS
1047
1121
  });
1048
1122
  } catch (err) {
1049
- if (err instanceof DeviceCodeLoginError) {
1050
- throw cliError(err.code, err.message, { cause: err });
1051
- }
1052
- throw err;
1123
+ throw asCliError(err);
1053
1124
  }
1054
- const mintRes = await undiciFetch2(
1055
- `${options.server.replace(/\/+$/, "")}/api/agents/${encodeURIComponent(options.agent)}/credentials`,
1125
+ await mintAndPersist(ctx, options, userSession.accessToken, paths);
1126
+ }
1127
+ );
1128
+ var agentLoginStatusCommand = defineCommand(
1129
+ {
1130
+ name: "status",
1131
+ description: "Report whether the local profile credential is usable, expired, or needs re-login.",
1132
+ options: [SERVER_OPTION, AGENT_OPTION, PROFILE_SLUG_OPTION, PROFILE_DIR_OPTION]
1133
+ },
1134
+ async (ctx, options) => {
1135
+ validateServerAgent(options);
1136
+ const paths = resolveProfilePaths(options);
1137
+ if (!await profileFileExists(paths.credentialPath)) {
1138
+ ctx.io.stdout.write(
1139
+ `state: missing
1140
+ No credential for profile '${paths.profileSlug}' at ${paths.credentialPath}.
1141
+ Next: run \`${startCommandLine(options, paths)}\` to log in.
1142
+ `
1143
+ );
1144
+ return;
1145
+ }
1146
+ const outcome = await tryIdempotentLogin(paths.credentialPath, options);
1147
+ ctx.io.stdout.write(formatStatus(outcome, options, paths));
1148
+ }
1149
+ );
1150
+ function registerAgentLoginCommand(parent, runtimeOptions = {}) {
1151
+ registerCliCommand(parent, agentLoginCommand, runtimeOptions);
1152
+ const loginCmd = parent.commands.find((c) => c.name() === "login");
1153
+ if (!loginCmd) {
1154
+ throw new Error("internal: `login` command was not registered before attaching subcommands");
1155
+ }
1156
+ registerCliCommand(loginCmd, agentLoginStartCommand, runtimeOptions);
1157
+ registerCliCommand(loginCmd, agentLoginWaitCommand, runtimeOptions);
1158
+ registerCliCommand(loginCmd, agentLoginStatusCommand, runtimeOptions);
1159
+ }
1160
+ function validateServerAgent(options) {
1161
+ if (!options.server?.trim()) {
1162
+ throw cliError("INVALID_ARG", "--server is required");
1163
+ }
1164
+ if (!options.agent?.trim()) {
1165
+ throw cliError("INVALID_AGENT_ID", "--agent must not be empty.");
1166
+ }
1167
+ const invalidShape = describeInvalidAgentIdShape(options.agent);
1168
+ if (invalidShape) {
1169
+ throw cliError("INVALID_AGENT_ID", invalidShape, {
1170
+ suggestedNextAction: `Run \`slock agent list --server ${options.server}\` to see valid agent ids, then rerun login with --agent <id>.`
1171
+ });
1172
+ }
1173
+ }
1174
+ function resolveProfilePaths(options) {
1175
+ const profileSlug = options.profileSlug ?? options.agent;
1176
+ const profileDir = options.profileDir ?? resolveProfileDir(profileSlug);
1177
+ const credentialPath = path3.join(profileDir, "credential.json");
1178
+ return { profileSlug, profileDir, credentialPath };
1179
+ }
1180
+ async function handleExistingCredential(ctx, options, paths) {
1181
+ if (!await profileFileExists(paths.credentialPath)) {
1182
+ return "continue";
1183
+ }
1184
+ const outcome = await tryIdempotentLogin(paths.credentialPath, options);
1185
+ if (outcome === "already_logged_in") {
1186
+ ctx.io.stdout.write(
1187
+ `state: already_logged_in
1188
+ Profile '${paths.profileSlug}' is already logged in and its credential is valid. No action needed.
1189
+ Next: use \`slock --profile ${paths.profileSlug} \u2026\` to act as this agent.
1190
+ `
1191
+ );
1192
+ return "handled";
1193
+ }
1194
+ if (outcome === "mismatch") {
1195
+ throw cliError(
1196
+ "PROFILE_ALREADY_EXISTS",
1197
+ `Profile '${paths.profileSlug}' is already bound to a different agent or server at ${paths.credentialPath}.`,
1056
1198
  {
1057
- method: "POST",
1058
- headers: {
1059
- "content-type": "application/json",
1060
- authorization: `Bearer ${userSession.accessToken}`
1061
- },
1062
- body: JSON.stringify({})
1199
+ suggestedNextAction: `Use a different \`--profile-slug <slug>\` to mint a coexistent credential.`
1063
1200
  }
1064
1201
  );
1065
- if (!mintRes.ok) {
1066
- const body = await safeJson2(mintRes);
1067
- const code = body?.code ?? `mint_failed_${mintRes.status}`;
1068
- const detail = describeMintError(code, options.server);
1069
- throw cliError(
1070
- code,
1071
- detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
1072
- detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
1073
- );
1074
- }
1075
- const minted = await mintRes.json();
1076
- if (!minted.apiKey || !minted.agentId || !minted.serverId) {
1077
- throw cliError(
1078
- "mint_response_invalid",
1079
- "Server mint response was missing apiKey / agentId / serverId."
1080
- );
1202
+ }
1203
+ if (outcome === "unverified") {
1204
+ throw cliError(
1205
+ "CREDENTIAL_CHECK_FAILED",
1206
+ `Could not verify the existing credential for profile '${paths.profileSlug}' because the server is unreachable.`,
1207
+ {
1208
+ suggestedNextAction: `The credential at ${paths.credentialPath} may still be valid. Retry when the server is reachable, or use \`--profile-slug <new-slug>\` to mint a fresh credential.`
1209
+ }
1210
+ );
1211
+ }
1212
+ ctx.io.stderr.write(
1213
+ `Existing credential for profile '${paths.profileSlug}' is no longer valid. Starting device-code login to re-mint.
1214
+ `
1215
+ );
1216
+ return "continue";
1217
+ }
1218
+ async function mintAndPersist(ctx, options, accessToken, paths) {
1219
+ const mintRes = await undiciFetch2(
1220
+ `${options.server.replace(/\/+$/, "")}/api/agents/${encodeURIComponent(options.agent)}/credentials`,
1221
+ {
1222
+ method: "POST",
1223
+ headers: {
1224
+ "content-type": "application/json",
1225
+ authorization: `Bearer ${accessToken}`
1226
+ },
1227
+ body: JSON.stringify({})
1081
1228
  }
1082
- await mkdir(profileDir, { recursive: true, mode: 448 });
1083
- await writeFile(
1084
- credentialPath,
1085
- JSON.stringify(
1086
- {
1087
- schemaVersion: 1,
1088
- serverUrl: options.server,
1089
- agentId: minted.agentId,
1090
- agentName: minted.agentName,
1091
- serverId: minted.serverId,
1092
- credentialId: minted.credentialId,
1093
- scopes: minted.scopes,
1094
- apiKey: minted.apiKey,
1095
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1096
- },
1097
- null,
1098
- 2
1099
- ) + "\n",
1100
- { mode: 384 }
1229
+ );
1230
+ if (!mintRes.ok) {
1231
+ const body = await safeJson2(mintRes);
1232
+ const code = body?.code ?? `mint_failed_${mintRes.status}`;
1233
+ const detail = describeMintError(code, options.server);
1234
+ throw cliError(
1235
+ code,
1236
+ detail?.message ?? body?.error ?? `Failed to mint agent credential (status ${mintRes.status}).`,
1237
+ detail?.suggestedNextAction ? { suggestedNextAction: detail.suggestedNextAction } : void 0
1101
1238
  );
1102
- writeJson(ctx.io, {
1103
- ok: true,
1104
- data: {
1239
+ }
1240
+ const minted = await mintRes.json();
1241
+ if (!minted.apiKey || !minted.agentId || !minted.serverId) {
1242
+ throw cliError("mint_response_invalid", "Server mint response was missing apiKey / agentId / serverId.");
1243
+ }
1244
+ await mkdir(paths.profileDir, { recursive: true, mode: 448 });
1245
+ await writeFile(
1246
+ paths.credentialPath,
1247
+ JSON.stringify(
1248
+ {
1249
+ schemaVersion: 1,
1250
+ serverUrl: options.server,
1105
1251
  agentId: minted.agentId,
1106
1252
  agentName: minted.agentName,
1107
1253
  serverId: minted.serverId,
1108
1254
  credentialId: minted.credentialId,
1109
1255
  scopes: minted.scopes,
1110
- profileSlug,
1111
- credentialPath
1112
- }
1113
- });
1256
+ apiKey: minted.apiKey,
1257
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1258
+ },
1259
+ null,
1260
+ 2
1261
+ ) + "\n",
1262
+ { mode: 384 }
1263
+ );
1264
+ ctx.io.stdout.write(
1265
+ `state: authorized
1266
+ Logged in as '${minted.agentName}' on ${options.server}. Credential saved to ${paths.credentialPath}.
1267
+ Next: run \`slock manual get slock-cli-overview\` to learn the Slock CLI, then use \`slock --profile ${paths.profileSlug} \u2026\` for all commands.
1268
+ `
1269
+ );
1270
+ }
1271
+ function formatVerificationHandoff(action) {
1272
+ const mins = Math.max(0, Math.floor(action.expiresInSeconds / 60));
1273
+ if (action.verificationUriComplete) {
1274
+ return `Open this link to approve (code pre-filled): ${action.verificationUriComplete}
1275
+ (fallback) Open ${action.verificationUri} and enter code ${action.userCode}.
1276
+ Expires in ~${mins}m.
1277
+ `;
1114
1278
  }
1115
- );
1116
- function registerAgentLoginCommand(parent, runtimeOptions = {}) {
1117
- registerCliCommand(parent, agentLoginCommand, runtimeOptions);
1279
+ return `Open ${action.verificationUri} in your browser, enter code ${action.userCode} (expires in ~${mins}m).
1280
+ `;
1281
+ }
1282
+ function formatStartHandoff(authorization, options, paths) {
1283
+ const mins = Math.max(0, Math.floor(authorization.expiresInSeconds / 60));
1284
+ let out = `state: pending_human_action
1285
+ `;
1286
+ out += `Login started for profile '${paths.profileSlug}'. Ask the user to approve in a browser:
1287
+ `;
1288
+ if (authorization.verificationUriComplete) {
1289
+ out += ` Open this link (code pre-filled): ${authorization.verificationUriComplete}
1290
+ `;
1291
+ out += ` (fallback) Open ${authorization.verificationUri} and enter code ${authorization.userCode}.
1292
+ `;
1293
+ } else {
1294
+ out += ` Open ${authorization.verificationUri} and enter code ${authorization.userCode}.
1295
+ `;
1296
+ }
1297
+ out += ` Code expires in ~${mins}m.
1298
+ `;
1299
+ out += `Next: after the user approves, run \`${waitCommandLine(options, paths, authorization.deviceCode)}\` to finish.
1300
+ `;
1301
+ return out;
1302
+ }
1303
+ function formatStatus(outcome, options, paths) {
1304
+ const slug = paths.profileSlug;
1305
+ switch (outcome) {
1306
+ case "already_logged_in":
1307
+ return `state: usable
1308
+ Profile '${slug}' is logged in and the credential is valid. No action needed.
1309
+ Next: use \`slock --profile ${slug} \u2026\` to act as this agent.
1310
+ `;
1311
+ case "expired":
1312
+ return `state: expired
1313
+ The credential for profile '${slug}' is no longer valid (revoked or expired on the server).
1314
+ Next: run \`${startCommandLine(options, paths)}\` to re-login.
1315
+ `;
1316
+ case "mismatch":
1317
+ return `state: mismatch
1318
+ Profile '${slug}' is bound to a different agent or server than requested.
1319
+ Next: re-check --agent / --server, or use a different \`--profile-slug <slug>\`.
1320
+ `;
1321
+ case "invalid":
1322
+ return `state: invalid
1323
+ The credential file for profile '${slug}' is malformed or missing required fields (${paths.credentialPath}).
1324
+ Next: run \`${startCommandLine(options, paths)}\` to re-login.
1325
+ `;
1326
+ case "unverified":
1327
+ default:
1328
+ return `state: unverified
1329
+ Could not reach the server to verify profile '${slug}' (network or server error). The credential may still be valid \u2014 do not assume it is invalid.
1330
+ Next: retry \`slock agent login status\` when connectivity is restored.
1331
+ `;
1332
+ }
1333
+ }
1334
+ function startCommandLine(options, paths) {
1335
+ const slugFlag = paths.profileSlug !== options.agent ? ` --profile-slug ${paths.profileSlug}` : "";
1336
+ return `slock agent login start --server ${options.server} --agent ${options.agent}${slugFlag}`;
1337
+ }
1338
+ function waitCommandLine(options, paths, deviceCode) {
1339
+ const slugFlag = paths.profileSlug !== options.agent ? ` --profile-slug ${paths.profileSlug}` : "";
1340
+ return `slock agent login wait --server ${options.server} --agent ${options.agent} --device-code ${deviceCode}${slugFlag}`;
1341
+ }
1342
+ function asCliError(err) {
1343
+ if (err instanceof DeviceCodeLoginError) {
1344
+ return cliError(err.code, err.message, { cause: err });
1345
+ }
1346
+ return err instanceof Error ? err : new Error(String(err));
1118
1347
  }
1119
1348
  async function safeJson2(res) {
1120
1349
  try {
@@ -1123,6 +1352,50 @@ async function safeJson2(res) {
1123
1352
  return null;
1124
1353
  }
1125
1354
  }
1355
+ async function tryIdempotentLogin(credentialPath, options) {
1356
+ let credential;
1357
+ try {
1358
+ const raw = await readFile(credentialPath, "utf-8");
1359
+ credential = JSON.parse(raw);
1360
+ } catch {
1361
+ return "invalid";
1362
+ }
1363
+ const localAgentId = typeof credential.agentId === "string" ? credential.agentId : "";
1364
+ const localApiKey = typeof credential.apiKey === "string" ? credential.apiKey : "";
1365
+ const localServerUrl = typeof credential.serverUrl === "string" ? credential.serverUrl : "";
1366
+ const localServerId = typeof credential.serverId === "string" ? credential.serverId : "";
1367
+ const targetServer = options.server.replace(/\/+$/, "");
1368
+ if (localAgentId && localAgentId !== options.agent) {
1369
+ return "mismatch";
1370
+ }
1371
+ if (localServerUrl && localServerUrl.replace(/\/+$/, "") !== targetServer) {
1372
+ return "mismatch";
1373
+ }
1374
+ if (!localAgentId || !localApiKey || !localServerUrl) {
1375
+ return "invalid";
1376
+ }
1377
+ try {
1378
+ const whoamiRes = await undiciFetch2(`${targetServer}/internal/agent-api/`, {
1379
+ headers: { authorization: `Bearer ${localApiKey}` }
1380
+ });
1381
+ if (whoamiRes.ok) {
1382
+ const body = await safeJson2(whoamiRes);
1383
+ if (!body) {
1384
+ return "unverified";
1385
+ }
1386
+ if (body.agentId === localAgentId && (body.serverId === localServerId || !localServerId)) {
1387
+ return "already_logged_in";
1388
+ }
1389
+ return "mismatch";
1390
+ }
1391
+ if (whoamiRes.status === 401 || whoamiRes.status === 403) {
1392
+ return "expired";
1393
+ }
1394
+ return "unverified";
1395
+ } catch {
1396
+ return "unverified";
1397
+ }
1398
+ }
1126
1399
  async function profileFileExists(filePath) {
1127
1400
  try {
1128
1401
  await stat(filePath);
@@ -1171,6 +1444,11 @@ function describeMintError(code, serverUrl) {
1171
1444
  return void 0;
1172
1445
  }
1173
1446
 
1447
+ // src/agentCommsCore/bridge.ts
1448
+ import fs2 from "fs";
1449
+ import path4 from "path";
1450
+ import { randomUUID } from "crypto";
1451
+
1174
1452
  // ../shared/src/slockRefs.ts
1175
1453
  var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
1176
1454
  var SLOCK_REF_USER_NAME_PATTERN = SLOCK_REF_CHANNEL_NAME_PATTERN;
@@ -2059,10 +2337,10 @@ function mergeDefs(...defs) {
2059
2337
  function cloneDef(schema) {
2060
2338
  return mergeDefs(schema._zod.def);
2061
2339
  }
2062
- function getElementAtPath(obj, path6) {
2063
- if (!path6)
2340
+ function getElementAtPath(obj, path7) {
2341
+ if (!path7)
2064
2342
  return obj;
2065
- return path6.reduce((acc, key) => acc?.[key], obj);
2343
+ return path7.reduce((acc, key) => acc?.[key], obj);
2066
2344
  }
2067
2345
  function promiseAllObject(promisesObj) {
2068
2346
  const keys = Object.keys(promisesObj);
@@ -2445,11 +2723,11 @@ function aborted(x, startIndex = 0) {
2445
2723
  }
2446
2724
  return false;
2447
2725
  }
2448
- function prefixIssues(path6, issues) {
2726
+ function prefixIssues(path7, issues) {
2449
2727
  return issues.map((iss) => {
2450
2728
  var _a2;
2451
2729
  (_a2 = iss).path ?? (_a2.path = []);
2452
- iss.path.unshift(path6);
2730
+ iss.path.unshift(path7);
2453
2731
  return iss;
2454
2732
  });
2455
2733
  }
@@ -2632,7 +2910,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
2632
2910
  }
2633
2911
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
2634
2912
  const result = { errors: [] };
2635
- const processError = (error49, path6 = []) => {
2913
+ const processError = (error49, path7 = []) => {
2636
2914
  var _a2, _b;
2637
2915
  for (const issue2 of error49.issues) {
2638
2916
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -2642,7 +2920,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2642
2920
  } else if (issue2.code === "invalid_element") {
2643
2921
  processError({ issues: issue2.issues }, issue2.path);
2644
2922
  } else {
2645
- const fullpath = [...path6, ...issue2.path];
2923
+ const fullpath = [...path7, ...issue2.path];
2646
2924
  if (fullpath.length === 0) {
2647
2925
  result.errors.push(mapper(issue2));
2648
2926
  continue;
@@ -2674,8 +2952,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
2674
2952
  }
2675
2953
  function toDotPath(_path) {
2676
2954
  const segs = [];
2677
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2678
- for (const seg of path6) {
2955
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2956
+ for (const seg of path7) {
2679
2957
  if (typeof seg === "number")
2680
2958
  segs.push(`[${seg}]`);
2681
2959
  else if (typeof seg === "symbol")
@@ -14652,13 +14930,13 @@ function resolveRef(ref, ctx) {
14652
14930
  if (!ref.startsWith("#")) {
14653
14931
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14654
14932
  }
14655
- const path6 = ref.slice(1).split("/").filter(Boolean);
14656
- if (path6.length === 0) {
14933
+ const path7 = ref.slice(1).split("/").filter(Boolean);
14934
+ if (path7.length === 0) {
14657
14935
  return ctx.rootSchema;
14658
14936
  }
14659
14937
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14660
- if (path6[0] === defsKey) {
14661
- const key = path6[1];
14938
+ if (path7[0] === defsKey) {
14939
+ const key = path7[1];
14662
14940
  if (!key || !ctx.defs[key]) {
14663
14941
  throw new Error(`Reference not found: ${ref}`);
14664
14942
  }
@@ -15173,6 +15451,167 @@ function shortMessageId(value) {
15173
15451
  return value.slice(0, 8);
15174
15452
  }
15175
15453
 
15454
+ // ../shared/src/externalAgentIntegration.ts
15455
+ var EXTERNAL_AGENT_COMMS_PROTOCOL_VERSION = "agent-comms-core.v1";
15456
+ var EXTERNAL_AGENT_PROOF_SCHEMA_VERSION = "agent-proof.v1";
15457
+ var EXTERNAL_RUNTIME_INTEGRATION_MANIFEST_SCHEMA = "slock-external-runtime-integration.v1";
15458
+ var EXTERNAL_AGENT_WAKE_EVENT_SCHEMA = "slock-external-agent-wake-event.v1";
15459
+ var externalAgentCommsModeValues = ["spawn-core", "attach-core", "import-core"];
15460
+ var externalAgentIntegrationPatternValues = ["external-harness-plugin", "integrated-runtime"];
15461
+ var externalAgentIsolationValues = ["profile-scoped", "session-scoped"];
15462
+ var externalAgentWakeAdapterKindValues = ["claude-code-channels", "hermes-in-process"];
15463
+ var externalAgentWakeProofLevelValues = [
15464
+ "server_delivered",
15465
+ "harness_accepted",
15466
+ "wake_injected"
15467
+ ];
15468
+ var externalAgentProofLevelValues = [
15469
+ ...externalAgentWakeProofLevelValues,
15470
+ "model_seen"
15471
+ ];
15472
+ var externalAgentCommsLifecycleStateValues = [
15473
+ "unbound",
15474
+ "bound_stopped",
15475
+ "starting",
15476
+ "connected_replaying",
15477
+ "listening_idle",
15478
+ "handoff_pending",
15479
+ "degraded_backoff",
15480
+ "stopping",
15481
+ "stopped",
15482
+ "auth_revoked"
15483
+ ];
15484
+ var externalAgentLifecycleSourceValues = ["server_core", "comms_core", "wake_adapter", "server"];
15485
+ var externalAgentLifecycleProvenanceValues = [
15486
+ "slock_core",
15487
+ "adapter_observed",
15488
+ "agent_reported"
15489
+ ];
15490
+ var externalAgentAdapterFailureValues = [
15491
+ "no_session",
15492
+ "busy",
15493
+ "injection_failed",
15494
+ "protocol_mismatch",
15495
+ "auth_revoked"
15496
+ ];
15497
+ var externalAgentServerApiModeValues = ["interim-agent-api-events", "agent-inbox-protocol"];
15498
+ var nonEmptyStringSchema = external_exports.string().min(1);
15499
+ var isoTimestampSchema = external_exports.string().refine((value) => !Number.isNaN(Date.parse(value)), {
15500
+ message: "must be a parseable timestamp"
15501
+ });
15502
+ var externalRuntimeIntegrationManifestSchema = external_exports.object({
15503
+ schema: external_exports.literal(EXTERNAL_RUNTIME_INTEGRATION_MANIFEST_SCHEMA),
15504
+ runtimeId: nonEmptyStringSchema,
15505
+ integrationPattern: external_exports.enum(externalAgentIntegrationPatternValues),
15506
+ commsMode: external_exports.enum(externalAgentCommsModeValues),
15507
+ commsProtocolVersion: external_exports.literal(EXTERNAL_AGENT_COMMS_PROTOCOL_VERSION),
15508
+ proofSchemaVersion: external_exports.literal(EXTERNAL_AGENT_PROOF_SCHEMA_VERSION),
15509
+ minSlockCliVersion: nonEmptyStringSchema,
15510
+ multiplex: external_exports.boolean(),
15511
+ agentIsolation: external_exports.enum(externalAgentIsolationValues),
15512
+ bridgeLifecycle: external_exports.object({
15513
+ explicitStartOnly: external_exports.literal(true),
15514
+ oneShotCommandsBridgeIndependent: external_exports.literal(true),
15515
+ requiresBridgeFailureCodes: external_exports.array(external_exports.enum(["BRIDGE_NOT_RUNNING", "NO_CORE_SESSION"])).min(1),
15516
+ autoStartDefault: external_exports.literal(false)
15517
+ }).strict(),
15518
+ serverApi: external_exports.object({
15519
+ mode: external_exports.enum(externalAgentServerApiModeValues),
15520
+ deliveryOnly: external_exports.boolean(),
15521
+ cursorAuthority: external_exports.literal("model_seen_only")
15522
+ }).strict(),
15523
+ wakeAdapter: external_exports.object({
15524
+ kind: external_exports.enum(externalAgentWakeAdapterKindValues),
15525
+ protocol: nonEmptyStringSchema,
15526
+ requiresInteractiveSession: external_exports.boolean().optional()
15527
+ }).strict()
15528
+ }).strict().superRefine((value, ctx) => {
15529
+ if (!value.bridgeLifecycle.requiresBridgeFailureCodes.includes("BRIDGE_NOT_RUNNING")) {
15530
+ ctx.addIssue({
15531
+ code: "custom",
15532
+ path: ["bridgeLifecycle", "requiresBridgeFailureCodes"],
15533
+ message: "bridge-dependent surfaces must include BRIDGE_NOT_RUNNING"
15534
+ });
15535
+ }
15536
+ if (!value.bridgeLifecycle.requiresBridgeFailureCodes.includes("NO_CORE_SESSION")) {
15537
+ ctx.addIssue({
15538
+ code: "custom",
15539
+ path: ["bridgeLifecycle", "requiresBridgeFailureCodes"],
15540
+ message: "bridge-dependent surfaces must include NO_CORE_SESSION"
15541
+ });
15542
+ }
15543
+ if (value.serverApi.mode === "interim-agent-api-events" && value.serverApi.deliveryOnly !== true) {
15544
+ ctx.addIssue({
15545
+ code: "custom",
15546
+ path: ["serverApi", "deliveryOnly"],
15547
+ message: "interim agent-api events mode must be delivery-only"
15548
+ });
15549
+ }
15550
+ });
15551
+ var externalAgentWakeEventBaseSchema = external_exports.object({
15552
+ schema: external_exports.literal(EXTERNAL_AGENT_WAKE_EVENT_SCHEMA),
15553
+ eventId: nonEmptyStringSchema,
15554
+ attemptId: nonEmptyStringSchema,
15555
+ messageId: nonEmptyStringSchema,
15556
+ agentId: nonEmptyStringSchema,
15557
+ profile: nonEmptyStringSchema,
15558
+ coreSessionId: nonEmptyStringSchema,
15559
+ adapterInstance: nonEmptyStringSchema,
15560
+ runtimeSession: nonEmptyStringSchema.nullable(),
15561
+ occurredAt: isoTimestampSchema,
15562
+ lifecycleState: external_exports.enum(externalAgentCommsLifecycleStateValues),
15563
+ authority: external_exports.object({
15564
+ source: external_exports.enum(externalAgentLifecycleSourceValues),
15565
+ provenance: external_exports.enum(externalAgentLifecycleProvenanceValues)
15566
+ }).strict()
15567
+ }).strict();
15568
+ var externalAgentProofEventEnvelopeSchema = externalAgentWakeEventBaseSchema.extend({
15569
+ kind: external_exports.literal("proof"),
15570
+ proofLevel: external_exports.enum(externalAgentWakeProofLevelValues),
15571
+ outcome: external_exports.literal("ok"),
15572
+ failureMeta: external_exports.undefined().optional(),
15573
+ reason: external_exports.string().optional()
15574
+ });
15575
+ var externalAgentFailedWakeEventEnvelopeSchema = externalAgentWakeEventBaseSchema.extend({
15576
+ kind: external_exports.literal("wake_attempt"),
15577
+ proofLevel: external_exports.undefined().optional(),
15578
+ outcome: external_exports.literal("failed"),
15579
+ failureMeta: external_exports.object({
15580
+ failureClass: external_exports.enum(externalAgentAdapterFailureValues),
15581
+ retryAfterMs: external_exports.number().int().positive().optional()
15582
+ }).strict(),
15583
+ reason: nonEmptyStringSchema
15584
+ });
15585
+ var externalAgentWakeEventEnvelopeSchema = external_exports.discriminatedUnion("kind", [
15586
+ externalAgentProofEventEnvelopeSchema,
15587
+ externalAgentFailedWakeEventEnvelopeSchema
15588
+ ]);
15589
+ function validateExternalRuntimeIntegrationManifest(value) {
15590
+ return externalRuntimeIntegrationManifestSchema.parse(value);
15591
+ }
15592
+ function validateExternalAgentWakeEventEnvelope(value) {
15593
+ const event = externalAgentWakeEventEnvelopeSchema.parse(value);
15594
+ assertExternalAgentWakeEventAuthority(event);
15595
+ return event;
15596
+ }
15597
+ function assertExternalAgentWakeEventAuthority(event) {
15598
+ if (event.outcome === "failed") {
15599
+ if (event.authority.source !== "wake_adapter") {
15600
+ throw new Error("failed wake_attempt events must be sourced by wake_adapter");
15601
+ }
15602
+ return;
15603
+ }
15604
+ if (event.proofLevel === "server_delivered" && event.authority.source !== "server_core") {
15605
+ throw new Error("server_delivered proof must be sourced by server_core");
15606
+ }
15607
+ if (event.proofLevel === "harness_accepted" && event.authority.source !== "comms_core") {
15608
+ throw new Error("harness_accepted proof must be sourced by comms_core");
15609
+ }
15610
+ if (event.proofLevel === "wake_injected" && event.authority.source !== "wake_adapter") {
15611
+ throw new Error("wake_injected proof must be sourced by wake_adapter");
15612
+ }
15613
+ }
15614
+
15176
15615
  // ../shared/src/translationLanguages.ts
15177
15616
  var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
15178
15617
  "en",
@@ -15372,6 +15811,592 @@ var DISPLAY_PLAN_CONFIG = {
15372
15811
  }
15373
15812
  };
15374
15813
 
15814
+ // src/agentCommsCore/bridge.ts
15815
+ var AGENT_COMMS_PROTOCOL_VERSION = "agent-comms-core.v1";
15816
+ var AGENT_PROOF_SCHEMA_VERSION = "agent-proof.v1";
15817
+ function resolveAgentCommsBridgeStateDir(input) {
15818
+ if (input.stateDir) return input.stateDir;
15819
+ const env = input.env ?? process.env;
15820
+ if (env.SLOCK_AGENT_BRIDGE_STATE_DIR) return env.SLOCK_AGENT_BRIDGE_STATE_DIR;
15821
+ const profileSlug = requireProfileSlug(input.agentContext);
15822
+ const adapterInstance = input.adapterInstance ?? "default";
15823
+ return path4.join(
15824
+ resolveProfileDir(profileSlug, env),
15825
+ "agent-comms-core",
15826
+ safePathSegment(input.agentContext.agentId),
15827
+ safePathSegment(adapterInstance)
15828
+ );
15829
+ }
15830
+ function createFileAgentCommsBridgeStore(input) {
15831
+ const rootDir = resolveAgentCommsBridgeStateDir(input);
15832
+ const paths = {
15833
+ rootDir,
15834
+ sessionFile: path4.join(rootDir, "session.json"),
15835
+ wakeHintsFile: path4.join(rootDir, "wake-hints.jsonl"),
15836
+ proofFile: path4.join(rootDir, "proofs.jsonl")
15837
+ };
15838
+ fs2.mkdirSync(rootDir, { recursive: true, mode: 448 });
15839
+ return {
15840
+ paths,
15841
+ readSession() {
15842
+ if (!fs2.existsSync(paths.sessionFile)) return null;
15843
+ try {
15844
+ const parsed = JSON.parse(fs2.readFileSync(paths.sessionFile, "utf-8"));
15845
+ return {
15846
+ ...typeof parsed.coreSessionId === "string" ? { coreSessionId: parsed.coreSessionId } : {},
15847
+ ...typeof parsed.lastSeenHintSeq === "number" ? { lastSeenHintSeq: parsed.lastSeenHintSeq } : {}
15848
+ };
15849
+ } catch {
15850
+ return null;
15851
+ }
15852
+ },
15853
+ writeSession(session) {
15854
+ fs2.writeFileSync(paths.sessionFile, `${JSON.stringify(session, null, 2)}
15855
+ `, { mode: 384 });
15856
+ },
15857
+ readWakeHints() {
15858
+ return readJsonl(paths.wakeHintsFile);
15859
+ },
15860
+ appendWakeHintIfAbsent(hint) {
15861
+ const key = wakeHintKey(hint);
15862
+ const pending = readJsonl(paths.wakeHintsFile);
15863
+ if (pending.some((candidate) => wakeHintKey(candidate) === key)) return false;
15864
+ appendJsonl(paths.wakeHintsFile, stripContentFields(hint));
15865
+ return true;
15866
+ },
15867
+ appendProof(proof2) {
15868
+ appendJsonl(paths.proofFile, proof2);
15869
+ }
15870
+ };
15871
+ }
15872
+ async function runAgentCommsBridgeOnce(input) {
15873
+ if (input.agentContext.clientMode !== "self-hosted-runner") {
15874
+ throw new Error("slock agent bridge requires a self-hosted profile credential; run with `slock --profile <slug> agent bridge`.");
15875
+ }
15876
+ const profileSlug = requireProfileSlug(input.agentContext);
15877
+ const adapterInstance = input.adapterInstance ?? "default";
15878
+ const store = createFileAgentCommsBridgeStore({
15879
+ agentContext: input.agentContext,
15880
+ env: input.env,
15881
+ stateDir: input.stateDir,
15882
+ adapterInstance
15883
+ });
15884
+ const existingSession = store.readSession();
15885
+ const coreSessionId = input.coreSessionId ?? existingSession?.coreSessionId ?? `core_${randomUUID()}`;
15886
+ const identity = {
15887
+ agentId: input.agentContext.agentId,
15888
+ profileSlug,
15889
+ adapterInstance,
15890
+ coreSessionId
15891
+ };
15892
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
15893
+ const output = [
15894
+ lifecycle(identity, "starting", now)
15895
+ ];
15896
+ if (input.replayPending !== false) {
15897
+ const hints = store.readWakeHints();
15898
+ if (hints.length > 0) {
15899
+ output.push(lifecycle(identity, "connected_replaying", now));
15900
+ for (const hint of hints) {
15901
+ const attemptId = `attempt_${randomUUID()}`;
15902
+ output.push(handoff(identity, hint, attemptId, true, now));
15903
+ await maybeRunWakeAdapter({
15904
+ identity,
15905
+ hint,
15906
+ attemptId,
15907
+ wakeAdapter: input.wakeAdapter,
15908
+ runtimeSession: input.runtimeSession ?? null,
15909
+ now,
15910
+ output,
15911
+ store
15912
+ });
15913
+ }
15914
+ }
15915
+ }
15916
+ const since = existingSession?.lastSeenHintSeq ?? "latest";
15917
+ const fetched = await input.source.fetchWakeHints({ since, limit: input.limit ?? 50 });
15918
+ let lastSeenHintSeq = existingSession?.lastSeenHintSeq;
15919
+ for (const hint of fetched.hints) {
15920
+ const attemptId = `attempt_${randomUUID()}`;
15921
+ const serverDelivered = proof(identity, hint, attemptId, "server_delivered", now);
15922
+ store.appendProof(serverDelivered);
15923
+ output.push(serverDelivered);
15924
+ store.appendWakeHintIfAbsent(hint);
15925
+ const accepted = proof(identity, hint, attemptId, "harness_accepted", now);
15926
+ store.appendProof(accepted);
15927
+ output.push(accepted);
15928
+ output.push(handoff(identity, hint, attemptId, false, now));
15929
+ await maybeRunWakeAdapter({
15930
+ identity,
15931
+ hint,
15932
+ attemptId,
15933
+ wakeAdapter: input.wakeAdapter,
15934
+ runtimeSession: input.runtimeSession ?? null,
15935
+ now,
15936
+ output,
15937
+ store
15938
+ });
15939
+ const seq = wakeHintSeq(hint);
15940
+ if (typeof seq === "number") lastSeenHintSeq = Math.max(lastSeenHintSeq ?? 0, seq);
15941
+ }
15942
+ const fetchedLastSeq = typeof fetched.last_seen_hint_seq === "number" ? fetched.last_seen_hint_seq : typeof fetched.last_hint_seq === "number" ? fetched.last_hint_seq : void 0;
15943
+ if (typeof fetchedLastSeq === "number") lastSeenHintSeq = Math.max(lastSeenHintSeq ?? 0, fetchedLastSeq);
15944
+ store.writeSession({
15945
+ coreSessionId,
15946
+ ...typeof lastSeenHintSeq === "number" ? { lastSeenHintSeq } : {}
15947
+ });
15948
+ output.push(lifecycle(identity, fetched.hints.length > 0 ? "handoff_pending" : "listening_idle", now));
15949
+ return output;
15950
+ }
15951
+ async function maybeRunWakeAdapter(input) {
15952
+ if (!input.wakeAdapter) return;
15953
+ const wakeInput = wakeAttemptInput({
15954
+ identity: input.identity,
15955
+ hint: input.hint,
15956
+ attemptId: input.attemptId,
15957
+ runtimeSession: input.runtimeSession,
15958
+ now: input.now
15959
+ });
15960
+ const event = await wakeAdapterEvent(input.wakeAdapter, wakeInput);
15961
+ input.store.appendProof(event);
15962
+ input.output.push(event);
15963
+ }
15964
+ function wakeAttemptInput(input) {
15965
+ return {
15966
+ eventId: `event_${randomUUID()}`,
15967
+ attemptId: input.attemptId,
15968
+ messageId: wakeHintMessageId(input.hint),
15969
+ agentId: input.identity.agentId,
15970
+ profile: input.identity.profileSlug,
15971
+ coreSessionId: input.identity.coreSessionId,
15972
+ adapterInstance: input.identity.adapterInstance,
15973
+ runtimeSession: input.runtimeSession,
15974
+ occurredAt: input.now().toISOString()
15975
+ };
15976
+ }
15977
+ async function wakeAdapterEvent(wakeAdapter, input) {
15978
+ try {
15979
+ return await wakeAdapter.wake(input);
15980
+ } catch (err) {
15981
+ const reason = err instanceof Error && err.message ? err.message : "Wake adapter failed before producing a proof event";
15982
+ return validateExternalAgentWakeEventEnvelope({
15983
+ ...input,
15984
+ schema: EXTERNAL_AGENT_WAKE_EVENT_SCHEMA,
15985
+ kind: "wake_attempt",
15986
+ outcome: "failed",
15987
+ lifecycleState: "degraded_backoff",
15988
+ failureMeta: { failureClass: "injection_failed" },
15989
+ reason,
15990
+ authority: {
15991
+ source: "wake_adapter",
15992
+ provenance: "adapter_observed"
15993
+ }
15994
+ });
15995
+ }
15996
+ }
15997
+ function lifecycle(identity, state, now) {
15998
+ return {
15999
+ type: "agent_comms.lifecycle",
16000
+ eventId: `event_${randomUUID()}`,
16001
+ protocolVersion: AGENT_COMMS_PROTOCOL_VERSION,
16002
+ proofSchemaVersion: AGENT_PROOF_SCHEMA_VERSION,
16003
+ timestamp: now().toISOString(),
16004
+ coreSessionId: identity.coreSessionId,
16005
+ agentId: identity.agentId,
16006
+ profile: identity.profileSlug,
16007
+ profileSlug: identity.profileSlug,
16008
+ adapterInstance: identity.adapterInstance,
16009
+ runtimeSession: null,
16010
+ state,
16011
+ lifecycleState: state,
16012
+ source: "slock-agent-bridge",
16013
+ provenance: commsCoreProvenance("comms_core"),
16014
+ commsMode: "spawn-core"
16015
+ };
16016
+ }
16017
+ function proof(identity, wakeHint, attemptId, level, now) {
16018
+ const id = wakeHintId(wakeHint);
16019
+ const seq = wakeHintSeq(wakeHint) ?? null;
16020
+ return {
16021
+ type: "agent_comms.proof",
16022
+ eventId: `event_${randomUUID()}`,
16023
+ attemptId,
16024
+ protocolVersion: AGENT_COMMS_PROTOCOL_VERSION,
16025
+ proofSchemaVersion: AGENT_PROOF_SCHEMA_VERSION,
16026
+ timestamp: now().toISOString(),
16027
+ coreSessionId: identity.coreSessionId,
16028
+ agentId: identity.agentId,
16029
+ profile: identity.profileSlug,
16030
+ profileSlug: identity.profileSlug,
16031
+ adapterInstance: identity.adapterInstance,
16032
+ runtimeSession: null,
16033
+ lifecycleState: "handoff_pending",
16034
+ source: "slock-agent-bridge",
16035
+ provenance: commsCoreProvenance(level === "server_delivered" ? "server_core" : "comms_core"),
16036
+ wakeHintId: id,
16037
+ seq,
16038
+ proofLevel: level,
16039
+ proof: {
16040
+ level,
16041
+ wakeHintId: id,
16042
+ seq,
16043
+ target: wakeHintTarget(wakeHint),
16044
+ cursorImpact: {
16045
+ wakeDedup: true,
16046
+ deliveryAck: false,
16047
+ modelSeen: false,
16048
+ read: false
16049
+ }
16050
+ }
16051
+ };
16052
+ }
16053
+ function handoff(identity, wakeHint, attemptId, replay, now) {
16054
+ const id = wakeHintId(wakeHint);
16055
+ const seq = wakeHintSeq(wakeHint) ?? null;
16056
+ return {
16057
+ type: "agent_comms.handoff",
16058
+ eventId: `event_${randomUUID()}`,
16059
+ attemptId,
16060
+ protocolVersion: AGENT_COMMS_PROTOCOL_VERSION,
16061
+ proofSchemaVersion: AGENT_PROOF_SCHEMA_VERSION,
16062
+ timestamp: now().toISOString(),
16063
+ coreSessionId: identity.coreSessionId,
16064
+ agentId: identity.agentId,
16065
+ profile: identity.profileSlug,
16066
+ profileSlug: identity.profileSlug,
16067
+ adapterInstance: identity.adapterInstance,
16068
+ runtimeSession: null,
16069
+ state: "handoff_pending",
16070
+ lifecycleState: "handoff_pending",
16071
+ source: "slock-agent-bridge",
16072
+ provenance: commsCoreProvenance("comms_core"),
16073
+ replay,
16074
+ wakeHintId: id,
16075
+ seq,
16076
+ proofLevel: "harness_accepted",
16077
+ wakeHint: stripContentFields(wakeHint),
16078
+ acceptedProof: {
16079
+ level: "harness_accepted",
16080
+ wakeHintId: id,
16081
+ seq
16082
+ }
16083
+ };
16084
+ }
16085
+ function commsCoreProvenance(authority) {
16086
+ return {
16087
+ producer: "agent-comms-core",
16088
+ authority,
16089
+ source: "slock-agent-bridge"
16090
+ };
16091
+ }
16092
+ function readJsonl(filePath) {
16093
+ if (!fs2.existsSync(filePath)) return [];
16094
+ return fs2.readFileSync(filePath, "utf-8").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
16095
+ }
16096
+ function appendJsonl(filePath, value) {
16097
+ fs2.mkdirSync(path4.dirname(filePath), { recursive: true, mode: 448 });
16098
+ fs2.appendFileSync(filePath, `${JSON.stringify(value)}
16099
+ `, { mode: 384 });
16100
+ }
16101
+ function requireProfileSlug(agentContext) {
16102
+ if (!agentContext.profileSlug) {
16103
+ throw new Error("slock agent bridge requires SLOCK_PROFILE / --profile so state is profile-scoped.");
16104
+ }
16105
+ return agentContext.profileSlug;
16106
+ }
16107
+ function wakeHintKey(wakeHint) {
16108
+ const id = wakeHintId(wakeHint);
16109
+ if (id) return `id:${id}`;
16110
+ const seq = wakeHintSeq(wakeHint);
16111
+ if (typeof seq === "number") return `seq:${seq}`;
16112
+ return `hint:${JSON.stringify(stripContentFields(wakeHint))}`;
16113
+ }
16114
+ function wakeHintId(wakeHint) {
16115
+ if (typeof wakeHint.hintId === "string" && wakeHint.hintId.length > 0) return wakeHint.hintId;
16116
+ if (typeof wakeHint.hint_id === "string" && wakeHint.hint_id.length > 0) return wakeHint.hint_id;
16117
+ if (typeof wakeHint.eventId === "string" && wakeHint.eventId.length > 0) return wakeHint.eventId;
16118
+ if (typeof wakeHint.event_id === "string" && wakeHint.event_id.length > 0) return wakeHint.event_id;
16119
+ if (typeof wakeHint.id === "string" && wakeHint.id.length > 0) return wakeHint.id;
16120
+ return null;
16121
+ }
16122
+ function wakeHintMessageId(wakeHint) {
16123
+ if (typeof wakeHint.messageId === "string" && wakeHint.messageId.length > 0) return wakeHint.messageId;
16124
+ if (typeof wakeHint.message_id === "string" && wakeHint.message_id.length > 0) return wakeHint.message_id;
16125
+ const id = wakeHintId(wakeHint);
16126
+ if (id) return id;
16127
+ const seq = wakeHintSeq(wakeHint);
16128
+ if (typeof seq === "number") return `seq:${seq}`;
16129
+ const target = wakeHintTarget(wakeHint);
16130
+ if (target) return `target:${target}`;
16131
+ return `wake-hint:${JSON.stringify(stripContentFields(wakeHint))}`;
16132
+ }
16133
+ function wakeHintSeq(wakeHint) {
16134
+ if (typeof wakeHint.seq !== "number" || !Number.isInteger(wakeHint.seq) || wakeHint.seq <= 0) return void 0;
16135
+ return wakeHint.seq;
16136
+ }
16137
+ function wakeHintTarget(wakeHint) {
16138
+ if (typeof wakeHint.target === "string" && wakeHint.target.length > 0) return wakeHint.target;
16139
+ return null;
16140
+ }
16141
+ function stripContentFields(wakeHint) {
16142
+ const clone2 = { ...wakeHint };
16143
+ delete clone2.content;
16144
+ delete clone2.body;
16145
+ delete clone2.text;
16146
+ delete clone2.message;
16147
+ return clone2;
16148
+ }
16149
+ function safePathSegment(value) {
16150
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120) || "unknown";
16151
+ }
16152
+
16153
+ // src/external/claudeCodeWakeAdapter.ts
16154
+ var CLAUDE_CODE_CHANNELS_WAKE_PROTOCOL = "claude-code-channels.v0";
16155
+ var claudeCodeChannelsWakeManifest = validateExternalRuntimeIntegrationManifest({
16156
+ schema: EXTERNAL_RUNTIME_INTEGRATION_MANIFEST_SCHEMA,
16157
+ runtimeId: "claude",
16158
+ integrationPattern: "external-harness-plugin",
16159
+ commsMode: "spawn-core",
16160
+ commsProtocolVersion: EXTERNAL_AGENT_COMMS_PROTOCOL_VERSION,
16161
+ proofSchemaVersion: EXTERNAL_AGENT_PROOF_SCHEMA_VERSION,
16162
+ minSlockCliVersion: "0.0.3",
16163
+ multiplex: false,
16164
+ agentIsolation: "session-scoped",
16165
+ bridgeLifecycle: {
16166
+ explicitStartOnly: true,
16167
+ oneShotCommandsBridgeIndependent: true,
16168
+ requiresBridgeFailureCodes: ["BRIDGE_NOT_RUNNING", "NO_CORE_SESSION"],
16169
+ autoStartDefault: false
16170
+ },
16171
+ serverApi: {
16172
+ mode: "interim-agent-api-events",
16173
+ deliveryOnly: true,
16174
+ cursorAuthority: "model_seen_only"
16175
+ },
16176
+ wakeAdapter: {
16177
+ kind: "claude-code-channels",
16178
+ protocol: CLAUDE_CODE_CHANNELS_WAKE_PROTOCOL,
16179
+ requiresInteractiveSession: true
16180
+ }
16181
+ });
16182
+ function createClaudeCodeChannelsWakeAdapter(options = {}) {
16183
+ return {
16184
+ manifest: claudeCodeChannelsWakeManifest,
16185
+ async wake(input) {
16186
+ if (!options.endpointUrl) {
16187
+ return buildClaudeCodeWakeFailedEvent(input, {
16188
+ failureClass: "no_session",
16189
+ reason: "Claude Code Slock channel plugin endpoint is not configured; start Claude Code with the Slock channel plugin and pass its localhost wake endpoint to the bridge."
16190
+ });
16191
+ }
16192
+ const request = options.fetchImpl ?? fetch;
16193
+ let response;
16194
+ try {
16195
+ response = await request(options.endpointUrl, {
16196
+ method: "POST",
16197
+ headers: {
16198
+ "content-type": "application/json",
16199
+ ...options.token ? { "x-slock-bridge-token": options.token } : {}
16200
+ },
16201
+ body: JSON.stringify({
16202
+ schema: "slock-claude-code-channel-wake.v1",
16203
+ attemptId: input.attemptId,
16204
+ eventId: input.eventId,
16205
+ messageId: input.messageId,
16206
+ agentId: input.agentId,
16207
+ profile: input.profile,
16208
+ coreSessionId: input.coreSessionId,
16209
+ adapterInstance: input.adapterInstance,
16210
+ occurredAt: input.occurredAt
16211
+ })
16212
+ });
16213
+ } catch (err) {
16214
+ return buildClaudeCodeWakeFailedEvent(input, {
16215
+ failureClass: "no_session",
16216
+ reason: err instanceof Error && err.message ? `Claude Code Slock channel plugin endpoint is unreachable: ${err.message}` : "Claude Code Slock channel plugin endpoint is unreachable"
16217
+ });
16218
+ }
16219
+ const body = await parseWakeResponse(response);
16220
+ if (!response.ok || body.ok === false) {
16221
+ return buildClaudeCodeWakeFailedEvent(input, {
16222
+ failureClass: body.failureClass ?? statusFailureClass(response.status),
16223
+ reason: body.reason ?? `Claude Code Slock channel plugin rejected wake attempt with HTTP ${response.status}`,
16224
+ ...typeof body.retryAfterMs === "number" ? { retryAfterMs: body.retryAfterMs } : {}
16225
+ });
16226
+ }
16227
+ const runtimeSession = body.runtimeSession ?? input.runtimeSession;
16228
+ if (!runtimeSession) {
16229
+ return buildClaudeCodeWakeFailedEvent(input, {
16230
+ failureClass: "protocol_mismatch",
16231
+ reason: "Claude Code Slock channel plugin accepted wake but did not return runtimeSession"
16232
+ });
16233
+ }
16234
+ return buildClaudeCodeWakeInjectedEvent({
16235
+ ...input,
16236
+ runtimeSession
16237
+ });
16238
+ }
16239
+ };
16240
+ }
16241
+ function buildClaudeCodeWakeInjectedEvent(input) {
16242
+ return validateExternalAgentWakeEventEnvelope({
16243
+ ...input,
16244
+ schema: "slock-external-agent-wake-event.v1",
16245
+ kind: "proof",
16246
+ proofLevel: "wake_injected",
16247
+ outcome: "ok",
16248
+ lifecycleState: "handoff_pending",
16249
+ authority: {
16250
+ source: "wake_adapter",
16251
+ provenance: "adapter_observed"
16252
+ }
16253
+ });
16254
+ }
16255
+ async function parseWakeResponse(response) {
16256
+ const contentType = response.headers.get("content-type") ?? "";
16257
+ if (!contentType.includes("application/json")) return {};
16258
+ try {
16259
+ return await response.json();
16260
+ } catch {
16261
+ return {};
16262
+ }
16263
+ }
16264
+ function statusFailureClass(status) {
16265
+ if (status === 401 || status === 403) return "auth_revoked";
16266
+ if (status === 409 || status === 429) return "busy";
16267
+ if (status === 426 || status === 501) return "protocol_mismatch";
16268
+ if (status === 404 || status === 410) return "no_session";
16269
+ return "injection_failed";
16270
+ }
16271
+ function buildClaudeCodeWakeFailedEvent(input, failure) {
16272
+ return validateExternalAgentWakeEventEnvelope({
16273
+ ...input,
16274
+ schema: "slock-external-agent-wake-event.v1",
16275
+ kind: "wake_attempt",
16276
+ outcome: "failed",
16277
+ failureMeta: {
16278
+ failureClass: failure.failureClass,
16279
+ ...failure.retryAfterMs ? { retryAfterMs: failure.retryAfterMs } : {}
16280
+ },
16281
+ reason: failure.reason,
16282
+ lifecycleState: "degraded_backoff",
16283
+ authority: {
16284
+ source: "wake_adapter",
16285
+ provenance: "adapter_observed"
16286
+ }
16287
+ });
16288
+ }
16289
+
16290
+ // src/commands/agent/bridge.ts
16291
+ var agentBridgeCommand = defineCommand(
16292
+ {
16293
+ name: "bridge",
16294
+ description: "Run the explicit long-lived Agent CommsCore bridge for self-hosted runtime wake integrations.",
16295
+ options: [
16296
+ { flags: "--json", description: "Emit newline-delimited JSON protocol events." },
16297
+ { flags: "--once", description: "Run one receive/replay iteration, then exit." },
16298
+ { flags: "--poll-interval-ms <ms>", description: "Polling interval for the long-running bridge loop." },
16299
+ { flags: "--state-dir <path>", description: "Override bridge state directory for tests/debugging." },
16300
+ { flags: "--adapter-instance <id>", description: "Wake adapter instance id for per-agent state partitioning." },
16301
+ { flags: "--limit <n>", description: "Maximum events to pull per iteration." },
16302
+ { flags: "--wake-adapter <kind>", description: "Enable a wake adapter. Supported: claude-code-channels." },
16303
+ { flags: "--claude-channel-endpoint <url>", description: "Localhost wake endpoint exposed by the Slock Claude Code channel plugin." },
16304
+ { flags: "--claude-channel-token <token>", description: "Optional shared token for the Claude Code channel plugin wake endpoint." },
16305
+ { flags: "--runtime-session <id>", description: "Optional runtime session id when the adapter endpoint does not return one." }
16306
+ ]
16307
+ },
16308
+ async (ctx, options) => {
16309
+ const agentContext = ctx.loadAgentContext();
16310
+ if (agentContext.clientMode !== "self-hosted-runner") {
16311
+ throw new CliError({
16312
+ code: "BRIDGE_REQUIRES_PROFILE",
16313
+ message: "slock agent bridge requires a self-hosted profile credential, not daemon-injected runner auth.",
16314
+ suggestedNextAction: "Run it as `slock --profile <slug> agent bridge --json`; create the profile with `slock agent login` if needed."
16315
+ });
16316
+ }
16317
+ const pollIntervalMs = parsePositiveInt(options.pollIntervalMs, 5e3, "poll-interval-ms");
16318
+ const limit = parsePositiveInt(options.limit, 50, "limit");
16319
+ const source = createAgentApiWakeHintSource(ctx.createApiClient(agentContext));
16320
+ const wakeAdapter = createWakeAdapter(options);
16321
+ const emit = (value) => {
16322
+ if (options.json) {
16323
+ writeText(ctx.io, `${JSON.stringify(value)}
16324
+ `);
16325
+ }
16326
+ };
16327
+ let replayPending = true;
16328
+ do {
16329
+ const events = await runAgentCommsBridgeOnce({
16330
+ agentContext,
16331
+ source,
16332
+ env: ctx.env,
16333
+ stateDir: options.stateDir,
16334
+ adapterInstance: options.adapterInstance,
16335
+ limit,
16336
+ replayPending,
16337
+ wakeAdapter,
16338
+ runtimeSession: options.runtimeSession ?? null
16339
+ });
16340
+ for (const event of events) emit(event);
16341
+ replayPending = false;
16342
+ if (options.once) break;
16343
+ await sleep(pollIntervalMs);
16344
+ } while (true);
16345
+ }
16346
+ );
16347
+ function registerAgentBridgeCommand(parent, runtimeOptions) {
16348
+ registerCliCommand(parent, agentBridgeCommand, runtimeOptions);
16349
+ }
16350
+ function createAgentApiWakeHintSource(client) {
16351
+ return {
16352
+ async fetchWakeHints(input) {
16353
+ const query = new URLSearchParams({
16354
+ since: String(input.since),
16355
+ limit: String(input.limit)
16356
+ });
16357
+ const response = await client.request("GET", `/internal/agent-api/wake-hints?${query.toString()}`);
16358
+ if (!response.ok) {
16359
+ throw new CliError({
16360
+ code: response.status >= 500 ? "SERVER_5XX" : "BRIDGE_WAKE_HINTS_FAILED",
16361
+ message: response.error ?? `HTTP ${response.status}`
16362
+ });
16363
+ }
16364
+ return {
16365
+ hints: response.data?.hints ?? response.data?.wake_hints ?? [],
16366
+ last_seen_hint_seq: response.data?.last_seen_hint_seq ?? response.data?.last_hint_seq ?? null,
16367
+ has_more: response.data?.has_more ?? false
16368
+ };
16369
+ }
16370
+ };
16371
+ }
16372
+ function parsePositiveInt(raw, fallback, label) {
16373
+ if (raw === void 0) return fallback;
16374
+ const parsed = Number(raw);
16375
+ if (!Number.isInteger(parsed) || parsed <= 0) {
16376
+ throw new CliError({
16377
+ code: "INVALID_ARG",
16378
+ message: `--${label} must be a positive integer`
16379
+ });
16380
+ }
16381
+ return parsed;
16382
+ }
16383
+ function createWakeAdapter(options) {
16384
+ if (!options.wakeAdapter) return void 0;
16385
+ if (options.wakeAdapter !== "claude-code-channels") {
16386
+ throw new CliError({
16387
+ code: "INVALID_ARG",
16388
+ message: "--wake-adapter must be `claude-code-channels`"
16389
+ });
16390
+ }
16391
+ return createClaudeCodeChannelsWakeAdapter({
16392
+ endpointUrl: options.claudeChannelEndpoint,
16393
+ token: options.claudeChannelToken
16394
+ });
16395
+ }
16396
+ function sleep(ms) {
16397
+ return new Promise((resolve) => setTimeout(resolve, ms));
16398
+ }
16399
+
15375
16400
  // src/commands/action/prepare.ts
15376
16401
  var ACTION_HEREDOC_DELIMITER = "SLOCKACTION";
15377
16402
  var PrepareActionInputError = class extends Error {
@@ -15867,9 +16892,15 @@ var inboxCheckCommand = defineCommand(
15867
16892
  },
15868
16893
  async (ctx) => {
15869
16894
  const agentContext = ctx.loadAgentContext();
16895
+ if (agentContext.clientMode !== "managed-runner") {
16896
+ throw new CliError({
16897
+ code: "INBOX_CHECK_FAILED",
16898
+ message: "`slock inbox check` is only available inside managed daemon runners.",
16899
+ suggestedNextAction: "Use `slock message check` to drain messages."
16900
+ });
16901
+ }
15870
16902
  const client = ctx.createApiClient(agentContext);
15871
- const path6 = `/internal/agent/${encodeURIComponent(agentContext.agentId)}/inbox`;
15872
- const response = await client.request("GET", path6);
16903
+ const response = await client.request("GET", "/internal/agent-api/inbox");
15873
16904
  if (!response.ok) {
15874
16905
  throw new CliError({
15875
16906
  code: response.status >= 500 ? "SERVER_5XX" : "INBOX_CHECK_FAILED",
@@ -16228,17 +17259,17 @@ ${opts.heldAction} Review the bounded context shown here, then choose one path.$
16228
17259
  }
16229
17260
 
16230
17261
  // src/commands/message/_continueDraftState.ts
16231
- import fs2 from "fs";
17262
+ import fs3 from "fs";
16232
17263
  import os2 from "os";
16233
- import path4 from "path";
17264
+ import path5 from "path";
16234
17265
  var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
16235
17266
  function stateFilePath(agentId) {
16236
- return path4.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
17267
+ return path5.join(process.env.SLOCK_CLI_DRAFT_STATE_DIR ?? os2.tmpdir(), "slock-cli-attested-send", agentId, "continue-state.json");
16237
17268
  }
16238
17269
  function readState(agentId) {
16239
17270
  const filePath = stateFilePath(agentId);
16240
17271
  try {
16241
- const raw = fs2.readFileSync(filePath, "utf8");
17272
+ const raw = fs3.readFileSync(filePath, "utf8");
16242
17273
  const parsed = JSON.parse(raw);
16243
17274
  return typeof parsed === "object" && parsed ? parsed : {};
16244
17275
  } catch {
@@ -16247,8 +17278,8 @@ function readState(agentId) {
16247
17278
  }
16248
17279
  function writeState(agentId, state) {
16249
17280
  const filePath = stateFilePath(agentId);
16250
- fs2.mkdirSync(path4.dirname(filePath), { recursive: true });
16251
- fs2.writeFileSync(filePath, JSON.stringify(state), "utf8");
17281
+ fs3.mkdirSync(path5.dirname(filePath), { recursive: true });
17282
+ fs3.writeFileSync(filePath, JSON.stringify(state), "utf8");
16252
17283
  }
16253
17284
  function getSavedDraft(agentId, target) {
16254
17285
  const state = readState(agentId);
@@ -16544,8 +17575,8 @@ async function drainInbox(ctx, opts, client = new ApiClient(ctx)) {
16544
17575
  const query = [];
16545
17576
  if (opts.block) query.push("block=true");
16546
17577
  if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
16547
- const path6 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
16548
- const res = await client.request("GET", path6);
17578
+ const path7 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
17579
+ const res = await client.request("GET", path7);
16549
17580
  if (!res.ok) {
16550
17581
  throw new CliError({
16551
17582
  code: res.status >= 500 ? "SERVER_5XX" : failCode,
@@ -16587,7 +17618,7 @@ function registerCheckCommand(parent, runtimeOptions) {
16587
17618
  }
16588
17619
 
16589
17620
  // src/commands/message/read.ts
16590
- function parsePositiveInt(name, raw) {
17621
+ function parsePositiveInt2(name, raw) {
16591
17622
  if (raw === void 0) return void 0;
16592
17623
  const n = Number(raw);
16593
17624
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
@@ -16641,7 +17672,7 @@ function validateReadOpts(opts) {
16641
17672
  message: "--channel is required"
16642
17673
  });
16643
17674
  }
16644
- const limit = parsePositiveInt("limit", opts.limit);
17675
+ const limit = parsePositiveInt2("limit", opts.limit);
16645
17676
  const before = opts.before?.trim();
16646
17677
  const after = opts.after?.trim();
16647
17678
  return {
@@ -16715,7 +17746,7 @@ function normalizeMemberHandleRef(raw) {
16715
17746
  }
16716
17747
  return handle;
16717
17748
  }
16718
- function parsePositiveInt2(name, raw) {
17749
+ function parsePositiveInt3(name, raw) {
16719
17750
  if (raw === void 0) return void 0;
16720
17751
  const n = Number(raw);
16721
17752
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
@@ -16740,7 +17771,7 @@ function normalizeSearchOpts(opts) {
16740
17771
  message: `--sort must be "relevance" or "recent"; got ${opts.sort}`
16741
17772
  });
16742
17773
  }
16743
- const limit = parsePositiveInt2("limit", opts.limit);
17774
+ const limit = parsePositiveInt3("limit", opts.limit);
16744
17775
  return {
16745
17776
  query,
16746
17777
  ...opts.channel ? { channel: opts.channel } : {},
@@ -17989,9 +19020,9 @@ function registerIntegrationLoginCommand(parent, runtimeOptions = {}) {
17989
19020
  }
17990
19021
 
17991
19022
  // src/commands/integration/manifest.ts
17992
- import fs3 from "fs";
19023
+ import fs4 from "fs";
17993
19024
  import os3 from "os";
17994
- import path5 from "path";
19025
+ import path6 from "path";
17995
19026
  var AGENT_MANIFEST_MAX_BYTES = 64 * 1024;
17996
19027
  var AgentManifestFetchError = class extends Error {
17997
19028
  constructor(message, status) {
@@ -18114,8 +19145,8 @@ function sanitizePathSegment(value) {
18114
19145
  return segment || "service";
18115
19146
  }
18116
19147
  function resolveSlockHome(env) {
18117
- if (env.SLOCK_HOME?.trim()) return path5.resolve(env.SLOCK_HOME);
18118
- return path5.join(env.HOME ?? os3.homedir(), ".slock");
19148
+ if (env.SLOCK_HOME?.trim()) return path6.resolve(env.SLOCK_HOME);
19149
+ return path6.join(env.HOME ?? os3.homedir(), ".slock");
18119
19150
  }
18120
19151
  function buildLocalCliProfileEnv(input) {
18121
19152
  if (input.manifest.execution.mode !== "local_cli" || !input.manifest.execution.command) {
@@ -18128,15 +19159,15 @@ function buildLocalCliProfileEnv(input) {
18128
19159
  throw new Error("manifest must set credential_boundary.forbid_user_home=true for local_cli isolation");
18129
19160
  }
18130
19161
  const root = resolveSlockHome(input.env ?? process.env);
18131
- const profileHome = path5.join(
19162
+ const profileHome = path6.join(
18132
19163
  root,
18133
19164
  "integration-profiles",
18134
19165
  sanitizePathSegment(input.ctx.serverId ?? "server"),
18135
19166
  sanitizePathSegment(input.ctx.agentId),
18136
19167
  sanitizePathSegment(input.serviceId)
18137
19168
  );
18138
- fs3.mkdirSync(profileHome, { recursive: true, mode: 448 });
18139
- fs3.chmodSync(profileHome, 448);
19169
+ fs4.mkdirSync(profileHome, { recursive: true, mode: 448 });
19170
+ fs4.chmodSync(profileHome, 448);
18140
19171
  return {
18141
19172
  serviceId: input.serviceId,
18142
19173
  command: input.manifest.execution.command,
@@ -18145,10 +19176,10 @@ function buildLocalCliProfileEnv(input) {
18145
19176
  SLOCK_INTEGRATION_SERVICE: input.serviceId,
18146
19177
  SLOCK_INTEGRATION_PROFILE_HOME: profileHome,
18147
19178
  HOME: profileHome,
18148
- XDG_CONFIG_HOME: path5.join(profileHome, ".config"),
18149
- XDG_CACHE_HOME: path5.join(profileHome, ".cache"),
18150
- XDG_DATA_HOME: path5.join(profileHome, ".local", "share"),
18151
- XDG_STATE_HOME: path5.join(profileHome, ".local", "state")
19179
+ XDG_CONFIG_HOME: path6.join(profileHome, ".config"),
19180
+ XDG_CACHE_HOME: path6.join(profileHome, ".cache"),
19181
+ XDG_DATA_HOME: path6.join(profileHome, ".local", "share"),
19182
+ XDG_STATE_HOME: path6.join(profileHome, ".local", "state")
18152
19183
  }
18153
19184
  };
18154
19185
  }
@@ -18752,6 +19783,7 @@ registerWhoamiCommand(authCmd);
18752
19783
  var agentCmd = program.command("agent").description("Self-managed agent onboarding (device-code login \u2192 sk_agent_* mint \u2192 ~/.slock/profiles/<slug>/credential.json)");
18753
19784
  registerAgentLoginCommand(agentCmd);
18754
19785
  registerAgentListCommand(agentCmd);
19786
+ registerAgentBridgeCommand(agentCmd);
18755
19787
  var channelCmd = program.command("channel").description("Channel membership operations");
18756
19788
  registerChannelMembersCommand(channelCmd);
18757
19789
  registerChannelJoinCommand(channelCmd);