@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/{chunk-H2QU4LAU.js → chunk-EXLDUFWR.js} +269 -15
- package/dist/cli/index.js +1174 -142
- package/dist/cli/package.json +1 -1
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
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
|
|
854
|
-
|
|
855
|
-
|
|
874
|
+
const absolutize = (uri) => uri.startsWith("http") ? uri : `${base}${uri}`;
|
|
875
|
+
return {
|
|
876
|
+
deviceCode: authorizeBody.deviceCode,
|
|
856
877
|
userCode: authorizeBody.userCode,
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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:
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if (
|
|
1016
|
-
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
|
1116
|
+
userSession = await pollDeviceToken({
|
|
1039
1117
|
serverUrl: options.server,
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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
|
-
|
|
1050
|
-
throw cliError(err.code, err.message, { cause: err });
|
|
1051
|
-
}
|
|
1052
|
-
throw err;
|
|
1123
|
+
throw asCliError(err);
|
|
1053
1124
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1117
|
-
|
|
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,
|
|
2063
|
-
if (!
|
|
2340
|
+
function getElementAtPath(obj, path7) {
|
|
2341
|
+
if (!path7)
|
|
2064
2342
|
return obj;
|
|
2065
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
2678
|
-
for (const seg of
|
|
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
|
|
14656
|
-
if (
|
|
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 (
|
|
14661
|
-
const key =
|
|
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
|
|
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
|
|
17262
|
+
import fs3 from "fs";
|
|
16232
17263
|
import os2 from "os";
|
|
16233
|
-
import
|
|
17264
|
+
import path5 from "path";
|
|
16234
17265
|
var DEFAULT_LOCAL_DRAFT_TTL_MS = 10 * 60 * 1e3;
|
|
16235
17266
|
function stateFilePath(agentId) {
|
|
16236
|
-
return
|
|
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 =
|
|
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
|
-
|
|
16251
|
-
|
|
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
|
|
16548
|
-
const res = await client.request("GET",
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
19023
|
+
import fs4 from "fs";
|
|
17993
19024
|
import os3 from "os";
|
|
17994
|
-
import
|
|
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
|
|
18118
|
-
return
|
|
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 =
|
|
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
|
-
|
|
18139
|
-
|
|
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:
|
|
18149
|
-
XDG_CACHE_HOME:
|
|
18150
|
-
XDG_DATA_HOME:
|
|
18151
|
-
XDG_STATE_HOME:
|
|
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);
|