commonswarm 0.1.54 → 0.1.55
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/cswarm.cjs +630 -28
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -13502,6 +13502,7 @@ var require_main3 = __commonJS({
|
|
|
13502
13502
|
// src/cli.ts
|
|
13503
13503
|
var cli_exports = {};
|
|
13504
13504
|
__export(cli_exports, {
|
|
13505
|
+
CHANNEL_SUBCOMMAND_NAMES: () => CHANNEL_SUBCOMMAND_NAMES,
|
|
13505
13506
|
EXIT_RESTARTABLE: () => EXIT_RESTARTABLE,
|
|
13506
13507
|
ListenerUnattendedRefusedError: () => ListenerUnattendedRefusedError,
|
|
13507
13508
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS: () => TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
@@ -13520,6 +13521,7 @@ __export(cli_exports, {
|
|
|
13520
13521
|
resolveDetachedClaudeExecutable: () => resolveDetachedClaudeExecutable,
|
|
13521
13522
|
resolveDetachedCodexExecutable: () => resolveDetachedCodexExecutable,
|
|
13522
13523
|
resolveTurnBudgetOrDefer: () => resolveTurnBudgetOrDefer,
|
|
13524
|
+
threadReplyMessage: () => threadReplyMessage,
|
|
13523
13525
|
usage: () => usage
|
|
13524
13526
|
});
|
|
13525
13527
|
module.exports = __toCommonJS(cli_exports);
|
|
@@ -22142,11 +22144,217 @@ async function logout(target2, store2, scope = "local", options = {}) {
|
|
|
22142
22144
|
|
|
22143
22145
|
// src/cloud/command-client.ts
|
|
22144
22146
|
var import_node_crypto3 = require("node:crypto");
|
|
22147
|
+
|
|
22148
|
+
// src/cloud/channels.ts
|
|
22149
|
+
var CHANNEL_SLUG_MAX = 32;
|
|
22150
|
+
var CHANNEL_PURPOSE_MAX = 500;
|
|
22151
|
+
var CHANNEL_SLUG_CLASSES = {
|
|
22152
|
+
edge: [
|
|
22153
|
+
{ fragment: "a-z", words: "lowercase letters", one: "a letter" },
|
|
22154
|
+
{ fragment: "0-9", words: "digits", one: "a digit" }
|
|
22155
|
+
],
|
|
22156
|
+
inner: [{ fragment: "-", words: "hyphens" }]
|
|
22157
|
+
};
|
|
22158
|
+
function classList(entries, conjunction = "and", singular = false) {
|
|
22159
|
+
const words = entries.map(
|
|
22160
|
+
(entry) => singular ? entry.one ?? entry.words : entry.words
|
|
22161
|
+
);
|
|
22162
|
+
return words.length === 1 ? words[0] : `${words.slice(0, -1).join(", ")} ${conjunction} ${words[words.length - 1]}`;
|
|
22163
|
+
}
|
|
22164
|
+
var SLUG_EDGE = CHANNEL_SLUG_CLASSES.edge.map((c) => c.fragment).join("");
|
|
22165
|
+
var SLUG_INNER = SLUG_EDGE + CHANNEL_SLUG_CLASSES.inner.map((c) => c.fragment).join("");
|
|
22166
|
+
var CHANNEL_SLUG_RE = new RegExp(
|
|
22167
|
+
`^[${SLUG_EDGE}]([${SLUG_INNER}]*[${SLUG_EDGE}])?$`
|
|
22168
|
+
);
|
|
22169
|
+
var RESERVED_CHANNEL_SLUGS = ["all-signals"];
|
|
22170
|
+
var CHANNEL_SLUG_RULE_TEXT = `A channel name uses ${classList([...CHANNEL_SLUG_CLASSES.edge, ...CHANNEL_SLUG_CLASSES.inner])}, starts and ends with ${classList(CHANNEL_SLUG_CLASSES.edge, "or", true)}, and is 1 to ${CHANNEL_SLUG_MAX} characters.`;
|
|
22171
|
+
var CHANNEL_ID_RULE_TEXT = "channel_id must be a UUID.";
|
|
22172
|
+
var RESERVED_CHANNEL_SLUG_TEXT = `Reserved names: ${RESERVED_CHANNEL_SLUGS.join(", ")}.`;
|
|
22173
|
+
function normalizeChannelSlug(value) {
|
|
22174
|
+
return value.trim().toLowerCase();
|
|
22175
|
+
}
|
|
22176
|
+
function isReservedChannelSlug(value) {
|
|
22177
|
+
return RESERVED_CHANNEL_SLUGS.includes(normalizeChannelSlug(value));
|
|
22178
|
+
}
|
|
22179
|
+
function channelNameProblem(value) {
|
|
22180
|
+
if (typeof value !== "string") return "not-text";
|
|
22181
|
+
const slug = normalizeChannelSlug(value);
|
|
22182
|
+
if (slug.length < 1 || slug.length > CHANNEL_SLUG_MAX) return "shape";
|
|
22183
|
+
if (!CHANNEL_SLUG_RE.test(slug)) return "shape";
|
|
22184
|
+
if (isReservedChannelSlug(slug)) return "reserved";
|
|
22185
|
+
return "ok";
|
|
22186
|
+
}
|
|
22187
|
+
function channelSlugProblem(value) {
|
|
22188
|
+
switch (channelNameProblem(value)) {
|
|
22189
|
+
case "ok":
|
|
22190
|
+
return null;
|
|
22191
|
+
case "not-text":
|
|
22192
|
+
return `A channel name must be text. ${CHANNEL_SLUG_RULE_TEXT}`;
|
|
22193
|
+
case "shape":
|
|
22194
|
+
return CHANNEL_SLUG_RULE_TEXT;
|
|
22195
|
+
case "reserved":
|
|
22196
|
+
return `${normalizeChannelSlug(value)} is reserved. ${RESERVED_CHANNEL_SLUG_TEXT}`;
|
|
22197
|
+
}
|
|
22198
|
+
}
|
|
22199
|
+
var CHANNEL_COLUMNS = [
|
|
22200
|
+
"channel_id",
|
|
22201
|
+
"workspace_id",
|
|
22202
|
+
"slug",
|
|
22203
|
+
"purpose",
|
|
22204
|
+
"created_by_principal",
|
|
22205
|
+
"created_by_kind",
|
|
22206
|
+
"created_at",
|
|
22207
|
+
"archived_at"
|
|
22208
|
+
];
|
|
22209
|
+
var CHANNEL_LIST_NEEDS_HUMAN_MESSAGE = "Listing channels needs a signed-in person: this deployment's read service has no channel list for an agent credential. Run cswarm channel ls from a session signed in with cswarm login, or name the channel you want by name wherever a command takes one.";
|
|
22210
|
+
var CHANNEL_SELECTOR_NEEDS_ID_MESSAGE = "Turning a channel name into a channel id needs a signed-in person, because this deployment's read service does not list channels for an agent credential. Pass the channel id instead. cswarm channel create prints it, and cswarm channel ls shows it from a session signed in with cswarm login.";
|
|
22211
|
+
function channelSelectorProblem(selector) {
|
|
22212
|
+
const problem = channelNameProblem(selector);
|
|
22213
|
+
if (problem === "ok") return null;
|
|
22214
|
+
if (problem === "reserved") return channelSlugProblem(selector);
|
|
22215
|
+
return `That is neither a channel name nor a channel id. ${CHANNEL_SLUG_RULE_TEXT} ${CHANNEL_ID_RULE_TEXT}`;
|
|
22216
|
+
}
|
|
22217
|
+
var ChannelListError = class extends Error {
|
|
22218
|
+
constructor(status, message, noResponse = false) {
|
|
22219
|
+
super(message);
|
|
22220
|
+
this.status = status;
|
|
22221
|
+
this.noResponse = noResponse;
|
|
22222
|
+
this.name = "ChannelListError";
|
|
22223
|
+
}
|
|
22224
|
+
status;
|
|
22225
|
+
noResponse;
|
|
22226
|
+
};
|
|
22227
|
+
async function listChannelsAsHuman(target2, accessToken, workspaceId2, fetcher = fetch, timeoutMs = 3e4) {
|
|
22228
|
+
const url = new URL("/rest/v1/channels", target2.url);
|
|
22229
|
+
url.searchParams.set("workspace_id", `eq.${workspaceId2}`);
|
|
22230
|
+
url.searchParams.set("select", CHANNEL_COLUMNS.join(","));
|
|
22231
|
+
url.searchParams.set("order", "slug.asc");
|
|
22232
|
+
const controller = new AbortController();
|
|
22233
|
+
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
22234
|
+
let response;
|
|
22235
|
+
try {
|
|
22236
|
+
response = await fetcher(url.toString(), {
|
|
22237
|
+
headers: {
|
|
22238
|
+
authorization: `Bearer ${accessToken}`,
|
|
22239
|
+
apikey: target2.anonKey,
|
|
22240
|
+
"accept-profile": "swarm_read"
|
|
22241
|
+
},
|
|
22242
|
+
signal: controller.signal
|
|
22243
|
+
});
|
|
22244
|
+
} catch {
|
|
22245
|
+
throw new ChannelListError(
|
|
22246
|
+
0,
|
|
22247
|
+
"The channel list did not complete. Nothing changed. Run the same command again.",
|
|
22248
|
+
true
|
|
22249
|
+
);
|
|
22250
|
+
} finally {
|
|
22251
|
+
clearTimeout(timer2);
|
|
22252
|
+
}
|
|
22253
|
+
if (!response.ok) {
|
|
22254
|
+
throw new ChannelListError(
|
|
22255
|
+
response.status,
|
|
22256
|
+
`The channel list was refused (HTTP ${response.status}). Nothing changed.`
|
|
22257
|
+
);
|
|
22258
|
+
}
|
|
22259
|
+
let body = null;
|
|
22260
|
+
try {
|
|
22261
|
+
body = await response.json();
|
|
22262
|
+
} catch {
|
|
22263
|
+
body = null;
|
|
22264
|
+
}
|
|
22265
|
+
if (!Array.isArray(body)) {
|
|
22266
|
+
throw new ChannelListError(
|
|
22267
|
+
response.status,
|
|
22268
|
+
"The channel list came back in a shape this version does not understand."
|
|
22269
|
+
);
|
|
22270
|
+
}
|
|
22271
|
+
return body;
|
|
22272
|
+
}
|
|
22273
|
+
function findChannelBySlug(rows3, slug) {
|
|
22274
|
+
const wanted = normalizeChannelSlug(slug);
|
|
22275
|
+
return rows3.find((row) => normalizeChannelSlug(row.slug) === wanted) ?? null;
|
|
22276
|
+
}
|
|
22277
|
+
function unknownChannelMessage(slug, rows3) {
|
|
22278
|
+
const live = rows3.filter((row) => row.archived_at === null).map((row) => row.slug).sort();
|
|
22279
|
+
return live.length === 0 ? `There is no channel named ${normalizeChannelSlug(slug)} in this workspace, and no channel has been created yet. Create it with cswarm channel create ${normalizeChannelSlug(slug)}.` : `There is no channel named ${normalizeChannelSlug(slug)} in this workspace. Channels here: ${live.join(", ")}.`;
|
|
22280
|
+
}
|
|
22281
|
+
function renderChannelList(rows3, options) {
|
|
22282
|
+
const live = rows3.filter((row) => row.archived_at === null);
|
|
22283
|
+
const archived = rows3.filter((row) => row.archived_at !== null);
|
|
22284
|
+
const shown = options.includeArchived ? [...live, ...archived] : live;
|
|
22285
|
+
if (shown.length === 0) {
|
|
22286
|
+
return live.length === 0 && archived.length > 0 ? "Every channel in this workspace is archived. See them with cswarm channel ls --include-archived.\n" : "No channels in this workspace yet. Create one with cswarm channel create <name>.\n";
|
|
22287
|
+
}
|
|
22288
|
+
const lines = shown.map((row) => {
|
|
22289
|
+
const marker = row.archived_at === null ? "" : " [archived; it keeps its history and takes no new messages]";
|
|
22290
|
+
const purpose = row.purpose === null ? "" : `: ${row.purpose}`;
|
|
22291
|
+
return `- ${row.slug}${purpose}${marker}`;
|
|
22292
|
+
});
|
|
22293
|
+
const head2 = `Channels in this workspace (${shown.length}):`;
|
|
22294
|
+
const tail = options.includeArchived || archived.length === 0 ? "" : `
|
|
22295
|
+
${archived.length} archived channel${archived.length === 1 ? "" : "s"} not shown. See them with cswarm channel ls --include-archived.`;
|
|
22296
|
+
return `${head2}
|
|
22297
|
+
${lines.join("\n")}${tail}
|
|
22298
|
+
`;
|
|
22299
|
+
}
|
|
22300
|
+
var CHANNEL_UNSUPPORTED_MESSAGE = `This deployment refused the request and did not say why. Channels need a deployment whose command service knows them; this cswarm speaks protocol ${CLIENT_PROTOCOL_VERSION}. Nothing was created or changed. Ask whoever runs this deployment to update it, or drop the channel options and post as before.`;
|
|
22301
|
+
|
|
22302
|
+
// src/cloud/command-client.ts
|
|
22145
22303
|
var AGENT_TOKEN_RE = /^swm_agt_[A-Za-z0-9_-]{43}$/;
|
|
22146
22304
|
var INVITATION_TOKEN_RE = /^swm_inv_[A-Za-z0-9_-]{43}$/;
|
|
22147
22305
|
var CAPABILITY_TOKEN_RE = /^swm_cap_[A-Za-z0-9_-]{43}$/;
|
|
22148
22306
|
var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/;
|
|
22149
22307
|
var WORKSPACE_NAME_MAX_LENGTH = 80;
|
|
22308
|
+
var ChannelCommandError = class extends Error {
|
|
22309
|
+
constructor(status, code, message) {
|
|
22310
|
+
super(message);
|
|
22311
|
+
this.status = status;
|
|
22312
|
+
this.code = code;
|
|
22313
|
+
this.name = "ChannelCommandError";
|
|
22314
|
+
}
|
|
22315
|
+
status;
|
|
22316
|
+
code;
|
|
22317
|
+
};
|
|
22318
|
+
function channelCommandError(status, body) {
|
|
22319
|
+
const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
22320
|
+
const code = typeof record.error === "string" ? record.error : "unknown";
|
|
22321
|
+
const served = typeof record.message === "string" && record.message.length > 0 ? record.message.slice(0, 600) : null;
|
|
22322
|
+
if (served !== null) return new ChannelCommandError(status, code, served);
|
|
22323
|
+
if (status === 426) {
|
|
22324
|
+
const minimum = typeof record.min_client_version === "string" ? record.min_client_version : null;
|
|
22325
|
+
return new ChannelCommandError(
|
|
22326
|
+
status,
|
|
22327
|
+
"upgrade_required",
|
|
22328
|
+
`This copy of cswarm is older than the deployment accepts${minimum === null ? "" : ` (minimum ${minimum})`}. Update cswarm, then run the same command again. Nothing changed.`
|
|
22329
|
+
);
|
|
22330
|
+
}
|
|
22331
|
+
if (status === 403) {
|
|
22332
|
+
return new ChannelCommandError(
|
|
22333
|
+
status,
|
|
22334
|
+
code === "unknown" ? "forbidden" : code,
|
|
22335
|
+
"This credential may not do that in this workspace. Nothing changed."
|
|
22336
|
+
);
|
|
22337
|
+
}
|
|
22338
|
+
if (status === 401) {
|
|
22339
|
+
return new ChannelCommandError(
|
|
22340
|
+
status,
|
|
22341
|
+
code === "unknown" ? "unauthenticated" : code,
|
|
22342
|
+
"Your sign-in is no longer valid for this deployment. Run cswarm login, then run the same command again. Nothing changed."
|
|
22343
|
+
);
|
|
22344
|
+
}
|
|
22345
|
+
if (status === 400) {
|
|
22346
|
+
return new ChannelCommandError(
|
|
22347
|
+
status,
|
|
22348
|
+
code === "unknown" ? "invalid_request" : code,
|
|
22349
|
+
CHANNEL_UNSUPPORTED_MESSAGE
|
|
22350
|
+
);
|
|
22351
|
+
}
|
|
22352
|
+
return new ChannelCommandError(
|
|
22353
|
+
status,
|
|
22354
|
+
code,
|
|
22355
|
+
`CommonSwarm could not tell whether the change was made (HTTP ${status}). Run cswarm channel ls to see the current channels before trying again.`
|
|
22356
|
+
);
|
|
22357
|
+
}
|
|
22150
22358
|
var CAPABILITY_MIN_TTL_MS = 6e4;
|
|
22151
22359
|
var CAPABILITY_MAX_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
22152
22360
|
var SIGNAL_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -22706,6 +22914,80 @@ var ThinCommandClient = class {
|
|
|
22706
22914
|
}
|
|
22707
22915
|
return { httpStatus: response.status, response: body };
|
|
22708
22916
|
}
|
|
22917
|
+
/**
|
|
22918
|
+
* Create, rename, or archive a channel.
|
|
22919
|
+
*
|
|
22920
|
+
* Deliberately not folded into `sendConnect`. That path throws a bare
|
|
22921
|
+
* `CommandHttpError(403)` and, on any other refusal, reads only the `error`
|
|
22922
|
+
* code — so every generated sentence the chat validator returns would be
|
|
22923
|
+
* thrown away at the one moment the caller needs it. This method reads the
|
|
22924
|
+
* body once and hands it to `channelCommandError`.
|
|
22925
|
+
*/
|
|
22926
|
+
async sendChannel(request) {
|
|
22927
|
+
if (!request.workspaceId) {
|
|
22928
|
+
throw new Error("workspaceId is required for a channel command");
|
|
22929
|
+
}
|
|
22930
|
+
const commandId = request.commandId ?? newCommandId();
|
|
22931
|
+
const controller = new AbortController();
|
|
22932
|
+
const timer2 = setTimeout(() => controller.abort(), 3e4);
|
|
22933
|
+
let response;
|
|
22934
|
+
try {
|
|
22935
|
+
response = await this.fetcher(commandEndpoint(this.target), {
|
|
22936
|
+
method: "POST",
|
|
22937
|
+
headers: {
|
|
22938
|
+
authorization: `Bearer ${request.credential}`,
|
|
22939
|
+
apikey: this.target.anonKey,
|
|
22940
|
+
"content-type": "application/json"
|
|
22941
|
+
},
|
|
22942
|
+
body: JSON.stringify({
|
|
22943
|
+
command_id: commandId,
|
|
22944
|
+
client_version: CLIENT_PROTOCOL_VERSION,
|
|
22945
|
+
workspace_id: request.workspaceId,
|
|
22946
|
+
stream: { kind: "workspace" },
|
|
22947
|
+
command: request.command
|
|
22948
|
+
}),
|
|
22949
|
+
signal: controller.signal
|
|
22950
|
+
});
|
|
22951
|
+
} catch (error) {
|
|
22952
|
+
if (error.name === "AbortError") {
|
|
22953
|
+
throw new CommandTransportError("channel request timed out");
|
|
22954
|
+
}
|
|
22955
|
+
throw new CommandTransportError(
|
|
22956
|
+
"channel request failed before a response"
|
|
22957
|
+
);
|
|
22958
|
+
} finally {
|
|
22959
|
+
clearTimeout(timer2);
|
|
22960
|
+
}
|
|
22961
|
+
let raw = null;
|
|
22962
|
+
try {
|
|
22963
|
+
raw = await parsedJson(response);
|
|
22964
|
+
} catch (error) {
|
|
22965
|
+
if (response.ok || error instanceof CommandTransportError) throw error;
|
|
22966
|
+
}
|
|
22967
|
+
if (!response.ok) throw channelCommandError(response.status, raw);
|
|
22968
|
+
const body = responseBody(raw);
|
|
22969
|
+
if (body.min_client_version !== void 0) {
|
|
22970
|
+
const order = compareVersion(
|
|
22971
|
+
CLIENT_PROTOCOL_VERSION,
|
|
22972
|
+
body.min_client_version
|
|
22973
|
+
);
|
|
22974
|
+
if (order === null) {
|
|
22975
|
+
throw new Error("server returned a malformed min_client_version");
|
|
22976
|
+
}
|
|
22977
|
+
if (order < 0) {
|
|
22978
|
+
throw new Error(
|
|
22979
|
+
`client upgrade required (minimum ${body.min_client_version})`
|
|
22980
|
+
);
|
|
22981
|
+
}
|
|
22982
|
+
}
|
|
22983
|
+
const channel = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.channel : null;
|
|
22984
|
+
if (channel === null || channel === void 0 || typeof channel.channel_id !== "string" || typeof channel.slug !== "string") {
|
|
22985
|
+
throw new Error(
|
|
22986
|
+
"the deployment accepted the change without saying which channel it applies to"
|
|
22987
|
+
);
|
|
22988
|
+
}
|
|
22989
|
+
return { httpStatus: response.status, response: body, channel };
|
|
22990
|
+
}
|
|
22709
22991
|
async sendSignal(request) {
|
|
22710
22992
|
const commandId = request.commandId ?? newCommandId();
|
|
22711
22993
|
const command2 = {
|
|
@@ -22717,7 +22999,17 @@ var ThinCommandClient = class {
|
|
|
22717
22999
|
in_reply_to: request.command.in_reply_to,
|
|
22718
23000
|
about: request.command.about,
|
|
22719
23001
|
...request.command.attachments === void 0 ? {} : { attachments: request.command.attachments },
|
|
22720
|
-
...request.command.until_ms === void 0 ? {} : { until_ms: request.command.until_ms }
|
|
23002
|
+
...request.command.until_ms === void 0 ? {} : { until_ms: request.command.until_ms },
|
|
23003
|
+
/* One spread per chat key, never a shared group. The edge reads each with
|
|
23004
|
+
* its own Object.hasOwn and refuses any key it did not expect, so sending
|
|
23005
|
+
* `channel: undefined` here would still put the key on the wire through
|
|
23006
|
+
* JSON.stringify's own omission rules only by accident — and sending a
|
|
23007
|
+
* null placeholder, the way to_user_id is sent, would make every post
|
|
23008
|
+
* demand a channel. This rebuild is also the reason the fields have to be
|
|
23009
|
+
* listed here at all: it drops anything it does not name. */
|
|
23010
|
+
...request.command.channel === void 0 ? {} : { channel: request.command.channel },
|
|
23011
|
+
...request.command.thread_root_id === void 0 ? {} : { thread_root_id: request.command.thread_root_id },
|
|
23012
|
+
...request.command.broadcast_to_channel === void 0 ? {} : { broadcast_to_channel: request.command.broadcast_to_channel }
|
|
22721
23013
|
};
|
|
22722
23014
|
const callerSignal = request.signal;
|
|
22723
23015
|
if (callerSignal?.aborted) {
|
|
@@ -29041,6 +29333,12 @@ function checkedUuid2(value, field) {
|
|
|
29041
29333
|
function checkedNullableUuid(value, field) {
|
|
29042
29334
|
return value === null ? null : checkedUuid2(value, field);
|
|
29043
29335
|
}
|
|
29336
|
+
function checkedBoolean(value, field) {
|
|
29337
|
+
if (typeof value !== "boolean") {
|
|
29338
|
+
throw new Error(`signal read returned a malformed ${field}`);
|
|
29339
|
+
}
|
|
29340
|
+
return value;
|
|
29341
|
+
}
|
|
29044
29342
|
function checkedTimestamp(value, field) {
|
|
29045
29343
|
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
29046
29344
|
throw new Error(`signal read returned a malformed ${field}`);
|
|
@@ -29120,7 +29418,29 @@ function parseSignalRecord(value, options = {}) {
|
|
|
29120
29418
|
}),
|
|
29121
29419
|
until: checkedTimestamp(row.until, "until"),
|
|
29122
29420
|
created_at: checkedTimestamp(row.created_at, "created_at"),
|
|
29123
|
-
sender_owner_relation: senderOwnerRelation
|
|
29421
|
+
sender_owner_relation: senderOwnerRelation,
|
|
29422
|
+
/* ABSENT AND NULL ARE DIFFERENT HERE, and the difference is a claim.
|
|
29423
|
+
*
|
|
29424
|
+
* `null` means the server said this signal is in no channel. Absent means
|
|
29425
|
+
* this reader never asked: the human REST path names the chat columns only
|
|
29426
|
+
* when a channel filter is set, and an edge that predates channels never
|
|
29427
|
+
* returns them. Normalizing absence to null, the way `to_agent` above does,
|
|
29428
|
+
* would put `"channel_id": null` in `cswarm feed --json` for a signal that
|
|
29429
|
+
* IS filed in a channel — a false statement, not a missing one. So an
|
|
29430
|
+
* absent key stays absent, and a present one is checked. */
|
|
29431
|
+
...row.channel_id === void 0 ? {} : { channel_id: checkedNullableUuid(row.channel_id, "channel_id") },
|
|
29432
|
+
...row.thread_root_id === void 0 ? {} : {
|
|
29433
|
+
thread_root_id: checkedNullableUuid(
|
|
29434
|
+
row.thread_root_id,
|
|
29435
|
+
"thread_root_id"
|
|
29436
|
+
)
|
|
29437
|
+
},
|
|
29438
|
+
...row.broadcast_to_channel === void 0 ? {} : {
|
|
29439
|
+
broadcast_to_channel: checkedBoolean(
|
|
29440
|
+
row.broadcast_to_channel,
|
|
29441
|
+
"broadcast_to_channel"
|
|
29442
|
+
)
|
|
29443
|
+
}
|
|
29124
29444
|
};
|
|
29125
29445
|
}
|
|
29126
29446
|
function cursorFromUnknown(value) {
|
|
@@ -29432,9 +29752,15 @@ async function humanSignals(target2, credential, query, options) {
|
|
|
29432
29752
|
const url = new URL("/rest/v1/signals", target2.url);
|
|
29433
29753
|
url.searchParams.set(
|
|
29434
29754
|
"select",
|
|
29435
|
-
|
|
29755
|
+
[
|
|
29756
|
+
"id,workspace_id,from,from_kind,to,to_agent,in_reply_to,about,kind,body,attachments,until,created_at",
|
|
29757
|
+
...query.channelId === void 0 ? [] : ["channel_id", "thread_root_id", "broadcast_to_channel"]
|
|
29758
|
+
].join(",")
|
|
29436
29759
|
);
|
|
29437
29760
|
url.searchParams.set("workspace_id", `eq.${query.workspaceId}`);
|
|
29761
|
+
if (query.channelId !== void 0) {
|
|
29762
|
+
url.searchParams.set("channel_id", `eq.${query.channelId}`);
|
|
29763
|
+
}
|
|
29438
29764
|
if (query.inbox) url.searchParams.set("to", `eq.${credential.userId}`);
|
|
29439
29765
|
if (!query.includeStale) {
|
|
29440
29766
|
url.searchParams.set("until", "gt.now");
|
|
@@ -29507,6 +29833,11 @@ async function agentSignalPage(target2, credential, query, options, allowLegacyC
|
|
|
29507
29833
|
about: query.about ?? null,
|
|
29508
29834
|
kind: query.kind ?? null,
|
|
29509
29835
|
in_reply_to: query.in_reply_to ?? null,
|
|
29836
|
+
/* Its OWN key, present only when asked for. The read edge groups it
|
|
29837
|
+
* with `chatReadKeys` and refuses a key it did not expect, and every
|
|
29838
|
+
* agent body already carries `in_reply_to`, so folding `channel` in
|
|
29839
|
+
* beside it would 400 every agent read that omits a channel. */
|
|
29840
|
+
...query.channel === void 0 ? {} : { channel: query.channel },
|
|
29510
29841
|
since: query.since ?? null,
|
|
29511
29842
|
...includeCursor ? {
|
|
29512
29843
|
after_created_at: query.after?.created_at ?? null,
|
|
@@ -42206,8 +42537,11 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42206
42537
|
"foreground",
|
|
42207
42538
|
"grok-executable",
|
|
42208
42539
|
"head-sha",
|
|
42540
|
+
"broadcast-to-channel",
|
|
42541
|
+
"channel",
|
|
42209
42542
|
"help",
|
|
42210
42543
|
"if-version",
|
|
42544
|
+
"include-archived",
|
|
42211
42545
|
"include-stale",
|
|
42212
42546
|
"include-tombstoned",
|
|
42213
42547
|
"invitation-id",
|
|
@@ -42227,6 +42561,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42227
42561
|
"permissions",
|
|
42228
42562
|
"principal-id",
|
|
42229
42563
|
"provider",
|
|
42564
|
+
"purpose",
|
|
42230
42565
|
"renewal-grant-id",
|
|
42231
42566
|
"repo",
|
|
42232
42567
|
"reveal-anon-key",
|
|
@@ -42236,6 +42571,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42236
42571
|
"site",
|
|
42237
42572
|
"slug",
|
|
42238
42573
|
"state-dir",
|
|
42574
|
+
"thread",
|
|
42239
42575
|
"renewal-horizon-days",
|
|
42240
42576
|
"standing",
|
|
42241
42577
|
"task-id",
|
|
@@ -42256,12 +42592,14 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42256
42592
|
"agent-token-stdin",
|
|
42257
42593
|
"all-devices",
|
|
42258
42594
|
"allow-unattended",
|
|
42595
|
+
"broadcast-to-channel",
|
|
42259
42596
|
"confirm-standing",
|
|
42260
42597
|
"force-file-store",
|
|
42261
42598
|
"follow",
|
|
42262
42599
|
"force",
|
|
42263
42600
|
"foreground",
|
|
42264
42601
|
"help",
|
|
42602
|
+
"include-archived",
|
|
42265
42603
|
"include-stale",
|
|
42266
42604
|
"include-tombstoned",
|
|
42267
42605
|
"invitation-token-stdin",
|
|
@@ -42274,13 +42612,14 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42274
42612
|
"reveal-anon-key",
|
|
42275
42613
|
"repo",
|
|
42276
42614
|
"standing",
|
|
42615
|
+
"thread",
|
|
42277
42616
|
"user",
|
|
42278
42617
|
"write"
|
|
42279
42618
|
]);
|
|
42280
42619
|
var UUID_RE23 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
42281
42620
|
function packageVersion() {
|
|
42282
|
-
if ("0.1.
|
|
42283
|
-
return "0.1.
|
|
42621
|
+
if ("0.1.55".length > 0) {
|
|
42622
|
+
return "0.1.55";
|
|
42284
42623
|
}
|
|
42285
42624
|
try {
|
|
42286
42625
|
const value = JSON.parse(
|
|
@@ -42399,15 +42738,19 @@ Usage:
|
|
|
42399
42738
|
cswarm whoami ${requiredAgentCredential} [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
42400
42739
|
cswarm resume --agent-token-file <path> [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
|
|
42401
42740
|
cswarm members [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
42402
|
-
cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--until <dur>] [--json]
|
|
42403
|
-
cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--attach <path> ...] [--until <dur>] [--json] # text: 1..8000 characters
|
|
42404
|
-
cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--attach <path> ...] [--until <dur>] [--wait <seconds>] [--json] # text: 1..8000 characters
|
|
42405
|
-
cswarm reply <signal-id> "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--attach <path> ...] [--until <dur>] [--json]
|
|
42741
|
+
cswarm working-on "<what>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--channel <name>] [--until <dur>] [--json]
|
|
42742
|
+
cswarm note "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--channel <name>] [--attach <path> ...] [--until <dur>] [--json] # text: 1..8000 characters
|
|
42743
|
+
cswarm ask "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--to <member|agent>] [--about <ref>] [--channel <name>] [--attach <path> ...] [--until <dur>] [--wait <seconds>] [--json] # text: 1..8000 characters
|
|
42744
|
+
cswarm reply <signal-id> "<text>" [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--thread [--broadcast-to-channel]] [--attach <path> ...] [--until <dur>] [--json]
|
|
42406
42745
|
cswarm receipt <signal-id> ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
|
|
42407
|
-
cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--kind <kind>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
|
|
42408
|
-
cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
|
|
42746
|
+
cswarm feed [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--about <ref>] [--kind <kind>] [--channel <name>] [--since <timestamp>] [--limit <n>] [--include-stale] [--json]
|
|
42747
|
+
cswarm inbox [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--channel <name>] [--since <timestamp>] [--limit <n>] [--include-stale] [--wait <seconds>] [--json]
|
|
42409
42748
|
cswarm inbox --notify ${requiredAgentCredential} [--url <url> --anon-key <key>] --workspace-id <uuid> [--json]
|
|
42410
42749
|
cswarm inbox --follow --ndjson [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--kind <kind>] [--about <ref>] [--since <timestamp>] [--limit <n>] [--include-stale]
|
|
42750
|
+
cswarm channel create <name> [--purpose <text>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json] # purpose: at most ${CHANNEL_PURPOSE_MAX} characters
|
|
42751
|
+
cswarm channel ls [--include-archived] [--url <url> --anon-key <key>] [--workspace-id <uuid>] [--json]
|
|
42752
|
+
cswarm channel rename <name|channel-id> <new-name> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
42753
|
+
cswarm channel archive <name|channel-id> [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
42411
42754
|
cswarm file put <local-path> [--name <name>] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
42412
42755
|
cswarm file ls [--include-tombstoned] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
42413
42756
|
cswarm file get <name|file-id> [--version <n>] [--out <local-path>] [--force] [--url <url> --anon-key <key>] [--workspace-id <uuid>] ${agentCredential2} [--json]
|
|
@@ -42464,6 +42807,11 @@ Credential selection for command/dogfood:
|
|
|
42464
42807
|
signal command/read only -- either form
|
|
42465
42808
|
receipt reads only -- either form
|
|
42466
42809
|
inbox --notify persists a per-agent cursor -- needs principal_id
|
|
42810
|
+
channel create, channel rename, channel archive
|
|
42811
|
+
command only, nothing persisted -- either form
|
|
42812
|
+
channel ls reads swarm_read.channels over REST -- signed-in
|
|
42813
|
+
person only; the read service has no channels
|
|
42814
|
+
resource for an agent credential
|
|
42467
42815
|
file put, file ls, file get, file rm, file restore,
|
|
42468
42816
|
brain ls, brain get, brain put
|
|
42469
42817
|
read and command, nothing persisted -- either form
|
|
@@ -42489,6 +42837,14 @@ Credential selection for command/dogfood:
|
|
|
42489
42837
|
Found a bug or missing feature in cswarm itself? cswarm feedback sends it to the
|
|
42490
42838
|
deployment's operators \u2014 agents are encouraged to report friction they hit.
|
|
42491
42839
|
|
|
42840
|
+
A channel is where a message is FILED, not who may read it. Everyone in the
|
|
42841
|
+
workspace reads every channel, and --channel changes nothing about who sees a
|
|
42842
|
+
signal. Archiving a channel keeps its history and its permalinks and refuses new
|
|
42843
|
+
messages. cswarm reply --thread answers in the open, in the thread of the signal
|
|
42844
|
+
you name, so it takes no recipient; add --broadcast-to-channel to send that reply
|
|
42845
|
+
to the thread's channel as well. Plain cswarm reply is unchanged and still
|
|
42846
|
+
answers the original author privately.
|
|
42847
|
+
|
|
42492
42848
|
Signals (intention sharing) accept the same credential selection. Agent mode
|
|
42493
42849
|
never opens a browser or infers a human's saved workspace. Durations use a whole
|
|
42494
42850
|
number plus m, h, or d (for example 90m, 24h, or 7d) and are capped at 30d.
|
|
@@ -43999,6 +44355,19 @@ function signalKind(value) {
|
|
|
43999
44355
|
}
|
|
44000
44356
|
return value;
|
|
44001
44357
|
}
|
|
44358
|
+
function channelOption(args) {
|
|
44359
|
+
const value = args.optional("channel");
|
|
44360
|
+
if (value === void 0) return void 0;
|
|
44361
|
+
const problem = channelSlugProblem(value);
|
|
44362
|
+
if (problem !== null) throw new Error(problem);
|
|
44363
|
+
return normalizeChannelSlug(value);
|
|
44364
|
+
}
|
|
44365
|
+
function unknownChannelReadMessage(error, slug) {
|
|
44366
|
+
const details = followHttpDetails(error);
|
|
44367
|
+
if (details === null || details.status !== 404) return null;
|
|
44368
|
+
if (followErrorEnvelope(error).error !== "channel_not_found") return null;
|
|
44369
|
+
return `There is no channel named ${slug} in this workspace. Nothing was read. Create it with cswarm channel create ${slug}, or drop --channel to read everything.`;
|
|
44370
|
+
}
|
|
44002
44371
|
function signalDuration(value) {
|
|
44003
44372
|
if (value === void 0) return void 0;
|
|
44004
44373
|
const match = /^([1-9]\d*)(m|h|d)$/.exec(value);
|
|
@@ -44300,11 +44669,13 @@ async function runPostSignal(args, kind) {
|
|
|
44300
44669
|
...CREDENTIAL_FLAGS,
|
|
44301
44670
|
...allowTo ? ["to"] : [],
|
|
44302
44671
|
"about",
|
|
44672
|
+
"channel",
|
|
44303
44673
|
"until",
|
|
44304
44674
|
...allowWait ? ["wait"] : [],
|
|
44305
44675
|
...allowTo ? ["attach"] : [],
|
|
44306
44676
|
"json"
|
|
44307
44677
|
], 2);
|
|
44678
|
+
const channel = channelOption(args);
|
|
44308
44679
|
const preparedAttachments = allowTo ? prepareSignalAttachments(args.all("attach")) : [];
|
|
44309
44680
|
const waitSeconds = allowWait && args.optional("wait") !== void 0 ? parseWaitSeconds(args.required("wait")) : void 0;
|
|
44310
44681
|
const cloud = await target(args);
|
|
@@ -44349,7 +44720,8 @@ async function runPostSignal(args, kind) {
|
|
|
44349
44720
|
...postSignalTargets(recipient),
|
|
44350
44721
|
about: args.optional("about") === void 0 ? null : signalText(args.required("about"), "about"),
|
|
44351
44722
|
...attachments.length === 0 ? {} : { attachments },
|
|
44352
|
-
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
|
|
44723
|
+
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 },
|
|
44724
|
+
...channel === void 0 ? {} : { channel }
|
|
44353
44725
|
};
|
|
44354
44726
|
let result;
|
|
44355
44727
|
try {
|
|
@@ -44474,15 +44846,35 @@ function replyRefusalHint(error) {
|
|
|
44474
44846
|
if (!(error instanceof CommandHttpError) || error.status !== 403) return null;
|
|
44475
44847
|
return "reply was refused (403). The most common cause is that the signal was not addressed to you \u2014 you cannot reply to your own ask; reply to the other party's signal, reach someone directly with cswarm ask --to <agent>, or post a channel-visible cswarm note. If you did receive that signal, the refusal is an authorization one instead: the credential may be revoked or expired, or it may not be a member of this workspace.";
|
|
44476
44848
|
}
|
|
44849
|
+
function threadReplyMessage(signal, options) {
|
|
44850
|
+
if (!options.inThread) {
|
|
44851
|
+
return "Reply shared. It is immutable and addressed to the original author.";
|
|
44852
|
+
}
|
|
44853
|
+
const inThread = "Reply shared in the thread. It is immutable and readable by everyone who can read the thread.";
|
|
44854
|
+
if (!options.broadcastToChannel) return inThread;
|
|
44855
|
+
if (signal.channel_id === void 0) {
|
|
44856
|
+
return `${inThread} This deployment did not say which channel the thread is in, so whether it also reached a channel is unknown.`;
|
|
44857
|
+
}
|
|
44858
|
+
return signal.channel_id === null ? `${inThread} Its thread is in no channel, so --broadcast-to-channel had nothing to send it to.` : "Reply shared in the thread and sent to the thread's channel as well. It is immutable and readable by everyone who can read the thread.";
|
|
44859
|
+
}
|
|
44477
44860
|
async function runReply(args) {
|
|
44478
44861
|
args.assertShape([
|
|
44479
44862
|
...TARGET_FLAGS,
|
|
44480
44863
|
"workspace-id",
|
|
44481
44864
|
...CREDENTIAL_FLAGS,
|
|
44482
44865
|
"attach",
|
|
44866
|
+
"broadcast-to-channel",
|
|
44867
|
+
"thread",
|
|
44483
44868
|
"until",
|
|
44484
44869
|
"json"
|
|
44485
44870
|
], 3);
|
|
44871
|
+
const inThread = args.has("thread");
|
|
44872
|
+
const broadcastToChannel = args.has("broadcast-to-channel");
|
|
44873
|
+
if (broadcastToChannel && !inThread) {
|
|
44874
|
+
throw new UsageError(
|
|
44875
|
+
"--broadcast-to-channel sends a thread reply to its channel as well, so it needs --thread"
|
|
44876
|
+
);
|
|
44877
|
+
}
|
|
44486
44878
|
const signalId = args.positionals[1];
|
|
44487
44879
|
if (signalId === void 0 || !UUID_RE23.test(signalId)) {
|
|
44488
44880
|
throw new Error("reply requires the signal UUID being answered");
|
|
@@ -44508,24 +44900,30 @@ async function runReply(args) {
|
|
|
44508
44900
|
body: signalText(body, "body"),
|
|
44509
44901
|
to_user_id: null,
|
|
44510
44902
|
to_agent_principal_id: null,
|
|
44511
|
-
in_reply_to: signalId.toLowerCase(),
|
|
44903
|
+
in_reply_to: inThread ? null : signalId.toLowerCase(),
|
|
44512
44904
|
about: null,
|
|
44513
44905
|
...attachments.length === 0 ? {} : { attachments },
|
|
44514
|
-
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 }
|
|
44906
|
+
...untilMs2 === void 0 ? {} : { until_ms: untilMs2 },
|
|
44907
|
+
...inThread ? { thread_root_id: signalId.toLowerCase() } : {},
|
|
44908
|
+
...broadcastToChannel ? { broadcast_to_channel: true } : {}
|
|
44515
44909
|
};
|
|
44516
44910
|
let result;
|
|
44517
44911
|
try {
|
|
44518
44912
|
result = await postSignalCommand(cloud, credential, command2);
|
|
44519
44913
|
} catch (error) {
|
|
44520
|
-
const hint = replyRefusalHint(error);
|
|
44914
|
+
const hint = inThread ? null : replyRefusalHint(error);
|
|
44521
44915
|
if (hint !== null) throw new Error(hint);
|
|
44522
44916
|
throw error;
|
|
44523
44917
|
}
|
|
44524
44918
|
const signal = result.response.signal;
|
|
44919
|
+
const replyMessage = threadReplyMessage(signal, {
|
|
44920
|
+
inThread,
|
|
44921
|
+
broadcastToChannel
|
|
44922
|
+
});
|
|
44525
44923
|
if (args.has("json")) {
|
|
44526
44924
|
printJson({
|
|
44527
44925
|
status: result.response.status,
|
|
44528
|
-
message: "Reply shared. It is immutable, tenancy-scoped, and will quietly expire at its horizon.",
|
|
44926
|
+
message: inThread ? replyMessage : "Reply shared. It is immutable, tenancy-scoped, and will quietly expire at its horizon.",
|
|
44529
44927
|
signal,
|
|
44530
44928
|
retried: result.retried,
|
|
44531
44929
|
attempts: result.attempts
|
|
@@ -44540,7 +44938,7 @@ async function runReply(args) {
|
|
|
44540
44938
|
)
|
|
44541
44939
|
);
|
|
44542
44940
|
process.stdout.write(
|
|
44543
|
-
|
|
44941
|
+
`${replyMessage}
|
|
44544
44942
|
${renderSignals([signal], {
|
|
44545
44943
|
inbox: false,
|
|
44546
44944
|
includeStale: true,
|
|
@@ -44822,6 +45220,7 @@ async function runSignalRead(args, inbox) {
|
|
|
44822
45220
|
"workspace-id",
|
|
44823
45221
|
...CREDENTIAL_FLAGS,
|
|
44824
45222
|
"about",
|
|
45223
|
+
"channel",
|
|
44825
45224
|
"kind",
|
|
44826
45225
|
...inbox ? ["wait", "follow", "ndjson", "notify"] : [],
|
|
44827
45226
|
"since",
|
|
@@ -44837,6 +45236,9 @@ async function runSignalRead(args, inbox) {
|
|
|
44837
45236
|
if (!args.has("ndjson")) {
|
|
44838
45237
|
throw new Error("inbox --follow requires --ndjson");
|
|
44839
45238
|
}
|
|
45239
|
+
if (args.has("channel")) {
|
|
45240
|
+
throw new Error("inbox --follow cannot be combined with --channel");
|
|
45241
|
+
}
|
|
44840
45242
|
if (args.optional("wait") !== void 0) {
|
|
44841
45243
|
throw new Error("inbox --follow cannot be combined with --wait");
|
|
44842
45244
|
}
|
|
@@ -44849,15 +45251,29 @@ async function runSignalRead(args, inbox) {
|
|
|
44849
45251
|
if (inbox && args.has("ndjson")) {
|
|
44850
45252
|
throw new Error("inbox --ndjson requires --follow");
|
|
44851
45253
|
}
|
|
45254
|
+
const channelSlug = channelOption(args);
|
|
44852
45255
|
const waitSeconds = inbox && args.optional("wait") !== void 0 ? parseWaitSeconds(args.required("wait")) : void 0;
|
|
44853
45256
|
const cloud = await target(args);
|
|
44854
45257
|
const selected = await commandWorkspaceAndCredential(args, cloud, {
|
|
44855
45258
|
validateHumanWorkspace: true
|
|
44856
45259
|
});
|
|
44857
45260
|
const credential = signalCredentialOf(selected);
|
|
45261
|
+
let channelId;
|
|
45262
|
+
if (channelSlug !== void 0 && selected.kind === "human") {
|
|
45263
|
+
const rows4 = await listChannelsAsHuman(
|
|
45264
|
+
cloud,
|
|
45265
|
+
selected.human.accessToken,
|
|
45266
|
+
selected.selectedWorkspace
|
|
45267
|
+
);
|
|
45268
|
+
const match = findChannelBySlug(rows4, channelSlug);
|
|
45269
|
+
if (match === null) throw new Error(unknownChannelMessage(channelSlug, rows4));
|
|
45270
|
+
channelId = match.channel_id;
|
|
45271
|
+
}
|
|
44858
45272
|
const queryBase = {
|
|
44859
45273
|
workspaceId: selected.selectedWorkspace,
|
|
44860
45274
|
inbox,
|
|
45275
|
+
...channelSlug === void 0 || selected.kind !== "agent" ? {} : { channel: channelSlug },
|
|
45276
|
+
...channelId === void 0 ? {} : { channelId },
|
|
44861
45277
|
...args.optional("about") === void 0 ? {} : { about: signalText(args.required("about"), "about") },
|
|
44862
45278
|
...args.optional("kind") === void 0 ? {} : { kind: signalKind(args.required("kind")) },
|
|
44863
45279
|
...args.optional("since") === void 0 ? {} : { since: args.required("since") },
|
|
@@ -44867,17 +45283,23 @@ async function runSignalRead(args, inbox) {
|
|
|
44867
45283
|
let rows3;
|
|
44868
45284
|
let timedOut = false;
|
|
44869
45285
|
let waited = false;
|
|
44870
|
-
|
|
44871
|
-
|
|
44872
|
-
|
|
44873
|
-
|
|
44874
|
-
|
|
44875
|
-
|
|
44876
|
-
|
|
44877
|
-
|
|
44878
|
-
|
|
44879
|
-
|
|
44880
|
-
|
|
45286
|
+
try {
|
|
45287
|
+
if (waitSeconds === void 0) {
|
|
45288
|
+
rows3 = await readSignals(cloud, credential, queryBase);
|
|
45289
|
+
} else {
|
|
45290
|
+
waited = true;
|
|
45291
|
+
const deadlineMs = waitDeadlineMs(waitSeconds);
|
|
45292
|
+
const waitResult = await pollForSignals({
|
|
45293
|
+
deadlineMs,
|
|
45294
|
+
read: () => readSignals(cloud, credential, queryBase, { deadlineMs })
|
|
45295
|
+
});
|
|
45296
|
+
rows3 = waitResult.signals;
|
|
45297
|
+
timedOut = waitResult.timedOut;
|
|
45298
|
+
}
|
|
45299
|
+
} catch (error) {
|
|
45300
|
+
const named = channelSlug === void 0 ? null : unknownChannelReadMessage(error, channelSlug);
|
|
45301
|
+
if (named !== null) throw new Error(named);
|
|
45302
|
+
throw error;
|
|
44881
45303
|
}
|
|
44882
45304
|
if (args.has("json")) {
|
|
44883
45305
|
printJson(
|
|
@@ -44910,6 +45332,12 @@ async function runSignalRead(args, inbox) {
|
|
|
44910
45332
|
);
|
|
44911
45333
|
return;
|
|
44912
45334
|
}
|
|
45335
|
+
if (channelSlug !== void 0) {
|
|
45336
|
+
process.stdout.write(
|
|
45337
|
+
`${inbox ? "Inbox" : "Feed"}, filed in ${channelSlug}:
|
|
45338
|
+
`
|
|
45339
|
+
);
|
|
45340
|
+
}
|
|
44913
45341
|
process.stdout.write(`${renderSignals(rows3, {
|
|
44914
45342
|
inbox,
|
|
44915
45343
|
includeStale: args.has("include-stale"),
|
|
@@ -47468,6 +47896,174 @@ async function runFeedback(args) {
|
|
|
47468
47896
|
"Feedback recorded for the operators of this deployment. It is stored durably with your workspace and identity attached, and it is read when they review feedback - there is no reply channel, so nothing further will happen in this session.\n"
|
|
47469
47897
|
);
|
|
47470
47898
|
}
|
|
47899
|
+
async function channelRows(context) {
|
|
47900
|
+
if (context.selected.kind === "agent") {
|
|
47901
|
+
throw new Error(CHANNEL_LIST_NEEDS_HUMAN_MESSAGE);
|
|
47902
|
+
}
|
|
47903
|
+
const read = async () => await listChannelsAsHuman(
|
|
47904
|
+
context.cloud,
|
|
47905
|
+
context.selected.human.accessToken,
|
|
47906
|
+
context.selected.selectedWorkspace
|
|
47907
|
+
);
|
|
47908
|
+
try {
|
|
47909
|
+
return await read();
|
|
47910
|
+
} catch (error) {
|
|
47911
|
+
if (error instanceof ChannelListError && error.noResponse) return await read();
|
|
47912
|
+
throw error;
|
|
47913
|
+
}
|
|
47914
|
+
}
|
|
47915
|
+
function channelSelectorKind(selector) {
|
|
47916
|
+
if (UUID_RE23.test(selector)) return "id";
|
|
47917
|
+
const problem = channelSelectorProblem(selector);
|
|
47918
|
+
if (problem !== null) throw new Error(problem);
|
|
47919
|
+
return "name";
|
|
47920
|
+
}
|
|
47921
|
+
async function resolveChannelSelector(context, selector, kind) {
|
|
47922
|
+
if (kind === "id") return selector.toLowerCase();
|
|
47923
|
+
if (context.selected.kind === "agent") {
|
|
47924
|
+
throw new Error(CHANNEL_SELECTOR_NEEDS_ID_MESSAGE);
|
|
47925
|
+
}
|
|
47926
|
+
const rows3 = await channelRows(context);
|
|
47927
|
+
const match = findChannelBySlug(rows3, selector);
|
|
47928
|
+
if (match === null) throw new Error(unknownChannelMessage(selector, rows3));
|
|
47929
|
+
return match.channel_id;
|
|
47930
|
+
}
|
|
47931
|
+
async function sendChannelCommand(context, command2) {
|
|
47932
|
+
const client = new ThinCommandClient(context.cloud);
|
|
47933
|
+
const result = await client.sendChannel({
|
|
47934
|
+
workspaceId: context.selected.selectedWorkspace,
|
|
47935
|
+
command: command2,
|
|
47936
|
+
credential: context.selected.bearer
|
|
47937
|
+
});
|
|
47938
|
+
return result.channel;
|
|
47939
|
+
}
|
|
47940
|
+
var CHANNEL_FLAGS = [
|
|
47941
|
+
...TARGET_FLAGS,
|
|
47942
|
+
"workspace-id",
|
|
47943
|
+
...CREDENTIAL_FLAGS,
|
|
47944
|
+
"json"
|
|
47945
|
+
];
|
|
47946
|
+
async function runChannelCreate(args) {
|
|
47947
|
+
const name = args.positionals[2];
|
|
47948
|
+
if (name === void 0) {
|
|
47949
|
+
throw new UsageError("cswarm channel create needs a channel name");
|
|
47950
|
+
}
|
|
47951
|
+
args.assertShape([...CHANNEL_FLAGS, "purpose"], 3);
|
|
47952
|
+
const problem = channelSlugProblem(name);
|
|
47953
|
+
if (problem !== null) throw new Error(problem);
|
|
47954
|
+
const purposeInput = args.optional("purpose");
|
|
47955
|
+
const purpose = purposeInput === void 0 ? void 0 : purposeInput.trim();
|
|
47956
|
+
if (purpose !== void 0 && purpose.length > CHANNEL_PURPOSE_MAX) {
|
|
47957
|
+
throw new Error(
|
|
47958
|
+
`A channel purpose is at most ${CHANNEL_PURPOSE_MAX} characters.`
|
|
47959
|
+
);
|
|
47960
|
+
}
|
|
47961
|
+
const context = await fileContext(args, ["purpose"], 3);
|
|
47962
|
+
const channel = await sendChannelCommand(context, {
|
|
47963
|
+
kind: "channel_create",
|
|
47964
|
+
slug: normalizeChannelSlug(name),
|
|
47965
|
+
...purpose === void 0 || purpose.length === 0 ? {} : { purpose }
|
|
47966
|
+
});
|
|
47967
|
+
if (args.has("json")) {
|
|
47968
|
+
printJson({ workspace_id: channel.workspace_id, channel });
|
|
47969
|
+
return;
|
|
47970
|
+
}
|
|
47971
|
+
process.stdout.write(
|
|
47972
|
+
`Channel ${channel.slug} created. Everyone in this workspace can read it and post to it; a channel is where a message is filed, not who may see it.
|
|
47973
|
+
Post to it with cswarm note "<text>" --channel ${channel.slug}
|
|
47974
|
+
Read it with cswarm feed --channel ${channel.slug}
|
|
47975
|
+
Its id, which rename and archive take: ${channel.channel_id}
|
|
47976
|
+
`
|
|
47977
|
+
);
|
|
47978
|
+
}
|
|
47979
|
+
async function runChannelLs(args) {
|
|
47980
|
+
args.assertShape([...CHANNEL_FLAGS, "include-archived"], 2);
|
|
47981
|
+
const context = await fileContext(args, ["include-archived"], 2);
|
|
47982
|
+
const rows3 = await channelRows(context);
|
|
47983
|
+
const includeArchived = args.has("include-archived");
|
|
47984
|
+
if (args.has("json")) {
|
|
47985
|
+
printJson({
|
|
47986
|
+
workspace_id: context.selected.selectedWorkspace,
|
|
47987
|
+
channels: includeArchived ? rows3 : rows3.filter((row) => row.archived_at === null)
|
|
47988
|
+
});
|
|
47989
|
+
return;
|
|
47990
|
+
}
|
|
47991
|
+
process.stdout.write(renderChannelList(rows3, { includeArchived }));
|
|
47992
|
+
}
|
|
47993
|
+
async function runChannelRename(args) {
|
|
47994
|
+
const selector = args.positionals[2];
|
|
47995
|
+
const nextName = args.positionals[3];
|
|
47996
|
+
if (selector === void 0 || nextName === void 0) {
|
|
47997
|
+
throw new UsageError(
|
|
47998
|
+
"cswarm channel rename needs the channel and its new name"
|
|
47999
|
+
);
|
|
48000
|
+
}
|
|
48001
|
+
args.assertShape([...CHANNEL_FLAGS], 4);
|
|
48002
|
+
const selectorKind = channelSelectorKind(selector);
|
|
48003
|
+
const problem = channelSlugProblem(nextName);
|
|
48004
|
+
if (problem !== null) throw new Error(problem);
|
|
48005
|
+
const context = await fileContext(args, [], 4);
|
|
48006
|
+
const channelId = await resolveChannelSelector(context, selector, selectorKind);
|
|
48007
|
+
const channel = await sendChannelCommand(context, {
|
|
48008
|
+
kind: "channel_rename",
|
|
48009
|
+
channel_id: channelId,
|
|
48010
|
+
slug: normalizeChannelSlug(nextName)
|
|
48011
|
+
});
|
|
48012
|
+
if (args.has("json")) {
|
|
48013
|
+
printJson({ workspace_id: channel.workspace_id, channel });
|
|
48014
|
+
return;
|
|
48015
|
+
}
|
|
48016
|
+
process.stdout.write(
|
|
48017
|
+
`Channel renamed to ${channel.slug}. Every message already filed in it is unchanged and its id has not moved.
|
|
48018
|
+
Post to it with cswarm note "<text>" --channel ${channel.slug}
|
|
48019
|
+
Its id: ${channel.channel_id}
|
|
48020
|
+
`
|
|
48021
|
+
);
|
|
48022
|
+
}
|
|
48023
|
+
async function runChannelArchive(args) {
|
|
48024
|
+
const selector = args.positionals[2];
|
|
48025
|
+
if (selector === void 0) {
|
|
48026
|
+
throw new UsageError("cswarm channel archive needs the channel");
|
|
48027
|
+
}
|
|
48028
|
+
args.assertShape([...CHANNEL_FLAGS], 3);
|
|
48029
|
+
const selectorKind = channelSelectorKind(selector);
|
|
48030
|
+
const context = await fileContext(args, [], 3);
|
|
48031
|
+
const channelId = await resolveChannelSelector(context, selector, selectorKind);
|
|
48032
|
+
const channel = await sendChannelCommand(context, {
|
|
48033
|
+
kind: "channel_archive",
|
|
48034
|
+
channel_id: channelId
|
|
48035
|
+
});
|
|
48036
|
+
if (args.has("json")) {
|
|
48037
|
+
printJson({ workspace_id: channel.workspace_id, channel });
|
|
48038
|
+
return;
|
|
48039
|
+
}
|
|
48040
|
+
process.stdout.write(
|
|
48041
|
+
`Channel ${channel.slug} is archived. It keeps its messages and its links, and it takes no new ones. Archiving it again changes nothing.
|
|
48042
|
+
See it with cswarm channel ls --include-archived
|
|
48043
|
+
Read what is in it with cswarm feed --channel ${channel.slug}
|
|
48044
|
+
`
|
|
48045
|
+
);
|
|
48046
|
+
}
|
|
48047
|
+
var CHANNEL_SUBCOMMANDS = {
|
|
48048
|
+
create: runChannelCreate,
|
|
48049
|
+
ls: runChannelLs,
|
|
48050
|
+
rename: runChannelRename,
|
|
48051
|
+
archive: runChannelArchive
|
|
48052
|
+
};
|
|
48053
|
+
var CHANNEL_SUBCOMMAND_NAMES = Object.keys(
|
|
48054
|
+
CHANNEL_SUBCOMMANDS
|
|
48055
|
+
);
|
|
48056
|
+
async function runChannel(args) {
|
|
48057
|
+
const action = args.positionals[1];
|
|
48058
|
+
const chosen = action === void 0 ? void 0 : CHANNEL_SUBCOMMANDS[action];
|
|
48059
|
+
if (chosen === void 0) {
|
|
48060
|
+
const names = Object.keys(CHANNEL_SUBCOMMANDS);
|
|
48061
|
+
throw new UsageError(
|
|
48062
|
+
`cswarm channel takes ${names.slice(0, -1).join(", ")}, or ${names[names.length - 1]}`
|
|
48063
|
+
);
|
|
48064
|
+
}
|
|
48065
|
+
return await chosen(args);
|
|
48066
|
+
}
|
|
47471
48067
|
async function runFile(args) {
|
|
47472
48068
|
const action = args.positionals[1];
|
|
47473
48069
|
if (action === "put") return await runFilePut(args);
|
|
@@ -47743,6 +48339,10 @@ async function main() {
|
|
|
47743
48339
|
await runFeedback(args);
|
|
47744
48340
|
return;
|
|
47745
48341
|
}
|
|
48342
|
+
if (verb === "channel") {
|
|
48343
|
+
await runChannel(args);
|
|
48344
|
+
return;
|
|
48345
|
+
}
|
|
47746
48346
|
if (verb === "file") {
|
|
47747
48347
|
await runFile(args);
|
|
47748
48348
|
return;
|
|
@@ -47884,6 +48484,7 @@ ${usage()}
|
|
|
47884
48484
|
});
|
|
47885
48485
|
// Annotate the CommonJS export names for ESM import in node:
|
|
47886
48486
|
0 && (module.exports = {
|
|
48487
|
+
CHANNEL_SUBCOMMAND_NAMES,
|
|
47887
48488
|
EXIT_RESTARTABLE,
|
|
47888
48489
|
ListenerUnattendedRefusedError,
|
|
47889
48490
|
TURN_BUDGET_CREDENTIAL_MARGIN_MS,
|
|
@@ -47902,5 +48503,6 @@ ${usage()}
|
|
|
47902
48503
|
resolveDetachedClaudeExecutable,
|
|
47903
48504
|
resolveDetachedCodexExecutable,
|
|
47904
48505
|
resolveTurnBudgetOrDefer,
|
|
48506
|
+
threadReplyMessage,
|
|
47905
48507
|
usage
|
|
47906
48508
|
});
|