@huynle/hub 0.11.0 → 0.11.1
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/CHANGELOG.md +26 -0
- package/README.md +19 -5
- package/package.json +1 -1
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/sessions.ts +4 -1
- package/packages/cli/dist/hub-cli.js +240 -83
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/cli.ts +12 -7
- package/packages/cli/src/decisions.ts +52 -4
- package/packages/cli/src/format.ts +9 -0
- package/packages/cli/src/mcp.ts +20 -4
- package/packages/cli/src/team-intake-mcp.ts +2 -1
- package/packages/cli/src/teams-mcp.ts +7 -3
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/sessions.ts +14 -3
- package/packages/node/package.json +1 -1
- package/packages/preset/package.json +1 -1
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/schema/api/v1/_root.json +119 -0
- package/packages/protocol/schema/api/v1/account.json +419 -0
- package/packages/protocol/schema/api/v1/decisions.json +635 -0
- package/packages/protocol/schema/api/v1/discovery.json +110 -0
- package/packages/protocol/schema/api/v1/events.json +679 -0
- package/packages/protocol/schema/api/v1/session-actions.json +660 -0
- package/packages/protocol/schema/api/v1/sessions.json +2055 -0
- package/packages/protocol/schema/api/v1/speech.json +492 -0
- package/packages/protocol/schema/api/v1/unspecified.json +282 -0
- package/packages/protocol/schema/app-delivery/v1.schema.json +67 -5
- package/packages/protocol/schema/app-front/v1.schema.json +7 -3
- package/packages/protocol/src/api-v1.ts +8 -0
- package/packages/protocol/src/index.ts +2 -0
- package/packages/protocol/src/teams.ts +13 -1
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/app-front.ts +2 -2
- package/packages/sdk/src/ask-fields.ts +76 -0
- package/packages/sdk/src/client.ts +11 -2
- package/packages/sdk/src/envelope.ts +3 -2
- package/packages/sdk/src/index.ts +1 -0
- package/packages/sdk/src/team-intake.ts +6 -0
- package/packages/sdk/src/teams.ts +2 -0
- package/packages/sdk/src/types.ts +36 -4
- package/packages/sdk/src/wake.ts +9 -4
- package/packages/spoke-sdk/package.json +1 -1
- package/src/central/agent/agent-limits.ts +185 -0
- package/src/central/agent/app-decisions.ts +6 -5
- package/src/central/agent/decisions.ts +130 -9
- package/src/central/agent/delivery.ts +134 -22
- package/src/central/agent/errors.ts +2 -1
- package/src/central/agent/events.ts +20 -2
- package/src/central/agent/launch-engine.ts +6 -3
- package/src/central/agent/message-limits.ts +5 -2
- package/src/central/agent/message-routes.ts +33 -6
- package/src/central/agent/messages.ts +7 -0
- package/src/central/agent/placement.ts +4 -2
- package/src/central/agent/read-routes.ts +5 -0
- package/src/central/agent/routes.ts +2 -0
- package/src/central/agent/subscriptions.ts +13 -4
- package/src/central/api-v1.ts +115 -0
- package/src/central/artifacts/store.ts +4 -2
- package/src/central/concierge/board-results.ts +2 -1
- package/src/central/concierge/notifier.ts +4 -2
- package/src/central/config.ts +20 -6
- package/src/central/health/classify.ts +209 -0
- package/src/central/http.ts +285 -23
- package/src/central/recording/index.ts +2 -1
- package/src/central/recording/schema.ts +2 -1
- package/src/central/registry.ts +34 -3
- package/src/central/teams/budgets.ts +144 -0
- package/src/central/teams/hop-stalls.ts +14 -6
- package/src/central/teams/intake.ts +38 -6
- package/src/central/teams/involvement.ts +95 -0
- package/src/central/teams/item-decisions.ts +60 -0
- package/src/central/teams/item-eligibility.ts +35 -0
- package/src/central/teams/item-stalls.ts +87 -0
- package/src/central/teams/loops.ts +40 -0
- package/src/central/teams/progress.ts +154 -0
- package/src/central/teams/routes.ts +2 -2
- package/src/central/teams/store.ts +21 -5
- package/src/central/teams/validation.ts +17 -6
- package/src/central/workspaces/coordinator.ts +3 -1
- package/src/spoke/hub-tool.ts +26 -11
- package/web/dist/assets/{ArtifactPage-C5MMqXLN.js → ArtifactPage-TbFJi59n.js} +1 -1
- package/web/dist/assets/{ArtifactPane-CXSCmhyJ.js → ArtifactPane-7D-M-Ich.js} +1 -1
- package/web/dist/assets/{ArtifactWorkbench-S0RxNGWy.js → ArtifactWorkbench-DlCLEjut.js} +1 -1
- package/web/dist/assets/{Diagram-BGmyRb-R.js → Diagram-PvOPetOI.js} +1 -1
- package/web/dist/assets/{Library-CFmMbXEa.js → Library-Cdi83pOV.js} +2 -2
- package/web/dist/assets/{ProfilesDestination-7jpOc2HM.js → ProfilesDestination-cryC7P6j.js} +1 -1
- package/web/dist/assets/TeamsPage-B4xrlGhp.js +1 -0
- package/web/dist/assets/Watchtower-60B1JX_J.js +2 -0
- package/web/dist/assets/index-DA0gKIzv.js +33 -0
- package/web/dist/assets/live-B2IfKm37.js +5 -0
- package/web/dist/assets/{live-BUofsQpz.css → live-De-kd3eW.css} +1 -1
- package/web/dist/assets/{renderer2d-B2Nh39UN.js → renderer2d-CsZnF3_o.js} +1 -1
- package/web/dist/assets/{renderer3d-CTYbljHH.js → renderer3d-BIMA0wFn.js} +2 -2
- package/web/dist/assets/{safe-area-CkhEclGo.js → safe-area-dtrnw9NF.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/assets/TeamsPage-C1NGrUR-.js +0 -1
- package/web/dist/assets/Watchtower-DnvlJaU0.js +0 -2
- package/web/dist/assets/index-Da8SKXMw.js +0 -33
- package/web/dist/assets/live-BmqLASSo.js +0 -5
|
@@ -7,7 +7,7 @@ import { createInterface } from "readline";
|
|
|
7
7
|
// packages/cli/package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@huynle/hub-cli",
|
|
10
|
-
version: "0.11.
|
|
10
|
+
version: "0.11.1",
|
|
11
11
|
private: true,
|
|
12
12
|
description: "The hub CLI (`hub` / `hubctl`) for agents and scripts (internal; bundled into @huynle/hub, not published on its own).",
|
|
13
13
|
license: "MIT",
|
|
@@ -306,6 +306,12 @@ function showText(s) {
|
|
|
306
306
|
if (s.roles?.length)
|
|
307
307
|
field("roles", s.roles.map((r) => `@${at(r)}`).join(" "));
|
|
308
308
|
field("now", s.now);
|
|
309
|
+
if (s.health && s.health.kind !== "ok") {
|
|
310
|
+
const h = s.health;
|
|
311
|
+
const reset = typeof h.resetAt === "number" && Number.isFinite(h.resetAt) && Math.abs(h.resetAt) <= 8640000000000000 ? new Date(h.resetAt).toISOString() : undefined;
|
|
312
|
+
const facts = [h.kind, reset ? `resets ${reset}` : undefined, h.attempt !== undefined ? `attempt ${h.attempt}` : undefined].filter(Boolean).join(", ");
|
|
313
|
+
field("health", h.detail ? `${facts} — ${oneLine(h.detail, 200)}` : facts);
|
|
314
|
+
}
|
|
309
315
|
if (s.children)
|
|
310
316
|
field("subagents", s.children);
|
|
311
317
|
if (s.online === false && s.lastSeen !== undefined)
|
|
@@ -419,6 +425,74 @@ function parseAddress(addr) {
|
|
|
419
425
|
const scope = project ? { project } : {};
|
|
420
426
|
return role ? { kind: "role", role: match[1], ...scope } : { kind: "name", name: match[1], ...scope };
|
|
421
427
|
}
|
|
428
|
+
// packages/sdk/src/ask-fields.ts
|
|
429
|
+
var URGENCIES = ["low", "normal", "high"];
|
|
430
|
+
var TIERS = ["confirm", "tap"];
|
|
431
|
+
var KINDS = ["question", "plan", "merge", "release"];
|
|
432
|
+
var DECISION_ASK_PROPERTIES = {
|
|
433
|
+
options: {
|
|
434
|
+
type: "array",
|
|
435
|
+
items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, safe: { type: "boolean" } }, required: ["id", "label"] },
|
|
436
|
+
description: "ask @me: buttons the user taps, [{id, label, safe?}] (at most 8; id: lowercase letters, digits, - or _)."
|
|
437
|
+
},
|
|
438
|
+
recommendation: {
|
|
439
|
+
type: "object",
|
|
440
|
+
properties: { option: { type: "string" }, why: { type: "string" } },
|
|
441
|
+
required: ["option"],
|
|
442
|
+
description: "ask @me: the option you recommend ({option: an option id, why})."
|
|
443
|
+
},
|
|
444
|
+
urgency: { type: "string", enum: [...URGENCIES], description: "ask @me: low, normal (default) or high." },
|
|
445
|
+
tier: { type: "string", enum: [...TIERS], description: "ask @me: confirm (default: any of the user's devices) or tap (only the signed-in console)." },
|
|
446
|
+
kind: { type: "string", enum: [...KINDS], description: "ask @me: what it is about: question (default), plan, merge or release." },
|
|
447
|
+
choices: {
|
|
448
|
+
type: "array",
|
|
449
|
+
items: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
id: { type: "string" },
|
|
453
|
+
label: { type: "string" },
|
|
454
|
+
options: { type: "array", items: { type: "object", properties: { id: { type: "string" }, label: { type: "string" }, recommended: { type: "boolean" } }, required: ["id", "label"] } }
|
|
455
|
+
},
|
|
456
|
+
required: ["id", "label", "options"]
|
|
457
|
+
},
|
|
458
|
+
description: 'ask @me: several points in one ask, instead of options: [{id: "D1", label, options: [{id, label, recommended?}]}], 1–12 groups of 2–6 options, at most one recommended each; the user picks one option per group and you get one "D1: label [a]" line per group.'
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
var DECISION_ASK_FIELDS = Object.keys(DECISION_ASK_PROPERTIES);
|
|
462
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
463
|
+
function decisionAskFields(args) {
|
|
464
|
+
const out = {};
|
|
465
|
+
const { options, recommendation, urgency, tier, kind, choices } = args;
|
|
466
|
+
const objects = (value, field, shape) => {
|
|
467
|
+
if (!Array.isArray(value) || !value.every(isObject))
|
|
468
|
+
throw new Error(`"${field}" must be a list of ${shape}`);
|
|
469
|
+
return value;
|
|
470
|
+
};
|
|
471
|
+
if (options !== undefined)
|
|
472
|
+
out.options = objects(options, "options", "{id, label, safe?}");
|
|
473
|
+
if (recommendation !== undefined) {
|
|
474
|
+
if (!isObject(recommendation) || typeof recommendation.option !== "string")
|
|
475
|
+
throw new Error(`"recommendation" must be {option, why?}`);
|
|
476
|
+
out.recommendation = recommendation;
|
|
477
|
+
}
|
|
478
|
+
const oneOf = (value, field, allowed) => {
|
|
479
|
+
if (value === undefined)
|
|
480
|
+
return;
|
|
481
|
+
if (typeof value !== "string" || !allowed.includes(value))
|
|
482
|
+
throw new Error(`"${field}" is one of ${allowed.join(", ")}`);
|
|
483
|
+
out[field] = value;
|
|
484
|
+
};
|
|
485
|
+
oneOf(urgency, "urgency", URGENCIES);
|
|
486
|
+
oneOf(tier, "tier", TIERS);
|
|
487
|
+
oneOf(kind, "kind", KINDS);
|
|
488
|
+
if (choices !== undefined) {
|
|
489
|
+
const shape = "{id, label, options: [{id, label, recommended?}]}";
|
|
490
|
+
for (const group of objects(choices, "choices", shape))
|
|
491
|
+
objects(group.options, "choices", shape);
|
|
492
|
+
out.choices = choices;
|
|
493
|
+
}
|
|
494
|
+
return out;
|
|
495
|
+
}
|
|
422
496
|
// packages/sdk/src/errors.ts
|
|
423
497
|
class HubError extends Error {
|
|
424
498
|
status;
|
|
@@ -843,7 +917,7 @@ var segment = (value) => encodeURIComponent(value).replace(/%40/g, "@");
|
|
|
843
917
|
var bareRole = (role) => role.replace(/^@@/, "");
|
|
844
918
|
var decisionFields = (options) => {
|
|
845
919
|
const out = {};
|
|
846
|
-
for (const key of ["options", "recommendation", "urgency", "tier", "deadline", "onTimeout", "about"])
|
|
920
|
+
for (const key of ["options", "recommendation", "choices", "kind", "urgency", "tier", "deadline", "onTimeout", "about"])
|
|
847
921
|
if (options[key] !== undefined)
|
|
848
922
|
out[key] = options[key];
|
|
849
923
|
return out;
|
|
@@ -852,6 +926,7 @@ var whyField = (options) => options.why !== undefined ? { why: options.why } : {
|
|
|
852
926
|
var signalOf = (options) => options.signal ? { signal: options.signal } : {};
|
|
853
927
|
var idempotent = (options) => ({ ...signalOf(options), ...options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {} });
|
|
854
928
|
var sendFields = (options) => ({
|
|
929
|
+
...options.item !== undefined ? { item: options.item } : {},
|
|
855
930
|
...options.wake !== undefined ? { wake: options.wake } : {},
|
|
856
931
|
...options.urgent !== undefined ? { urgent: options.urgent } : {},
|
|
857
932
|
...options.notAfter !== undefined ? { notAfter: options.notAfter } : {}
|
|
@@ -1200,7 +1275,7 @@ class HubClient {
|
|
|
1200
1275
|
// packages/sdk/src/envelope.ts
|
|
1201
1276
|
var LABEL_MAX = 120;
|
|
1202
1277
|
var ANSI = /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?|\u001b[P^_][^\u001b]*(?:\u001b\\)?|\u001b[ -/]*[0-~]?|\u009b[0-?]*[ -/]*[@-~]/g;
|
|
1203
|
-
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff]/
|
|
1278
|
+
var CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u206f\ufeff\p{Cf}]/gu;
|
|
1204
1279
|
function sanitizeText(text) {
|
|
1205
1280
|
return text.replace(/\r\n?/g, `
|
|
1206
1281
|
`).replace(ANSI, "").replace(CONTROL, "");
|
|
@@ -1247,13 +1322,16 @@ function renderEnvelope(input) {
|
|
|
1247
1322
|
var WAKE_SKIP_TEXT = {
|
|
1248
1323
|
wake_false: "sent without a wake",
|
|
1249
1324
|
subagent: "subagents are never woken",
|
|
1250
|
-
hop_cap: "hop limit reached (
|
|
1251
|
-
budget: "wake budget spent
|
|
1325
|
+
hop_cap: "hop limit reached (too many agent messages without a human turn)",
|
|
1326
|
+
budget: "wake budget spent",
|
|
1327
|
+
item_budget: "item wake budget spent",
|
|
1328
|
+
item_loop: "this pair is looping on the item without progress; the user decides"
|
|
1252
1329
|
};
|
|
1253
|
-
function wakeSkipNote({ delivery, woke, wakeSkipped }) {
|
|
1330
|
+
function wakeSkipNote({ delivery, woke, wakeSkipped, wakeLimit }) {
|
|
1254
1331
|
if (woke !== false || !wakeSkipped || delivery === "steer" || delivery === "turn")
|
|
1255
1332
|
return "";
|
|
1256
|
-
|
|
1333
|
+
const detail = wakeLimit !== undefined && Number.isFinite(wakeLimit) && wakeLimit >= 1 ? wakeSkipped === "hop_cap" ? `hop limit reached (${wakeLimit} agent messages without a human turn)` : wakeSkipped === "budget" ? `wake budget spent (${wakeLimit} per 10 minutes)` : wakeSkipped === "item_budget" ? `item wake budget spent (${wakeLimit} wakes)` : undefined : undefined;
|
|
1334
|
+
return ` — wake skipped: ${detail ?? WAKE_SKIP_TEXT[wakeSkipped] ?? wakeSkipped}`;
|
|
1257
1335
|
}
|
|
1258
1336
|
// packages/cli/src/http.ts
|
|
1259
1337
|
var EXIT = {
|
|
@@ -4868,7 +4946,7 @@ function readConfig(file, machineName) {
|
|
|
4868
4946
|
return defaults(machineName);
|
|
4869
4947
|
}
|
|
4870
4948
|
}
|
|
4871
|
-
var
|
|
4949
|
+
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4872
4950
|
function readRawConfig(file) {
|
|
4873
4951
|
let text;
|
|
4874
4952
|
try {
|
|
@@ -4884,7 +4962,7 @@ function readRawConfig(file) {
|
|
|
4884
4962
|
} catch {
|
|
4885
4963
|
parsed = undefined;
|
|
4886
4964
|
}
|
|
4887
|
-
if (!
|
|
4965
|
+
if (!isObject2(parsed))
|
|
4888
4966
|
throw new Error(`${file} is not a JSON object; fix or remove it first`);
|
|
4889
4967
|
return parsed;
|
|
4890
4968
|
}
|
|
@@ -5318,7 +5396,7 @@ class ProfileError extends RuntimeError {
|
|
|
5318
5396
|
this.code = code;
|
|
5319
5397
|
}
|
|
5320
5398
|
}
|
|
5321
|
-
var
|
|
5399
|
+
var isObject3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5322
5400
|
var show = (v) => JSON.stringify(v)?.slice(0, 80) ?? String(v);
|
|
5323
5401
|
var builtinDefault = () => ({ name: DEFAULT_PROFILE, providers: [], endpoints: [], allowGitRemote: false });
|
|
5324
5402
|
function egressHost(v) {
|
|
@@ -5342,7 +5420,7 @@ function egressEndpoint(v) {
|
|
|
5342
5420
|
return;
|
|
5343
5421
|
host = v.slice(0, colon);
|
|
5344
5422
|
port = Number(v.slice(colon + 1));
|
|
5345
|
-
} else if (
|
|
5423
|
+
} else if (isObject3(v)) {
|
|
5346
5424
|
host = v.host;
|
|
5347
5425
|
port = v.port;
|
|
5348
5426
|
}
|
|
@@ -5399,9 +5477,9 @@ function normalizeOpenShellProfiles(raw) {
|
|
|
5399
5477
|
const problems = [];
|
|
5400
5478
|
const profiles = { [DEFAULT_PROFILE]: builtinDefault() };
|
|
5401
5479
|
const unsupported = {};
|
|
5402
|
-
if (raw !== undefined && !
|
|
5480
|
+
if (raw !== undefined && !isObject3(raw))
|
|
5403
5481
|
problems.push("openshell.profiles must be an object of profile name to profile");
|
|
5404
|
-
let entries = Object.entries(
|
|
5482
|
+
let entries = Object.entries(isObject3(raw) ? raw : {});
|
|
5405
5483
|
if (entries.length > PROFILES_MAX) {
|
|
5406
5484
|
problems.push(`openshell.profiles: at most ${PROFILES_MAX} profiles; the rest are ignored`);
|
|
5407
5485
|
entries = entries.slice(0, PROFILES_MAX);
|
|
@@ -5411,7 +5489,7 @@ function normalizeOpenShellProfiles(raw) {
|
|
|
5411
5489
|
problems.push(`openshell.profiles: ${show(name)} is not a profile name (lowercase letters, digits, . _ -; at most ${NAME_MAX})`);
|
|
5412
5490
|
continue;
|
|
5413
5491
|
}
|
|
5414
|
-
if (!
|
|
5492
|
+
if (!isObject3(value)) {
|
|
5415
5493
|
problems.push(`openshell.profiles.${name} must be an object`);
|
|
5416
5494
|
continue;
|
|
5417
5495
|
}
|
|
@@ -5845,7 +5923,7 @@ var HARNESS = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
|
5845
5923
|
function nodeConfigPath(env = process.env, home = homedir2()) {
|
|
5846
5924
|
return path9.join(path9.dirname(configPath(env, home)), NODE_CONFIG_FILE);
|
|
5847
5925
|
}
|
|
5848
|
-
var
|
|
5926
|
+
var isObject4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5849
5927
|
var whole = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
5850
5928
|
var show2 = (v) => JSON.stringify(v)?.slice(0, 80) ?? String(v);
|
|
5851
5929
|
function configPathOf(raw, options) {
|
|
@@ -5867,7 +5945,7 @@ var isRoot = (p) => p === "/" || /^[A-Z]:\/$/.test(p);
|
|
|
5867
5945
|
function normalizeNodeConfig(raw, options = {}) {
|
|
5868
5946
|
const problems = [];
|
|
5869
5947
|
let r = {};
|
|
5870
|
-
if (
|
|
5948
|
+
if (isObject4(raw))
|
|
5871
5949
|
r = raw;
|
|
5872
5950
|
else if (raw !== undefined)
|
|
5873
5951
|
problems.push("node.json must hold a JSON object");
|
|
@@ -5959,7 +6037,7 @@ function loopbackOrigin(raw) {
|
|
|
5959
6037
|
return url.origin;
|
|
5960
6038
|
}
|
|
5961
6039
|
function normalizeOpenCode(raw, problems, options) {
|
|
5962
|
-
if (!
|
|
6040
|
+
if (!isObject4(raw)) {
|
|
5963
6041
|
problems.push("opencode must be an object");
|
|
5964
6042
|
return;
|
|
5965
6043
|
}
|
|
@@ -5970,7 +6048,7 @@ function normalizeOpenCode(raw, problems, options) {
|
|
|
5970
6048
|
const list = Array.isArray(raw.servers) ? raw.servers : [];
|
|
5971
6049
|
list.forEach((entry, i) => {
|
|
5972
6050
|
const field = `opencode.servers[${i}]`;
|
|
5973
|
-
if (!
|
|
6051
|
+
if (!isObject4(entry))
|
|
5974
6052
|
return void problems.push(`${field} must be an object {url, passwordFile, directories?}`);
|
|
5975
6053
|
const url = loopbackOrigin(entry.url);
|
|
5976
6054
|
if (!url)
|
|
@@ -5998,7 +6076,7 @@ function normalizeOpenCode(raw, problems, options) {
|
|
|
5998
6076
|
return { ...directories ? { directories } : {}, servers, ...timeoutSeconds !== undefined ? { timeoutSeconds } : {} };
|
|
5999
6077
|
}
|
|
6000
6078
|
function normalizeHarnesses(raw, problems) {
|
|
6001
|
-
if (!
|
|
6079
|
+
if (!isObject4(raw)) {
|
|
6002
6080
|
problems.push("harnesses must be an object of harness names to true or false");
|
|
6003
6081
|
return;
|
|
6004
6082
|
}
|
|
@@ -6014,7 +6092,7 @@ function normalizeHarnesses(raw, problems) {
|
|
|
6014
6092
|
return out;
|
|
6015
6093
|
}
|
|
6016
6094
|
function normalizeOpenShell(raw, problems) {
|
|
6017
|
-
if (!
|
|
6095
|
+
if (!isObject4(raw)) {
|
|
6018
6096
|
problems.push("openshell must be an object");
|
|
6019
6097
|
return;
|
|
6020
6098
|
}
|
|
@@ -6256,10 +6334,13 @@ var DECISION_FLAGS = {
|
|
|
6256
6334
|
deadline: "string",
|
|
6257
6335
|
"on-timeout": "string",
|
|
6258
6336
|
tier: "string",
|
|
6259
|
-
about: "multi"
|
|
6337
|
+
about: "multi",
|
|
6338
|
+
choice: "multi",
|
|
6339
|
+
kind: "string"
|
|
6260
6340
|
};
|
|
6261
|
-
var
|
|
6262
|
-
var
|
|
6341
|
+
var URGENCIES2 = ["low", "normal", "high"];
|
|
6342
|
+
var KINDS2 = ["question", "plan", "merge", "release"];
|
|
6343
|
+
var TIERS2 = ["confirm", "tap"];
|
|
6263
6344
|
var STATES = ["open", "answered", "expired", "closed"];
|
|
6264
6345
|
var DECISION_STATES = STATES;
|
|
6265
6346
|
function optionArg(value) {
|
|
@@ -6275,6 +6356,37 @@ function optionArg(value) {
|
|
|
6275
6356
|
throw new UsageError(`--option ${id}: the label is empty`);
|
|
6276
6357
|
return { id, label, ...safe ? { safe: true } : {} };
|
|
6277
6358
|
}
|
|
6359
|
+
function choiceArg(value) {
|
|
6360
|
+
const usage = `--choice takes GROUP=Label:a=Option*,b=Option (got "${value}")`;
|
|
6361
|
+
const eq = value.indexOf("=");
|
|
6362
|
+
if (eq <= 0)
|
|
6363
|
+
throw new UsageError(usage);
|
|
6364
|
+
const id = value.slice(0, eq);
|
|
6365
|
+
const rest = value.slice(eq + 1);
|
|
6366
|
+
let split = -1;
|
|
6367
|
+
for (let i = rest.lastIndexOf(":");i >= 0; i = rest.lastIndexOf(":", i - 1)) {
|
|
6368
|
+
if (/^[^=,:\s][^=,:]*=/.test(rest.slice(i + 1))) {
|
|
6369
|
+
split = i;
|
|
6370
|
+
break;
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
if (split < 0)
|
|
6374
|
+
throw new UsageError(usage);
|
|
6375
|
+
const label = rest.slice(0, split);
|
|
6376
|
+
if (!label.trim())
|
|
6377
|
+
throw new UsageError(`--choice ${id}: the label is empty`);
|
|
6378
|
+
const options = rest.slice(split + 1).split(",").map((option) => {
|
|
6379
|
+
const at = option.indexOf("=");
|
|
6380
|
+
if (at <= 0)
|
|
6381
|
+
throw new UsageError(usage);
|
|
6382
|
+
const recommended = option.endsWith("*");
|
|
6383
|
+
const text = option.slice(at + 1, recommended ? -1 : undefined);
|
|
6384
|
+
if (!text.trim())
|
|
6385
|
+
throw new UsageError(`--choice ${id}: option ${option.slice(0, at)} has an empty label`);
|
|
6386
|
+
return { id: option.slice(0, at), label: text, ...recommended ? { recommended: true } : {} };
|
|
6387
|
+
});
|
|
6388
|
+
return { id, label, options };
|
|
6389
|
+
}
|
|
6278
6390
|
var oneOf2 = (value, allowed, flag) => {
|
|
6279
6391
|
if (value !== undefined && !allowed.includes(value))
|
|
6280
6392
|
throw new UsageError(`${flag} is one of ${allowed.join(", ")}`);
|
|
@@ -6291,13 +6403,19 @@ function decisionOptions(flags, to) {
|
|
|
6291
6403
|
if (why !== undefined && recommend === undefined)
|
|
6292
6404
|
throw new UsageError("--why explains --recommend");
|
|
6293
6405
|
const deadline = str(flags, "deadline");
|
|
6294
|
-
const urgency = oneOf2(str(flags, "urgency"),
|
|
6295
|
-
const tier = oneOf2(str(flags, "tier"),
|
|
6406
|
+
const urgency = oneOf2(str(flags, "urgency"), URGENCIES2, "--urgency");
|
|
6407
|
+
const tier = oneOf2(str(flags, "tier"), TIERS2, "--tier");
|
|
6296
6408
|
const onTimeout = str(flags, "on-timeout");
|
|
6297
6409
|
const options = flags.option;
|
|
6298
6410
|
const about = flags.about;
|
|
6411
|
+
const choices = flags.choice;
|
|
6412
|
+
if (choices && options)
|
|
6413
|
+
throw new UsageError("--choice and --option don't mix: give one or the other");
|
|
6414
|
+
const kind = oneOf2(str(flags, "kind"), KINDS2, "--kind");
|
|
6299
6415
|
return {
|
|
6300
6416
|
...options ? { options: options.map(optionArg) } : {},
|
|
6417
|
+
...choices ? { choices: choices.map(choiceArg) } : {},
|
|
6418
|
+
...kind ? { kind } : {},
|
|
6301
6419
|
...recommend !== undefined ? { recommendation: { option: recommend, ...why !== undefined ? { why } : {} } } : {},
|
|
6302
6420
|
...urgency ? { urgency } : {},
|
|
6303
6421
|
...tier ? { tier } : {},
|
|
@@ -6311,6 +6429,10 @@ function stateArg(value) {
|
|
|
6311
6429
|
}
|
|
6312
6430
|
var iso2 = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
6313
6431
|
function choicesText(decision) {
|
|
6432
|
+
if (decision.choices?.length) {
|
|
6433
|
+
return decision.choices.map((g) => `${g.id} ${oneLine(g.label, 120)}: ${g.options.map((o) => `${o.id} ${oneLine(o.label, 80)}${o.recommended ? " (recommended)" : ""}`).join(" · ")}`).join(`
|
|
6434
|
+
`);
|
|
6435
|
+
}
|
|
6314
6436
|
const options = decision.options.map((o) => `${o.id} ${oneLine(o.label, 80)}${o.safe ? " (safe)" : ""}`).join(" · ");
|
|
6315
6437
|
const r = decision.recommendation;
|
|
6316
6438
|
const recommended = r ? `recommended: ${r.option}${r.why ? ` (${oneLine(r.why, 200)})` : ""}` : "";
|
|
@@ -6319,7 +6441,8 @@ function choicesText(decision) {
|
|
|
6319
6441
|
function stateText(decision) {
|
|
6320
6442
|
if (decision.state === "answered") {
|
|
6321
6443
|
const a = decision.answer;
|
|
6322
|
-
|
|
6444
|
+
const picks = a?.picks ? ` ${Object.entries(a.picks).map(([group, option]) => `${group}=${option}`).join(" ")}` : "";
|
|
6445
|
+
return `answered${a?.option ? ` ${a.option}` : ""}${picks}${a?.answeredBy === "timeout" ? " (timeout)" : ""}`;
|
|
6323
6446
|
}
|
|
6324
6447
|
if (decision.state === "open" && decision.deadline !== undefined)
|
|
6325
6448
|
return `open (deadline ${iso2(decision.deadline)})`;
|
|
@@ -6496,7 +6619,7 @@ class ToolArgError extends Error {
|
|
|
6496
6619
|
|
|
6497
6620
|
// packages/cli/src/artifacts-mcp.ts
|
|
6498
6621
|
var MCP_INLINE_TEXT_BYTES = 256 * 1024;
|
|
6499
|
-
var
|
|
6622
|
+
var KINDS3 = ["markdown", "code", "image", "table", "chart", "dashboard", "html", "app", "file"];
|
|
6500
6623
|
var DEFAULT_MEDIA = { markdown: "text/markdown", html: "text/html", table: "text/csv", chart: "application/json", dashboard: "application/json" };
|
|
6501
6624
|
var PATH3 = "/api/agent/artifacts";
|
|
6502
6625
|
var STRING = { type: "string" };
|
|
@@ -6592,7 +6715,7 @@ var artifactTools = [
|
|
|
6592
6715
|
description: "Create a persistent artifact with its first revision from small inline text or references to staged bytes. Needs scope artifacts:write for an app token.",
|
|
6593
6716
|
properties: {
|
|
6594
6717
|
title: STRING,
|
|
6595
|
-
kind: { type: "string", enum:
|
|
6718
|
+
kind: { type: "string", enum: KINDS3 },
|
|
6596
6719
|
description: STRING,
|
|
6597
6720
|
visibility: { type: "string", enum: ["private", "workspace"] },
|
|
6598
6721
|
workspaceID: STRING,
|
|
@@ -6604,8 +6727,8 @@ var artifactTools = [
|
|
|
6604
6727
|
run: async (hub, args, signal) => {
|
|
6605
6728
|
const title = need(args, "title");
|
|
6606
6729
|
const kind = need(args, "kind");
|
|
6607
|
-
if (!
|
|
6608
|
-
throw new ToolArgError(`"kind" must be one of ${
|
|
6730
|
+
if (!KINDS3.includes(kind))
|
|
6731
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS3.join(", ")}`);
|
|
6609
6732
|
const body = files(args, kind);
|
|
6610
6733
|
const description = optional2(args, "description");
|
|
6611
6734
|
const visibility = optional2(args, "visibility");
|
|
@@ -6640,7 +6763,7 @@ var STRING2 = { type: "string" };
|
|
|
6640
6763
|
var ID3 = { type: "string", description: "The artifact's ID (art_…)." };
|
|
6641
6764
|
var JOB = { type: "string", description: "The job's ID (job_…)." };
|
|
6642
6765
|
var REVISION = { type: "integer", description: "The job revision you read (from artifact_job)." };
|
|
6643
|
-
var
|
|
6766
|
+
var KINDS4 = ["ask", "propose-edit", "run-action"];
|
|
6644
6767
|
var need2 = (args, key) => {
|
|
6645
6768
|
const value = args[key];
|
|
6646
6769
|
if (typeof value !== "string" || !value.trim())
|
|
@@ -6717,7 +6840,7 @@ var artifactJobTools = [
|
|
|
6717
6840
|
id: ID3,
|
|
6718
6841
|
baseRevisionID: { type: "string", description: "The revision the job is about (propose-edit: the latest)." },
|
|
6719
6842
|
instruction: { type: "string", description: "What to do (plain text, at most 8000 characters)." },
|
|
6720
|
-
kind: { type: "string", enum:
|
|
6843
|
+
kind: { type: "string", enum: KINDS4 },
|
|
6721
6844
|
action: { type: "string", description: "run-action: one of the artifact's declared actions." },
|
|
6722
6845
|
machine: { type: "string", description: "Launch a handler session on this machine…" },
|
|
6723
6846
|
repo: { type: "string", description: "…in this absolute repository path." },
|
|
@@ -6732,8 +6855,8 @@ var artifactJobTools = [
|
|
|
6732
6855
|
readOnly: false,
|
|
6733
6856
|
run: async (hub, args, signal) => {
|
|
6734
6857
|
const kind = optional3(args, "kind");
|
|
6735
|
-
if (kind && !
|
|
6736
|
-
throw new ToolArgError(`"kind" must be one of ${
|
|
6858
|
+
if (kind && !KINDS4.includes(kind))
|
|
6859
|
+
throw new ToolArgError(`"kind" must be one of ${KINDS4.join(", ")}`);
|
|
6737
6860
|
const action = optional3(args, "action");
|
|
6738
6861
|
const apply = optional3(args, "apply");
|
|
6739
6862
|
const key = optional3(args, "key");
|
|
@@ -6905,7 +7028,7 @@ function notAfterOf(args) {
|
|
|
6905
7028
|
}
|
|
6906
7029
|
throw new ToolArgError(`"notAfter" must be epoch milliseconds or an ISO 8601 date-time like 2027-01-15T12:00:00Z`);
|
|
6907
7030
|
}
|
|
6908
|
-
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}`;
|
|
7031
|
+
var line4 = (i) => `${i.id} ${i.status}${i.progress ? `/${i.progress}` : ""} ${i.priority} r${i.revision} ${sanitizeText(i.title).replace(/\s+/g, " ")}${i.usage ? ` wakes ${i.usage.wakes}/${i.usage.budget.wakes} · ${i.usage.hours.toFixed(1)}/${i.usage.budget.hours}h` : ""}`;
|
|
6909
7032
|
function intakeText(i) {
|
|
6910
7033
|
const from = i.source.kind === "user" ? "the user" : i.source.kind === "app" ? `app ${i.source.tokenID}` : `session ${i.source.sessionID}`;
|
|
6911
7034
|
return [
|
|
@@ -6919,7 +7042,8 @@ function intakeText(i) {
|
|
|
6919
7042
|
...i.criteria?.length ? ["done means:", ...i.criteria.map((c) => `- ${sanitizeText(c)}`)] : [],
|
|
6920
7043
|
...i.ref ? [`ref: ${sanitizeText(i.ref)}`] : [],
|
|
6921
7044
|
...i.reason ? [`reason: ${sanitizeText(i.reason)}`] : [],
|
|
6922
|
-
...i.lastError ? [`last routing error: ${i.lastError} (retried)`] : []
|
|
7045
|
+
...i.lastError ? [`last routing error: ${i.lastError} (retried)`] : [],
|
|
7046
|
+
...i.usage ? [`usage: ${i.usage.wakes}/${i.usage.budget.wakes} wakes · ${i.usage.hours.toFixed(1)}/${i.usage.budget.hours} hours (paused and blocked time counts)${i.usage.chargedTo ? `; last charged to ${i.usage.chargedTo}` : ""}`] : []
|
|
6923
7047
|
].join(`
|
|
6924
7048
|
`);
|
|
6925
7049
|
}
|
|
@@ -7189,7 +7313,7 @@ var STRINGS = { type: "array", items: STRING6 };
|
|
|
7189
7313
|
var TEAM2 = { type: "string", description: "The team: its ID (team_…) or its name." };
|
|
7190
7314
|
var REVISION2 = { type: "integer", minimum: 0, description: "The team's revision you read (team_show); a stale one answers conflict with the current revision." };
|
|
7191
7315
|
var IDEMPOTENCY = { type: "string", description: "Idempotency-Key: the same key and arguments within 24 h answer the first result again." };
|
|
7192
|
-
var
|
|
7316
|
+
var KINDS5 = { type: "array", items: { type: "string", enum: ["msg", "ask"] }, description: "What the path permits: msg and/or ask (replies to a permitted ask never need a path)." };
|
|
7193
7317
|
var DIRECTION = { type: "string", enum: ["one-way", "two-way"], description: "two-way: `to` may also contact `from` with the same kinds." };
|
|
7194
7318
|
var ROLE_FIELDS = {
|
|
7195
7319
|
label: { ...STRING6, description: "A display label (confers no authority)." },
|
|
@@ -7202,12 +7326,14 @@ var ROLE_FIELDS = {
|
|
|
7202
7326
|
maxMembers: { type: "integer", minimum: 1, maximum: 16 }
|
|
7203
7327
|
};
|
|
7204
7328
|
var ROLE = { type: "object", properties: { name: { ...STRING6, description: "A slug: lowercase letters, digits, dashes." }, ...ROLE_FIELDS }, required: ["name"] };
|
|
7205
|
-
var EDGE = { type: "object", properties: { from: STRING6, to: STRING6, kinds:
|
|
7329
|
+
var EDGE = { type: "object", properties: { from: STRING6, to: STRING6, kinds: KINDS5, direction: DIRECTION }, required: ["from", "to", "kinds", "direction"] };
|
|
7206
7330
|
var SPEC_FIELDS = {
|
|
7207
7331
|
title: { ...STRING6, description: "1–120 characters." },
|
|
7208
7332
|
description: STRING6,
|
|
7209
7333
|
mode: { type: "string", enum: ["open", "restricted"], description: "open: every member may msg/ask every other member. restricted: only the drawn paths (edges)." },
|
|
7210
|
-
hopLimit: { type: "integer", minimum: 1, maximum:
|
|
7334
|
+
hopLimit: { type: "integer", minimum: 1, maximum: 1e4, description: "Agent-only wake hop limit between this team's seats; at most the hub's hop ceiling (agent_limits hopCeiling, default 100). The effective limit is the lower of this and the hub-wide hopLimit. Unset uses the hub-wide limit." },
|
|
7335
|
+
itemBudget: { type: "object", properties: { wakes: { type: "integer", minimum: 10, maximum: 5000 }, hours: { type: "integer", minimum: 1, maximum: 720 } }, required: ["wakes", "hours"], description: "User-only per-item budget override: agent wakes and hours in progress." },
|
|
7336
|
+
loop: { type: "object", properties: { messages: { type: "integer", minimum: 4, maximum: 200 }, windowMinutes: { type: "integer", minimum: 5, maximum: 240 } }, required: ["messages", "windowMinutes"], description: "User-only no-progress pair-message threshold and window." },
|
|
7211
7337
|
roles: { type: "array", items: ROLE, description: "1–32 roles." },
|
|
7212
7338
|
entryRole: { ...STRING6, description: "The role that receives submitted work (e.g. pm) and supervises members." },
|
|
7213
7339
|
edges: { type: "array", items: EDGE, description: "Communication paths between roles (restricted mode)." },
|
|
@@ -7332,6 +7458,8 @@ function specLines(spec) {
|
|
|
7332
7458
|
return [
|
|
7333
7459
|
`mode: ${spec.mode}${spec.mode === "open" ? " (every member may msg/ask every other)" : " (only the paths below)"}`,
|
|
7334
7460
|
...spec.hopLimit !== undefined ? [`hop limit: ${spec.hopLimit}`] : [],
|
|
7461
|
+
...spec.itemBudget ? [`item budget: ${spec.itemBudget.wakes} wakes, ${spec.itemBudget.hours} hours`] : [],
|
|
7462
|
+
...spec.loop ? [`loop: ${spec.loop.messages} messages per ${spec.loop.windowMinutes} minutes`] : [],
|
|
7335
7463
|
`entry role: ${spec.entryRole}`,
|
|
7336
7464
|
"roles:",
|
|
7337
7465
|
...spec.roles.map(role),
|
|
@@ -7501,7 +7629,7 @@ var teamTools = [
|
|
|
7501
7629
|
need6(args, "name");
|
|
7502
7630
|
need6(args, "title");
|
|
7503
7631
|
const key = optional6(args, "idempotencyKey");
|
|
7504
|
-
const spec = picked(args, ["name", "title", "description", "mode", "hopLimit", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7632
|
+
const spec = picked(args, ["name", "title", "description", "mode", "hopLimit", "itemBudget", "loop", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7505
7633
|
const { team, created } = await hub.teams.create({ ...spec, edges: spec.edges ?? [] }, { signal, ...key ? { idempotencyKey: key } : {} });
|
|
7506
7634
|
return [`${created ? "created" : "already created (same key)"}: ${teamLine(team)}`, ...specLines(team)].join(`
|
|
7507
7635
|
`);
|
|
@@ -7517,7 +7645,7 @@ var teamTools = [
|
|
|
7517
7645
|
idempotent: true,
|
|
7518
7646
|
run: async (hub, args, signal) => {
|
|
7519
7647
|
const expectedRevision = revision(args);
|
|
7520
|
-
const patch = picked(args, ["title", "description", "mode", "hopLimit", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7648
|
+
const patch = picked(args, ["title", "description", "mode", "hopLimit", "itemBudget", "loop", "roles", "entryRole", "edges", "tags", "metadata", "projects"]);
|
|
7521
7649
|
if (Object.keys(patch).length === 0)
|
|
7522
7650
|
throw new ArgError("give at least one field to change");
|
|
7523
7651
|
const id = need6(args, "team");
|
|
@@ -7527,7 +7655,7 @@ var teamTools = [
|
|
|
7527
7655
|
},
|
|
7528
7656
|
draftTool("team_role_set", "Add a role to a team's draft, or replace the role of that name (all its fields).", { role: { ...STRING6, description: "The role's name (a slug)." }, ...ROLE_FIELDS }, ["role"], (teams, team, args, rev, signal) => teams.setRole(team, { name: need6(args, "role"), ...picked(args, Object.keys(ROLE_FIELDS)) }, rev, { signal })),
|
|
7529
7657
|
draftTool("team_role_remove", "Remove a role (not the entry role) and every path touching it from a team's draft.", { role: STRING6 }, ["role"], (teams, team, args, rev, signal) => teams.removeRole(team, need6(args, "role"), rev, { signal })),
|
|
7530
|
-
draftTool("team_edge_set", "Draw a communication path from one role to another in a team's draft (replaces the path drawn between them).", { from: STRING6, to: STRING6, kinds:
|
|
7658
|
+
draftTool("team_edge_set", "Draw a communication path from one role to another in a team's draft (replaces the path drawn between them).", { from: STRING6, to: STRING6, kinds: KINDS5, direction: DIRECTION }, ["from", "to", "kinds", "direction"], (teams, team, args, rev, signal) => {
|
|
7531
7659
|
const kinds = strings3(args, "kinds");
|
|
7532
7660
|
if (!kinds?.length)
|
|
7533
7661
|
throw new ArgError(`"kinds" is a list of msg and/or ask`);
|
|
@@ -8214,7 +8342,8 @@ var sendOptions = (args) => {
|
|
|
8214
8342
|
const wake = optionalBool(args, "wake");
|
|
8215
8343
|
const urgent = optionalBool(args, "urgent");
|
|
8216
8344
|
const notAfter = optionalNotAfter(args);
|
|
8217
|
-
|
|
8345
|
+
const item = optional8(args, "item");
|
|
8346
|
+
return { ...wake !== undefined ? { wake } : {}, ...urgent !== undefined ? { urgent } : {}, ...notAfter !== undefined ? { notAfter } : {}, ...item !== undefined ? { item } : {} };
|
|
8218
8347
|
};
|
|
8219
8348
|
var whyOption = (args) => {
|
|
8220
8349
|
const why = optional8(args, "why");
|
|
@@ -8337,6 +8466,7 @@ function messageText({ message, answer }) {
|
|
|
8337
8466
|
var DECISIONS_NOTE = "Decisions are asked by sessions: the question and options are not from the user. Only the user answers them, in the console.";
|
|
8338
8467
|
var STRING9 = { type: "string" };
|
|
8339
8468
|
var SEND = {
|
|
8469
|
+
item: { type: "string", description: "msg/ask: optional itk_ ID of an open item; sender and recipient must hold current seats in the item's team (older hubs ignore it)." },
|
|
8340
8470
|
wake: { type: "boolean", description: "false: admit the message without starting a turn." },
|
|
8341
8471
|
urgent: { type: "boolean", description: "Steer into the recipient's running turn." },
|
|
8342
8472
|
notAfter: { type: ["number", "string"], description: "Never deliver after this time: epoch milliseconds or an ISO 8601 date-time (at most 30 days ahead). Already passed: nothing is sent (expired)." },
|
|
@@ -8951,6 +9081,13 @@ var TOOLS = [
|
|
|
8951
9081
|
...operationTools,
|
|
8952
9082
|
...teamTools,
|
|
8953
9083
|
...watchlistTools,
|
|
9084
|
+
{
|
|
9085
|
+
name: "agent_limits",
|
|
9086
|
+
description: "Read the hub-wide agent hop limit, the hop ceiling (the highest hop limit the hub accepts) and launch caps, their sources and revision. Read-only; only an admin may change them in the console.",
|
|
9087
|
+
properties: {},
|
|
9088
|
+
readOnly: true,
|
|
9089
|
+
run: async (hub, _args, signal) => JSON.stringify(await hub.call("GET", "/api/agent/agent-limits", { signal }), null, 2)
|
|
9090
|
+
},
|
|
8954
9091
|
{
|
|
8955
9092
|
name: "whoami",
|
|
8956
9093
|
description: "Who this hub credential is: the app token of `hub login` (or the session of HUB_SESSION_TOKEN) and its user.",
|
|
@@ -9031,10 +9168,11 @@ var TOOLS = [
|
|
|
9031
9168
|
},
|
|
9032
9169
|
{
|
|
9033
9170
|
name: "ask",
|
|
9034
|
-
description: `Ask a session a question, tracked until answered, declined or expired. wait (at most ${WAIT_POLL_MAX_S} s) blocks for the answer; otherwise it shows up in the inbox tool.`,
|
|
9171
|
+
description: `Ask a session a question, tracked until answered, declined or expired. wait (at most ${WAIT_POLL_MAX_S} s) blocks for the answer; otherwise it shows up in the inbox tool. To @me it opens a Decision for the user: give options (buttons) with a recommendation, or choices (several points in one ask), and urgency, tier and kind.`,
|
|
9035
9172
|
properties: {
|
|
9036
9173
|
to: TO,
|
|
9037
9174
|
text: { ...STRING9, description: "The question." },
|
|
9175
|
+
...DECISION_ASK_PROPERTIES,
|
|
9038
9176
|
wait: { type: "number", minimum: 0, maximum: WAIT_POLL_MAX_S, description: `Seconds to wait for the answer (0–${WAIT_POLL_MAX_S}).` },
|
|
9039
9177
|
expires: { type: ["number", "string"], description: "When the ask expires: seconds, or 30s, 5m, 2h, 1d (1 minute to 7 days; default 24 h)." },
|
|
9040
9178
|
...SEND,
|
|
@@ -9047,7 +9185,13 @@ var TOOLS = [
|
|
|
9047
9185
|
const text = need10(args, "text");
|
|
9048
9186
|
const wait = optionalWait(args);
|
|
9049
9187
|
const expires = optionalExpires(args);
|
|
9050
|
-
|
|
9188
|
+
let decision;
|
|
9189
|
+
try {
|
|
9190
|
+
decision = decisionAskFields(args);
|
|
9191
|
+
} catch (cause) {
|
|
9192
|
+
throw new ArgError3(cause.message);
|
|
9193
|
+
}
|
|
9194
|
+
const options = { ...sendOptions(args), ...expires !== undefined ? { expires } : {}, ...idemOption(args), ...decision, signal };
|
|
9051
9195
|
if (optionalBool(args, "all")) {
|
|
9052
9196
|
if (wait !== undefined)
|
|
9053
9197
|
throw new ArgError3(`"wait" cannot be combined with "all"`);
|
|
@@ -9311,7 +9455,7 @@ var clean = (text, token) => {
|
|
|
9311
9455
|
var toolResult = (text, isError) => ({ content: [{ type: "text", text }], isError });
|
|
9312
9456
|
var ok2 = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
9313
9457
|
var rpcError = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
9314
|
-
var
|
|
9458
|
+
var isObject5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9315
9459
|
var isId = (value) => typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
9316
9460
|
function createMcpServer(options) {
|
|
9317
9461
|
const initialize = (params) => {
|
|
@@ -9326,7 +9470,7 @@ function createMcpServer(options) {
|
|
|
9326
9470
|
if (!tool)
|
|
9327
9471
|
return rpcError(id, RPC.invalidParams, typeof params.name === "string" ? `unknown tool: ${params.name}` : "params.name (the tool) is required");
|
|
9328
9472
|
const args = params.arguments ?? {};
|
|
9329
|
-
if (!
|
|
9473
|
+
if (!isObject5(args))
|
|
9330
9474
|
return rpcError(id, RPC.invalidParams, "params.arguments must be an object");
|
|
9331
9475
|
let token;
|
|
9332
9476
|
const controller = new AbortController;
|
|
@@ -9357,7 +9501,7 @@ function createMcpServer(options) {
|
|
|
9357
9501
|
inFlight.clear();
|
|
9358
9502
|
},
|
|
9359
9503
|
async handle(message) {
|
|
9360
|
-
if (!
|
|
9504
|
+
if (!isObject5(message))
|
|
9361
9505
|
return rpcError(null, RPC.invalidRequest, Array.isArray(message) ? "batches are not supported" : "not a JSON-RPC message");
|
|
9362
9506
|
const hasId = "id" in message;
|
|
9363
9507
|
const id = isId(message.id) ? message.id : null;
|
|
@@ -9368,12 +9512,12 @@ function createMcpServer(options) {
|
|
|
9368
9512
|
if (typeof message.method !== "string")
|
|
9369
9513
|
return rpcError(id, RPC.invalidRequest, "method must be a string");
|
|
9370
9514
|
if (!hasId) {
|
|
9371
|
-
notify(message.method,
|
|
9515
|
+
notify(message.method, isObject5(message.params) ? message.params : {});
|
|
9372
9516
|
return;
|
|
9373
9517
|
}
|
|
9374
9518
|
if (!isId(message.id))
|
|
9375
9519
|
return rpcError(null, RPC.invalidRequest, "id must be a string or a number");
|
|
9376
|
-
const params =
|
|
9520
|
+
const params = isObject5(message.params) ? message.params : {};
|
|
9377
9521
|
switch (message.method) {
|
|
9378
9522
|
case "initialize":
|
|
9379
9523
|
return ok2(id, initialize(params));
|
|
@@ -9692,7 +9836,7 @@ import { arch as osArch, platform as osPlatform } from "node:os";
|
|
|
9692
9836
|
// packages/node/package.json
|
|
9693
9837
|
var package_default2 = {
|
|
9694
9838
|
name: "@huynle/hub-node",
|
|
9695
|
-
version: "0.11.
|
|
9839
|
+
version: "0.11.1",
|
|
9696
9840
|
private: true,
|
|
9697
9841
|
description: "`hub node`: the per-machine daemon that prepares, collects and cleans up launch workspaces (internal; shipped inside @huynle/hub, not published on its own).",
|
|
9698
9842
|
license: "MIT",
|
|
@@ -11147,7 +11291,7 @@ import path14 from "node:path";
|
|
|
11147
11291
|
// packages/runtime-openshell/package.json
|
|
11148
11292
|
var package_default3 = {
|
|
11149
11293
|
name: "@huynle/hub-runtime-openshell",
|
|
11150
|
-
version: "0.11.
|
|
11294
|
+
version: "0.11.1",
|
|
11151
11295
|
private: true,
|
|
11152
11296
|
description: "The `openshell` runtime of `hub node`: launches in NVIDIA OpenShell sandboxes, driven through the openshell CLI (internal; shipped inside @huynle/hub, not published on its own).",
|
|
11153
11297
|
license: "MIT",
|
|
@@ -12887,7 +13031,7 @@ function ask(socketPath, req, options = {}) {
|
|
|
12887
13031
|
var ENV_NAME3 = /^[A-Z_][A-Z0-9_]*$/;
|
|
12888
13032
|
var DROPPED = ["tool_response"];
|
|
12889
13033
|
var quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12890
|
-
var
|
|
13034
|
+
var isObject6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
12891
13035
|
function flags(argv) {
|
|
12892
13036
|
const rewake = argv.includes("--rewake");
|
|
12893
13037
|
const at = argv.indexOf("--wait");
|
|
@@ -12925,16 +13069,16 @@ async function runHook(io) {
|
|
|
12925
13069
|
const launch = io.env.HUB_CLAUDE_LAUNCH;
|
|
12926
13070
|
const req = { v: WIRE_VERSION, kind: "hook", input: sent, ...launch ? { launch } : {}, ...rewake ? { rewake: true } : {}, ...waitMs !== undefined ? { waitMs } : {} };
|
|
12927
13071
|
const reply = await ask(io.socketPath, req, io.timeoutMs !== undefined ? { timeoutMs: io.timeoutMs } : {});
|
|
12928
|
-
if (!
|
|
13072
|
+
if (!isObject6(reply))
|
|
12929
13073
|
return 0;
|
|
12930
|
-
if (
|
|
13074
|
+
if (isObject6(reply.env))
|
|
12931
13075
|
writeEnvFile(io.env.CLAUDE_ENV_FILE, reply.env);
|
|
12932
13076
|
if (rewake && reply.exit === 2) {
|
|
12933
13077
|
if (typeof reply.stderr === "string")
|
|
12934
13078
|
io.stderr(reply.stderr);
|
|
12935
13079
|
return 2;
|
|
12936
13080
|
}
|
|
12937
|
-
if (
|
|
13081
|
+
if (isObject6(reply.output))
|
|
12938
13082
|
io.stdout(`${JSON.stringify(reply.output)}
|
|
12939
13083
|
`);
|
|
12940
13084
|
return 0;
|
|
@@ -13828,7 +13972,7 @@ function ensurePrivateDir(dir) {
|
|
|
13828
13972
|
import { Socket as Socket2 } from "node:net";
|
|
13829
13973
|
var MCP_END = { hub: "end" };
|
|
13830
13974
|
var ACT_TOOLS = new Set(["approve", "deny", "answer", "prompt", "stop"]);
|
|
13831
|
-
var
|
|
13975
|
+
var isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13832
13976
|
function createMcpRouter(options) {
|
|
13833
13977
|
const credentials = (pick, what) => () => {
|
|
13834
13978
|
const sessionID = options.sessionID();
|
|
@@ -13841,9 +13985,9 @@ function createMcpRouter(options) {
|
|
|
13841
13985
|
const act = createMcpServer({ version: options.version, credentials: credentials(options.actToken, "hub session act token") });
|
|
13842
13986
|
return {
|
|
13843
13987
|
async handle(message) {
|
|
13844
|
-
if (
|
|
13988
|
+
if (isObject7(message) && message.method === "tools/call" && isObject7(message.params) && ACT_TOOLS.has(message.params.name))
|
|
13845
13989
|
return act.handle(message);
|
|
13846
|
-
if (
|
|
13990
|
+
if (isObject7(message) && !("id" in message))
|
|
13847
13991
|
act.handle(message);
|
|
13848
13992
|
return shell.handle(message);
|
|
13849
13993
|
},
|
|
@@ -14687,9 +14831,10 @@ class ClaudeSessions {
|
|
|
14687
14831
|
this.#settle(s, undefined, true);
|
|
14688
14832
|
s.stopRequested = false;
|
|
14689
14833
|
const detail = [input.error_details, input.error].find((v) => typeof v === "string" && v.length > 0) ?? "the turn failed";
|
|
14834
|
+
const token = typeof input.error === "string" && /^[a-z][a-z0-9_]{0,40}$/.test(input.error) ? input.error : undefined;
|
|
14690
14835
|
const turnID = s.turnID;
|
|
14691
14836
|
s.busy = false;
|
|
14692
|
-
this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...turnID ? { turnID } : {}, error: { message: cut4(detail, HARNESS_ERROR_MAX) } } });
|
|
14837
|
+
this.#emit({ type: "session.execution.failed", data: { sessionID: s.hubID, ...turnID ? { turnID } : {}, error: { message: cut4(detail, HARNESS_ERROR_MAX), ...token ? { type: `claude.${token}` } : {} } } });
|
|
14693
14838
|
return {};
|
|
14694
14839
|
}
|
|
14695
14840
|
#child(s, input, start = true) {
|
|
@@ -15105,7 +15250,7 @@ import path23 from "node:path";
|
|
|
15105
15250
|
// packages/claude-code/package.json
|
|
15106
15251
|
var package_default4 = {
|
|
15107
15252
|
name: "@huynle/hub-claude-code",
|
|
15108
|
-
version: "0.11.
|
|
15253
|
+
version: "0.11.1",
|
|
15109
15254
|
private: true,
|
|
15110
15255
|
description: "The Claude Code hooks spoke: per-user daemon, hook bridge, plugin dir, `hub claude` wrapper and daemon-backed MCP server (internal; shipped inside @huynle/hub, not published on its own).",
|
|
15111
15256
|
license: "MIT",
|
|
@@ -15394,7 +15539,7 @@ import path26 from "node:path";
|
|
|
15394
15539
|
// packages/preset/package.json
|
|
15395
15540
|
var package_default5 = {
|
|
15396
15541
|
name: "@huynle/hub-preset",
|
|
15397
|
-
version: "0.11.
|
|
15542
|
+
version: "0.11.1",
|
|
15398
15543
|
private: true,
|
|
15399
15544
|
description: "The preset spoke: drives a harness without a spoke of its own through a terminal pane (tmux), described by a data-only preset; official presets in presets/ (internal; shipped inside @huynle/hub, not published on its own).",
|
|
15400
15545
|
license: "MIT",
|
|
@@ -15919,7 +16064,7 @@ import { openSync as openSync6, closeSync as closeSync6, readFileSync as readFil
|
|
|
15919
16064
|
// packages/codex/package.json
|
|
15920
16065
|
var package_default6 = {
|
|
15921
16066
|
name: "@huynle/hub-codex",
|
|
15922
|
-
version: "0.11.
|
|
16067
|
+
version: "0.11.1",
|
|
15923
16068
|
private: true,
|
|
15924
16069
|
description: "The Codex app-server spoke: attaches to Codex's app-server (its own over stdio, and the shared daemon's socket when present), and the `hub codex` wrapper (internal; shipped inside @huynle/hub, not published on its own).",
|
|
15925
16070
|
license: "MIT",
|
|
@@ -16269,6 +16414,12 @@ var DEFAULT_TITLE = "Codex session";
|
|
|
16269
16414
|
var TOOL_ITEMS = new Set(["commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall", "webSearch"]);
|
|
16270
16415
|
var APPROVALS = { "item/commandExecution/requestApproval": "shell", "item/fileChange/requestApproval": "edit" };
|
|
16271
16416
|
var RECENT_TURNS = 50;
|
|
16417
|
+
function codexErrorType(info) {
|
|
16418
|
+
const name = typeof info === "string" ? info : isObj3(info) && Object.keys(info).length === 1 ? Object.keys(info)[0] : undefined;
|
|
16419
|
+
if (!name || !/^[a-z][A-Za-z0-9]{0,40}$/.test(name))
|
|
16420
|
+
return;
|
|
16421
|
+
return `codex.${name.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`;
|
|
16422
|
+
}
|
|
16272
16423
|
var isObj3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16273
16424
|
var str6 = (v) => typeof v === "string" && v !== "" ? v : undefined;
|
|
16274
16425
|
var cut5 = (text, max) => text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
@@ -16656,7 +16807,8 @@ class CodexSessions {
|
|
|
16656
16807
|
return;
|
|
16657
16808
|
const status = typeof turn.status === "string" ? turn.status : "completed";
|
|
16658
16809
|
const error = isObj3(turn.error) && typeof turn.error.message === "string" ? cut5(turn.error.message, HARNESS_ERROR_MAX) : undefined;
|
|
16659
|
-
const
|
|
16810
|
+
const errorType = isObj3(turn.error) ? codexErrorType(turn.error.codexErrorInfo) : undefined;
|
|
16811
|
+
const end = { status, ...error !== undefined ? { error } : {}, ...errorType !== undefined ? { errorType } : {} };
|
|
16660
16812
|
if (thread.turnID === turnID)
|
|
16661
16813
|
thread.turnID = undefined;
|
|
16662
16814
|
thread.ended.set(turnID, end);
|
|
@@ -16669,7 +16821,7 @@ class CodexSessions {
|
|
|
16669
16821
|
if (status === "interrupted")
|
|
16670
16822
|
this.#emit({ type: "session.execution.interrupted", data });
|
|
16671
16823
|
else if (status === "failed")
|
|
16672
|
-
this.#emit({ type: "session.execution.failed", data: { ...data, ...error !== undefined ? { error: { message: error } } : {} } });
|
|
16824
|
+
this.#emit({ type: "session.execution.failed", data: { ...data, ...error !== undefined ? { error: { message: error, ...end.errorType ? { type: end.errorType } : {} } } : {} } });
|
|
16673
16825
|
else
|
|
16674
16826
|
this.#emit({ type: "session.execution.succeeded", data });
|
|
16675
16827
|
thread.items.clear();
|
|
@@ -18672,7 +18824,7 @@ function mergeConfigText(text, format, sets) {
|
|
|
18672
18824
|
return txFail("manual_edit_required", `the config cannot be edited losslessly (${error instanceof Error ? error.message : "unreadable"}); make the edit by hand`);
|
|
18673
18825
|
}
|
|
18674
18826
|
const tree = toValue(current, cst);
|
|
18675
|
-
if (!
|
|
18827
|
+
if (!isObject8(tree))
|
|
18676
18828
|
return txFail("conflict", "the config is not a JSON object");
|
|
18677
18829
|
const changed = applySet(tree, set);
|
|
18678
18830
|
if (!changed.ok)
|
|
@@ -18687,7 +18839,7 @@ function mergeConfigText(text, format, sets) {
|
|
|
18687
18839
|
}
|
|
18688
18840
|
return txOk(current);
|
|
18689
18841
|
}
|
|
18690
|
-
var
|
|
18842
|
+
var isObject8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18691
18843
|
var same = (a, b) => canonicalJSON(a) === canonicalJSON(b);
|
|
18692
18844
|
var clone = (v) => JSON.parse(JSON.stringify(v));
|
|
18693
18845
|
function segments(pointer) {
|
|
@@ -18711,7 +18863,7 @@ function applySet(root, set) {
|
|
|
18711
18863
|
if (index === undefined)
|
|
18712
18864
|
return txFail("conflict", `${set.pointer}: no array element ${seg}`);
|
|
18713
18865
|
next = current[index];
|
|
18714
|
-
} else if (
|
|
18866
|
+
} else if (isObject8(current)) {
|
|
18715
18867
|
next = Object.hasOwn(current, seg) ? current[seg] : undefined;
|
|
18716
18868
|
if (next === undefined) {
|
|
18717
18869
|
next = segs[i + 1] === "-" ? [] : {};
|
|
@@ -18739,7 +18891,7 @@ function applySet(root, set) {
|
|
|
18739
18891
|
current[index] = value;
|
|
18740
18892
|
return txOk(true);
|
|
18741
18893
|
}
|
|
18742
|
-
if (!
|
|
18894
|
+
if (!isObject8(current))
|
|
18743
18895
|
return txFail("conflict", `${set.pointer}: the parent is not an object or array`);
|
|
18744
18896
|
if (Object.hasOwn(current, last) && same(current[last], value))
|
|
18745
18897
|
return txOk(false);
|
|
@@ -18908,7 +19060,7 @@ var LEGACY_PLUGIN_PACKAGE = "@huynle/opencode-voice";
|
|
|
18908
19060
|
var HUB_MCP_NAME = "hub";
|
|
18909
19061
|
var MCP_SUBCOMMAND = "mcp";
|
|
18910
19062
|
var HUB_PROGRAMS = new Set(["hub", "hubctl"]);
|
|
18911
|
-
var
|
|
19063
|
+
var isObject9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
18912
19064
|
var basename = (p) => p.slice(p.lastIndexOf("/") + 1);
|
|
18913
19065
|
var WINDOWS_PATH = /^[A-Za-z]:[\\/]/;
|
|
18914
19066
|
var programName = (p) => {
|
|
@@ -18943,12 +19095,12 @@ function classifyPluginSpec(spec) {
|
|
|
18943
19095
|
return { source: "custom" };
|
|
18944
19096
|
return;
|
|
18945
19097
|
}
|
|
18946
|
-
var specOf = (entry) => typeof entry === "string" ? entry :
|
|
19098
|
+
var specOf = (entry) => typeof entry === "string" ? entry : isObject9(entry) && typeof entry.package === "string" ? entry.package : undefined;
|
|
18947
19099
|
var parse3 = (text) => {
|
|
18948
19100
|
const parsed = parseJsonc(text);
|
|
18949
19101
|
if (!parsed.ok)
|
|
18950
19102
|
return txFail("manual_edit_required", `the OpenCode config cannot be edited losslessly (${parsed.message}); make the edit by hand`);
|
|
18951
|
-
if (!
|
|
19103
|
+
if (!isObject9(parsed.value))
|
|
18952
19104
|
return txFail("conflict", "the OpenCode config is not a JSON object");
|
|
18953
19105
|
return txOk(parsed.value);
|
|
18954
19106
|
};
|
|
@@ -18964,7 +19116,7 @@ function inspectPlugin(config, desired, findings) {
|
|
|
18964
19116
|
const spec = specOf(entry);
|
|
18965
19117
|
const kind = spec === undefined ? undefined : classifyPluginSpec(spec);
|
|
18966
19118
|
if (kind)
|
|
18967
|
-
found.push({ pointer: `/${key}/${i}`, spec, object:
|
|
19119
|
+
found.push({ pointer: `/${key}/${i}`, spec, object: isObject9(entry), ...kind });
|
|
18968
19120
|
});
|
|
18969
19121
|
}
|
|
18970
19122
|
if (found.length === 0)
|
|
@@ -18995,17 +19147,17 @@ function inspectPlugin(config, desired, findings) {
|
|
|
18995
19147
|
findings.push({ code: "plugin-drift", message: `the Hub plugin is pinned to ${entry.version}; it becomes ${pluginSpec(desired)}` });
|
|
18996
19148
|
return txOk({ state: "drift", ...base, object: entry.object });
|
|
18997
19149
|
}
|
|
18998
|
-
var isHubMcp = (server) =>
|
|
19150
|
+
var isHubMcp = (server) => isObject9(server) && server.type === "local" && Array.isArray(server.command) && server.command.length === 2 && server.command.every((w) => typeof w === "string") && HUB_PROGRAMS.has(programName(server.command[0])) && server.command[1] === MCP_SUBCOMMAND;
|
|
18999
19151
|
function inspectMcp(config, desired, findings) {
|
|
19000
19152
|
const mcp = config.mcp;
|
|
19001
19153
|
if (mcp === undefined)
|
|
19002
19154
|
return txOk({ state: "absent" });
|
|
19003
|
-
if (!
|
|
19155
|
+
if (!isObject9(mcp))
|
|
19004
19156
|
return txFail("conflict", '"mcp" in the OpenCode config is not an object; fix it by hand');
|
|
19005
19157
|
const servers = mcp.servers;
|
|
19006
19158
|
if (servers === undefined)
|
|
19007
19159
|
return txOk({ state: "absent" });
|
|
19008
|
-
if (!
|
|
19160
|
+
if (!isObject9(servers))
|
|
19009
19161
|
return txFail("conflict", '"mcp.servers" in the OpenCode config is not an object; fix it by hand');
|
|
19010
19162
|
const elsewhere = Object.entries(servers).find(([name, server]) => name !== HUB_MCP_NAME && isHubMcp(server));
|
|
19011
19163
|
if (elsewhere)
|
|
@@ -26474,19 +26626,22 @@ Identity
|
|
|
26474
26626
|
hub role release <role[.project]> [--session <addr>]
|
|
26475
26627
|
|
|
26476
26628
|
Messaging (text "-" reads it from stdin)
|
|
26477
|
-
hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--not-after <ms|ISO time>] [--key K]
|
|
26629
|
+
hub msg <addr> "text" [--all] [--no-wake] [--urgent] [--item itk_…] [--not-after <ms|ISO time>] [--key K]
|
|
26478
26630
|
tell: fire and forget; --not-after: never delivered after that time
|
|
26479
|
-
hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--not-after <ms|ISO time>] [--all] [--no-wake] [--urgent] [--key K]
|
|
26631
|
+
hub ask <addr> "text" [--wait [secs]] [--expires 2h] [--item itk_…] [--not-after <ms|ISO time>] [--all] [--no-wake] [--urgent] [--key K]
|
|
26480
26632
|
tracked until answered, declined or expired; --wait alone waits until
|
|
26481
26633
|
it expires; exit 0 answered, 1 declined or no answer yet, 8 expired;
|
|
26482
26634
|
--key (Idempotency-Key): a retry with the same key and text answers
|
|
26483
|
-
the first send again and sends nothing (24 h, per credential)
|
|
26635
|
+
the first send again and sends nothing (24 h, per credential);
|
|
26636
|
+
--item: open team intake item, both members seated (older hubs ignore it)
|
|
26484
26637
|
hub reply <msgID> "text" [--no-wake] goes to the real sender
|
|
26485
26638
|
hub decline <msgID> --why "reason"
|
|
26486
26639
|
hub ask @me "question" [--option id=label[:safe]]... [--recommend id [--why "…"]] [--urgency low|normal|high]
|
|
26487
26640
|
[--deadline 2h [--on-timeout <safe id>]] [--tier confirm|tap] [--about <addr>]...
|
|
26641
|
+
[--choice "D1=Label:a=Option*,b=Option"]... [--kind question|plan|merge|release]
|
|
26488
26642
|
opens a Decision for the user (answered in the console; the answer
|
|
26489
|
-
comes back as a reply); --on-timeout picks a safe option at the deadline
|
|
26643
|
+
comes back as a reply); --on-timeout picks a safe option at the deadline;
|
|
26644
|
+
--choice (instead of --option) asks several points, * marks the recommendation
|
|
26490
26645
|
hub decisions list [--state open|answered|expired|closed] the Decisions you asked
|
|
26491
26646
|
hub inbox asks to answer, your open asks, recent messages, mutes
|
|
26492
26647
|
hub mute <addr> · hub unmute <addr> refuse (or accept again) messages from a session
|
|
@@ -26659,11 +26814,13 @@ var whyOf = (flags) => {
|
|
|
26659
26814
|
var sendFlags = (flags) => {
|
|
26660
26815
|
const notAfter = str(flags, "not-after");
|
|
26661
26816
|
const key = str(flags, "key");
|
|
26817
|
+
const item = str(flags, "item");
|
|
26662
26818
|
return {
|
|
26663
26819
|
...flags["no-wake"] === true ? { wake: false } : {},
|
|
26664
26820
|
...flags.urgent === true ? { urgent: true } : {},
|
|
26665
26821
|
...notAfter === undefined ? {} : { notAfter: notAfterArg(notAfter, "--not-after") },
|
|
26666
|
-
...key !== undefined ? { idempotencyKey: key } : {}
|
|
26822
|
+
...key !== undefined ? { idempotencyKey: key } : {},
|
|
26823
|
+
...item !== undefined ? { item } : {}
|
|
26667
26824
|
};
|
|
26668
26825
|
};
|
|
26669
26826
|
var nodeIo = (io) => ({
|
|
@@ -26787,7 +26944,7 @@ var COMMANDS = {
|
|
|
26787
26944
|
},
|
|
26788
26945
|
msg: {
|
|
26789
26946
|
args: ["<addr>", "<text>"],
|
|
26790
|
-
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string" },
|
|
26947
|
+
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", item: "string", key: "string" },
|
|
26791
26948
|
async handle(context) {
|
|
26792
26949
|
const { flags, args } = context.parsed;
|
|
26793
26950
|
const text = await textArg(args[1], context.io.stdin);
|
|
@@ -26799,7 +26956,7 @@ var COMMANDS = {
|
|
|
26799
26956
|
},
|
|
26800
26957
|
ask: {
|
|
26801
26958
|
args: ["<addr>", "<text>"],
|
|
26802
|
-
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", key: "string", expires: "string", wait: "optional", ...DECISION_FLAGS },
|
|
26959
|
+
flags: { all: "bool", "no-wake": "bool", urgent: "bool", "not-after": "string", item: "string", key: "string", expires: "string", wait: "optional", ...DECISION_FLAGS },
|
|
26803
26960
|
async handle(context) {
|
|
26804
26961
|
const { flags, args } = context.parsed;
|
|
26805
26962
|
const decision = decisionOptions(flags, args[0]);
|