@kaiba-cloud/orbital 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10421,7 +10421,7 @@ var require_compile = __commonJS({
10421
10421
  const schOrFunc = root.refs[ref];
10422
10422
  if (schOrFunc)
10423
10423
  return schOrFunc;
10424
- let _sch = resolve25.call(this, root, ref);
10424
+ let _sch = resolve26.call(this, root, ref);
10425
10425
  if (_sch === void 0) {
10426
10426
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
10427
10427
  const { schemaId } = this.opts;
@@ -10448,7 +10448,7 @@ var require_compile = __commonJS({
10448
10448
  function sameSchemaEnv(s1, s2) {
10449
10449
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
10450
10450
  }
10451
- function resolve25(root, ref) {
10451
+ function resolve26(root, ref) {
10452
10452
  let sch;
10453
10453
  while (typeof (sch = this.refs[ref]) == "string")
10454
10454
  ref = sch;
@@ -11312,7 +11312,7 @@ var require_fast_uri = __commonJS({
11312
11312
  }
11313
11313
  return uri;
11314
11314
  }
11315
- function resolve25(baseURI, relativeURI, options) {
11315
+ function resolve26(baseURI, relativeURI, options) {
11316
11316
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
11317
11317
  const {
11318
11318
  parsed: baseParsed,
@@ -11703,7 +11703,7 @@ var require_fast_uri = __commonJS({
11703
11703
  var fastUri = {
11704
11704
  SCHEMES,
11705
11705
  normalize,
11706
- resolve: resolve25,
11706
+ resolve: resolve26,
11707
11707
  resolveComponent,
11708
11708
  equal,
11709
11709
  serialize,
@@ -14786,12 +14786,12 @@ var require_isexe = __commonJS({
14786
14786
  if (typeof Promise !== "function") {
14787
14787
  throw new TypeError("callback not provided");
14788
14788
  }
14789
- return new Promise(function(resolve25, reject) {
14789
+ return new Promise(function(resolve26, reject) {
14790
14790
  isexe(path3, options || {}, function(er, is) {
14791
14791
  if (er) {
14792
14792
  reject(er);
14793
14793
  } else {
14794
- resolve25(is);
14794
+ resolve26(is);
14795
14795
  }
14796
14796
  });
14797
14797
  });
@@ -14857,27 +14857,27 @@ var require_which = __commonJS({
14857
14857
  opt = {};
14858
14858
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
14859
14859
  const found = [];
14860
- const step = (i) => new Promise((resolve25, reject) => {
14860
+ const step = (i) => new Promise((resolve26, reject) => {
14861
14861
  if (i === pathEnv.length)
14862
- return opt.all && found.length ? resolve25(found) : reject(getNotFoundError(cmd));
14862
+ return opt.all && found.length ? resolve26(found) : reject(getNotFoundError(cmd));
14863
14863
  const ppRaw = pathEnv[i];
14864
14864
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
14865
14865
  const pCmd = path3.join(pathPart, cmd);
14866
14866
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
14867
- resolve25(subStep(p, i, 0));
14867
+ resolve26(subStep(p, i, 0));
14868
14868
  });
14869
- const subStep = (p, i, ii) => new Promise((resolve25, reject) => {
14869
+ const subStep = (p, i, ii) => new Promise((resolve26, reject) => {
14870
14870
  if (ii === pathExt.length)
14871
- return resolve25(step(i + 1));
14871
+ return resolve26(step(i + 1));
14872
14872
  const ext = pathExt[ii];
14873
14873
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
14874
14874
  if (!er && is) {
14875
14875
  if (opt.all)
14876
14876
  found.push(p + ext);
14877
14877
  else
14878
- return resolve25(p + ext);
14878
+ return resolve26(p + ext);
14879
14879
  }
14880
- return resolve25(subStep(p, i, ii + 1));
14880
+ return resolve26(subStep(p, i, ii + 1));
14881
14881
  });
14882
14882
  });
14883
14883
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -15190,7 +15190,7 @@ var require_cross_spawn = __commonJS({
15190
15190
  });
15191
15191
 
15192
15192
  // src/kit/command/brand.ts
15193
- var VERSION = true ? "0.0.4" : "0.0.0-dev";
15193
+ var VERSION = true ? "0.0.6" : "0.0.0-dev";
15194
15194
  function defineBrand(overrides) {
15195
15195
  const { program } = overrides;
15196
15196
  return {
@@ -20652,7 +20652,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
20652
20652
  var NONE = 0;
20653
20653
  var ASSUMED = 1;
20654
20654
  var PROVEN = 2;
20655
- function isRecursive(inst, stack, resolve25) {
20655
+ function isRecursive(inst, stack, resolve26) {
20656
20656
  const cached2 = recursive.get(inst);
20657
20657
  if (cached2 !== void 0)
20658
20658
  return cached2 ? PROVEN : NONE;
@@ -20662,7 +20662,7 @@ function isRecursive(inst, stack, resolve25) {
20662
20662
  let result2 = NONE;
20663
20663
  const check3 = (child) => {
20664
20664
  if (result2 !== PROVEN && child?._zod) {
20665
- const answer2 = isRecursive(child, stack, resolve25);
20665
+ const answer2 = isRecursive(child, stack, resolve26);
20666
20666
  if (answer2 > result2)
20667
20667
  result2 = answer2;
20668
20668
  }
@@ -20673,7 +20673,7 @@ function isRecursive(inst, stack, resolve25) {
20673
20673
  const desc = Object.getOwnPropertyDescriptor(sh, key2);
20674
20674
  if (spread && !desc.enumerable)
20675
20675
  continue;
20676
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve25) : NONE;
20676
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve26) : NONE;
20677
20677
  if (child > answer2)
20678
20678
  answer2 = child;
20679
20679
  }
@@ -20737,7 +20737,7 @@ function isRecursive(inst, stack, resolve25) {
20737
20737
  break;
20738
20738
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
20739
20739
  case "lazy": {
20740
- const inner = def._cachedInner ?? (resolve25 ? inst._zod.innerType : void 0);
20740
+ const inner = def._cachedInner ?? (resolve26 ? inst._zod.innerType : void 0);
20741
20741
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
20742
20742
  break;
20743
20743
  }
@@ -35891,7 +35891,7 @@ var MissionAskedSchema = external_exports.object({
35891
35891
  question: external_exports.string().min(1),
35892
35892
  at: external_exports.string()
35893
35893
  });
35894
- function waitsOnGiver(mission) {
35894
+ function waitsOnDecision(mission) {
35895
35895
  return mission.state === "waiting" || mission.state === "active" && mission.questions.length > 0;
35896
35896
  }
35897
35897
  var MISSION_QUESTION_META = "orbital/question";
@@ -35929,7 +35929,7 @@ var MissionStateFileSchema = external_exports.object({
35929
35929
  source: external_exports.string().nullable(),
35930
35930
  result: external_exports.string().nullable(),
35931
35931
  question: external_exports.string().nullable(),
35932
- giver: external_exports.string().nullable(),
35932
+ receivers: external_exports.array(external_exports.string().min(1)).default([]),
35933
35933
  lead: external_exports.string().nullable(),
35934
35934
  claim: external_exports.string().nullable(),
35935
35935
  claimedAt: external_exports.string().nullable(),
@@ -35956,7 +35956,7 @@ var MissionRecordSchema = external_exports.object({
35956
35956
  result: external_exports.string().nullable(),
35957
35957
  question: external_exports.string().nullable(),
35958
35958
  questions: external_exports.array(MissionAskedSchema).default([]),
35959
- giver: external_exports.string().nullable(),
35959
+ receivers: external_exports.array(external_exports.string().min(1)).default([]),
35960
35960
  lead: external_exports.string().nullable(),
35961
35961
  lastActivity: external_exports.string().nullable().default(null),
35962
35962
  claim: external_exports.string().nullable().default(null),
@@ -35998,9 +35998,9 @@ var MissionReplySchema = external_exports.object({
35998
35998
  text: external_exports.string().min(1),
35999
35999
  questionId: external_exports.string().min(1).optional()
36000
36000
  });
36001
- var RemoteGiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
36001
+ var RemoteReceiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
36002
36002
  var SENDER_FIELDS = { team: external_exports.string().min(1).nullable().default(null), agent: external_exports.string().min(1).nullable().default(null) };
36003
- var RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, giver: RemoteGiverSchema, ...SENDER_FIELDS });
36003
+ var RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, receiver: RemoteReceiverSchema, ...SENDER_FIELDS });
36004
36004
  var RelayReplySchema = external_exports.object({ missionId: MissionIdSchema, text: external_exports.string().min(1), questionId: external_exports.string().min(1).optional(), by: external_exports.string().min(1), ...SENDER_FIELDS });
36005
36005
  var MissionControlSchema = external_exports.object({ missionId: MissionIdSchema });
36006
36006
  var STEER_MARK = "[orbital] message during your turn";
@@ -36034,18 +36034,27 @@ var MissionQuestionSchema = external_exports.object({
36034
36034
  leadSessionId: external_exports.string().min(1),
36035
36035
  question: external_exports.string(),
36036
36036
  questionId: external_exports.string().min(1).optional(),
36037
- giver: RemoteGiverSchema,
36037
+ receiver: RemoteReceiverSchema,
36038
36038
  team: external_exports.string().min(1).nullable().default(null)
36039
36039
  });
36040
- function giverRef(giver) {
36041
- return `${giver.sessionId}@${giver.node}`;
36040
+ var EXTERNAL_RECEIVER = "external:";
36041
+ function externalReceiver(externalId) {
36042
+ return `${EXTERNAL_RECEIVER}${externalId}`;
36042
36043
  }
36043
- function remoteGiverOf(giver) {
36044
- const at = giver.lastIndexOf("@");
36045
- return at > 0 && at < giver.length - 1 ? { sessionId: giver.slice(0, at), node: giver.slice(at + 1) } : null;
36044
+ function externalIdOf(receiver) {
36045
+ return receiver.startsWith(EXTERNAL_RECEIVER) ? receiver.slice(EXTERNAL_RECEIVER.length) : null;
36046
+ }
36047
+ function receiverRef(receiver) {
36048
+ return `${receiver.sessionId}@${receiver.node}`;
36049
+ }
36050
+ function remoteReceiverOf(receiver) {
36051
+ if (externalIdOf(receiver) !== null) return null;
36052
+ const at = receiver.lastIndexOf("@");
36053
+ return at > 0 && at < receiver.length - 1 ? { sessionId: receiver.slice(0, at), node: receiver.slice(at + 1) } : null;
36046
36054
  }
36047
36055
  var MissionJoinSchema = external_exports.object({
36048
- missionId: MissionIdSchema
36056
+ missionId: MissionIdSchema,
36057
+ externalId: external_exports.string().min(1).optional()
36049
36058
  });
36050
36059
 
36051
36060
  // src/protocol/charter.ts
@@ -36183,7 +36192,7 @@ var MissionStateEventSchema = external_exports.object({
36183
36192
  transition: MissionTransitionSchema,
36184
36193
  reason: external_exports.string().nullable(),
36185
36194
  by: external_exports.string(),
36186
- giver: external_exports.string().nullable(),
36195
+ receivers: external_exports.array(external_exports.string()).default([]),
36187
36196
  sessionId: external_exports.string().nullable(),
36188
36197
  lead: external_exports.string().nullable(),
36189
36198
  question: external_exports.string().nullable(),
@@ -36237,7 +36246,7 @@ var TelemetrySnapshotSchema = external_exports.object({
36237
36246
  state: MissionStateSchema,
36238
36247
  sessionId: external_exports.string().nullable(),
36239
36248
  lead: external_exports.string().nullable(),
36240
- giver: external_exports.string().nullable(),
36249
+ receivers: external_exports.array(external_exports.string()).default([]),
36241
36250
  givenBy: external_exports.string().nullable(),
36242
36251
  question: external_exports.string().nullable(),
36243
36252
  questions: external_exports.array(MissionAskedSchema).default([]),
@@ -36831,6 +36840,9 @@ var NodeClient = class {
36831
36840
  async joinMission(missionId) {
36832
36841
  return MissionRecordSchema.parse(await this.request("/orbital/missions/join", { method: "POST", body: { missionId } }));
36833
36842
  }
36843
+ async leaveMission(missionId) {
36844
+ return MissionRecordSchema.parse(await this.request("/orbital/missions/leave", { method: "POST", body: { missionId } }));
36845
+ }
36834
36846
  async handoff(handoff2) {
36835
36847
  return MissionRecordSchema.parse(await this.request("/orbital/missions/handoff", { method: "POST", body: handoff2 }));
36836
36848
  }
@@ -37472,6 +37484,13 @@ var DeliverySchema = external_exports.object({
37472
37484
  question: external_exports.string().nullable().default(null),
37473
37485
  text: external_exports.string()
37474
37486
  });
37487
+ var AnsweredSchema = external_exports.object({
37488
+ kind: external_exports.literal("answered"),
37489
+ mission: external_exports.string().min(1),
37490
+ question: external_exports.string().nullable(),
37491
+ by: external_exports.string().min(1),
37492
+ text: external_exports.string()
37493
+ });
37475
37494
  var SettleSchema = external_exports.object({
37476
37495
  taskId: external_exports.string().min(1).optional(),
37477
37496
  text: external_exports.string()
@@ -37717,6 +37736,60 @@ var PORT_METHODS = {
37717
37736
  reasoner: ["extractModule", "classify", "align", "checkConsistency"],
37718
37737
  missions: ["join", "reply", "pause", "resume", "ask", "say"]
37719
37738
  };
37739
+ var PLUGIN_COMMAND_RESERVED_OPTIONS = ["output", "quiet", "non-interactive", "confirm", "timeout", "help", "version", "workspace", "node"];
37740
+ var PluginCommandOptionSchema = external_exports.object({
37741
+ name: external_exports.string().regex(NAME_SHAPE).refine((name) => !PLUGIN_COMMAND_RESERVED_OPTIONS.includes(name), { error: `an option cannot take a name orbital gives every command: ${PLUGIN_COMMAND_RESERVED_OPTIONS.join(", ")}` }),
37742
+ type: external_exports.enum(["string", "boolean", "integer", "enum", "path"]),
37743
+ summary: external_exports.string().min(1),
37744
+ values: external_exports.array(external_exports.string().min(1)).min(1).optional(),
37745
+ required: external_exports.boolean().default(false),
37746
+ default: external_exports.union([external_exports.string(), external_exports.number().int(), external_exports.boolean()]).optional()
37747
+ }).strict().superRefine((option, issues2) => {
37748
+ if (option.type === "enum" !== (option.values !== void 0)) issues2.addIssue({ code: "custom", message: `the option ${option.name} names values only when its type is enum` });
37749
+ if (option.type === "boolean" && option.required) issues2.addIssue({ code: "custom", message: `the boolean option ${option.name} cannot be required` });
37750
+ if (option.default === void 0) return;
37751
+ const fits = option.type === "boolean" ? typeof option.default === "boolean" : option.type === "integer" ? typeof option.default === "number" : typeof option.default === "string" && (option.values?.includes(option.default) ?? true);
37752
+ if (!fits) issues2.addIssue({ code: "custom", message: `the default of the option ${option.name} is not a ${option.type === "enum" ? `value of ${option.values.join(", ")}` : option.type}` });
37753
+ });
37754
+ var PluginCommandArgSchema = external_exports.object({
37755
+ name: external_exports.string().regex(NAME_SHAPE),
37756
+ summary: external_exports.string().min(1),
37757
+ required: external_exports.boolean().default(false),
37758
+ variadic: external_exports.boolean().default(false)
37759
+ }).strict();
37760
+ var PluginCommandSchema = external_exports.object({
37761
+ name: external_exports.string().regex(NAME_SHAPE),
37762
+ summary: external_exports.string().min(1),
37763
+ args: external_exports.array(PluginCommandArgSchema).default([]),
37764
+ options: external_exports.array(PluginCommandOptionSchema).default([]),
37765
+ interactive: external_exports.boolean().default(false),
37766
+ node: external_exports.boolean().default(false)
37767
+ }).strict().superRefine((command, issues2) => {
37768
+ const twice = (names) => names.find((name, index) => names.indexOf(name) !== index);
37769
+ const arg = twice(command.args.map((one) => one.name));
37770
+ if (arg) issues2.addIssue({ code: "custom", message: `the command ${command.name} names the argument ${arg} twice` });
37771
+ const option = twice(command.options.map((one) => one.name));
37772
+ if (option) issues2.addIssue({ code: "custom", message: `the command ${command.name} names the option ${option} twice` });
37773
+ if (command.args.slice(0, -1).some((one) => one.variadic)) issues2.addIssue({ code: "custom", message: `only the last argument of the command ${command.name} can be variadic` });
37774
+ if (command.args.some((one, index) => one.required && command.args.slice(0, index).some((before) => !before.required))) issues2.addIssue({ code: "custom", message: `a required argument of the command ${command.name} cannot come after an optional one` });
37775
+ if (command.interactive && command.node) issues2.addIssue({ code: "custom", message: `the command ${command.name} cannot be interactive and need the node: the node has no terminal to give it` });
37776
+ });
37777
+ var PluginCommandsSchema = external_exports.array(PluginCommandSchema).default([]).superRefine((commands, issues2) => {
37778
+ const names = commands.map((one) => one.name);
37779
+ const twice = names.find((name, index) => names.indexOf(name) !== index);
37780
+ if (twice) issues2.addIssue({ code: "custom", message: `the command ${twice} is declared twice` });
37781
+ });
37782
+ var PluginCommandRequestSchema = external_exports.object({
37783
+ name: external_exports.string().regex(NAME_SHAPE),
37784
+ args: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.array(external_exports.string())])).default({}),
37785
+ options: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).default({}),
37786
+ terminal: external_exports.object({ tty: external_exports.boolean() }).nullable().default(null)
37787
+ }).strict();
37788
+ var PluginCommandResultSchema = external_exports.object({
37789
+ exitCode: external_exports.union([external_exports.literal(0), external_exports.literal(1), external_exports.literal(2), external_exports.literal(3), external_exports.literal(5)]).default(0),
37790
+ text: external_exports.string().nullable().default(null),
37791
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).default({})
37792
+ }).strict();
37720
37793
  var PluginManifestSchema = external_exports.object({
37721
37794
  name: external_exports.string().regex(NAME_SHAPE),
37722
37795
  version: external_exports.string().min(1),
@@ -37746,7 +37819,7 @@ var PluginManifestSchema = external_exports.object({
37746
37819
  secrets: external_exports.array(external_exports.string().min(1)).default([]),
37747
37820
  models: external_exports.array(external_exports.string().regex(/^[a-z0-9-]+:.+$/, "a model pattern is <provider>:<model>, such as anthropic:*")).default([])
37748
37821
  }).prefault({}),
37749
- commands: external_exports.array(external_exports.object({ name: external_exports.string().regex(NAME_SHAPE), summary: external_exports.string() })).default([]),
37822
+ commands: PluginCommandsSchema,
37750
37823
  panels: external_exports.array(external_exports.object({ id: external_exports.string().min(1), title: external_exports.string(), kind: external_exports.enum(["list", "tree", "transcript", "form"]) })).default([])
37751
37824
  }).strict();
37752
37825
  var PluginInitSchema = external_exports.object({
@@ -37769,7 +37842,8 @@ var PluginPackageSchema = external_exports.object({
37769
37842
  dir: external_exports.string().min(1).optional(),
37770
37843
  env: external_exports.array(external_exports.string().regex(PLUGIN_ENV_NAME, "is not the name of an environment variable")).default([]),
37771
37844
  config: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
37772
- harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([])
37845
+ harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([]),
37846
+ commands: PluginCommandsSchema
37773
37847
  }).strict();
37774
37848
  var PLUGIN_PACKAGE_FILE = "orbital-plugin.json";
37775
37849
  var MarketplaceSchema = external_exports.object({
@@ -37815,12 +37889,12 @@ var RpcPeer = class {
37815
37889
  request(method, params, timeoutMs = this.timeoutMs) {
37816
37890
  if (this.closedWith) return Promise.reject(this.closedWith);
37817
37891
  const id = this.nextId++;
37818
- return new Promise((resolve25, reject) => {
37892
+ return new Promise((resolve26, reject) => {
37819
37893
  const timer = timeoutMs > 0 ? setTimeout(() => {
37820
37894
  this.pending.delete(id);
37821
37895
  reject(new RpcError(RPC_ERRORS.internal, `${method} got no answer in ${timeoutMs} ms`));
37822
37896
  }, timeoutMs) : null;
37823
- this.pending.set(id, { resolve: resolve25, reject, timer });
37897
+ this.pending.set(id, { resolve: resolve26, reject, timer });
37824
37898
  this.write({ jsonrpc: "2.0", id, method, ...params !== void 0 && { params } });
37825
37899
  });
37826
37900
  }
@@ -38878,12 +38952,9 @@ function readDescriptorName(dir) {
38878
38952
  if (!existsSync6(file2)) throw refuse("invalid-input", `${dir} has no ${PLUGIN_PACKAGE_FILE}, so it is not a plugin.`);
38879
38953
  return PluginPackageSchema.parse(JSON.parse(readFileSync7(file2, "utf8"))).name;
38880
38954
  }
38881
- function lastPart(value2) {
38882
- return value2.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() ?? value2;
38883
- }
38884
38955
  function resolvePluginRef(home, ref) {
38885
- if (ref.startsWith("npm:")) return { kind: "npm", name: lastPart(ref.slice(4).replace(/@[^/]*$/, "")), pkg: ref.slice(4) };
38886
- if (ref.startsWith("git:")) return { kind: "git", name: lastPart(ref.slice(4)), url: ref.slice(4) };
38956
+ if (ref.startsWith("npm:")) return { kind: "npm", name: null, pkg: ref.slice(4) };
38957
+ if (ref.startsWith("git:")) return { kind: "git", name: null, url: ref.slice(4) };
38887
38958
  if (isFolder(ref)) return { kind: "folder", name: readDescriptorName(resolve6(ref)), dir: resolve6(ref) };
38888
38959
  const at = ref.lastIndexOf("@");
38889
38960
  if (at <= 0) throw refuse("invalid-input", `"${ref}" is not a plugin reference.`, "Use <name>@<marketplace>, npm:<package>, git:<url>, or a folder such as ./plugins/mine.");
@@ -38915,17 +38986,17 @@ function installPlugin(home, ref, exec2 = run) {
38915
38986
  const installed = Object.keys(JSON.parse(readFileSync7(join10(scratch, "package.json"), "utf8")).dependencies ?? {});
38916
38987
  const pkgDir = join10("node_modules", ...(installed[0] ?? ref.pkg.replace(/(.)@[^/]*$/, "$1")).split("/"));
38917
38988
  const inner = PluginPackageSchema.parse(JSON.parse(readFileSync7(join10(scratch, pkgDir, PLUGIN_PACKAGE_FILE), "utf8")));
38918
- writeFileSync5(join10(scratch, PLUGIN_PACKAGE_FILE), `${JSON.stringify({ name: ref.name, command: inner.command, dir: inner.dir ? join10(pkgDir, inner.dir) : pkgDir, env: inner.env, ...Object.keys(inner.config).length > 0 && { config: inner.config }, ...inner.harnesses.length > 0 && { harnesses: inner.harnesses } }, null, 2)}
38989
+ writeFileSync5(join10(scratch, PLUGIN_PACKAGE_FILE), `${JSON.stringify({ name: inner.name, command: inner.command, dir: inner.dir ? join10(pkgDir, inner.dir) : pkgDir, env: inner.env, ...Object.keys(inner.config).length > 0 && { config: inner.config }, ...inner.harnesses.length > 0 && { harnesses: inner.harnesses }, ...inner.commands.length > 0 && { commands: inner.commands } }, null, 2)}
38919
38990
  `);
38920
38991
  break;
38921
38992
  }
38922
38993
  }
38923
38994
  const named2 = readDescriptorName(scratch);
38924
- if (named2 !== ref.name) throw refuse("invalid-input", `The plugin calls itself ${named2}, and its reference names it ${ref.name}.`);
38925
- const dir = join10(parent, ref.name);
38995
+ if (ref.name !== null && named2 !== ref.name) throw refuse("invalid-input", `The plugin calls itself ${named2}, and its reference names it ${ref.name}.`);
38996
+ const dir = join10(parent, named2);
38926
38997
  rmSync3(dir, { recursive: true, force: true });
38927
38998
  renameSync(scratch, dir);
38928
- return dir;
38999
+ return { name: named2, dir };
38929
39000
  } finally {
38930
39001
  rmSync3(scratch, { recursive: true, force: true });
38931
39002
  }
@@ -38972,8 +39043,8 @@ function isPackageSource(source) {
38972
39043
  function ensurePluginInstalled(home, name, source, exec2 = run) {
38973
39044
  const dir = join10(pluginsDir(home), name);
38974
39045
  if (existsSync6(join10(dir, PLUGIN_PACKAGE_FILE))) return dir;
38975
- if (source.startsWith("npm:")) return installPlugin(home, { kind: "npm", name, pkg: source.slice(4) }, exec2);
38976
- if (source.startsWith("git:")) return installPlugin(home, { kind: "git", name, url: source.slice(4) }, exec2);
39046
+ if (source.startsWith("npm:")) return installPlugin(home, { kind: "npm", name, pkg: source.slice(4) }, exec2).dir;
39047
+ if (source.startsWith("git:")) return installPlugin(home, { kind: "git", name, url: source.slice(4) }, exec2).dir;
38977
39048
  throw refuse("invalid-input", `The plugin ${name} has the source ${source}, and only an npm: or git: source installs into the plugin cache.`);
38978
39049
  }
38979
39050
 
@@ -38981,15 +39052,22 @@ function ensurePluginInstalled(home, name, source, exec2 = run) {
38981
39052
  function pluginsDir(home) {
38982
39053
  return join11(home, "plugins");
38983
39054
  }
39055
+ function isLocalSource(source) {
39056
+ return source.startsWith(".") || isAbsolute5(source);
39057
+ }
39058
+ function pluginDir(name, source, roots) {
39059
+ if (!isLocalSource(source)) return join11(pluginsDir(roots.home), name);
39060
+ return isAbsolute5(source) ? source : resolve7(roots.harnessRoot ?? process.cwd(), source);
39061
+ }
38984
39062
  function commandOf(name, source, roots, entryEnv = []) {
38985
39063
  if (source.startsWith("exec:")) {
38986
39064
  const [command2, ...args2] = source.slice("exec:".length).trim().split(/\s+/);
38987
39065
  if (!command2) throw new Error(`The plugin ${name}'s source "${source}" names no command.`);
38988
- return { command: command2, args: args2, cwd: roots.harnessRoot ?? process.cwd(), env: [...entryEnv], defaults: {}, harnesses: null };
39066
+ return { command: command2, args: args2, cwd: roots.harnessRoot ?? process.cwd(), env: [...entryEnv], defaults: {}, harnesses: null, commands: null };
38989
39067
  }
38990
39068
  if (entryEnv.length > 0) throw new Error(`The plugin ${name}'s ${ORBITAL_FILE} entry names env:, and only an exec: source does: its ${PLUGIN_PACKAGE_FILE} declares the variables it reads.`);
38991
- const local = source.startsWith(".") || isAbsolute5(source);
38992
- const dir = local ? isAbsolute5(source) ? source : resolve7(roots.harnessRoot ?? process.cwd(), source) : join11(pluginsDir(roots.home), name);
39069
+ const local = isLocalSource(source);
39070
+ const dir = pluginDir(name, source, roots);
38993
39071
  const descriptor = join11(dir, PLUGIN_PACKAGE_FILE);
38994
39072
  if (!local && !existsSync7(descriptor) && /^(npm|git):/.test(source)) ensurePluginInstalled(roots.home, name, source);
38995
39073
  if (!existsSync7(descriptor)) {
@@ -38997,7 +39075,7 @@ function commandOf(name, source, roots, entryEnv = []) {
38997
39075
  }
38998
39076
  const plugin = PluginPackageSchema.parse(JSON.parse(readFileSync8(descriptor, "utf8")));
38999
39077
  const [command, ...args] = plugin.command;
39000
- return { command: command === "node" ? process.execPath : command, args, cwd: plugin.dir ? resolve7(dir, plugin.dir) : dir, env: plugin.env, defaults: plugin.config, harnesses: plugin.harnesses };
39078
+ return { command: command === "node" ? process.execPath : command, args, cwd: plugin.dir ? resolve7(dir, plugin.dir) : dir, env: plugin.env, defaults: plugin.config, harnesses: plugin.harnesses, commands: plugin.commands };
39001
39079
  }
39002
39080
  var PLUGIN_ENV = [
39003
39081
  "PATH",
@@ -39042,6 +39120,15 @@ function folderMismatch(manifest, folders) {
39042
39120
  const extra = folders.find((one) => !manifest.provides.harnesses.some((harness) => harness.id === one.id && harness.folder !== null));
39043
39121
  return extra ? `declares the harness ${extra.id} in its ${PLUGIN_PACKAGE_FILE}, and its harnesses port serves no folder for it` : null;
39044
39122
  }
39123
+ function commandMismatch(manifest, declared) {
39124
+ for (const command of manifest.commands) {
39125
+ const same2 = declared.find((one) => one.name === command.name);
39126
+ if (!same2) return `serves the command ${command.name}, and its ${PLUGIN_PACKAGE_FILE} declares no command ${command.name}`;
39127
+ if (JSON.stringify(same2) !== JSON.stringify(command)) return `declares the command ${command.name} one way in its ${PLUGIN_PACKAGE_FILE} and another way in its code`;
39128
+ }
39129
+ const extra = declared.find((one) => !manifest.commands.some((command) => command.name === one.name));
39130
+ return extra ? `declares the command ${extra.name} in its ${PLUGIN_PACKAGE_FILE}, and serves no command ${extra.name}` : null;
39131
+ }
39045
39132
  function manifestEnv(manifest) {
39046
39133
  return manifest.permissions.secrets.filter((name) => PLUGIN_ENV_NAME.test(name));
39047
39134
  }
@@ -39070,15 +39157,16 @@ async function startRemotePlugin(options) {
39070
39157
  let reconnecting = false;
39071
39158
  const connect = async () => {
39072
39159
  const start = commandOf(options.name, options.source, options.roots, options.entryEnv);
39073
- const child = spawn2(start.command, start.args, { cwd: start.cwd, env: pluginProcessEnv(options.env, start.env), stdio: ["pipe", "pipe", "pipe"] });
39160
+ const terminal = options.terminal ? [options.terminal.input, options.terminal.output, options.terminal.error] : [];
39161
+ const child = spawn2(start.command, start.args, { cwd: start.cwd, env: pluginProcessEnv(options.env, start.env), stdio: ["pipe", "pipe", "pipe", ...terminal] });
39074
39162
  try {
39075
- return await initialize(child, start.env, start.defaults, start.harnesses);
39163
+ return await initialize(child, start.env, start.defaults, start.harnesses, start.commands);
39076
39164
  } catch (error62) {
39077
39165
  child.kill("SIGKILL");
39078
39166
  throw error62;
39079
39167
  }
39080
39168
  };
39081
- const initialize = async (child, env, defaults, folders = null) => {
39169
+ const initialize = async (child, env, defaults, folders, commands) => {
39082
39170
  const peer = new RpcPeer({ send: (line) => child.stdin?.write(`${line}
39083
39171
  `) });
39084
39172
  createInterface2({ input: child.stdout }).on("line", (line) => peer.receive(line));
@@ -39113,12 +39201,16 @@ async function startRemotePlugin(options) {
39113
39201
  const where2 = options.source.startsWith("exec:") ? `its ${ORBITAL_FILE} entry` : `its ${PLUGIN_PACKAGE_FILE}`;
39114
39202
  throw new Error(`The plugin ${options.name} declares the variables [${declared.join(", ")}] as secrets, and ${where2} declares env: [${env.join(", ")}]. Make them the same.`);
39115
39203
  }
39204
+ if (commands) {
39205
+ const mismatch = commandMismatch(manifest2, commands);
39206
+ if (mismatch) throw new Error(`The plugin ${options.name} ${mismatch}. The commands of its ${PLUGIN_PACKAGE_FILE} and the commands of its code must be the same.`);
39207
+ }
39116
39208
  if (options.offline && manifest2.permissions.network.length > 0) {
39117
39209
  throw new Error(`The plugin ${options.name} reaches ${manifest2.permissions.network.join(", ")}, and ${ORBITAL_FILE} says offline: true.`);
39118
39210
  }
39119
39211
  await options.policy.check({ kind: "plugin.load", plugin: options.name, network: manifest2.permissions.network, secrets: manifest2.permissions.secrets, models: manifest2.permissions.models });
39120
- child.once("exit", () => {
39121
- peer.close(new Error(`The plugin ${options.name} stopped.`));
39212
+ child.once("exit", (code, signal) => {
39213
+ peer.close(new Error(`The plugin ${options.name} stopped (${signal ? `signal ${signal}` : `exit code ${code}`}).`));
39122
39214
  if (stopped || reconnecting) return;
39123
39215
  current2 = reconnect();
39124
39216
  current2.catch((error62) => options.log("error", error62 instanceof Error ? error62.message : String(error62)));
@@ -39298,7 +39390,7 @@ async function startRemotePlugin(options) {
39298
39390
  endpoint = given;
39299
39391
  void current2.then((connection) => connection.peer.notify(PLUGIN_METHODS.hostEndpoint, given));
39300
39392
  },
39301
- command: async (name, args) => (await call(PLUGIN_METHODS.commandRun, { name, args })).output,
39393
+ command: async (request) => PluginCommandResultSchema.parse(await (await current2).peer.request(PLUGIN_METHODS.commandRun, request, 0)),
39302
39394
  close: async () => {
39303
39395
  stopped = true;
39304
39396
  const connection = await current2.catch(() => null);
@@ -40653,7 +40745,7 @@ var readBodyDirect = (request) => {
40653
40745
  request[bodyBufferKey] = buffered;
40654
40746
  return Promise.resolve(buffered);
40655
40747
  }
40656
- const promise2 = new Promise((resolve25, reject) => {
40748
+ const promise2 = new Promise((resolve26, reject) => {
40657
40749
  const chunks = [];
40658
40750
  let settled = false;
40659
40751
  const finish = (callback) => {
@@ -40671,7 +40763,7 @@ var readBodyDirect = (request) => {
40671
40763
  else if (recovered === void 0) reject(error62 ?? normalizeAbortError(request, incoming));
40672
40764
  else {
40673
40765
  request[bodyBufferKey] = recovered;
40674
- resolve25(recovered);
40766
+ resolve26(recovered);
40675
40767
  }
40676
40768
  });
40677
40769
  return true;
@@ -40683,7 +40775,7 @@ var readBodyDirect = (request) => {
40683
40775
  finish(() => {
40684
40776
  const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
40685
40777
  request[bodyBufferKey] = buffer;
40686
- resolve25(buffer);
40778
+ resolve26(buffer);
40687
40779
  });
40688
40780
  };
40689
40781
  const onError = (error62) => {
@@ -41230,7 +41322,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
41230
41322
  });
41231
41323
  if (!chunk) {
41232
41324
  if (i === 1) {
41233
- await new Promise((resolve25) => setTimeout(resolve25));
41325
+ await new Promise((resolve26) => setTimeout(resolve26));
41234
41326
  maxReadCount = 3;
41235
41327
  continue;
41236
41328
  }
@@ -43499,9 +43591,9 @@ data:
43499
43591
  const initRequest = messages.find((m) => isInitializeRequest(m));
43500
43592
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
43501
43593
  if (this._enableJsonResponse) {
43502
- return new Promise((resolve25) => {
43594
+ return new Promise((resolve26) => {
43503
43595
  this._streamMapping.set(streamId, {
43504
- resolveJson: resolve25,
43596
+ resolveJson: resolve26,
43505
43597
  cleanup: () => {
43506
43598
  this._streamMapping.delete(streamId);
43507
43599
  }
@@ -49864,7 +49956,7 @@ var Protocol = class {
49864
49956
  return;
49865
49957
  }
49866
49958
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
49867
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
49959
+ await new Promise((resolve26) => setTimeout(resolve26, pollInterval));
49868
49960
  options?.signal?.throwIfAborted();
49869
49961
  }
49870
49962
  } catch (error62) {
@@ -49881,7 +49973,7 @@ var Protocol = class {
49881
49973
  */
49882
49974
  request(request, resultSchema, options) {
49883
49975
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
49884
- return new Promise((resolve25, reject) => {
49976
+ return new Promise((resolve26, reject) => {
49885
49977
  const earlyReject = (error62) => {
49886
49978
  reject(error62);
49887
49979
  };
@@ -49959,7 +50051,7 @@ var Protocol = class {
49959
50051
  if (!parseResult.success) {
49960
50052
  reject(parseResult.error);
49961
50053
  } else {
49962
- resolve25(parseResult.data);
50054
+ resolve26(parseResult.data);
49963
50055
  }
49964
50056
  } catch (error62) {
49965
50057
  reject(error62);
@@ -50221,12 +50313,12 @@ var Protocol = class {
50221
50313
  }
50222
50314
  } catch {
50223
50315
  }
50224
- return new Promise((resolve25, reject) => {
50316
+ return new Promise((resolve26, reject) => {
50225
50317
  if (signal.aborted) {
50226
50318
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
50227
50319
  return;
50228
50320
  }
50229
- const timeoutId = setTimeout(resolve25, interval);
50321
+ const timeoutId = setTimeout(resolve26, interval);
50230
50322
  signal.addEventListener("abort", () => {
50231
50323
  clearTimeout(timeoutId);
50232
50324
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -51366,7 +51458,7 @@ var McpServer = class {
51366
51458
  let task = createTaskResult.task;
51367
51459
  const pollInterval = task.pollInterval ?? 5e3;
51368
51460
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
51369
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
51461
+ await new Promise((resolve26) => setTimeout(resolve26, pollInterval));
51370
51462
  const updatedTask = await extra.taskStore.getTask(taskId);
51371
51463
  if (!updatedTask) {
51372
51464
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -52091,14 +52183,14 @@ function claudeService(env) {
52091
52183
  if (!dir) return SERVICE;
52092
52184
  return `${SERVICE}-${createHash4("sha256").update(dir.normalize("NFC")).digest("hex").slice(0, 8)}`;
52093
52185
  }
52094
- var findWithSecurity = (args) => new Promise((resolve25) => {
52186
+ var findWithSecurity = (args) => new Promise((resolve26) => {
52095
52187
  const child = spawn3(SECURITY, [...args], { stdio: ["ignore", "pipe", "ignore"], timeout: SECURITY_TIMEOUT_MS });
52096
52188
  let stdout = "";
52097
52189
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
52098
52190
  stdout += chunk;
52099
52191
  });
52100
- child.once("error", () => resolve25({ status: 1, stdout: "" }));
52101
- child.once("close", (code) => resolve25({ status: code ?? 1, stdout }));
52192
+ child.once("error", () => resolve26({ status: 1, stdout: "" }));
52193
+ child.once("close", (code) => resolve26({ status: code ?? 1, stdout }));
52102
52194
  });
52103
52195
  function keychainRead(account, service, find = findWithSecurity) {
52104
52196
  return async () => {
@@ -52119,7 +52211,7 @@ function renewEnv(env) {
52119
52211
  return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0 && !KEPT_OUT.includes(entry[0])));
52120
52212
  }
52121
52213
  function claudeRenewal(binary, env) {
52122
- return () => new Promise((resolve25, reject) => {
52214
+ return () => new Promise((resolve26, reject) => {
52123
52215
  const cwd = mkdtempSync(join16(tmpdir(), "orbital-claude-renew-"));
52124
52216
  const child = spawn3(binary, RENEW_ARGS, { cwd, env: renewEnv(env), stdio: ["ignore", "ignore", "pipe"], timeout: RENEW_TIMEOUT_MS });
52125
52217
  let stderr = "";
@@ -52129,7 +52221,7 @@ function claudeRenewal(binary, env) {
52129
52221
  const done = (error62) => {
52130
52222
  rmSync5(cwd, { recursive: true, force: true });
52131
52223
  if (error62) reject(error62);
52132
- else resolve25();
52224
+ else resolve26();
52133
52225
  };
52134
52226
  child.once("error", done);
52135
52227
  child.once("close", (code) => done(code === 0 ? null : new Error(`claude exited ${code ?? "on a signal"}${stderr.trim() ? `: ${stderr.trim().split("\n")[0]}` : ""}`)));
@@ -53396,7 +53488,7 @@ function missionOfRow(row2) {
53396
53488
  result: textOf(row2, "result"),
53397
53489
  question: textOf(row2, "question"),
53398
53490
  questions: questionsOf(row2),
53399
- giver: null,
53491
+ receivers: [],
53400
53492
  lead: textOf(row2, "lead"),
53401
53493
  lastActivity: textOf(row2, "activity"),
53402
53494
  claim: textOf(row2, "claim"),
@@ -53549,7 +53641,7 @@ var MissionStore = class {
53549
53641
  source: request.source !== void 0 ? request.source : known?.source ?? null,
53550
53642
  result: request.result !== void 0 ? request.result : known?.result ?? null,
53551
53643
  question: request.question !== void 0 ? request.question : request.state === "waiting" ? known?.question ?? null : null,
53552
- giver: request.giver !== void 0 ? request.giver : known?.giver ?? null,
53644
+ receivers: request.receivers !== void 0 ? request.receivers : known?.receivers ?? [],
53553
53645
  lead: request.lead !== void 0 ? request.lead : known?.lead ?? null,
53554
53646
  claim: request.claim !== void 0 ? request.claim : known?.claim ?? null,
53555
53647
  claimedAt: request.claimedAt !== void 0 ? request.claimedAt : known?.claimedAt ?? null,
@@ -53604,7 +53696,7 @@ function missionRecord(node2, mission, declared, stored) {
53604
53696
  result: stored?.result ?? null,
53605
53697
  question: stored?.question ?? null,
53606
53698
  questions: stored?.questions ?? [],
53607
- giver: stored?.giver ?? null,
53699
+ receivers: stored?.receivers ?? [],
53608
53700
  lead: stored?.lead ?? null,
53609
53701
  lastActivity: null,
53610
53702
  claim: stored?.claim ?? null,
@@ -53711,9 +53803,9 @@ var Telemetry = class {
53711
53803
  async drain(timeoutMs) {
53712
53804
  if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) return true;
53713
53805
  let done = null;
53714
- const empty = new Promise((resolve25) => {
53806
+ const empty = new Promise((resolve26) => {
53715
53807
  done = () => {
53716
- if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve25(true);
53808
+ if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve26(true);
53717
53809
  };
53718
53810
  this.drained.add(done);
53719
53811
  });
@@ -53966,7 +54058,7 @@ var SessionHost = class {
53966
54058
  transition: missionTransition(before?.state ?? "open", after.state, after.question),
53967
54059
  reason: change?.note ?? null,
53968
54060
  by: change?.by ?? "node",
53969
- giver: after.giver,
54061
+ receivers: after.receivers,
53970
54062
  sessionId: after.sessionId,
53971
54063
  lead: after.lead,
53972
54064
  question: after.question,
@@ -53974,7 +54066,7 @@ var SessionHost = class {
53974
54066
  });
53975
54067
  }
53976
54068
  missionWritten(mission, team, by) {
53977
- this.telemetry.emit({ kind: "mission.state", mission, team, from: null, to: "open", transition: "new", reason: "written", by, giver: null, sessionId: null, lead: null, question: null, result: null });
54069
+ this.telemetry.emit({ kind: "mission.state", mission, team, from: null, to: "open", transition: "new", reason: "written", by, receivers: [], sessionId: null, lead: null, question: null, result: null });
53978
54070
  }
53979
54071
  async snapshot() {
53980
54072
  const definition = await this.definitionOf(this.options.defaultWorkspace);
@@ -54003,7 +54095,7 @@ var SessionHost = class {
54003
54095
  retiring: note?.retiring ?? null
54004
54096
  };
54005
54097
  }), ...[...notes].filter(([name]) => !definition.teams.some((team) => team.name === name)).map(([name, note]) => ({ name, node: this.node, purpose: null, lead: null, members: [], problems: [...note.problems], retiring: note.retiring }))],
54006
- missions: missions.map((record2) => ({ missionId: record2.missionId, team: record2.team, state: record2.state, sessionId: record2.sessionId, lead: record2.lead, giver: record2.giver, givenBy: record2.givenBy, question: record2.question, questions: record2.questions, result: record2.result, updatedAt: record2.updatedAt })),
54098
+ missions: missions.map((record2) => ({ missionId: record2.missionId, team: record2.team, state: record2.state, sessionId: record2.sessionId, lead: record2.lead, receivers: record2.receivers, givenBy: record2.givenBy, question: record2.question, questions: record2.questions, result: record2.result, updatedAt: record2.updatedAt })),
54007
54099
  sessions: this.liveHosted().map((session) => ({ ...this.contextOf(session), role: this.roleOf(session), placement: session.placement, running: session.running !== null, since: this.turnSince.get(session.id) ?? null })),
54008
54100
  jobs: [...this.openJobs].map(([jobId, open2]) => ({ ...open2.context, jobId, since: new Date(open2.since).toISOString(), via: open2.via })),
54009
54101
  tokens: this.telemetry.tokens(),
@@ -54057,19 +54149,48 @@ var SessionHost = class {
54057
54149
  this.redeliverQuestions(id);
54058
54150
  return info;
54059
54151
  }
54060
- redeliverQuestions(giverId) {
54152
+ redeliverQuestions(sessionId) {
54153
+ const session = this.sessions.get(sessionId);
54154
+ if (!session) return;
54061
54155
  for (const stored of this.missions.all()) {
54062
- if (stored.giver !== giverId || !stored.sessionId || stored.state === "paused" || !hasLead(stored.state)) continue;
54063
- if (stored.state === "waiting" && !this.questionOpen(giverId, stored.mission, null)) this.deliverQuestion(giverId, { team: stored.team, mission: stored.mission, lead: stored.lead, question: stored.question ?? NO_HANDOFF_TELL, questionId: null }, stored.sessionId);
54064
- for (const asked2 of stored.questions) {
54065
- if (!this.questionOpen(giverId, stored.mission, asked2.id)) this.deliverQuestion(giverId, { team: stored.team, mission: stored.mission, lead: stored.lead, question: asked2.question, questionId: asked2.id }, stored.sessionId);
54066
- }
54156
+ if (!stored.receivers.some((key2) => this.receives(session, key2))) continue;
54157
+ for (const open2 of this.openQuestions(stored)) if (!this.questionOpen(sessionId, open2.mission, open2.questionId)) this.deliverQuestion(sessionId, open2, stored.sessionId);
54067
54158
  }
54068
54159
  }
54069
- questionOpen(giverId, mission, questionId) {
54070
- if (this.questionsInFlight.has(`${giverId}:${mission}:${questionId ?? ""}`)) return true;
54160
+ openQuestions(stored) {
54161
+ if (!stored.sessionId || stored.state === "paused" || !hasLead(stored.state)) return [];
54162
+ const base2 = { team: stored.team, mission: stored.mission, lead: stored.lead };
54163
+ return [
54164
+ ...stored.state === "waiting" ? [{ ...base2, question: stored.question ?? NO_HANDOFF_TELL, questionId: null }] : [],
54165
+ ...stored.questions.map((asked2) => ({ ...base2, question: asked2.question, questionId: asked2.id }))
54166
+ ];
54167
+ }
54168
+ receiverKey(session) {
54169
+ return session.external?.externalId ? externalReceiver(session.external.externalId) : session.id;
54170
+ }
54171
+ receives(session, key2) {
54172
+ return key2 === session.id || key2 === this.receiverKey(session);
54173
+ }
54174
+ liveReceivers(receivers, except = null) {
54175
+ return [...this.sessions.values()].filter((session) => session.id !== except && !this.ended.has(session.id) && (session.external ? session.external.attached : true) && receivers.some((key2) => this.receives(session, key2)));
54176
+ }
54177
+ keyOfRemote(receiver) {
54178
+ if (receiver.node !== this.node) return receiverRef(receiver);
54179
+ const session = this.sessions.get(receiver.sessionId);
54180
+ return session ? this.receiverKey(session) : receiver.sessionId;
54181
+ }
54182
+ starterKey(by) {
54183
+ if (!by.startsWith("session:")) return null;
54184
+ const named2 = by.slice("session:".length);
54185
+ const remote = remoteReceiverOf(named2);
54186
+ if (remote) return this.keyOfRemote(remote);
54187
+ const session = this.sessions.get(named2);
54188
+ return session ? this.receiverKey(session) : named2;
54189
+ }
54190
+ questionOpen(sessionId, mission, questionId) {
54191
+ if (this.questionsInFlight.has(`${sessionId}:${mission}:${questionId ?? ""}`)) return true;
54071
54192
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
54072
- if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== giverId) continue;
54193
+ if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== sessionId) continue;
54073
54194
  const task = this.tasks.get(taskId);
54074
54195
  if (task && !(task.status && isTerminal(task.status.state))) return true;
54075
54196
  this.questionDeliveries.delete(taskId);
@@ -54101,7 +54222,7 @@ var SessionHost = class {
54101
54222
  async settleExternal(sessionId, settle2) {
54102
54223
  const session = this.sessions.get(sessionId);
54103
54224
  if (!session?.external) throw new SessionHostError("not-found", `There is no attached session ${sessionId} on this node.`);
54104
- const taskId = settle2.taskId ?? session.running;
54225
+ const taskId = settle2.taskId ?? session.running ?? session.external.open.keys().next().value ?? null;
54105
54226
  const task = taskId ? this.tasks.get(taskId) : void 0;
54106
54227
  if (!task || this.taskSession.get(task.id) !== sessionId || task.status && isTerminal(task.status.state)) throw new SessionHostError("invalid", `Session ${sessionId} has no open delivery${settle2.taskId ? ` ${settle2.taskId}` : ""} to settle.`);
54107
54228
  await this.finish(sessionId, task, "TASK_STATE_COMPLETED", settle2.text);
@@ -54135,23 +54256,21 @@ var SessionHost = class {
54135
54256
  await this.finish(session.id, task, "TASK_STATE_FAILED", `The attached session ${session.id} is detached.`);
54136
54257
  return;
54137
54258
  }
54138
- session.running = task.id;
54259
+ const asked2 = this.questionOf(message);
54260
+ if (asked2 === null) session.running = task.id;
54139
54261
  await this.update(session.id, task, "TASK_STATE_WORKING");
54140
54262
  const fromAgent = message.metadata?.[ORBITAL_META.fromAgent];
54141
54263
  const fromSession = message.metadata?.[ORBITAL_META.fromSession];
54142
- const asked2 = this.questionOf(message);
54143
54264
  const delivery = { kind: "delivery", taskId: task.id, fromAgent: typeof fromAgent === "string" ? fromAgent : null, fromSession: typeof fromSession === "string" ? fromSession : null, mission: asked2, question: asked2 === null ? null : this.questionIdOf(message), text: messageText(message) };
54144
- const settled = new Promise((resolve25) => this.externalWaits.set(task.id, resolve25));
54265
+ const settled = new Promise((resolve26) => this.externalWaits.set(task.id, resolve26)).finally(() => {
54266
+ this.externalWaits.delete(task.id);
54267
+ external.open.delete(task.id);
54268
+ if (session.running === task.id) session.running = null;
54269
+ });
54145
54270
  if (asked2 !== null) external.open.set(task.id, delivery);
54146
54271
  if (external.listeners.size === 0) external.waiting.push(delivery);
54147
54272
  for (const listener of external.listeners) listener(delivery);
54148
- try {
54149
- await settled;
54150
- } finally {
54151
- this.externalWaits.delete(task.id);
54152
- external.open.delete(task.id);
54153
- session.running = null;
54154
- }
54273
+ if (asked2 === null) await settled;
54155
54274
  }
54156
54275
  stopFollowing() {
54157
54276
  this.following.abort();
@@ -54166,9 +54285,9 @@ var SessionHost = class {
54166
54285
  if (this.followingHub) return;
54167
54286
  let listener = null;
54168
54287
  try {
54169
- await Promise.race([this.settled(), new Promise((resolve25) => {
54170
- listener = resolve25;
54171
- this.followListeners.add(resolve25);
54288
+ await Promise.race([this.settled(), new Promise((resolve26) => {
54289
+ listener = resolve26;
54290
+ this.followListeners.add(resolve26);
54172
54291
  })]);
54173
54292
  } finally {
54174
54293
  if (listener) this.followListeners.delete(listener);
@@ -54259,7 +54378,7 @@ var SessionHost = class {
54259
54378
  const leadLives = known?.sessionId ? this.sessions.has(known.sessionId) : false;
54260
54379
  if (known?.state === "paused" && this.leadKept(known.sessionId)) throw new SessionHostError("invalid", `The mission ${missionId} is paused, in session ${known.sessionId}. Resume it with mission resume ${missionId}.`);
54261
54380
  if (known?.state === "active" && leadLives) throw new SessionHostError("invalid", `The mission ${missionId} is active already, in session ${known.sessionId}. Its lead hands it off first.`);
54262
- if (known?.state === "waiting" && leadLives) throw new SessionHostError("invalid", `The mission ${missionId} waits for its giver, in session ${known.sessionId}. Answer it with mission reply.`);
54381
+ if (known?.state === "waiting" && leadLives) throw new SessionHostError("invalid", `The mission ${missionId} waits for a decision, in session ${known.sessionId}. Answer it with mission reply.`);
54263
54382
  const ended = known && hasLead(known.state) && !leadLives ? `the lead session ${known.sessionId ?? "(none)"} ended with its node` : null;
54264
54383
  if (known?.state === "done") throw new SessionHostError("invalid", `The mission ${missionId} is done. Give the team a new mission for new work.`);
54265
54384
  const claim2 = declared?.source ?? missionId;
@@ -54270,12 +54389,13 @@ var SessionHost = class {
54270
54389
  const resumable = ended && known?.sessionId && readSessionRecord(this.paths.sessionsDir, known.sessionId) !== null ? known.sessionId : null;
54271
54390
  if (resumable) {
54272
54391
  const task2 = await this.submit({ sessionId: resumable, message });
54273
- this.missions.change(mission, { state: "active", by, team, note: `${source.event}; ${ended}, so it goes on in the same session`, taskId: task2.id, source: key2, question: null, giver: by.startsWith("session:") ? by.slice("session:".length) : known?.giver ?? null });
54392
+ const starter = this.starterKey(by);
54393
+ this.missions.change(mission, { state: "active", by, team, note: `${source.event}; ${ended}, so it goes on in the same session`, taskId: task2.id, source: key2, question: null, receivers: [.../* @__PURE__ */ new Set([...known?.receivers ?? [], ...starter ? [starter] : []])] });
54274
54394
  return { task: task2, duplicate: false };
54275
54395
  }
54276
54396
  const task = await this.submit({ message, start: { agent: team, workspaceDir, team, mission } });
54277
54397
  const takenOver = held2.map(claimHolder);
54278
- this.missions.change(mission, { state: "active", by, team, note: [source.event, ended, takenOver.length > 0 ? `took over the ${live2.length > 0 ? "claim" : "stale claim"} of ${takenOver.join(", ")}` : null].filter((part) => part !== null).join("; "), claim: claim2, claimedAt: new Date(this.options.context.now()).toISOString(), sessionId: task.contextId ?? null, taskId: task.id, source: key2, result: null, question: null, questions: [], giver: by.startsWith("session:") ? by.slice("session:".length) : null, lead: here.members[0]?.agent ?? null });
54398
+ this.missions.change(mission, { state: "active", by, team, note: [source.event, ended, takenOver.length > 0 ? `took over the ${live2.length > 0 ? "claim" : "stale claim"} of ${takenOver.join(", ")}` : null].filter((part) => part !== null).join("; "), claim: claim2, claimedAt: new Date(this.options.context.now()).toISOString(), sessionId: task.contextId ?? null, taskId: task.id, source: key2, result: null, question: null, questions: [], receivers: [this.starterKey(by)].filter((one) => one !== null), lead: here.members[0]?.agent ?? null });
54279
54399
  return { task, duplicate: false };
54280
54400
  }
54281
54401
  pauseInterrupted() {
@@ -54378,7 +54498,7 @@ var SessionHost = class {
54378
54498
  for (const { relay: port } of this.options.ports.missionRelays()) {
54379
54499
  const answered = await port.reply({ missionId, text: text3, ...questionId && { questionId }, by, ...this.claimOfBy(by) });
54380
54500
  if (!answered) continue;
54381
- await this.closeQuestions(missionId, questionId ?? void 0, `answered: ${text3}`);
54501
+ await this.closeQuestions(missionId, questionId ?? void 0, by, text3);
54382
54502
  return answered;
54383
54503
  }
54384
54504
  }
@@ -54389,14 +54509,14 @@ var SessionHost = class {
54389
54509
  if (known?.state !== "waiting" || !known.sessionId) throw new SessionHostError("invalid", `The mission ${missionId} is ${known?.state ?? "open"}, not waiting, and has no open question, so it takes no reply.`);
54390
54510
  const lead = known.sessionId;
54391
54511
  this.missions.change(missionId, { state: "active", by, note: "reply" });
54392
- await this.closeQuestions(missionId, null, `answered: ${text3}`);
54512
+ await this.closeQuestions(missionId, null, by, text3);
54393
54513
  await this.submit({ sessionId: lead, message: { messageId: randomUUID4(), role: "ROLE_USER", parts: [textPart(`[reply to your question on the mission ${missionId}, from ${by}]
54394
54514
  ${text3}`)] } });
54395
54515
  return this.missionOf(missionId);
54396
54516
  }
54397
54517
  async answerAsked(by, missionId, lead, asked2, text3) {
54398
54518
  this.missions.answer(missionId, asked2.id);
54399
- await this.closeQuestions(missionId, asked2.id, `answered: ${text3}`);
54519
+ await this.closeQuestions(missionId, asked2.id, by, text3);
54400
54520
  const session = this.sessions.get(lead);
54401
54521
  if (session?.running && !session.external) {
54402
54522
  this.steering.set(lead, [...this.steering.get(lead) ?? [], { from: by, missionId, text: `reply to your question ${asked2.id}: ${asked2.question}
@@ -54413,7 +54533,7 @@ ${text3}`)] } });
54413
54533
  const missionId = given.mission;
54414
54534
  const known = this.missions.get(missionId);
54415
54535
  if (!known || !hasLead(known.state) || known.state === "paused" || !known.sessionId) throw new SessionHostError("invalid", `The mission ${missionId} is ${known?.state ?? "open"}, so its lead asks nothing.`);
54416
- if (given.sessionId !== null && known.sessionId !== given.sessionId) throw new SessionHostError("invalid", `Only the lead session of the mission ${missionId} asks its giver: session ${known.sessionId}.`);
54536
+ if (given.sessionId !== null && known.sessionId !== given.sessionId) throw new SessionHostError("invalid", `Only the lead session of the mission ${missionId} asks its decision receivers: session ${known.sessionId}.`);
54417
54537
  this.askedInTurn.add(known.sessionId);
54418
54538
  this.recordQuestion(missionId, known.sessionId, given.question);
54419
54539
  return this.missionOf(missionId);
@@ -54421,8 +54541,8 @@ ${text3}`)] } });
54421
54541
  recordQuestion(missionId, sessionId, question) {
54422
54542
  const asked2 = { id: `q-${randomUUID4().slice(0, 8)}`, question, at: new Date(this.options.context.now()).toISOString() };
54423
54543
  const after = this.missions.ask(missionId, asked2);
54424
- this.telemetry.emit({ kind: "mission.state", mission: missionId, team: after.team, from: after.state, to: after.state, transition: "question", reason: `asked ${asked2.id}`, by: `session:${sessionId}`, giver: after.giver, sessionId: after.sessionId, lead: after.lead, question: asked2.question, result: after.result });
54425
- this.tellGiver({ team: after.team, mission: missionId, giver: after.giver, lead: after.lead, question: asked2.question, questionId: asked2.id }, sessionId);
54544
+ this.telemetry.emit({ kind: "mission.state", mission: missionId, team: after.team, from: after.state, to: after.state, transition: "question", reason: `asked ${asked2.id}`, by: `session:${sessionId}`, receivers: after.receivers, sessionId: after.sessionId, lead: after.lead, question: asked2.question, result: after.result });
54545
+ this.tellReceivers(after.receivers, { team: after.team, mission: missionId, lead: after.lead, question: asked2.question, questionId: asked2.id }, sessionId);
54426
54546
  }
54427
54547
  async say(by, target, text3, reader, relay = true) {
54428
54548
  const named2 = this.missions.get(target);
@@ -54478,16 +54598,28 @@ ${text3}`)] } });
54478
54598
  const mission = message.metadata?.[ORBITAL_META.mission];
54479
54599
  return typeof mission === "string" ? mission : null;
54480
54600
  }
54481
- async closeQuestions(mission, questionId, text3) {
54601
+ async closeQuestions(mission, questionId, by, text3) {
54602
+ const answerer = by.startsWith("session:") ? by.slice("session:".length) : null;
54603
+ const told = /* @__PURE__ */ new Set();
54482
54604
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
54483
54605
  if (asked2.mission !== mission || questionId !== void 0 && asked2.questionId !== questionId) continue;
54484
54606
  this.questionDeliveries.delete(taskId);
54485
54607
  const task = this.tasks.get(taskId);
54486
- const giver = this.taskSession.get(taskId);
54487
- if (!task || !giver || task.status && isTerminal(task.status.state)) continue;
54488
- await this.finish(giver, task, "TASK_STATE_COMPLETED", text3);
54608
+ const receiver = this.taskSession.get(taskId);
54609
+ if (!task || !receiver || task.status && isTerminal(task.status.state)) continue;
54610
+ await this.finish(receiver, task, "TASK_STATE_COMPLETED", `answered by ${by}: ${text3}`);
54489
54611
  this.externalWaits.get(taskId)?.();
54612
+ if (receiver !== answerer) this.tellAnswered(receiver, { kind: "answered", mission, question: asked2.questionId, by, text: text3 }, told);
54490
54613
  }
54614
+ for (const session of this.liveReceivers(this.missions.get(mission)?.receivers ?? [], answerer)) this.tellAnswered(session.id, { kind: "answered", mission, question: questionId ?? null, by, text: text3 }, told);
54615
+ }
54616
+ tellAnswered(sessionId, answered, told) {
54617
+ const external = this.sessions.get(sessionId)?.external;
54618
+ if (!external?.attached || told.has(sessionId)) return;
54619
+ told.add(sessionId);
54620
+ const answers = (delivery) => delivery.mission === answered.mission && (answered.question === null || delivery.question === answered.question);
54621
+ for (let index = external.waiting.length - 1; index >= 0; index -= 1) if (answers(external.waiting[index])) external.waiting.splice(index, 1);
54622
+ for (const listener of external.listeners) listener(answered);
54491
54623
  }
54492
54624
  busyBelow(sessionId) {
54493
54625
  return (this.pendingWakes.get(sessionId) ?? []).some((taskId) => !this.delivered.has(taskId)) || [...this.sessions.values(), ...this.remoteSessions.values()].some((one) => one.parentSessionId === sessionId && one.running !== null);
@@ -54514,32 +54646,46 @@ ${text3}`)] } });
54514
54646
  }
54515
54647
  this.nudged.delete(sessionId);
54516
54648
  this.missions.change(known.mission, { state: "waiting", by: `session:${sessionId}`, note: question === "" ? NO_HANDOFF_NOTE : stateWord(state), question: question === "" ? null : question });
54517
- this.tellGiver({ ...known, question: question === "" ? NO_HANDOFF_TELL : question, questionId: null }, sessionId);
54649
+ this.tellReceivers(known.receivers, { team: known.team, mission: known.mission, lead: known.lead, question: question === "" ? NO_HANDOFF_TELL : question, questionId: null }, sessionId);
54518
54650
  }
54519
- async joinMission(sessionId, missionId) {
54520
- const session = this.sessions.get(sessionId);
54521
- if (!session?.external) throw new SessionHostError("invalid", `Only an attached session joins a mission as its giver, and ${sessionId} is not one.`);
54651
+ async joinMission(receiver, missionId) {
54652
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
54653
+ if ("sessionId" in receiver && !session) throw new SessionHostError("not-found", `There is no session ${receiver.sessionId} on this node.`);
54522
54654
  const known = this.missions.get(missionId);
54523
- if (!known) {
54655
+ if (!known && session) {
54524
54656
  for (const { relay } of this.options.ports.missionRelays()) {
54525
- const joined = await relay.join({ missionId, giver: { node: this.node, sessionId }, ...this.claimOfSession(sessionId) });
54657
+ const joined = await relay.join({ missionId, receiver: { node: this.node, sessionId: session.id }, ...this.claimOfSession(session.id) });
54526
54658
  if (joined) return joined;
54527
54659
  }
54528
54660
  }
54529
- return this.joinHere(giverRef({ node: this.node, sessionId }), `session:${sessionId}`, missionId);
54661
+ if (session && known?.sessionId === session.id) throw new SessionHostError("invalid", `The session ${session.id} leads the mission ${missionId}, so it asks its questions and receives none.`);
54662
+ if (!session) return this.joinHere(externalReceiver("externalId" in receiver ? receiver.externalId : ""), "cli", missionId, null);
54663
+ return this.joinHere(this.receiverKey(session), `session:${session.id}`, missionId, [session.id]);
54530
54664
  }
54531
54665
  async joinRemote(request) {
54532
- return this.joinHere(giverRef(request.giver), `session:${giverRef(request.giver)}`, request.missionId);
54666
+ const key2 = this.keyOfRemote(request.receiver);
54667
+ const local = remoteReceiverOf(key2) === null ? [request.receiver.sessionId] : null;
54668
+ return this.joinHere(key2, `session:${receiverRef(request.receiver)}`, request.missionId, local);
54533
54669
  }
54534
- async joinHere(giverId, by, missionId) {
54535
- const self = this.node;
54536
- const sessionId = remoteGiverOf(giverId)?.node === self ? remoteGiverOf(giverId).sessionId : giverId;
54670
+ async leaveMission(receiver, missionId) {
54671
+ const known = this.missions.get(missionId);
54672
+ if (!known || !hasLead(known.state)) throw new SessionHostError("invalid", `The mission ${missionId} is ${known?.state ?? "open"} on this node, so it has no decision receivers to leave.`);
54673
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
54674
+ const leaves = (key2) => session ? this.receives(session, key2) : "sessionId" in receiver ? key2 === receiver.sessionId : externalIdOf(key2) === receiver.externalId;
54675
+ const kept = known.receivers.filter((key2) => !leaves(key2));
54676
+ if (kept.length === known.receivers.length) return this.missionOf(missionId);
54677
+ this.missions.change(missionId, { state: known.state, by: "sessionId" in receiver ? `session:${receiver.sessionId}` : "cli", note: `${known.receivers.filter(leaves).join(", ")} left the decision receivers`, question: known.question, receivers: kept });
54678
+ return this.missionOf(missionId);
54679
+ }
54680
+ async joinHere(key2, by, missionId, deliverTo) {
54537
54681
  const known = this.missions.get(missionId);
54538
54682
  if (!known || !hasLead(known.state) || !known.sessionId) throw new SessionHostError("invalid", `The mission ${missionId} is ${known?.state ?? "open"}, so it has no lead to join.`);
54539
- if (known.giver !== sessionId) {
54540
- const changed = this.missions.change(missionId, { state: known.state, by, note: `joined as giver${known.giver ? `, in place of session ${known.giver}` : ""}`, giver: sessionId });
54541
- if (changed.state === "waiting") this.tellGiver({ ...changed, question: changed.question ?? NO_HANDOFF_TELL, questionId: null }, changed.sessionId);
54542
- if (changed.state !== "paused") for (const asked2 of changed.questions) this.tellGiver({ ...changed, question: asked2.question, questionId: asked2.id }, changed.sessionId);
54683
+ const stored = known.receivers.includes(key2) ? known : this.missions.change(missionId, { state: known.state, by, note: `${key2} joined the decision receivers`, question: known.question, receivers: [...known.receivers, key2] });
54684
+ const remote = remoteReceiverOf(key2);
54685
+ const live2 = this.liveReceivers([key2]).filter((session) => deliverTo === null || deliverTo.includes(session.id));
54686
+ for (const open2 of this.openQuestions(stored)) {
54687
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, stored.sessionId);
54688
+ for (const session of live2) if (!this.questionOpen(session.id, open2.mission, open2.questionId)) this.deliverQuestion(session.id, open2, stored.sessionId);
54543
54689
  }
54544
54690
  return this.missionOf(missionId);
54545
54691
  }
@@ -54549,36 +54695,34 @@ ${text3}`)] } });
54549
54695
  missionTeam(missionId) {
54550
54696
  return this.missions.get(missionId)?.team ?? null;
54551
54697
  }
54552
- async askGiver(question) {
54553
- const giver = this.sessions.get(question.giver.sessionId);
54554
- if (!giver?.external || question.giver.node !== this.node) return false;
54555
- this.deliverQuestion(giver.id, { team: null, mission: question.missionId, lead: question.lead ? `${question.lead} on ${question.node}` : null, question: question.question, questionId: question.questionId ?? null }, question.leadSessionId);
54698
+ async askReceiver(question) {
54699
+ const session = this.sessions.get(question.receiver.sessionId);
54700
+ if (!session || question.receiver.node !== this.node || this.liveReceivers([session.id]).length === 0) return false;
54701
+ this.deliverQuestion(session.id, { team: null, mission: question.missionId, lead: question.lead ? `${question.lead} on ${question.node}` : null, question: question.question, questionId: question.questionId ?? null }, question.leadSessionId);
54556
54702
  return true;
54557
54703
  }
54558
- tellGiver(known, sessionId) {
54559
- const missionId = known.mission;
54560
- const remote = known.giver ? remoteGiverOf(known.giver) : null;
54561
- const self = this.node;
54562
- if (remote && remote.node !== self) {
54563
- const question = { missionId, node: self, lead: known.lead, leadSessionId: sessionId, question: known.question, ...known.questionId && { questionId: known.questionId }, giver: remote, team: known.team };
54564
- void (async () => {
54565
- for (const { relay } of this.options.ports.missionRelays()) if (await relay.ask(question)) return;
54566
- this.options.context.output.warn(`no plugin carried the question of ${missionId} to ${known.giver}`);
54567
- })().catch((error62) => this.options.context.output.warn(`could not tell the giver of ${missionId}: ${error62 instanceof Error ? error62.message : String(error62)}`));
54568
- return;
54704
+ tellReceivers(receivers, open2, leadSessionId) {
54705
+ for (const key2 of receivers) {
54706
+ const remote = remoteReceiverOf(key2);
54707
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, leadSessionId);
54569
54708
  }
54570
- const giver = known.giver ? this.sessions.get(remote ? remote.sessionId : known.giver) : void 0;
54571
- if (!giver) return;
54572
- this.deliverQuestion(giver.id, known, sessionId);
54709
+ for (const session of this.liveReceivers(receivers)) this.deliverQuestion(session.id, open2, leadSessionId);
54573
54710
  }
54574
- deliverQuestion(giverId, known, sessionId) {
54711
+ askRemote(receiver, open2, leadSessionId) {
54712
+ const question = { missionId: open2.mission, node: this.node, lead: open2.lead, leadSessionId, question: open2.question, ...open2.questionId && { questionId: open2.questionId }, receiver, team: open2.team };
54713
+ void (async () => {
54714
+ for (const { relay } of this.options.ports.missionRelays()) if (await relay.ask(question)) return;
54715
+ this.options.context.output.warn(`no plugin carried the question of ${open2.mission} to ${receiverRef(receiver)}`);
54716
+ })().catch((error62) => this.options.context.output.warn(`could not tell ${receiverRef(receiver)} the question of ${open2.mission}: ${error62 instanceof Error ? error62.message : String(error62)}`));
54717
+ }
54718
+ deliverQuestion(receiverId, known, sessionId) {
54575
54719
  const missionId = known.mission;
54576
54720
  const question = known.question;
54577
54721
  const questionId = known.questionId;
54578
- const key2 = `${giverId}:${missionId}:${questionId ?? ""}`;
54722
+ const key2 = `${receiverId}:${missionId}:${questionId ?? ""}`;
54579
54723
  this.questionsInFlight.add(key2);
54580
54724
  void this.submit({
54581
- sessionId: giverId,
54725
+ sessionId: receiverId,
54582
54726
  message: {
54583
54727
  messageId: randomUUID4(),
54584
54728
  role: "ROLE_USER",
@@ -54593,7 +54737,7 @@ Answer with missionSay on ${missionId}.`)],
54593
54737
  }
54594
54738
  }).then((task) => {
54595
54739
  this.questionDeliveries.set(task.id, { mission: known.mission, questionId });
54596
- }).catch((error62) => this.options.context.output.warn(`could not tell the giver of ${missionId}: ${error62 instanceof Error ? error62.message : String(error62)}`)).finally(() => this.questionsInFlight.delete(key2));
54740
+ }).catch((error62) => this.options.context.output.warn(`could not tell ${receiverId} the question of ${missionId}: ${error62 instanceof Error ? error62.message : String(error62)}`)).finally(() => this.questionsInFlight.delete(key2));
54597
54741
  }
54598
54742
  async missionActivity(missionId, limit) {
54599
54743
  const sessionId = this.missions.get(missionId)?.sessionId;
@@ -54811,8 +54955,8 @@ Answer with missionSay on ${missionId}.`)],
54811
54955
  }
54812
54956
  }
54813
54957
  if (signal?.aborted) return;
54814
- await new Promise((resolve25) => {
54815
- wake = resolve25;
54958
+ await new Promise((resolve26) => {
54959
+ wake = resolve26;
54816
54960
  });
54817
54961
  wake = null;
54818
54962
  }
@@ -54888,9 +55032,13 @@ Answer with missionSay on ${missionId}.`)],
54888
55032
  }
54889
55033
  missionRoots(readerSessionId) {
54890
55034
  const outside = this.sessions.get(readerSessionId)?.external !== void 0;
54891
- const missionRoots = this.missions.all().filter((stored) => stored.sessionId !== null && (outside || stored.giver === readerSessionId) && (this.sessions.has(stored.sessionId) || this.remoteSessions.has(stored.sessionId))).map((stored) => this.rootOf(stored.sessionId));
55035
+ const missionRoots = this.missions.all().filter((stored) => stored.sessionId !== null && (outside || this.receivesFor(readerSessionId, stored.receivers)) && (this.sessions.has(stored.sessionId) || this.remoteSessions.has(stored.sessionId))).map((stored) => this.rootOf(stored.sessionId));
54892
55036
  return new Set(missionRoots);
54893
55037
  }
55038
+ receivesFor(sessionId, receivers) {
55039
+ const session = this.sessions.get(sessionId);
55040
+ return session !== void 0 && receivers.some((key2) => this.receives(session, key2));
55041
+ }
54894
55042
  reachRoots(readerSessionId) {
54895
55043
  const outside = this.sessions.get(readerSessionId)?.external !== void 0;
54896
55044
  const attachedRoots = outside ? [...this.sessions.values()].filter((one) => one.external?.attached === true).map((one) => this.rootOf(one.id)) : [];
@@ -56457,7 +56605,7 @@ var StdioClientTransport = class {
56457
56605
  if (this._process) {
56458
56606
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
56459
56607
  }
56460
- return new Promise((resolve25, reject) => {
56608
+ return new Promise((resolve26, reject) => {
56461
56609
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
56462
56610
  // merge default env with server env because mcp server needs some env vars
56463
56611
  env: {
@@ -56474,7 +56622,7 @@ var StdioClientTransport = class {
56474
56622
  this.onerror?.(error62);
56475
56623
  });
56476
56624
  this._process.on("spawn", () => {
56477
- resolve25();
56625
+ resolve26();
56478
56626
  });
56479
56627
  this._process.on("close", (_code) => {
56480
56628
  this._process = void 0;
@@ -56539,22 +56687,22 @@ var StdioClientTransport = class {
56539
56687
  if (this._process) {
56540
56688
  const processToClose = this._process;
56541
56689
  this._process = void 0;
56542
- const closePromise = new Promise((resolve25) => {
56690
+ const closePromise = new Promise((resolve26) => {
56543
56691
  processToClose.once("close", () => {
56544
- resolve25();
56692
+ resolve26();
56545
56693
  });
56546
56694
  });
56547
56695
  try {
56548
56696
  processToClose.stdin?.end();
56549
56697
  } catch {
56550
56698
  }
56551
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
56699
+ await Promise.race([closePromise, new Promise((resolve26) => setTimeout(resolve26, 2e3).unref())]);
56552
56700
  if (processToClose.exitCode === null) {
56553
56701
  try {
56554
56702
  processToClose.kill("SIGTERM");
56555
56703
  } catch {
56556
56704
  }
56557
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
56705
+ await Promise.race([closePromise, new Promise((resolve26) => setTimeout(resolve26, 2e3).unref())]);
56558
56706
  }
56559
56707
  if (processToClose.exitCode === null) {
56560
56708
  try {
@@ -56566,15 +56714,15 @@ var StdioClientTransport = class {
56566
56714
  this._readBuffer.clear();
56567
56715
  }
56568
56716
  send(message) {
56569
- return new Promise((resolve25) => {
56717
+ return new Promise((resolve26) => {
56570
56718
  if (!this._process?.stdin) {
56571
56719
  throw new Error("Not connected");
56572
56720
  }
56573
56721
  const json4 = serializeMessage(message);
56574
56722
  if (this._process.stdin.write(json4)) {
56575
- resolve25();
56723
+ resolve26();
56576
56724
  } else {
56577
- this._process.stdin.once("drain", resolve25);
56725
+ this._process.stdin.once("drain", resolve26);
56578
56726
  }
56579
56727
  });
56580
56728
  }
@@ -56667,12 +56815,12 @@ var StdioServerTransport = class {
56667
56815
  this.onclose?.();
56668
56816
  }
56669
56817
  send(message) {
56670
- return new Promise((resolve25) => {
56818
+ return new Promise((resolve26) => {
56671
56819
  const json4 = serializeMessage(message);
56672
56820
  if (this._stdout.write(json4)) {
56673
- resolve25();
56821
+ resolve26();
56674
56822
  } else {
56675
- this._stdout.once("drain", resolve25);
56823
+ this._stdout.once("drain", resolve26);
56676
56824
  }
56677
56825
  });
56678
56826
  }
@@ -56781,6 +56929,7 @@ function agentTools(options) {
56781
56929
  };
56782
56930
  const send = (message, wait) => node2.send(message, wait);
56783
56931
  const a2a = () => node2.a2a();
56932
+ const own2 = async () => options.attached ? await options.attached() : options.sessionId;
56784
56933
  return [
56785
56934
  defineMcpTool({
56786
56935
  name: "agentList",
@@ -56824,7 +56973,7 @@ function agentTools(options) {
56824
56973
  description: "List the sessions of your team's work: the lead and every agent it or another member started. Each has its agent, its session id (a jobId for agentMessage), its role (lead or member), its status and its harness.",
56825
56974
  inputSchema: {},
56826
56975
  run: async () => {
56827
- if (!options.sessionId) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
56976
+ if (!await own2()) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
56828
56977
  return await node2.peers();
56829
56978
  }
56830
56979
  }),
@@ -56904,13 +57053,13 @@ function agentTools(options) {
56904
57053
  }),
56905
57054
  defineMcpTool({
56906
57055
  name: "missionList",
56907
- description: "List every mission and its state (open, active, waiting, paused, done or failed): the missions of this node and of the other nodes it knows, such as the nodes a lan plugin finds. Each has its id, its team, its node, its state, its open questions (questions: each lead question that waits on its giver, with its id and text; answer one with missionReply) and, when it ended, its result.",
57056
+ description: "List every mission and its state (open, active, waiting, paused, done or failed): the missions of this node and of the other nodes it knows, such as the nodes a lan plugin finds. Each has its id, its team, its node, its state, its open questions (questions: each lead question that waits on a decision, with its id and text; answer one with missionReply), its decision receivers and, when it ended, its result.",
56908
57057
  inputSchema: {},
56909
57058
  run: async () => node2.missions()
56910
57059
  }),
56911
57060
  defineMcpTool({
56912
57061
  name: "missionShow",
56913
- description: "Show one mission by its id: its brief, its state, its open questions (questions: each lead question that waits on its giver, with its id and text; answer one with missionReply), its session, its result and the history of its states.",
57062
+ description: "Show one mission by its id: its brief, its state, its open questions (questions: each lead question that waits on a decision, with its id and text; answer one with missionReply), its decision receivers, its session, its result and the history of its states.",
56914
57063
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
56915
57064
  run: async (params) => {
56916
57065
  const found = (await node2.missions()).filter((record3) => record3.missionId === params.mission);
@@ -56962,7 +57111,7 @@ function agentTools(options) {
56962
57111
  }),
56963
57112
  defineMcpTool({
56964
57113
  name: "missionReply",
56965
- description: "Answer a question of a mission, by its id: the question its waiting lead ended its turn with, or a question its lead asked with missionAsk, by the question id. The answer reaches the lead and closes that question. With no question id, the reply answers the question of a waiting mission, or else the oldest open question.",
57114
+ description: "Answer a question of a mission, by its id: the question its waiting lead ended its turn with, or a question its lead asked with missionAsk, by the question id. The answer reaches the lead and closes that question for every decision receiver, and the node tells the other receivers that it is answered. With no question id, the reply answers the question of a waiting mission, or else the oldest open question.",
56966
57115
  inputSchema: {
56967
57116
  mission: MissionIdSchema.describe("The mission id."),
56968
57117
  text: external_exports.string().min(1).describe("The answer, in full: the lead has none of your context."),
@@ -56996,13 +57145,22 @@ function agentTools(options) {
56996
57145
  }),
56997
57146
  defineMcpTool({
56998
57147
  name: "missionJoin",
56999
- description: "Become the giver of an active, waiting or paused mission of this node, by its id: the questions of its lead then come to this session, and you answer them with missionSay. A mission that waits tells you its question at once.",
57148
+ description: "Join an active, waiting or paused mission, by its id, as one of its decision receivers: any session can join, also a Claude Code session that the orbital mod attached. Each question of its lead goes to every live receiver, and the questions that are open now come to you at once. Answer a question with missionReply and its question id, or the question of a waiting mission with missionSay: the first answer from any receiver closes it for all. An attached session stays a receiver after it or the node restarts, so join once.",
57000
57149
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
57001
57150
  run: async (params) => {
57002
- if (!options.sessionId) throw new Error("missionJoin needs your own session: only a session of the node becomes the giver of a mission.");
57151
+ if (!await own2()) throw new Error("missionJoin needs your own session: run it in a session of the node, or in a session that the orbital mod attached.");
57003
57152
  return node2.joinMission(params.mission);
57004
57153
  }
57005
57154
  }),
57155
+ defineMcpTool({
57156
+ name: "missionLeave",
57157
+ description: "Leave the decision receivers of a mission, by its id: the questions of its lead stop coming to this session. The other receivers still get them.",
57158
+ inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
57159
+ run: async (params) => {
57160
+ if (!await own2()) throw new Error("missionLeave needs your own session: only a decision receiver leaves a mission.");
57161
+ return node2.leaveMission(params.mission);
57162
+ }
57163
+ }),
57006
57164
  defineMcpTool({
57007
57165
  name: "missionNew",
57008
57166
  description: "Write a new mission for a team of this project: missions/<id>/MISSION.md in the folder that holds the files of orbital, with the team in its frontmatter. One id names one mission. It is open until missionStart starts it.",
@@ -57022,7 +57180,7 @@ function agentTools(options) {
57022
57180
  description: "Read the mission of your session: its id, its team, its brief with the intent, the end state and the limits, its state (open, active, waiting, paused, done or failed), and its history.",
57023
57181
  inputSchema: {},
57024
57182
  run: async () => {
57025
- if (!options.sessionId) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
57183
+ if (!await own2()) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
57026
57184
  return await node2.mission();
57027
57185
  }
57028
57186
  }),
@@ -57034,7 +57192,7 @@ function agentTools(options) {
57034
57192
  state: external_exports.enum(["done", "failed"]).optional().describe("done when the end state is reached, failed when it cannot be. Default: done.")
57035
57193
  },
57036
57194
  run: async (params) => {
57037
- if (!options.sessionId) throw new Error("missionHandoff needs your own session: only the lead session of a mission hands it off.");
57195
+ if (!await own2()) throw new Error("missionHandoff needs your own session: only the lead session of a mission hands it off.");
57038
57196
  return await node2.handoff({ result: params.result, state: params.state ?? "done" });
57039
57197
  }
57040
57198
  }),
@@ -57047,18 +57205,18 @@ function agentTools(options) {
57047
57205
  state: external_exports.string().min(1).optional().describe("The state of the linked thing, such as open, merged or closed.")
57048
57206
  },
57049
57207
  run: async (params) => {
57050
- if (!options.sessionId) throw new Error("missionLink needs your own session: only the lead session of a mission adds a link to it.");
57208
+ if (!await own2()) throw new Error("missionLink needs your own session: only the lead session of a mission adds a link to it.");
57051
57209
  return await node2.link({ label: params.label, url: params.url, state: params.state ?? null });
57052
57210
  }
57053
57211
  }),
57054
57212
  defineMcpTool({
57055
57213
  name: "missionAsk",
57056
- description: "Ask the giver of the mission of your session a question, as its lead, at any moment, also while your workers run. The node keeps each question open with the mission until the giver answers it, shows it to the person as waiting on them, and pushes it to the giver at once. The answer comes back to you as a message that names the question. Use it for every decision you need from the giver: a question only in your reply text reaches no one. Keep working on what does not depend on the answer.",
57214
+ description: "Ask the decision receivers of the mission of your session a question, as its lead, at any moment, also while your workers run. The node keeps each question open with the mission until a receiver answers it, shows it to the person as waiting on them, and pushes it at once to every live receiver. The answer comes back to you as a message that names the question. Use it for every decision you need: a question only in your reply text reaches no one. Keep working on what does not depend on the answer.",
57057
57215
  inputSchema: {
57058
- question: external_exports.string().min(1).describe("The question, in full, with the options you see and what each one means: the giver has none of your context.")
57216
+ question: external_exports.string().min(1).describe("The question, in full, with the options you see and what each one means: the receivers have none of your context.")
57059
57217
  },
57060
57218
  run: async (params) => {
57061
- if (!options.sessionId) throw new Error("missionAsk needs your own session: only the lead session of a mission asks its giver.");
57219
+ if (!await own2()) throw new Error("missionAsk needs your own session: only the lead session of a mission asks its decision receivers.");
57062
57220
  return await node2.ask({ question: params.question });
57063
57221
  }
57064
57222
  })
@@ -57620,8 +57778,8 @@ function relaySender(plugin, by) {
57620
57778
  function pluginSender(plugin) {
57621
57779
  return { channel: plugin, peer: plugin, from: { kind: "node", name: plugin } };
57622
57780
  }
57623
- function remoteSessionSender(plugin, giver) {
57624
- return { channel: plugin, peer: giver.node, from: { kind: "session", name: giver.sessionId, ade: giver.node } };
57781
+ function remoteSessionSender(plugin, session) {
57782
+ return { channel: plugin, peer: session.node, from: { kind: "session", name: session.sessionId, ade: session.node } };
57625
57783
  }
57626
57784
  function missionTarget(node2, team) {
57627
57785
  return team ? { kind: "team", name: team, ade: node2 } : { kind: "node", name: node2 };
@@ -57760,10 +57918,10 @@ function a2aHandlerFor(host, reader, confined = false, network = null) {
57760
57918
  };
57761
57919
  }
57762
57920
  function readBody(request) {
57763
- return new Promise((resolve25, reject) => {
57921
+ return new Promise((resolve26, reject) => {
57764
57922
  const chunks = [];
57765
57923
  request.on("data", (chunk) => chunks.push(chunk));
57766
- request.on("end", () => resolve25(Buffer.concat(chunks).toString("utf8")));
57924
+ request.on("end", () => resolve26(Buffer.concat(chunks).toString("utf8")));
57767
57925
  request.on("error", reject);
57768
57926
  });
57769
57927
  }
@@ -57827,7 +57985,7 @@ async function serveHostApi(request, response, url2, api) {
57827
57985
  if (request.method === "POST" && url2.pathname === "/orbital/host/missions/ask") {
57828
57986
  const asked2 = MissionQuestionSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
57829
57987
  if (!asked2.success) return json3(response, 400, { error: asked2.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ") });
57830
- return json3(response, 200, { delivered: await api.askGiver(asked2.data) });
57988
+ return json3(response, 200, { delivered: await api.askReceiver(asked2.data) });
57831
57989
  }
57832
57990
  if (request.method === "POST" && url2.pathname === "/orbital/host/settled") {
57833
57991
  const { orFollowing } = JSON.parse(await readBody(request) || "{}");
@@ -57946,11 +58104,16 @@ async function startNodeServer(options) {
57946
58104
  const by = caller.bound ? `session:${caller.bound}` : "cli";
57947
58105
  return json3(response, 200, control[1] === "pause" ? await options.host.pauseMission(by, asked2.data.missionId) : await options.host.resumeMission(by, asked2.data.missionId));
57948
58106
  }
57949
- if (request.method === "POST" && url2.pathname === "/orbital/missions/join") {
57950
- if (!caller.bound) return json3(response, 403, { error: "Only an attached session joins a mission as its giver: present its session token." });
58107
+ const membership = /^\/orbital\/missions\/(join|leave)$/.exec(url2.pathname);
58108
+ if (request.method === "POST" && membership) {
57951
58109
  const asked2 = MissionJoinSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
57952
58110
  if (!asked2.success) return json3(response, 400, { error: asked2.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ") });
57953
- return json3(response, 200, await options.host.joinMission(caller.bound, asked2.data.missionId));
58111
+ const session = caller.bound ?? caller.reader;
58112
+ const holder = caller.presented === caller.token;
58113
+ if (asked2.data.externalId && !holder) return json3(response, 403, { error: "Only a holder of the node secret names an external id: a session joins as itself." });
58114
+ if (!session && !asked2.data.externalId) return json3(response, 403, { error: `Only a session ${membership[1]}s the decision receivers of a mission: present its session token, or name the external id of an attached session.` });
58115
+ const receiver = asked2.data.externalId ? { externalId: asked2.data.externalId } : { sessionId: session };
58116
+ return json3(response, 200, membership[1] === "join" ? await options.host.joinMission(receiver, asked2.data.missionId) : await options.host.leaveMission(receiver, asked2.data.missionId));
57954
58117
  }
57955
58118
  if (request.method === "POST" && url2.pathname === "/orbital/missions/new") {
57956
58119
  if (caller.presented !== caller.token && !caller.bound) return json3(response, 403, { error: "Only a holder of the node secret or a session of the node writes a mission here." });
@@ -57988,7 +58151,7 @@ async function startNodeServer(options) {
57988
58151
  const named2 = asked2.data.missionId ?? own2?.missionId ?? null;
57989
58152
  if (!named2) return json3(response, 400, { error: "Name the mission by its id." });
57990
58153
  if (own2 && own2.missionId !== named2) return json3(response, 403, { error: `The session ${session} works on the mission ${own2.missionId}, not on ${named2}.` });
57991
- if (!session && caller.presented !== caller.token) return json3(response, 403, { error: "Only the lead session or a holder of the node secret asks the giver of a mission." });
58154
+ if (!session && caller.presented !== caller.token) return json3(response, 403, { error: "Only the lead session or a holder of the node secret asks the decision receivers of a mission." });
57992
58155
  return json3(response, 200, await options.host.askMission({ mission: named2, question: asked2.data.question, sessionId: session }));
57993
58156
  }
57994
58157
  const activity = /^\/orbital\/missions\/([^/]+)\/activity$/.exec(url2.pathname);
@@ -58029,6 +58192,20 @@ async function startNodeServer(options) {
58029
58192
  return json3(response, 502, { error: `The panel ${plugin2}/${id} did not answer: ${error62 instanceof Error ? error62.message : String(error62)}` });
58030
58193
  }
58031
58194
  }
58195
+ const commandPath = /^\/orbital\/plugins\/([^/]+)\/commands\/([^/]+)$/.exec(url2.pathname);
58196
+ if (request.method === "POST" && commandPath) {
58197
+ if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret runs a command of a plugin." });
58198
+ const [name, command] = [decodeURIComponent(commandPath[1]), decodeURIComponent(commandPath[2])];
58199
+ const plugin2 = options.ports.plugins.find((one) => one.name === name);
58200
+ if (!plugin2?.command) return json3(response, 404, { error: `No plugin ${name} with commands runs on this node.` });
58201
+ const asked2 = PluginCommandRequestSchema.safeParse(await readBody(request).then((raw) => JSON.parse(raw)).catch(() => null));
58202
+ if (!asked2.success || asked2.data.name !== command || asked2.data.terminal !== null) return json3(response, 400, { error: `The request does not run the command ${command}: it names another command or asks for a terminal.` });
58203
+ try {
58204
+ return json3(response, 200, await plugin2.command(asked2.data));
58205
+ } catch (error62) {
58206
+ return json3(response, 502, { error: `The command ${name} ${command} failed: ${error62 instanceof Error ? error62.message : String(error62)}` });
58207
+ }
58208
+ }
58032
58209
  if (url2.pathname === "/orbital/telemetry/snapshot" || url2.pathname === "/orbital/telemetry/events") {
58033
58210
  if (request.method === "GET" && url2.pathname === "/orbital/telemetry/snapshot" && (presented === token || bound)) return json3(response, 200, await options.host.snapshot());
58034
58211
  if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret reads the telemetry stream of the node." });
@@ -58188,7 +58365,7 @@ async function startNodeServer(options) {
58188
58365
  const result2 = await dispatchA2a(await readBody(request), a2aHandlerFor(options.host, reader, bound !== null, httpSender(request.socket.remoteAddress)));
58189
58366
  if (result2.kind === "json") return json3(response, 200, result2.body);
58190
58367
  response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
58191
- const closed = new Promise((resolve25) => response.on("close", resolve25));
58368
+ const closed = new Promise((resolve26) => response.on("close", resolve26));
58192
58369
  const iterator = result2.events[Symbol.asyncIterator]();
58193
58370
  for (; ; ) {
58194
58371
  const next = await Promise.race([iterator.next(), closed.then(() => null)]);
@@ -58201,9 +58378,9 @@ async function startNodeServer(options) {
58201
58378
  }
58202
58379
  response.end();
58203
58380
  }
58204
- await new Promise((resolve25, reject) => {
58381
+ await new Promise((resolve26, reject) => {
58205
58382
  server.once("error", reject);
58206
- server.listen(options.port, address, () => resolve25());
58383
+ server.listen(options.port, address, () => resolve26());
58207
58384
  });
58208
58385
  const info = { name: options.name, workspaceDir: options.workspaceDir, url: nodeUrl(), token, pid: process.pid, startedAt: new Date(options.now()).toISOString(), restart: restartOf(process.env[NODE_RESTART_ENV]), code: pinnedCodeKey() };
58209
58386
  mkdirSync17(nodeDir(options.home, options.name), { recursive: true, mode: 448 });
@@ -58214,7 +58391,7 @@ async function startNodeServer(options) {
58214
58391
  close: async () => {
58215
58392
  if (readNodeInfo(options.home, options.name)?.pid === process.pid) rmSync9(nodeInfoPath(options.home, options.name), { force: true });
58216
58393
  server.closeAllConnections();
58217
- await new Promise((resolve25) => server.close(() => resolve25()));
58394
+ await new Promise((resolve26) => server.close(() => resolve26()));
58218
58395
  }
58219
58396
  };
58220
58397
  }
@@ -59220,7 +59397,7 @@ function hostApiOf(host, plugin, catalog, channels, models) {
59220
59397
  startMission: (start) => host.startMission(plugin, start),
59221
59398
  missions: () => host.missionList(),
59222
59399
  joinMission: async (request) => {
59223
- await host.receive(networkReceive(remoteSessionSender(plugin, request.giver), missionTarget(host.node, host.missionTeam(request.missionId)), `join the mission ${request.missionId} as its giver`, relayClaim(request)));
59400
+ await host.receive(networkReceive(remoteSessionSender(plugin, request.receiver), missionTarget(host.node, host.missionTeam(request.missionId)), `join the mission ${request.missionId} as a decision receiver`, relayClaim(request)));
59224
59401
  return host.joinRemote(request);
59225
59402
  },
59226
59403
  replyMission: async (request) => {
@@ -59235,9 +59412,9 @@ function hostApiOf(host, plugin, catalog, channels, models) {
59235
59412
  await relayed(request, `resume the mission ${request.missionId}`);
59236
59413
  return host.resumeMission(request.by, request.missionId, false);
59237
59414
  },
59238
- askGiver: async (question) => {
59239
- await host.receive(networkReceive(remoteSessionSender(plugin, { node: question.node, sessionId: question.leadSessionId }), { kind: "session", name: question.giver.sessionId, ade: host.node }, question.question, relayClaim({ team: question.team, agent: question.lead })));
59240
- return host.askGiver(question);
59415
+ askReceiver: async (question) => {
59416
+ await host.receive(networkReceive(remoteSessionSender(plugin, { node: question.node, sessionId: question.leadSessionId }), { kind: "session", name: question.receiver.sessionId, ade: host.node }, question.question, relayClaim({ team: question.team, agent: question.lead })));
59417
+ return host.askReceiver(question);
59241
59418
  },
59242
59419
  sayMission: async (request) => {
59243
59420
  await relayed(request, request.text);
@@ -59451,8 +59628,8 @@ var ChannelHub = class {
59451
59628
  if (state && isTerminal(state)) return;
59452
59629
  }
59453
59630
  if (signal?.aborted) return;
59454
- await new Promise((resolve25) => {
59455
- wake = resolve25;
59631
+ await new Promise((resolve26) => {
59632
+ wake = resolve26;
59456
59633
  });
59457
59634
  wake = null;
59458
59635
  }
@@ -59626,8 +59803,8 @@ Answer yes or no in this thread.`)], metadata: { [ORBITAL_META.channel]: binding
59626
59803
  thread: null
59627
59804
  });
59628
59805
  const threadKey = `${binding.channel}|${binding.id}|${sent.thread ?? sent.ref}`;
59629
- const answer2 = await new Promise((resolve25, reject) => {
59630
- this.asks.set(threadKey, { resolve: resolve25, question, channel: binding.channel, since: new Date(this.options.context.now()).toISOString() });
59806
+ const answer2 = await new Promise((resolve26, reject) => {
59807
+ this.asks.set(threadKey, { resolve: resolve26, question, channel: binding.channel, since: new Date(this.options.context.now()).toISOString() });
59631
59808
  signal?.addEventListener("abort", () => {
59632
59809
  this.asks.delete(threadKey);
59633
59810
  reject(new Error("The question was withdrawn."));
@@ -60301,8 +60478,9 @@ var panel = {
60301
60478
  var panelCommands = [panel];
60302
60479
 
60303
60480
  // src/core/cli/plugin.ts
60304
- import { readFileSync as readFileSync28 } from "node:fs";
60305
- import { basename as basename9, resolve as resolve14 } from "node:path";
60481
+ var import_yaml8 = __toESM(require_dist(), 1);
60482
+ import { readFileSync as readFileSync29 } from "node:fs";
60483
+ import { basename as basename9, resolve as resolve15 } from "node:path";
60306
60484
 
60307
60485
  // src/sdk/conformance.ts
60308
60486
  import { existsSync as existsSync29, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync2, readFileSync as readFileSync27, rmSync as rmSync11, writeFileSync as writeFileSync19 } from "node:fs";
@@ -60673,13 +60851,144 @@ async function runConformance(ports, fixtures = {}) {
60673
60851
  return results;
60674
60852
  }
60675
60853
 
60854
+ // src/core/cli/plugin-commands.ts
60855
+ import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
60856
+ import { join as join37, resolve as resolve14 } from "node:path";
60857
+ var INTROSPECTION_WORDS = ["describe", "schema", "help"];
60858
+ function commandsOf(dir) {
60859
+ const descriptor = join37(dir, PLUGIN_PACKAGE_FILE);
60860
+ if (!existsSync30(descriptor)) return [];
60861
+ try {
60862
+ return PluginPackageSchema.parse(JSON.parse(readFileSync28(descriptor, "utf8"))).commands;
60863
+ } catch {
60864
+ return [];
60865
+ }
60866
+ }
60867
+ function declaredPluginCommands(workspaceDir, env) {
60868
+ try {
60869
+ const source = definitionSource(workspaceDir, env);
60870
+ const config2 = source ? workspaceConfig(workspaceDir, env) : null;
60871
+ if (!source || !config2) return [];
60872
+ return Object.entries(config2.plugins).filter(([, entry]) => entry.enabled && entry.source).flatMap(([plugin, entry]) => {
60873
+ const from = pluginSource(plugin, entry);
60874
+ const commands = from.startsWith("exec:") ? [] : commandsOf(pluginDir(plugin, from, { harnessRoot: source.root, home: orbitalHome(env) }));
60875
+ return commands.length > 0 ? [{ plugin, commands }] : [];
60876
+ });
60877
+ } catch {
60878
+ return [];
60879
+ }
60880
+ }
60881
+ function workspaceOf(input2, fallback = ".") {
60882
+ return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : fallback);
60883
+ }
60884
+ async function startWorkspacePlugin(context, workspaceDir, name, terminal) {
60885
+ const root = definitionSource(workspaceDir, context.env)?.root ?? null;
60886
+ const config2 = workspaceConfig(workspaceDir, context.env);
60887
+ const entry = config2?.plugins[name];
60888
+ if (!config2 || !entry) throw new CliError({ code: "not-found", message: `No plugin ${name} is named in ${ORBITAL_FILE} or found in ${PROJECT_PLUGINS_DIR}.`, exitCode: ExitCode.Validation, nextCommand: `${commandPrefix(context.env)} plugin add ${name}@<marketplace>` });
60889
+ const secrets = new PluginSecrets(context.env);
60890
+ return startRemotePlugin({
60891
+ name,
60892
+ source: pluginSource(name, entry),
60893
+ config: entry.config,
60894
+ entryEnv: entry.env,
60895
+ node: { name: `plugin-${name}`, workspaceDir, harness: defaultHarness(context.env), harnesses: [...RUNNER_HARNESSES], once: true, idleExitMs: null, project: null },
60896
+ roots: { harnessRoot: root, home: orbitalHome(context.env) },
60897
+ offline: config2.offline,
60898
+ policy: new PolicyChain([]),
60899
+ env: context.env,
60900
+ log: (level, message) => level === "info" ? context.output.step(message) : context.output.warn(message),
60901
+ secret: async (secret) => secrets.get(name, secret),
60902
+ restarts: 0,
60903
+ ...terminal && { terminal }
60904
+ });
60905
+ }
60906
+ function flagOf(option) {
60907
+ return {
60908
+ name: option.name,
60909
+ type: option.type,
60910
+ summary: option.summary,
60911
+ ...option.required && { required: true },
60912
+ ...option.default !== void 0 && { default: option.default },
60913
+ ...option.values && { enumValues: option.values }
60914
+ };
60915
+ }
60916
+ function requestOf(command, input2, terminal) {
60917
+ const args = Object.fromEntries(command.args.flatMap((arg) => {
60918
+ const value2 = input2.positionals[arg.name];
60919
+ return value2 === void 0 ? [] : [[arg.name, value2]];
60920
+ }));
60921
+ const options = Object.fromEntries(command.options.flatMap((option) => {
60922
+ const value2 = input2.flags[option.name];
60923
+ if (option.type === "boolean") return [[option.name, value2 === true]];
60924
+ return typeof value2 === "string" || typeof value2 === "number" ? [[option.name, value2]] : [];
60925
+ }));
60926
+ return { name: command.name, args, options, terminal };
60927
+ }
60928
+ async function runOnNode(context, workspaceDir, input2, plugin, request) {
60929
+ const configured = workspaceConfig(workspaceDir, context.env)?.node ?? null;
60930
+ const name = chooseNode({ ...typeof input2.flags.node === "string" && { flag: input2.flags.node }, env: context.env, configured, workspaceDir }).name;
60931
+ const node2 = await liveNode(orbitalHome(context.env), name, context.fetch);
60932
+ if (!node2) throw new CliError({ code: "not-found", message: `The command ${plugin} ${request.name} needs the node ${name}, and it does not run.`, exitCode: ExitCode.Validation, nextCommand: `${commandPrefix(context.env)} up` });
60933
+ const response = await context.fetch(`${node2.url}/orbital/plugins/${encodeURIComponent(plugin)}/commands/${encodeURIComponent(request.name)}`, {
60934
+ method: "POST",
60935
+ headers: { authorization: `Bearer ${node2.token}`, "content-type": "application/json" },
60936
+ body: JSON.stringify(request)
60937
+ });
60938
+ const body2 = await response.json();
60939
+ if (!response.ok) throw new CliError({ code: response.status === 404 ? "not-found" : "transport", message: body2.error ?? `The node answered ${response.status}.`, exitCode: response.status === 404 ? ExitCode.Validation : ExitCode.Error });
60940
+ return PluginCommandResultSchema.parse(body2);
60941
+ }
60942
+ async function runInPlugin(context, workspaceDir, input2, plugin, command, terminal) {
60943
+ if (command.interactive && context.nonInteractive) {
60944
+ throw new CliError({ code: "invalid-input", message: `The command ${plugin} ${command.name} is interactive: it needs a terminal.`, exitCode: ExitCode.Validation, hint: "Run it in a terminal, without --non-interactive." });
60945
+ }
60946
+ const given = command.interactive ? { input: terminal.input, output: context.outputMode === "text" ? terminal.output : terminal.error, error: terminal.error } : void 0;
60947
+ const started = await startWorkspacePlugin(context, workspaceDir, plugin, given);
60948
+ try {
60949
+ return await started.command(requestOf(command, input2, given ? { tty: context.isTty } : null));
60950
+ } catch (error62) {
60951
+ if (error62 instanceof CliError) throw error62;
60952
+ const message = error62 instanceof Error ? error62.message : String(error62);
60953
+ throw new CliError({ code: "internal", message: error62 instanceof RpcError ? `The command ${plugin} ${command.name} failed: ${message}` : `The command ${plugin} ${command.name} got no answer: ${message}`, exitCode: ExitCode.Error });
60954
+ } finally {
60955
+ await started.close?.();
60956
+ }
60957
+ }
60958
+ function specOf(workspaceDir, plugin, command, terminal) {
60959
+ return {
60960
+ path: [plugin, command.name],
60961
+ summary: command.summary,
60962
+ group: CommandGroup.Packs,
60963
+ credential: "none",
60964
+ flags: [
60965
+ { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` },
60966
+ ...command.node ? [{ name: "node", type: "string", identifier: true, summary: "The node that runs the plugin. Default: the node of this workspace." }] : [],
60967
+ ...command.options.map(flagOf)
60968
+ ],
60969
+ positionals: command.args.map((arg) => ({ name: arg.name, type: "string", summary: arg.summary, ...arg.required && { required: true }, ...arg.variadic && { variadic: true } })),
60970
+ outputShape: { kind: "object", ref: "PluginCommandResult", fields: [{ name: "data", type: "object", summary: `What the command of the plugin ${plugin} answered.` }] },
60971
+ exitCodes: [0, 1, 2, 3, 5],
60972
+ routes: [],
60973
+ examples: [{ command: `orbital ${plugin} ${command.name}`, why: command.summary }],
60974
+ run: async (input2, context) => {
60975
+ const workspace = workspaceOf(input2, workspaceDir);
60976
+ const result2 = command.node ? await runOnNode(context, workspace, input2, plugin, requestOf(command, input2, null)) : await runInPlugin(context, workspace, input2, plugin, command, terminal);
60977
+ if (result2.text !== null) context.output.line(result2.text);
60978
+ if (result2.text === null && Object.keys(result2.data).length > 0) context.output.line(JSON.stringify(result2.data, null, 2));
60979
+ return { data: result2.data, ...result2.exitCode !== 0 && { exitCode: result2.exitCode } };
60980
+ }
60981
+ };
60982
+ }
60983
+ function pluginCommandSpecs(options) {
60984
+ const terminal = options.terminal ?? { input: 0, output: 1, error: 2 };
60985
+ return declaredPluginCommands(options.workspaceDir, options.env).filter(({ plugin }) => !options.reserved.includes(plugin)).flatMap(({ plugin, commands }) => commands.map((command) => specOf(options.workspaceDir, plugin, command, terminal)));
60986
+ }
60987
+
60676
60988
  // src/core/cli/plugin.ts
60677
60989
  function workspaceFlag() {
60678
60990
  return { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` };
60679
60991
  }
60680
- function workspaceOf(input2) {
60681
- return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
60682
- }
60683
60992
  function sourceOrRefuse(context, input2) {
60684
60993
  const workspaceDir = workspaceOf(input2);
60685
60994
  const source = definitionSource(workspaceDir, context.env);
@@ -60703,6 +61012,25 @@ async function manifestOf(context, workspaceDir, name, source, root, config2) {
60703
61012
  await plugin.close?.();
60704
61013
  return plugin.manifest;
60705
61014
  }
61015
+ function configOf(input2, known) {
61016
+ const file2 = typeof input2.flags["config-file"] === "string" ? resolve15(input2.flags["config-file"]) : null;
61017
+ const fromFile = file2 ? (0, import_yaml8.parse)(readFileSync29(file2, "utf8")) : {};
61018
+ if (fromFile === null || typeof fromFile !== "object" || Array.isArray(fromFile)) throw new CliError({ code: "invalid-input", message: `${file2} holds no object of config keys.`, exitCode: ExitCode.Validation, hint: "Write the config as YAML or JSON keys, such as channel: general." });
61019
+ const pairs = Array.isArray(input2.flags.config) ? input2.flags.config.map(String) : typeof input2.flags.config === "string" ? [input2.flags.config] : [];
61020
+ const fromPairs = Object.fromEntries(pairs.map((pair) => {
61021
+ const at = pair.indexOf("=");
61022
+ if (at <= 0) throw new CliError({ code: "invalid-input", message: `--config ${pair} is not key=value.`, exitCode: ExitCode.Validation, hint: "Write one key and its value, such as --config channel=general." });
61023
+ return [pair.slice(0, at), (0, import_yaml8.parse)(pair.slice(at + 1)) ?? ""];
61024
+ }));
61025
+ return { ...known, ...fromFile, ...fromPairs };
61026
+ }
61027
+ async function checkedManifest(context, workspaceDir, name, source, root, config2) {
61028
+ try {
61029
+ return await manifestOf(context, workspaceDir, name, source, root, config2);
61030
+ } catch (error62) {
61031
+ throw new CliError({ code: "invalid-input", message: `${name} did not start with the config ${JSON.stringify(config2)}, so no config names it: ${error62 instanceof Error ? error62.message : String(error62)}`, exitCode: ExitCode.Validation, hint: "Give the plugin its config with --config key=value or --config-file <file>." });
61032
+ }
61033
+ }
60706
61034
  function permissionLines(manifest) {
60707
61035
  const { network, secrets, models } = manifest.permissions;
60708
61036
  return [
@@ -60718,7 +61046,9 @@ var add = () => ({
60718
61046
  flags: [
60719
61047
  workspaceFlag(),
60720
61048
  { name: "install-only", type: "boolean", summary: `Install the plugin on this machine only: do not name it in ${ORBITAL_FILE}.` },
60721
- { name: "user", type: "boolean", summary: `Add the plugin for you, in your user config, not in ${ORBITAL_FILE}: every project of yours loads it, after the plugins its ${ORBITAL_FILE} names.` }
61049
+ { name: "user", type: "boolean", summary: `Add the plugin for you, in your user config, not in ${ORBITAL_FILE}: every project of yours loads it, after the plugins its ${ORBITAL_FILE} names.` },
61050
+ { name: "config", type: "string", repeatable: true, summary: `One config key of the plugin, as key=value; the value reads as YAML, so 3 is a number and true a boolean. The plugin starts with it before ${ORBITAL_FILE} names it.` },
61051
+ { name: "config-file", type: "path", summary: "A YAML or JSON file of config keys for the plugin. A --config key wins over the same key of the file." }
60722
61052
  ],
60723
61053
  positionals: [
60724
61054
  { name: "plugin", type: "string", required: true, summary: "<name>@<marketplace>, npm:<package>, git:<url>, or a folder such as ./plugins/mine." }
@@ -60739,34 +61069,39 @@ var add = () => ({
60739
61069
  examples: [
60740
61070
  { command: "orbital plugin add slack@community", why: "Install the slack plugin from the community marketplace, and use it in this workspace." },
60741
61071
  { command: "orbital plugin add ./plugins/mine", why: `Use a plugin you write in this project: ${ORBITAL_FILE} names it by its path.` },
60742
- { command: "orbital plugin add npm:@acme/orbital-discord --install-only", why: "Install a plugin from npm on this machine, for a workspace to name later." }
61072
+ { command: "orbital plugin add npm:@acme/orbital-discord --install-only", why: "Install a plugin from npm on this machine, for a workspace to name later." },
61073
+ { command: "orbital plugin add ./plugins/notify --config channel=general --config retries=3", why: "Add a plugin that checks its config when it starts." }
60743
61074
  ],
60744
61075
  run: async (input2, context) => {
60745
61076
  const home = orbitalHome(context.env);
60746
61077
  const given = String(input2.positionals.plugin);
60747
61078
  const ref = resolvePluginRef(home, given);
60748
61079
  const copied = ref.kind !== "folder" || given.includes("@");
60749
- const installed = copied ? installPlugin(home, ref) : null;
60750
- if (installed) context.output.step(`\u2713 installed ${ref.name} in ${installed}`);
60751
- if (input2.flags["install-only"] === true) return { data: { name: ref.name, source: given, installed, enabled: false, manifest: null } };
61080
+ const install2 = copied ? installPlugin(home, ref) : null;
61081
+ const installed = install2?.dir ?? null;
61082
+ const name = install2?.name ?? ref.name ?? given;
61083
+ if (install2) context.output.step(`\u2713 installed ${name} in ${install2.dir}`);
61084
+ if (input2.flags["install-only"] === true) return { data: { name, source: given, installed, enabled: false, manifest: null } };
60752
61085
  if (input2.flags.user === true) {
60753
61086
  const source2 = ref.kind === "folder" && !given.includes("@") ? ref.dir : ref.kind === "npm" ? `npm:${ref.pkg}` : ref.kind === "git" ? `git:${ref.url}` : given;
60754
- const manifest2 = await manifestOf(context, workspaceOf(input2), ref.name, source2, null, {});
60755
- context.output.step(`${ref.name} ${manifest2.version} reaches:`);
61087
+ const config3 = configOf(input2, userPlugins(context.env)[name]?.config ?? {});
61088
+ const manifest2 = await checkedManifest(context, workspaceOf(input2), name, source2, null, config3);
61089
+ context.output.step(`${name} ${manifest2.version} reaches:`);
60756
61090
  for (const line of permissionLines(manifest2)) context.output.step(line);
60757
- setUserPlugin(context.env, ref.name, { source: source2, enabled: true, config: {}, env: [] });
60758
- context.output.step(`\u2713 your user config names ${ref.name}: source ${source2}`);
60759
- return { data: { name: ref.name, source: source2, installed, enabled: true, manifest: manifest2 } };
61091
+ setUserPlugin(context.env, name, { source: source2, enabled: true, config: config3, env: [] });
61092
+ context.output.step(`\u2713 your user config names ${name}: source ${source2}`);
61093
+ return { data: { name, source: source2, installed, enabled: true, manifest: manifest2 } };
60760
61094
  }
60761
61095
  const definition = sourceOrRefuse(context, input2);
60762
61096
  const root = definition.root;
60763
61097
  const source = sourceOf(ref, given, root);
60764
- const manifest = await manifestOf(context, workspaceOf(input2), ref.name, source, root, {});
60765
- context.output.step(`${ref.name} ${manifest.version} reaches:`);
61098
+ const config2 = configOf(input2, sourceConfig(definition).plugins[name]?.config ?? {});
61099
+ const manifest = await checkedManifest(context, workspaceOf(input2), name, source, root, config2);
61100
+ context.output.step(`${name} ${manifest.version} reaches:`);
60766
61101
  for (const line of permissionLines(manifest)) context.output.step(line);
60767
- setPluginEntry(definition.configFile, ref.name, { source, enabled: true, config: sourceConfig(definition).plugins[ref.name]?.config ?? {}, env: [] });
60768
- context.output.step(`\u2713 ${basename9(definition.configFile)} names ${ref.name}: source ${source}`);
60769
- return { data: { name: ref.name, source, installed, enabled: true, manifest } };
61102
+ setPluginEntry(definition.configFile, name, { source, enabled: true, config: config2, env: [] });
61103
+ context.output.step(`\u2713 ${basename9(definition.configFile)} names ${name}: source ${source}`);
61104
+ return { data: { name, source, installed, enabled: true, manifest } };
60770
61105
  }
60771
61106
  });
60772
61107
  var list3 = () => ({
@@ -60842,57 +61177,6 @@ var remove = () => ({
60842
61177
  return { data: { name, unnamed, uninstalled } };
60843
61178
  }
60844
61179
  });
60845
- var runCommand = () => ({
60846
- path: ["plugin", "run"],
60847
- summary: "Run one of a plugin's own commands, such as a login, and print what it answers.",
60848
- credential: "none",
60849
- flags: [workspaceFlag()],
60850
- positionals: [
60851
- { name: "plugin", type: "string", required: true, identifier: true, summary: `A plugin ${ORBITAL_FILE} names.` },
60852
- { name: "command", type: "string", required: true, summary: "The plugin's command." },
60853
- { name: "args", type: "string", summary: "Arguments for the command, after `--`.", passthrough: true }
60854
- ],
60855
- outputShape: { kind: "object", ref: "PluginCommand", fields: [{ name: "output", type: "string", summary: "What the command answered." }] },
60856
- exitCodes: [0, 1, 3],
60857
- routes: [],
60858
- examples: [{ command: "orbital plugin run slack login", why: "Run the slack plugin's login command." }],
60859
- run: async (input2, context) => {
60860
- const name = String(input2.positionals.plugin);
60861
- const plugin = await startNamed(context, input2, name);
60862
- try {
60863
- const command = String(input2.positionals.command);
60864
- if (!plugin.manifest.commands.some((one) => one.name === command)) {
60865
- throw new CliError({ code: "not-found", message: `The plugin ${name} has no command ${command}.`, exitCode: ExitCode.Validation, hint: plugin.manifest.commands.length > 0 ? `Its commands: ${plugin.manifest.commands.map((one) => one.name).join(", ")}.` : "It has no commands." });
60866
- }
60867
- const output2 = await plugin.command(command, [...input2.passthrough]);
60868
- context.output.line(output2);
60869
- return { data: { output: output2 } };
60870
- } finally {
60871
- await plugin.close?.();
60872
- }
60873
- }
60874
- });
60875
- async function startNamed(context, input2, name) {
60876
- const root = definitionSource(workspaceOf(input2), context.env)?.root ?? null;
60877
- const config2 = workspaceConfig(workspaceOf(input2), context.env);
60878
- const entry = config2?.plugins[name];
60879
- if (!config2 || !entry) throw new CliError({ code: "not-found", message: `No plugin ${name} is named in ${ORBITAL_FILE} or found in ${PROJECT_PLUGINS_DIR}.`, exitCode: ExitCode.Validation, nextCommand: `${commandPrefix(context.env)} plugin add ${name}@<marketplace>` });
60880
- const secrets = new PluginSecrets(context.env);
60881
- return startRemotePlugin({
60882
- name,
60883
- source: pluginSource(name, entry),
60884
- config: entry.config,
60885
- entryEnv: entry.env,
60886
- node: { name: `plugin-${name}`, workspaceDir: workspaceOf(input2), harness: defaultHarness(context.env), harnesses: [...RUNNER_HARNESSES], once: true, idleExitMs: null, project: null },
60887
- roots: { harnessRoot: root, home: orbitalHome(context.env) },
60888
- offline: config2.offline,
60889
- policy: new PolicyChain([]),
60890
- env: context.env,
60891
- log: (level, message) => level === "info" ? context.output.step(message) : context.output.warn(message),
60892
- secret: async (secret) => secrets.get(name, secret),
60893
- restarts: 0
60894
- });
60895
- }
60896
61180
  var check2 = () => ({
60897
61181
  path: ["plugin", "check"],
60898
61182
  summary: "Run the conformance checks of each port a plugin provides, so any orbital host can use it.",
@@ -60918,8 +61202,8 @@ var check2 = () => ({
60918
61202
  ],
60919
61203
  run: async (input2, context) => {
60920
61204
  const name = String(input2.positionals.plugin);
60921
- const fixtures = typeof input2.flags.fixtures === "string" ? JSON.parse(readFileSync28(input2.flags.fixtures, "utf8")) : {};
60922
- const plugin = await startNamed(context, input2, name);
61205
+ const fixtures = typeof input2.flags.fixtures === "string" ? JSON.parse(readFileSync29(input2.flags.fixtures, "utf8")) : {};
61206
+ const plugin = await startWorkspacePlugin(context, workspaceOf(input2), name);
60923
61207
  try {
60924
61208
  const results = await runConformance(plugin.ports, fixtures);
60925
61209
  const mark = { pass: "\u2713", fail: "\u2717", skip: "\u25CB" };
@@ -61047,13 +61331,13 @@ var secretRemove = {
61047
61331
  return { data: { plugin, name } };
61048
61332
  }
61049
61333
  };
61050
- var pluginCommands = () => [add(), list3(), remove(), runCommand(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
61334
+ var pluginCommands = () => [add(), list3(), remove(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
61051
61335
 
61052
61336
  // src/core/cli/plugin-new.ts
61053
- import { existsSync as existsSync30, mkdirSync as mkdirSync22, readdirSync as readdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
61054
- import { dirname as dirname16, join as join37, relative as relative7, resolve as resolve15 } from "node:path";
61337
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readdirSync as readdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
61338
+ import { dirname as dirname16, join as join38, relative as relative7, resolve as resolve16 } from "node:path";
61055
61339
  var PLUGIN_PORTS = ["channel", "store", "workspace", "harness", "panel", "policy", "telemetry", "actions", "workflows", "command"];
61056
- var SDK_VERSION = true ? `^${"0.0.3"}` : "*";
61340
+ var SDK_VERSION = true ? `^${"0.0.4"}` : "*";
61057
61341
  var ESBUILD_VERSION = "^0.25.0";
61058
61342
  var TYPES_NODE_VERSION = "^20.19.43";
61059
61343
  var HARNESS_ID = /^[a-z][a-z0-9-]{0,31}$/;
@@ -61432,7 +61716,7 @@ export function plugin(context: PluginContext): PluginImplementation {
61432
61716
  switch (event.kind) {
61433
61717
  case 'mission.state': {
61434
61718
  const known = graph.missions.find((mission) => mission.missionId === event.mission);
61435
- const state = { missionId: event.mission, team: event.team, state: event.to, sessionId: event.sessionId, lead: event.lead, giver: event.giver, givenBy: known?.givenBy ?? null, question: event.question, result: event.result, updatedAt: event.at };
61719
+ const state = { missionId: event.mission, team: event.team, state: event.to, sessionId: event.sessionId, lead: event.lead, receivers: event.receivers, givenBy: known?.givenBy ?? null, question: event.question, result: event.result, updatedAt: event.at };
61436
61720
  graph.missions = [...graph.missions.filter((mission) => mission.missionId !== event.mission), state];
61437
61721
  return;
61438
61722
  }
@@ -61573,7 +61857,7 @@ export function plugin(_context: PluginContext): PluginImplementation {
61573
61857
  case "command":
61574
61858
  return {
61575
61859
  summary: `An orbital plugin with a command: ${name}.`,
61576
- plugin: `${header("one command. A person runs it with `orbital plugin run " + name + " hello [name]`.")}import type { PluginContext, PluginImplementation } from '@kaiba-cloud/orbital-sdk';
61860
+ plugin: `${header("one command. A person runs it with `orbital " + name + " hello [name]`.\n * orbital-plugin.json declares the same command, so orbital lists it without starting the plugin.")}import { defineCommand, type PluginContext, type PluginImplementation } from '@kaiba-cloud/orbital-sdk';
61577
61861
 
61578
61862
  export const NAME = '${name}';
61579
61863
 
@@ -61583,17 +61867,19 @@ export function plugin(context: PluginContext): PluginImplementation {
61583
61867
  version: '0.1.0',
61584
61868
  permissions: { network: [], secrets: [], models: [] },
61585
61869
  ports: {},
61586
- commands: {
61587
- hello: {
61588
- summary: 'Greet the name given, or the node.',
61589
- run: async (args) => \`Hello, \${args.join(' ') || context.init.node.name}.\`,
61590
- },
61591
- },
61870
+ commands: [
61871
+ defineCommand({
61872
+ name: 'hello',
61873
+ summary: 'Greet the name given, or the workspace.',
61874
+ args: [{ name: 'name', summary: 'The name to greet.' }],
61875
+ run: async ({ args }) => \`Hello, \${args.name ?? context.init.node.workspaceDir}.\`,
61876
+ }),
61877
+ ],
61592
61878
  };
61593
61879
  }
61594
61880
  `,
61595
61881
  fixtures: {},
61596
- test: ` assert.equal(await implementation.commands?.hello?.run(['Ada']), 'Hello, Ada.');`
61882
+ test: ` assert.equal(await implementation.commands?.[0]?.run({ args: { name: 'Ada' }, options: {}, terminal: null }), 'Hello, Ada.');`
61597
61883
  };
61598
61884
  }
61599
61885
  }
@@ -61627,10 +61913,11 @@ ${template.test ? `${template.test}
61627
61913
  ` : ""}});
61628
61914
  `;
61629
61915
  }
61916
+ var COMMAND_DECLARATION = { name: "hello", summary: "Greet the name given, or the workspace.", args: [{ name: "name", summary: "The name to greet." }] };
61630
61917
  function pluginFiles(name, port) {
61631
61918
  const template = templateOf(name, port);
61632
61919
  const files = {
61633
- "orbital-plugin.json": `${JSON.stringify({ name, command: ["node", "dist/plugin.js"], ...port === "harness" && { harnesses: [{ id: name, folder: `.${name}`, instructions: `${name.toUpperCase()}.md`, agents: "agents", skills: "skills" }] } }, null, 2)}
61920
+ "orbital-plugin.json": `${JSON.stringify({ name, command: ["node", "dist/plugin.js"], ...port === "harness" && { harnesses: [{ id: name, folder: `.${name}`, instructions: `${name.toUpperCase()}.md`, agents: "agents", skills: "skills" }] }, ...port === "command" && { commands: [COMMAND_DECLARATION] } }, null, 2)}
61634
61921
  `,
61635
61922
  "package.json": `${JSON.stringify({
61636
61923
  name,
@@ -61688,14 +61975,14 @@ var pluginNewCommand = {
61688
61975
  if (port === "harness" && !HARNESS_ID.test(name)) {
61689
61976
  throw new CliError({ code: "invalid-input", message: `"${name}" is not a harness id.`, exitCode: ExitCode.Validation, hint: "A harness plugin's name starts with a letter and has at most 32 characters." });
61690
61977
  }
61691
- const dir = resolve15(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
61692
- if (existsSync30(dir) && readdirSync20(dir).length > 0) {
61978
+ const dir = resolve16(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
61979
+ if (existsSync31(dir) && readdirSync20(dir).length > 0) {
61693
61980
  throw new CliError({ code: "conflict", message: `${dir} is not empty.`, exitCode: ExitCode.Validation, hint: "Name a new folder with --dir." });
61694
61981
  }
61695
61982
  const files = pluginFiles(name, port);
61696
61983
  for (const [path3, content] of Object.entries(files)) {
61697
- mkdirSync22(dirname16(join37(dir, path3)), { recursive: true });
61698
- writeFileSync20(join37(dir, path3), content);
61984
+ mkdirSync22(dirname16(join38(dir, path3)), { recursive: true });
61985
+ writeFileSync20(join38(dir, path3), content);
61699
61986
  }
61700
61987
  const program = commandPrefix(context.env);
61701
61988
  const shown = relative7(process.cwd(), dir) || ".";
@@ -61838,8 +62125,8 @@ If the browser does not return here in ${CALLBACK_WAIT_MS / 6e4} minutes, paste
61838
62125
  ${url2}
61839
62126
  Then paste the code, or the full URL the browser ends on.`);
61840
62127
  },
61841
- onManualCodeInput: () => new Promise((resolve25) => {
61842
- timer = setTimeout(() => resolve25(""), opened2 ? CALLBACK_WAIT_MS : 0);
62128
+ onManualCodeInput: () => new Promise((resolve26) => {
62129
+ timer = setTimeout(() => resolve26(""), opened2 ? CALLBACK_WAIT_MS : 0);
61843
62130
  timer.unref();
61844
62131
  }),
61845
62132
  onPrompt: async (prompt) => (await context.ask(`${prompt.message} `))?.trim() ?? "",
@@ -61932,12 +62219,12 @@ function createProviderCommands(signIn = loginAnthropic) {
61932
62219
  }
61933
62220
 
61934
62221
  // src/core/cli/run.ts
61935
- import { resolve as resolve17 } from "node:path";
62222
+ import { resolve as resolve18 } from "node:path";
61936
62223
 
61937
62224
  // src/core/local/local-run.ts
61938
62225
  import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
61939
62226
  import { mkdirSync as mkdirSync23, rmSync as rmSync12 } from "node:fs";
61940
- import { join as join38, resolve as resolve16 } from "node:path";
62227
+ import { join as join39, resolve as resolve17 } from "node:path";
61941
62228
 
61942
62229
  // src/core/local/interrupt.ts
61943
62230
  var EXIT_CODE = { SIGINT: 130, SIGTERM: 143 };
@@ -61960,7 +62247,7 @@ function cleanupOnInterrupt(cleanup, options = {}) {
61960
62247
  async function runLocal(context, request, plugins) {
61961
62248
  const { workspaceDir } = request;
61962
62249
  const home = orbitalHome(context.env);
61963
- const sessionsDir = join38(home, "sessions");
62250
+ const sessionsDir = join39(home, "sessions");
61964
62251
  mkdirSync23(sessionsDir, { recursive: true, mode: 448 });
61965
62252
  const resumed = request.resumeSessionId ? readSessionRecord(sessionsDir, request.resumeSessionId) : null;
61966
62253
  if (request.resumeSessionId && !resumed) {
@@ -61990,9 +62277,9 @@ async function runLocal(context, request, plugins) {
61990
62277
  let channels = null;
61991
62278
  const asks = new PersonDesk(async (question, name) => await channels?.askPerson(`${name} asks: ${question}`, null) ?? null);
61992
62279
  const policy = new PolicyChain(ports.policies(), asks.ask);
61993
- channels = new ChannelHub({ context, ports, policy, self: nodeName, stateDir: join38(home, "channels", nodeName), plugins: () => definition.config.plugins });
62280
+ channels = new ChannelHub({ context, ports, policy, self: nodeName, stateDir: join39(home, "channels", nodeName), plugins: () => definition.config.plugins });
61994
62281
  const models = modelGatewayOf(context, policy, remote);
61995
- const actions = nodeActions({ ports: () => ports, policy: () => policy, definition: { workspaceDir: resolve16(workspaceDir), definition }, span: (span2) => host.telemetry.span(span2), now: context.now });
62282
+ const actions = nodeActions({ ports: () => ports, policy: () => policy, definition: { workspaceDir: resolve17(workspaceDir), definition }, span: (span2) => host.telemetry.span(span2), now: context.now });
61996
62283
  const agentName = request.agent ?? resumed?.agent ?? null;
61997
62284
  const target = await resolveTarget(definition, agentName, ports);
61998
62285
  const harness = request.harness ?? resumed?.harness ?? defaultHarness(context.env);
@@ -62053,7 +62340,7 @@ async function runLocal(context, request, plugins) {
62053
62340
  const result2 = chat ? await chatTurns(chat, host, { host, asks, panels: () => panelsOf(ownPanels, ports.plugins), sessionId, agent: target.local?.name ?? target.ref.name, harness, model: target.local?.model ?? null, workspaceDir, node: nodeName }) : request.interactive ? await interactiveTurn(context, request, { host, ports, policy, definition, target, sessionId, harness, harnessArgs, credentials, home, sessionsDir, node: { url: `${node2.info.url}/a2a`, token: node2.sessionToken(sessionId) } }) : await headlessTurn(context, request, host, { sessionId, agent: target.local?.name ?? target.ref.name, harness });
62054
62341
  if (result2 !== null) context.output.line(result2);
62055
62342
  const cli = commandPrefix(context.env);
62056
- context.output.step(`\u2713 transcript: ${join38(sessionsDir, sessionId, "conversation.jsonl")}`);
62343
+ context.output.step(`\u2713 transcript: ${join39(sessionsDir, sessionId, "conversation.jsonl")}`);
62057
62344
  context.output.step(`\u2713 resume with: ${cli} run --resume ${sessionId}`);
62058
62345
  return {
62059
62346
  sessionId,
@@ -62061,7 +62348,7 @@ async function runLocal(context, request, plugins) {
62061
62348
  done: true,
62062
62349
  harness,
62063
62350
  result: result2,
62064
- transcript: join38(sessionsDir, sessionId, "conversation.jsonl"),
62351
+ transcript: join39(sessionsDir, sessionId, "conversation.jsonl"),
62065
62352
  interactive: request.interactive
62066
62353
  };
62067
62354
  } finally {
@@ -62121,7 +62408,7 @@ async function interactiveTurn(context, request, run2) {
62121
62408
  const info = sessionInfoOf(run2.sessionId, run2.target, { node: run2.host.node, agent: run2.target.local?.name ?? run2.target.ref.name, parentSessionId: null, workspaceDir: request.workspaceDir, harness: run2.harness, team: null, mission: null, definition: request.useDefinition ? run2.definition : null });
62122
62409
  const opened2 = await openSession(run2.ports, run2.policy, info);
62123
62410
  const record2 = run2.host.attach(info, opened2.sinks, { agent: info.agent, harness: run2.harness, workspaceDir: request.workspaceDir });
62124
- const runDir = join38(run2.home, "runs", randomUUID8());
62411
+ const runDir = join39(run2.home, "runs", randomUUID8());
62125
62412
  mkdirSync23(runDir, { recursive: true, mode: 448 });
62126
62413
  let usage = [];
62127
62414
  try {
@@ -62228,7 +62515,7 @@ function createRunCommand(options) {
62228
62515
  const terminal = log ? await options.terminal() : null;
62229
62516
  const data = await runLocal(log ? { ...context, output: log.output(context.output) } : context, {
62230
62517
  agent,
62231
- workspaceDir: resolve17(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
62518
+ workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
62232
62519
  prompt,
62233
62520
  useDefinition: true,
62234
62521
  follow: false,
@@ -62244,24 +62531,24 @@ function createRunCommand(options) {
62244
62531
  }
62245
62532
 
62246
62533
  // src/core/cli/session.ts
62247
- import { resolve as resolve18 } from "node:path";
62534
+ import { resolve as resolve19 } from "node:path";
62248
62535
 
62249
62536
  // src/core/local/attachments.ts
62250
- import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync21 } from "node:fs";
62251
- import { join as join39 } from "node:path";
62537
+ import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync21 } from "node:fs";
62538
+ import { join as join40 } from "node:path";
62252
62539
  function fileOf(home, externalId) {
62253
- return join39(home, "attachments", `${encodeURIComponent(externalId)}.json`);
62540
+ return join40(home, "attachments", `${encodeURIComponent(externalId)}.json`);
62254
62541
  }
62255
62542
  function writeAttachment(home, externalId, node2) {
62256
- mkdirSync24(join39(home, "attachments"), { recursive: true, mode: 448 });
62543
+ mkdirSync24(join40(home, "attachments"), { recursive: true, mode: 448 });
62257
62544
  writeFileSync21(fileOf(home, externalId), `${JSON.stringify({ node: node2 })}
62258
62545
  `, { mode: 384 });
62259
62546
  }
62260
62547
  function readAttachment(home, externalId) {
62261
62548
  const file2 = fileOf(home, externalId);
62262
- if (!existsSync32(file2)) return null;
62549
+ if (!existsSync33(file2)) return null;
62263
62550
  try {
62264
- const parsed = JSON.parse(readFileSync29(file2, "utf8"));
62551
+ const parsed = JSON.parse(readFileSync30(file2, "utf8"));
62265
62552
  return typeof parsed === "object" && parsed !== null && "node" in parsed && typeof parsed.node === "string" ? parsed.node : null;
62266
62553
  } catch {
62267
62554
  return null;
@@ -62329,7 +62616,7 @@ async function joinMission(context, attached, missionId) {
62329
62616
  const body2 = await response.json();
62330
62617
  if (!response.ok) {
62331
62618
  const error62 = typeof body2 === "object" && body2 !== null && "error" in body2 ? String(body2.error) : `The node answered ${response.status}.`;
62332
- throw new CliError({ code: "invalid-input", message: `The node refused to make this session the giver of ${missionId}: ${error62}`, exitCode: ExitCode.Validation });
62619
+ throw new CliError({ code: "invalid-input", message: `The node refused to make this session a decision receiver of ${missionId}: ${error62}`, exitCode: ExitCode.Validation });
62333
62620
  }
62334
62621
  return MissionRecordSchema.parse(body2);
62335
62622
  }
@@ -62409,7 +62696,7 @@ var sessionAttachCommand = {
62409
62696
  { name: "node", type: "string", summary: "The node to join. Default: ORBITAL_NODE, then the node key of the definition, then the node of the folder." },
62410
62697
  { name: "team", type: "string", summary: "The team the session joins. Sessions of one team are peers." },
62411
62698
  { name: "external-id", type: "string", summary: "The id the outside program gives the session. With it, the command attaches again after a node restarts." },
62412
- { name: "mission", type: "string", summary: "A mission id: join the node that runs it, from any folder, as the giver of the mission, so its questions come to this session." }
62699
+ { name: "mission", type: "string", summary: "A mission id: join the node that runs it, from any folder, as a decision receiver of the mission, so its questions come to this session." }
62413
62700
  ],
62414
62701
  positionals: [],
62415
62702
  outputShape: {
@@ -62426,7 +62713,7 @@ var sessionAttachCommand = {
62426
62713
  examples: [
62427
62714
  { command: "orbital session attach --harness claude-code --output ndjson", why: "Join the node of this folder, and print each delivery as a record." },
62428
62715
  { command: "orbital session attach --node shared --team review --output ndjson", why: "Join the node shared, as a member of the team review." },
62429
- { command: "orbital session attach --mission build/pr-7 --output ndjson", why: "Join the node that runs the mission build/pr-7 as its giver, and print each question of its lead as a record." }
62716
+ { command: "orbital session attach --mission build/pr-7 --output ndjson", why: "Join the node that runs the mission build/pr-7 as a decision receiver, and print each question of its lead as a record." }
62430
62717
  ],
62431
62718
  run: async (input2, context) => {
62432
62719
  const harness = typeof input2.flags.harness === "string" ? input2.flags.harness : "external";
@@ -62438,7 +62725,7 @@ var sessionAttachCommand = {
62438
62725
  const found = await keepAttached(context, {
62439
62726
  harness,
62440
62727
  agent: typeof input2.flags.agent === "string" ? input2.flags.agent : harness,
62441
- workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
62728
+ workspaceDir: resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
62442
62729
  ...typeof input2.flags.node === "string" && { node: input2.flags.node },
62443
62730
  team: typeof input2.flags.team === "string" ? input2.flags.team : null,
62444
62731
  externalId: typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : null,
@@ -62451,6 +62738,8 @@ var sessionAttachCommand = {
62451
62738
  delivery: (event) => {
62452
62739
  const delivery = DeliverySchema.safeParse(event);
62453
62740
  if (delivery.success) context.output.record({ type: StreamRecordType.Event, ...delivery.data });
62741
+ const answered = AnsweredSchema.safeParse(event);
62742
+ if (answered.success) context.output.record({ type: StreamRecordType.Event, ...answered.data });
62454
62743
  },
62455
62744
  detached: (reason) => {
62456
62745
  context.output.record({ type: StreamRecordType.Event, kind: "detached", reason });
@@ -62538,8 +62827,8 @@ var remove3 = {
62538
62827
  var workspaceCommands = [list5, show, remove3];
62539
62828
 
62540
62829
  // src/core/cli/up.ts
62541
- import { existsSync as existsSync33 } from "node:fs";
62542
- import { resolve as resolve19 } from "node:path";
62830
+ import { existsSync as existsSync34 } from "node:fs";
62831
+ import { resolve as resolve20 } from "node:path";
62543
62832
 
62544
62833
  // src/core/cli/node-control.ts
62545
62834
  var STOP_WAIT_MS = 3e4;
@@ -62613,7 +62902,7 @@ function groupOf(state) {
62613
62902
  }
62614
62903
  }
62615
62904
  function missionGroup(mission) {
62616
- return waitsOnGiver(mission) ? "waiting" : groupOf(mission.state);
62905
+ return waitsOnDecision(mission) ? "waiting" : groupOf(mission.state);
62617
62906
  }
62618
62907
  var SESSIONS_PER_MEMBER = 3;
62619
62908
  var RUNNING_SHOWN = 5;
@@ -63092,7 +63381,7 @@ async function liveView(context, nodes, signal, terminal, home) {
63092
63381
  for (const line of events(viewed, states.get(one.node), now)) context.output.line(line);
63093
63382
  states.set(one.node, now);
63094
63383
  }
63095
- await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve25) => signal.addEventListener("abort", () => resolve25(), { once: true }))]);
63384
+ await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve26) => signal.addEventListener("abort", () => resolve26(), { once: true }))]);
63096
63385
  }
63097
63386
  context.output.line(closing);
63098
63387
  }
@@ -63101,7 +63390,7 @@ async function liveView(context, nodes, signal, terminal, home) {
63101
63390
  var WORKSPACE_FLAG = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
63102
63391
  var TEAMS = { name: "teams", type: "string", variadic: true, summary: "Only the nodes of these teams. Default: every node of the project." };
63103
63392
  async function planOf(context, input2) {
63104
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63393
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63105
63394
  const { definition, refusals } = await loadDefinitionPartial(workspaceDir, context.env);
63106
63395
  for (const refusal of refusals) context.output.warn(refusal.message);
63107
63396
  const project = projectNodeOf(definition, workspaceDir);
@@ -63248,7 +63537,7 @@ var statusCommand = {
63248
63537
  for (const { charters: _charters, named: _named, ...planned } of nodes) {
63249
63538
  const log = nodeLogPath(home, planned.node);
63250
63539
  const supervisor = readSupervisor(home, planned.node);
63251
- if (existsSync33(log)) logs.push({ node: planned.node, log, lastCrash: supervisor?.crashes.at(-1) ?? null, lastExit: supervisor?.lastExit ?? null, gaveUp: supervisor?.gaveUp ?? null });
63540
+ if (existsSync34(log)) logs.push({ node: planned.node, log, lastCrash: supervisor?.crashes.at(-1) ?? null, lastExit: supervisor?.lastExit ?? null, gaveUp: supervisor?.gaveUp ?? null });
63252
63541
  const running = await liveNode(home, planned.node, context.fetch);
63253
63542
  const served = running ? await servedTeams(context, running) : null;
63254
63543
  for (const note of served ?? []) if (note.problems.length > 0 || note.retiring) notes.push({ node: planned.node, ...note });
@@ -63281,7 +63570,7 @@ var downCommand = {
63281
63570
  const { workspaceDir, nodes } = await planOf(context, input2);
63282
63571
  const home = orbitalHome(context.env);
63283
63572
  const rows = [];
63284
- const separate2 = (await liveNodes(home, context.fetch)).filter((info) => resolve19(info.workspaceDir) === resolve19(workspaceDir) && nodes.some((planned) => new RegExp(`^${planned.node}-[0-9]+$`).test(info.name)));
63573
+ const separate2 = (await liveNodes(home, context.fetch)).filter((info) => resolve20(info.workspaceDir) === resolve20(workspaceDir) && nodes.some((planned) => new RegExp(`^${planned.node}-[0-9]+$`).test(info.name)));
63285
63574
  const targets = [...nodes.map(({ charters: _charters, named: _named, ...planned }) => planned), ...separate2.map((info) => ({ node: info.name, teams: nodes.find((planned) => info.name.startsWith(`${planned.node}-`))?.teams ?? [] }))];
63286
63575
  for (const planned of targets) {
63287
63576
  const running = await liveNode(home, planned.node, context.fetch);
@@ -63340,7 +63629,7 @@ var createJoinCommand = (options) => ({
63340
63629
  }
63341
63630
  let charters = [];
63342
63631
  try {
63343
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63632
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63344
63633
  charters = (await loadDefinition(workspaceDir, context.env)).teams;
63345
63634
  } catch {
63346
63635
  charters = [];
@@ -63365,9 +63654,9 @@ function createUpCommands(options) {
63365
63654
  // src/core/cli/service.ts
63366
63655
  import { spawnSync as spawnSync5 } from "node:child_process";
63367
63656
  import { createHash as createHash10 } from "node:crypto";
63368
- import { existsSync as existsSync34, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
63657
+ import { existsSync as existsSync35, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
63369
63658
  import { homedir as homedir8 } from "node:os";
63370
- import { join as join40, resolve as resolve20 } from "node:path";
63659
+ import { join as join41, resolve as resolve21 } from "node:path";
63371
63660
  var exec = (command, args) => {
63372
63661
  const result2 = spawnSync5(command, args, { encoding: "utf8" });
63373
63662
  return { status: result2.status, output: `${result2.stdout ?? ""}${result2.stderr ?? ""}${result2.error?.message ?? ""}` };
@@ -63385,7 +63674,7 @@ function serviceSpec(env, workspaceDir) {
63385
63674
  workspaceDir,
63386
63675
  program: [process.execPath, installedProgram(), ...mountPath(), "up", "--detach", "--workspace", workspaceDir],
63387
63676
  env: Object.fromEntries(KEPT_ENV.flatMap((name) => env[name] ? [[name, env[name]]] : [])),
63388
- log: join40(orbitalHome(env), "logs", `service-${hash2}.log`)
63677
+ log: join41(orbitalHome(env), "logs", `service-${hash2}.log`)
63389
63678
  };
63390
63679
  }
63391
63680
  function xml(text3) {
@@ -63448,7 +63737,7 @@ function userHome(env) {
63448
63737
  return env.HOME ?? homedir8();
63449
63738
  }
63450
63739
  function fileOf2(platform, env, label) {
63451
- return platform === "launchd" ? join40(userHome(env), "Library", "LaunchAgents", `${label}.plist`) : join40(env.XDG_CONFIG_HOME ?? join40(userHome(env), ".config"), "systemd", "user", `${label}.service`);
63740
+ return platform === "launchd" ? join41(userHome(env), "Library", "LaunchAgents", `${label}.plist`) : join41(env.XDG_CONFIG_HOME ?? join41(userHome(env), ".config"), "systemd", "user", `${label}.service`);
63452
63741
  }
63453
63742
  function domain2() {
63454
63743
  return `gui/${process.getuid?.() ?? 0}`;
@@ -63461,13 +63750,13 @@ function serviceState(platform, env, workspaceDir, run2 = exec) {
63461
63750
  const { label } = serviceSpec(env, workspaceDir);
63462
63751
  const file2 = fileOf2(platform, env, label);
63463
63752
  const loaded = platform === "launchd" ? run2("launchctl", ["print", `${domain2()}/${label}`]).status === 0 : run2("systemctl", ["--user", "is-enabled", `${label}.service`]).status === 0;
63464
- return { platform, label, file: file2, installed: existsSync34(file2), loaded };
63753
+ return { platform, label, file: file2, installed: existsSync35(file2), loaded };
63465
63754
  }
63466
63755
  function installService(platform, env, workspaceDir, run2 = exec) {
63467
63756
  const spec = serviceSpec(env, workspaceDir);
63468
63757
  const file2 = fileOf2(platform, env, spec.label);
63469
- mkdirSync25(join40(file2, ".."), { recursive: true });
63470
- mkdirSync25(join40(spec.log, ".."), { recursive: true, mode: 448 });
63758
+ mkdirSync25(join41(file2, ".."), { recursive: true });
63759
+ mkdirSync25(join41(spec.log, ".."), { recursive: true, mode: 448 });
63471
63760
  if (platform === "launchd") {
63472
63761
  writeFileSync22(file2, launchdPlist(spec));
63473
63762
  run2("launchctl", ["bootout", `${domain2()}/${spec.label}`]);
@@ -63489,7 +63778,7 @@ function uninstallService(platform, env, workspaceDir, run2 = exec) {
63489
63778
  return serviceState(platform, env, workspaceDir, run2);
63490
63779
  }
63491
63780
  function workspaceOf2(input2) {
63492
- return resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63781
+ return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
63493
63782
  }
63494
63783
  function platformOrRefuse() {
63495
63784
  const platform = platformOf();
@@ -63564,9 +63853,9 @@ var serviceCommands = [
63564
63853
  ];
63565
63854
 
63566
63855
  // src/core/cli/browser.ts
63567
- import { existsSync as existsSync35, mkdirSync as mkdirSync26, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "node:fs";
63856
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync31, writeFileSync as writeFileSync23 } from "node:fs";
63568
63857
  import { homedir as homedir9 } from "node:os";
63569
- import { dirname as dirname17, join as join41 } from "node:path";
63858
+ import { dirname as dirname17, join as join42 } from "node:path";
63570
63859
 
63571
63860
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.32.2_supports-color@7.2.0_zod@4.6.7/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js
63572
63861
  function normalizeHeaders(headers) {
@@ -65179,7 +65468,7 @@ function isChromeDevtools(entry) {
65179
65468
  return isChromeDevtoolsMcp(command, args);
65180
65469
  }
65181
65470
  function claudeJsonPath(env) {
65182
- return join41(env.CLAUDE_CONFIG_DIR ?? env.HOME ?? homedir9(), ".claude.json");
65471
+ return join42(env.CLAUDE_CONFIG_DIR ?? env.HOME ?? homedir9(), ".claude.json");
65183
65472
  }
65184
65473
  function chromeServersOf(config2) {
65185
65474
  return Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => isChromeDevtools(entry)).map(([name]) => name);
@@ -65191,21 +65480,21 @@ function sharedBrowserEntry() {
65191
65480
  return { type: "stdio", ...sharedBrowserCommand(), env: {} };
65192
65481
  }
65193
65482
  function backupPath(env) {
65194
- return join41(browserDir(orbitalHome(env)), "claude-mcp-backup.json");
65483
+ return join42(browserDir(orbitalHome(env)), "claude-mcp-backup.json");
65195
65484
  }
65196
65485
  function codexBackupPath(env) {
65197
- return join41(browserDir(orbitalHome(env)), "codex-config-backup.json");
65486
+ return join42(browserDir(orbitalHome(env)), "codex-config-backup.json");
65198
65487
  }
65199
65488
  function codexConfigPath(env) {
65200
- return join41(env.CODEX_HOME ?? join41(env.HOME ?? homedir9(), ".codex"), "config.toml");
65489
+ return join42(env.CODEX_HOME ?? join42(env.HOME ?? homedir9(), ".codex"), "config.toml");
65201
65490
  }
65202
65491
  function pointClaudeAtSharedBrowser(env, entry = sharedBrowserEntry()) {
65203
65492
  const file2 = claudeJsonPath(env);
65204
- const config2 = existsSync35(file2) ? JSON.parse(readFileSync30(file2, "utf8")) : {};
65493
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
65205
65494
  const found = chromeServersOf(config2);
65206
65495
  const names = found.length > 0 ? found : [SHARED_BROWSER_SERVER];
65207
65496
  const backup = backupPath(env);
65208
- const previous = existsSync35(backup) ? JSON.parse(readFileSync30(backup, "utf8")) : {};
65497
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
65209
65498
  const saved = { ...Object.fromEntries(names.filter((name) => config2.mcpServers?.[name] !== void 0).map((name) => [name, config2.mcpServers[name]])), ...previous };
65210
65499
  mkdirSync26(browserDir(orbitalHome(env)), { recursive: true, mode: 448 });
65211
65500
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -65218,9 +65507,9 @@ function pointClaudeAtSharedBrowser(env, entry = sharedBrowserEntry()) {
65218
65507
  function revertClaudeBrowser(env) {
65219
65508
  const file2 = claudeJsonPath(env);
65220
65509
  const backup = backupPath(env);
65221
- if (!existsSync35(backup)) return null;
65222
- const saved = JSON.parse(readFileSync30(backup, "utf8"));
65223
- const config2 = existsSync35(file2) ? JSON.parse(readFileSync30(file2, "utf8")) : {};
65510
+ if (!existsSync36(backup)) return null;
65511
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
65512
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
65224
65513
  const shared = Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => Array.isArray(entry.args) && entry.args.join(" ").endsWith("browser connect")).map(([name]) => name);
65225
65514
  const servers = { ...config2.mcpServers };
65226
65515
  for (const name of shared) delete servers[name];
@@ -65231,18 +65520,18 @@ function revertClaudeBrowser(env) {
65231
65520
  }
65232
65521
  function pointCodexAtSharedBrowser(env, shared = sharedBrowserCommand()) {
65233
65522
  const file2 = codexConfigPath(env);
65234
- if (!existsSync35(file2) && !existsSync35(dirname17(file2))) return null;
65235
- const text3 = existsSync35(file2) ? readFileSync30(file2, "utf8") : "";
65523
+ if (!existsSync36(file2) && !existsSync36(dirname17(file2))) return null;
65524
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
65236
65525
  const tables = mcpServerTables(text3);
65237
65526
  const found = tables.filter((table2) => isChromeDevtoolsMcp(table2.command, table2.args)).map((table2) => table2.name);
65238
65527
  const connected = tables.filter((table2) => isSharedBrowserConnect(table2.args)).map((table2) => table2.name);
65239
65528
  const names = found.length > 0 ? found : connected.length > 0 ? [] : [SHARED_BROWSER_SERVER];
65240
65529
  const backup = codexBackupPath(env);
65241
- if (names.length === 0) return { file: file2, servers: connected, backup: existsSync35(backup) ? backup : null };
65530
+ if (names.length === 0) return { file: file2, servers: connected, backup: existsSync36(backup) ? backup : null };
65242
65531
  const block = (name) => stdioServerBlock(name, shared.command, shared.args);
65243
65532
  const replaced = replaceMcpServers(text3, (table2) => names.includes(table2.name) ? block(table2.name) : null);
65244
65533
  const next = names.filter((name) => !replaced.replaced.some((one) => one.name === name)).reduce((current2, name) => appendMcpServer(current2, block(name)), replaced.text);
65245
- const previous = existsSync35(backup) ? JSON.parse(readFileSync30(backup, "utf8")) : {};
65534
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
65246
65535
  const saved = { ...Object.fromEntries(replaced.replaced.map((one) => [one.name, one.original])), ...previous };
65247
65536
  mkdirSync26(browserDir(orbitalHome(env)), { recursive: true, mode: 448 });
65248
65537
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -65254,9 +65543,9 @@ function pointCodexAtSharedBrowser(env, shared = sharedBrowserCommand()) {
65254
65543
  function revertCodexBrowser(env) {
65255
65544
  const file2 = codexConfigPath(env);
65256
65545
  const backup = codexBackupPath(env);
65257
- if (!existsSync35(backup)) return null;
65258
- const saved = JSON.parse(readFileSync30(backup, "utf8"));
65259
- const text3 = existsSync35(file2) ? readFileSync30(file2, "utf8") : "";
65546
+ if (!existsSync36(backup)) return null;
65547
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
65548
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
65260
65549
  const replaced = replaceMcpServers(text3, (table2) => isSharedBrowserConnect(table2.args) ? saved[table2.name] ?? "" : null);
65261
65550
  const next = Object.entries(saved).filter(([name]) => !replaced.replaced.some((one) => one.name === name)).reduce((current2, [, original]) => appendMcpServer(current2, original), replaced.text);
65262
65551
  mkdirSync26(dirname17(file2), { recursive: true });
@@ -65441,12 +65730,12 @@ var browserCommands = [
65441
65730
  ];
65442
65731
 
65443
65732
  // src/core/cli/mission.ts
65444
- import { existsSync as existsSync37 } from "node:fs";
65445
- import { join as join43, relative as relative8, resolve as resolve21 } from "node:path";
65733
+ import { existsSync as existsSync38 } from "node:fs";
65734
+ import { join as join44, relative as relative8, resolve as resolve22 } from "node:path";
65446
65735
 
65447
65736
  // src/core/local/mission-migrate.ts
65448
- import { existsSync as existsSync36, mkdirSync as mkdirSync27, readFileSync as readFileSync31, readdirSync as readdirSync21, renameSync as renameSync8, rmSync as rmSync14, rmdirSync as rmdirSync2, writeFileSync as writeFileSync24 } from "node:fs";
65449
- import { join as join42 } from "node:path";
65737
+ import { existsSync as existsSync37, mkdirSync as mkdirSync27, readFileSync as readFileSync32, readdirSync as readdirSync21, renameSync as renameSync8, rmSync as rmSync14, rmdirSync as rmdirSync2, writeFileSync as writeFileSync24 } from "node:fs";
65738
+ import { join as join43 } from "node:path";
65450
65739
  function withTeam(content, team) {
65451
65740
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n[\s\S]*)?$/);
65452
65741
  if (!match) return content;
@@ -65458,26 +65747,26 @@ ${head.join("\n")}
65458
65747
  ---${match[2] ?? "\n"}`;
65459
65748
  }
65460
65749
  function emptyDir(dir) {
65461
- if (existsSync36(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
65750
+ if (existsSync37(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
65462
65751
  }
65463
65752
  function migrateMissions(root, nodesDir, nodes, dryRun) {
65464
65753
  const moved = [];
65465
65754
  const refused3 = [];
65466
- const teamsDir = join42(root, TEAMS_DIR);
65467
- const target = join42(root, MISSIONS_DIR);
65755
+ const teamsDir = join43(root, TEAMS_DIR);
65756
+ const target = join43(root, MISSIONS_DIR);
65468
65757
  const seen = /* @__PURE__ */ new Map();
65469
- for (const team of existsSync36(teamsDir) ? readdirSync21(teamsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : []) {
65470
- const missionsDir = join42(teamsDir, team.name, MISSIONS_DIR);
65471
- if (!existsSync36(missionsDir)) continue;
65758
+ for (const team of existsSync37(teamsDir) ? readdirSync21(teamsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : []) {
65759
+ const missionsDir = join43(teamsDir, team.name, MISSIONS_DIR);
65760
+ if (!existsSync37(missionsDir)) continue;
65472
65761
  for (const mission of readdirSync21(missionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory())) {
65473
- const from = join42(missionsDir, mission.name);
65474
- const to = join42(target, mission.name);
65762
+ const from = join43(missionsDir, mission.name);
65763
+ const to = join43(target, mission.name);
65475
65764
  const other = seen.get(mission.name);
65476
65765
  if (other) {
65477
65766
  refused3.push(`${from}: the team ${other} has a mission ${mission.name} too. Rename one folder and its mission: key, then run the migration again.`);
65478
65767
  continue;
65479
65768
  }
65480
- if (existsSync36(to)) {
65769
+ if (existsSync37(to)) {
65481
65770
  refused3.push(`${from}: ${to} exists already.`);
65482
65771
  continue;
65483
65772
  }
@@ -65486,22 +65775,22 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
65486
65775
  if (dryRun) continue;
65487
65776
  mkdirSync27(target, { recursive: true });
65488
65777
  renameSync8(from, to);
65489
- const file2 = join42(to, MISSION_FILE);
65490
- if (existsSync36(file2)) writeFileSync24(file2, withTeam(readFileSync31(file2, "utf8"), team.name));
65778
+ const file2 = join43(to, MISSION_FILE);
65779
+ if (existsSync37(file2)) writeFileSync24(file2, withTeam(readFileSync32(file2, "utf8"), team.name));
65491
65780
  }
65492
65781
  if (!dryRun) emptyDir(missionsDir);
65493
65782
  }
65494
65783
  for (const node2 of nodes) {
65495
- const nodeDir2 = join42(nodesDir, node2);
65496
- if (!existsSync36(nodeDir2)) continue;
65784
+ const nodeDir2 = join43(nodesDir, node2);
65785
+ if (!existsSync37(nodeDir2)) continue;
65497
65786
  const files = [];
65498
65787
  for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) {
65499
- if (entry.isFile() && entry.name.endsWith(".json")) files.push({ from: join42(nodeDir2, entry.name), mission: entry.name.slice(0, -".json".length), team: null, backup: false });
65500
- if (entry.isFile() && entry.name.endsWith(".json.migrated")) files.push({ from: join42(nodeDir2, entry.name), mission: entry.name.slice(0, -".json.migrated".length), team: null, backup: true });
65788
+ if (entry.isFile() && entry.name.endsWith(".json")) files.push({ from: join43(nodeDir2, entry.name), mission: entry.name.slice(0, -".json".length), team: null, backup: false });
65789
+ if (entry.isFile() && entry.name.endsWith(".json.migrated")) files.push({ from: join43(nodeDir2, entry.name), mission: entry.name.slice(0, -".json.migrated".length), team: null, backup: true });
65501
65790
  if (!entry.isDirectory()) continue;
65502
- for (const name of readdirSync21(join42(nodeDir2, entry.name))) {
65503
- if (name.endsWith(".json")) files.push({ from: join42(nodeDir2, entry.name, name), mission: name.slice(0, -".json".length), team: entry.name, backup: false });
65504
- if (name.endsWith(".json.migrated")) files.push({ from: join42(nodeDir2, entry.name, name), mission: name.slice(0, -".json.migrated".length), team: entry.name, backup: true });
65791
+ for (const name of readdirSync21(join43(nodeDir2, entry.name))) {
65792
+ if (name.endsWith(".json")) files.push({ from: join43(nodeDir2, entry.name, name), mission: name.slice(0, -".json".length), team: entry.name, backup: false });
65793
+ if (name.endsWith(".json.migrated")) files.push({ from: join43(nodeDir2, entry.name, name), mission: name.slice(0, -".json.migrated".length), team: entry.name, backup: true });
65505
65794
  }
65506
65795
  }
65507
65796
  files.sort((a, b) => Number(a.backup) - Number(b.backup) || Number(a.team !== null) - Number(b.team !== null));
@@ -65513,13 +65802,13 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
65513
65802
  if (!dryRun) rmSync14(file2.from);
65514
65803
  continue;
65515
65804
  }
65516
- if (taken.has(file2.mission) || existsSync36(to)) {
65805
+ if (taken.has(file2.mission) || existsSync37(to)) {
65517
65806
  refused3.push(`${file2.from}: ${to} exists already.`);
65518
65807
  continue;
65519
65808
  }
65520
- const raw = JSON.parse(readFileSync31(file2.from, "utf8"));
65809
+ const raw = JSON.parse(readFileSync32(file2.from, "utf8"));
65521
65810
  const { file: _file2, ...rest } = raw;
65522
- const state = MissionStateFileSchema.safeParse({ question: null, giver: null, lead: null, claim: null, claimedAt: null, ...rest, mission: file2.mission, node: node2, team: rest.team ?? file2.team });
65811
+ const state = MissionStateFileSchema.safeParse({ question: null, lead: null, claim: null, claimedAt: null, ...rest, mission: file2.mission, node: node2, team: rest.team ?? file2.team });
65523
65812
  if (!state.success) {
65524
65813
  refused3.push(`${file2.from}: it is not the state of a mission: ${state.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ")}`);
65525
65814
  continue;
@@ -65531,7 +65820,7 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
65531
65820
  rmSync14(file2.from);
65532
65821
  }
65533
65822
  if (dryRun) continue;
65534
- for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join42(nodeDir2, entry.name));
65823
+ for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join43(nodeDir2, entry.name));
65535
65824
  emptyDir(nodeDir2);
65536
65825
  }
65537
65826
  emptyDir(nodesDir);
@@ -65553,13 +65842,13 @@ var MISSION_FIELDS = {
65553
65842
  { name: "result", type: "string", nullable: true, summary: "The hand-off of the mission, when it ended." },
65554
65843
  { name: "summary", type: "string", nullable: true, summary: "The first sentence of the Intent of the brief." },
65555
65844
  { name: "question", type: "string", nullable: true, summary: "The question a waiting lead ended its turn with." },
65556
- { name: "questions", type: "array", summary: "The open questions the lead asked its giver with missionAsk, each with an id, the question and the time: the mission waits on its giver until each is answered." },
65845
+ { name: "questions", type: "array", summary: "The open questions the lead asked with missionAsk, each with an id, the question and the time: the mission waits on a decision until each is answered." },
65557
65846
  { name: "links", type: "array", summary: "The links of the mission, each with a label, a URL and a state: the issue of its source, and the links its lead adds, such as its pull request." },
65558
65847
  { name: "history", type: "array", summary: "Each change of state, with its time and who made it." }
65559
65848
  ]
65560
65849
  };
65561
65850
  function workspaceOf3(input2) {
65562
- return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
65851
+ return resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
65563
65852
  }
65564
65853
  function refused2(message, hint) {
65565
65854
  return new CliError({ code: "invalid-input", message, exitCode: ExitCode.Validation, ...hint && { hint } });
@@ -65641,7 +65930,7 @@ function missionNote(context, record2) {
65641
65930
  }
65642
65931
  }
65643
65932
  function shownState(record2) {
65644
- return waitsOnGiver(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
65933
+ return waitsOnDecision(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
65645
65934
  }
65646
65935
  function missionLines2(context, records) {
65647
65936
  const widths = ["mission", "node", "state"].map((key2) => Math.max(key2.length, ...records.map((record2) => (key2 === "mission" ? record2.missionId : key2 === "node" ? record2.node : shownState(record2)).length)));
@@ -65693,7 +65982,7 @@ var missionNew = {
65693
65982
  if (!id.success) throw refused2(`"${String(input2.positionals.id)}" is not a mission id: use lowercase letters, digits and hyphens.`);
65694
65983
  const file2 = missionFile(teamRoot(team), id.data);
65695
65984
  if (definition.missions.some((one) => one.id === id.data && one.team !== team.name)) throw refused2(`The mission ${id.data} belongs to another team. A mission id names one mission: choose another id.`);
65696
- if (existsSync37(file2) && input2.flags.force !== true) throw refused2(`${relative8(workspaceDir, file2)} exists.`, "Pass --force to write over it.");
65985
+ if (existsSync38(file2) && input2.flags.force !== true) throw refused2(`${relative8(workspaceDir, file2)} exists.`, "Pass --force to write over it.");
65697
65986
  const intent = await asked(context, input2, "intent", "Intent: why does the team do this mission? ");
65698
65987
  const endState = await asked(context, input2, "end-state", "End state: what is true when the mission is done? ");
65699
65988
  const limits = await asked(context, input2, "limits", "Limits: what must the team not do or change? ");
@@ -65772,7 +66061,7 @@ var missionShow = {
65772
66061
  if (record2.sessionId) context.output.line(`session: ${record2.sessionId}`);
65773
66062
  if (record2.brief) context.output.line(["", record2.brief].join("\n"));
65774
66063
  if (record2.lead) context.output.line(`lead: ${record2.lead}`);
65775
- if (record2.giver) context.output.line(`giver: session ${record2.giver} (its questions go to that session)`);
66064
+ if (record2.receivers.length > 0) context.output.line(`decision receivers: ${record2.receivers.join(", ")} (each question goes to every live one)`);
65776
66065
  if (record2.claim) context.output.line(`claim: ${record2.claim}${record2.claimedAt ? ` since ${record2.claimedAt}` : ""}${record2.stale ? " (stale)" : ""}`);
65777
66066
  if (!record2.question && record2.state === "waiting") context.output.line(["", "The lead stopped without a hand-off.", `Tell it how to go on with: ${commandPrefix(context.env)} mission reply ${record2.missionId} "<message>"`].join("\n"));
65778
66067
  if (record2.question && record2.state === "waiting") context.output.line(["", "question:", record2.question, "", `Answer with: ${commandPrefix(context.env)} mission reply ${record2.missionId} "<answer>"`].join("\n"));
@@ -65847,6 +66136,31 @@ var missionSay = {
65847
66136
  return { data: { ...said2, node: node2.name } };
65848
66137
  }
65849
66138
  };
66139
+ var CLAUDE_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
66140
+ function missionMembership(verb, summary, why) {
66141
+ return {
66142
+ path: ["mission", verb],
66143
+ summary,
66144
+ credential: "none",
66145
+ flags: [WORKSPACE2, { name: "external-id", type: "string", summary: `The external id of the attached session, such as the Claude Code session id. Default: ${CLAUDE_SESSION_ENV}, so a Claude Code session names itself.` }],
66146
+ positionals: [MISSION_ID2],
66147
+ outputShape: MISSION_FIELDS,
66148
+ exitCodes: [0, 3],
66149
+ routes: [],
66150
+ examples: [{ command: `orbital mission ${verb} pr-42`, why }],
66151
+ run: async (input2, context) => {
66152
+ const mission = idOf(input2);
66153
+ const externalId = typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : context.env[CLAUDE_SESSION_ENV] || null;
66154
+ if (!externalId) throw refused2(`Name the session that ${verb}s the decision receivers of ${mission}.`, `Pass --external-id <id>, or run the command in a Claude Code session, which sets ${CLAUDE_SESSION_ENV}.`);
66155
+ const node2 = await nodeOfMission(context, mission, () => nodeOfProject(context, workspaceOf3(input2)));
66156
+ const record2 = MissionRecordSchema.parse(await nodeCall(context, node2, `/orbital/missions/${verb}`, { missionId: mission, externalId }));
66157
+ context.output.line(`${record2.missionId} on ${record2.node}: ${record2.state}; decision receivers: ${record2.receivers.join(", ") || "none"}`);
66158
+ return { data: record2 };
66159
+ }
66160
+ };
66161
+ }
66162
+ var missionJoin = missionMembership("join", "Make an attached session, such as this Claude Code session, a decision receiver of a running mission: each question of its lead goes to every live receiver, and the open questions come at once. The node keys the receiver by its external id, so the session stays a receiver after it or the node restarts.", "Get the questions of the lead of pr-42 in this Claude Code session.");
66163
+ var missionLeave = missionMembership("leave", "Remove an attached session from the decision receivers of a mission: its lead's questions stop coming to it.", "Stop getting the questions of the lead of pr-42 in this Claude Code session.");
65850
66164
  var missionMigrate = {
65851
66165
  path: ["mission", "migrate"],
65852
66166
  summary: `Move the missions of the old layout, teams/<team>/missions/<id>/, to missions/<id>/${MISSION_FILE} with team: <team> in the frontmatter, and the state of each mission of the nodes of this project from the node home, missions/<node>/<id>.json, to missions/<id>/${MISSION_STATE_FILE} beside its ${MISSION_FILE}. Stop the nodes of the project first.`,
@@ -65861,11 +66175,11 @@ var missionMigrate = {
65861
66175
  const workspaceDir = workspaceOf3(input2);
65862
66176
  const source = definitionSource(workspaceDir, context.env);
65863
66177
  if (!source) throw refused2(`${workspaceDir} has no definition of orbital.`);
65864
- const running = (await liveNodes(orbitalHome(context.env), context.fetch)).filter((node2) => resolve21(node2.workspaceDir) === resolve21(source.projectDir));
66178
+ const running = (await liveNodes(orbitalHome(context.env), context.fetch)).filter((node2) => resolve22(node2.workspaceDir) === resolve22(source.projectDir));
65865
66179
  if (running.length > 0 && input2.flags["dry-run"] !== true) throw refused2(`The node ${running.map((node2) => node2.name).join(", ")} of this project runs.`, `Stop it first, with ${commandPrefix(context.env)} down, then run the migration.`);
65866
66180
  const project = sourceConfig(source).node ?? defaultNodeName(workspaceDir);
65867
66181
  const nodes = teamNodes(readTeams(source.root).teams, project);
65868
- const migration = migrateMissions(source.root, join43(orbitalHome(context.env), "missions"), nodes, input2.flags["dry-run"] === true);
66182
+ const migration = migrateMissions(source.root, join44(orbitalHome(context.env), "missions"), nodes, input2.flags["dry-run"] === true);
65869
66183
  for (const move of migration.moved) context.output.line(`${input2.flags["dry-run"] === true ? "would move" : "moved"} ${move.from} -> ${move.to}`);
65870
66184
  for (const reason of migration.refused) context.output.warn(reason);
65871
66185
  if (migration.moved.length === 0 && migration.refused.length === 0) context.output.line("Nothing to move: the missions are in the new layout.");
@@ -65890,10 +66204,10 @@ var missionReply = {
65890
66204
  return { data: record2 };
65891
66205
  }
65892
66206
  };
65893
- var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
66207
+ var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionJoin, missionLeave, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
65894
66208
 
65895
66209
  // src/core/cli/check.ts
65896
- import { resolve as resolve22 } from "node:path";
66210
+ import { resolve as resolve23 } from "node:path";
65897
66211
  var LOAD_RULE = "orbital.load";
65898
66212
  async function loadRefusals(workspaceDir, env) {
65899
66213
  try {
@@ -65931,7 +66245,7 @@ var checkCommand = {
65931
66245
  { command: "orbital check --annotations", why: "Show the findings on a pull request." }
65932
66246
  ],
65933
66247
  run: async (input2, context) => {
65934
- const workspaceDir = resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
66248
+ const workspaceDir = resolve23(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
65935
66249
  const source = definitionSource(workspaceDir, context.env);
65936
66250
  const findings = await loadRefusals(workspaceDir, context.env);
65937
66251
  for (const finding of findings) context.output.line(`${finding.finding} ${finding.rule} ${finding.path} ${finding.message}`);
@@ -66241,18 +66555,18 @@ function createOrbitalCommands(options) {
66241
66555
  }
66242
66556
 
66243
66557
  // src/core/cli/shipped-skills.ts
66244
- var import_yaml8 = __toESM(require_dist(), 1);
66245
- import { cpSync as cpSync4, existsSync as existsSync38, mkdirSync as mkdirSync28, readFileSync as readFileSync32, readdirSync as readdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync25 } from "node:fs";
66558
+ var import_yaml9 = __toESM(require_dist(), 1);
66559
+ import { cpSync as cpSync4, existsSync as existsSync39, mkdirSync as mkdirSync28, readFileSync as readFileSync33, readdirSync as readdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync25 } from "node:fs";
66246
66560
  import { createHash as createHash11 } from "node:crypto";
66247
66561
  import { homedir as homedir11 } from "node:os";
66248
- import { dirname as dirname18, join as join44, relative as relative9, resolve as resolve23, sep as sep7 } from "node:path";
66562
+ import { dirname as dirname18, join as join45, relative as relative9, resolve as resolve24, sep as sep7 } from "node:path";
66249
66563
  import { fileURLToPath as fileURLToPath3 } from "node:url";
66250
66564
  var HASH_FILE = ".installed-hash";
66251
66565
  function skillsRoot(shipped) {
66252
66566
  let directory = dirname18(fileURLToPath3(shipped.anchor));
66253
66567
  for (let depth = 0; depth < 5; depth += 1) {
66254
- const candidate = join44(directory, "skills");
66255
- if (existsSync38(join44(candidate, shipped.names[0], "SKILL.md"))) return candidate;
66568
+ const candidate = join45(directory, "skills");
66569
+ if (existsSync39(join45(candidate, shipped.names[0], "SKILL.md"))) return candidate;
66256
66570
  directory = dirname18(directory);
66257
66571
  }
66258
66572
  throw new CliError({
@@ -66264,33 +66578,33 @@ function skillsRoot(shipped) {
66264
66578
  }
66265
66579
  function filesOf2(dir) {
66266
66580
  return readdirSync22(dir, { withFileTypes: true }).flatMap((entry) => {
66267
- const path3 = join44(dir, entry.name);
66581
+ const path3 = join45(dir, entry.name);
66268
66582
  if (entry.isDirectory()) return filesOf2(path3);
66269
66583
  return entry.name === HASH_FILE ? [] : [path3];
66270
66584
  });
66271
66585
  }
66272
66586
  function descriptionOf(skillFile) {
66273
- const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync32(skillFile, "utf8"));
66587
+ const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync33(skillFile, "utf8"));
66274
66588
  if (!match?.[1]) return "";
66275
- const frontmatter = (0, import_yaml8.parse)(match[1]);
66589
+ const frontmatter = (0, import_yaml9.parse)(match[1]);
66276
66590
  return typeof frontmatter?.description === "string" ? frontmatter.description : "";
66277
66591
  }
66278
66592
  function shippedSkills(shipped) {
66279
66593
  const root = skillsRoot(shipped);
66280
66594
  return shipped.names.map((name) => {
66281
- const dir = join44(root, name);
66282
- if (!existsSync38(join44(dir, "SKILL.md"))) {
66595
+ const dir = join45(root, name);
66596
+ if (!existsSync39(join45(dir, "SKILL.md"))) {
66283
66597
  throw new CliError({ code: "internal", message: `This build is missing its skill ${name}.`, exitCode: ExitCode.Error, hint: "Reinstall the CLI. The skills ship inside the package." });
66284
66598
  }
66285
66599
  const files = filesOf2(dir).map((file2) => relative9(dir, file2).split(sep7).join("/")).sort();
66286
- return { name, description: descriptionOf(join44(dir, "SKILL.md")), dir, files };
66600
+ return { name, description: descriptionOf(join45(dir, "SKILL.md")), dir, files };
66287
66601
  });
66288
66602
  }
66289
66603
  function folderHash(dir) {
66290
66604
  const hash2 = createHash11("sha256");
66291
66605
  for (const file2 of filesOf2(dir).map((path3) => relative9(dir, path3).split(sep7).join("/")).sort()) {
66292
66606
  hash2.update(`${file2}\0`);
66293
- hash2.update(readFileSync32(join44(dir, file2)));
66607
+ hash2.update(readFileSync33(join45(dir, file2)));
66294
66608
  hash2.update("\0");
66295
66609
  }
66296
66610
  return hash2.digest("hex");
@@ -66310,18 +66624,18 @@ function pick2(all, names) {
66310
66624
  });
66311
66625
  }
66312
66626
  function installBase(input2) {
66313
- if (typeof input2.flags.dir === "string") return resolve23(input2.flags.dir);
66314
- if (input2.flags.global === true) return join44(homedir11(), ".claude", "skills");
66315
- return resolve23(".claude", "skills");
66627
+ if (typeof input2.flags.dir === "string") return resolve24(input2.flags.dir);
66628
+ if (input2.flags.global === true) return join45(homedir11(), ".claude", "skills");
66629
+ return resolve24(".claude", "skills");
66316
66630
  }
66317
66631
  function planOne(skill, base2) {
66318
- const target = join44(base2, skill.name);
66632
+ const target = join45(base2, skill.name);
66319
66633
  const hash2 = folderHash(skill.dir);
66320
- if (!existsSync38(join44(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
66634
+ if (!existsSync39(join45(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
66321
66635
  const onDisk = folderHash(target);
66322
66636
  if (onDisk === hash2) return { skill, target, hash: hash2, action: "unchanged", edited: false };
66323
- const hashFile = join44(target, HASH_FILE);
66324
- const recorded = existsSync38(hashFile) ? readFileSync32(hashFile, "utf8").trim() : "";
66637
+ const hashFile = join45(target, HASH_FILE);
66638
+ const recorded = existsSync39(hashFile) ? readFileSync33(hashFile, "utf8").trim() : "";
66325
66639
  return { skill, target, hash: hash2, action: "updated", edited: onDisk !== recorded };
66326
66640
  }
66327
66641
  function writeOne(plan) {
@@ -66329,7 +66643,7 @@ function writeOne(plan) {
66329
66643
  rmSync15(plan.target, { recursive: true, force: true });
66330
66644
  mkdirSync28(plan.target, { recursive: true });
66331
66645
  cpSync4(plan.skill.dir, plan.target, { recursive: true });
66332
- writeFileSync25(join44(plan.target, HASH_FILE), `${plan.hash}
66646
+ writeFileSync25(join45(plan.target, HASH_FILE), `${plan.hash}
66333
66647
  `);
66334
66648
  } catch (error62) {
66335
66649
  throw new CliError({
@@ -66374,7 +66688,7 @@ function createSkillCommands(shipped) {
66374
66688
  context.output.line(skill.dir);
66375
66689
  return { data: { name: skill.name, path: skill.dir, content: null } };
66376
66690
  }
66377
- const content = readFileSync32(join44(skill.dir, "SKILL.md"), "utf8");
66691
+ const content = readFileSync33(join45(skill.dir, "SKILL.md"), "utf8");
66378
66692
  context.output.line(content);
66379
66693
  return { data: { name: skill.name, path: skill.dir, content } };
66380
66694
  }
@@ -66454,21 +66768,21 @@ function createSkillCommands(shipped) {
66454
66768
  }
66455
66769
 
66456
66770
  // src/init.ts
66457
- import { existsSync as existsSync39, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
66458
- import { dirname as dirname19, isAbsolute as isAbsolute8, relative as relative10, resolve as resolve24 } from "node:path";
66459
- var import_yaml9 = __toESM(require_dist(), 1);
66771
+ import { existsSync as existsSync40, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
66772
+ import { dirname as dirname19, isAbsolute as isAbsolute8, relative as relative10, resolve as resolve25 } from "node:path";
66773
+ var import_yaml10 = __toESM(require_dist(), 1);
66460
66774
  function initHarnessFolder(workspaceDir, adapter = CLAUDE_ADAPTER) {
66461
66775
  const file2 = harnessRootAt(workspaceDir, adapter).file;
66462
- if (existsSync39(file2)) {
66776
+ if (existsSync40(file2)) {
66463
66777
  throw new CliError({ code: "conflict", message: `This project has ${file2} already.`, exitCode: ExitCode.Validation, hint: "A harness folder holds one orbital.yaml." });
66464
66778
  }
66465
66779
  mkdirSync29(dirname19(file2), { recursive: true });
66466
- writeFileSync26(file2, (0, import_yaml9.stringify)({ apiVersion: ORBITAL_API_VERSION }));
66780
+ writeFileSync26(file2, (0, import_yaml10.stringify)({ apiVersion: ORBITAL_API_VERSION }));
66467
66781
  return file2;
66468
66782
  }
66469
66783
  function workspaceOf4(flag) {
66470
- const workspaceDir = resolve24(typeof flag === "string" ? flag : ".");
66471
- if (!existsSync39(workspaceDir)) throw new CliError({ code: "not-found", message: `${workspaceDir} does not exist.`, exitCode: ExitCode.Validation });
66784
+ const workspaceDir = resolve25(typeof flag === "string" ? flag : ".");
66785
+ if (!existsSync40(workspaceDir)) throw new CliError({ code: "not-found", message: `${workspaceDir} does not exist.`, exitCode: ExitCode.Validation });
66472
66786
  return workspaceDir;
66473
66787
  }
66474
66788
  var WORKSPACE3 = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
@@ -66506,9 +66820,9 @@ function pluginRefOf(home, given) {
66506
66820
  return path3.startsWith(".") ? path3 : `./${path3}`;
66507
66821
  } };
66508
66822
  }
66509
- installPlugin(home, resolved);
66823
+ const { name } = installPlugin(home, resolved);
66510
66824
  const source = resolved.kind === "npm" ? `npm:${resolved.pkg}` : resolved.kind === "git" ? `git:${resolved.url}` : spec;
66511
- return { ref: { name: resolved.name, source, base: home }, recorded: () => source };
66825
+ return { ref: { name, source, base: home }, recorded: () => source };
66512
66826
  }
66513
66827
  function knownRef(context, known) {
66514
66828
  const home = orbitalHome(context.env);
@@ -66544,7 +66858,7 @@ function targetOf2(context, workspaceDir, named2, given, adapters) {
66544
66858
  });
66545
66859
  }
66546
66860
  async function chooseTargets(context, workspaceDir, adapters) {
66547
- const present = adapters.filter((adapter) => existsSync39(resolve24(workspaceDir, adapter.folder.folder)));
66861
+ const present = adapters.filter((adapter) => existsSync40(resolve25(workspaceDir, adapter.folder.folder)));
66548
66862
  if (present.length === 1) {
66549
66863
  context.output.step(`installing ${present[0].id}: ${present[0].folder.folder} is the harness folder of this project`);
66550
66864
  return [{ adapter: present[0], given: null }];
@@ -66578,9 +66892,9 @@ async function adapterInstall(context, adapter, workspaceDir, force) {
66578
66892
  async function installTarget(context, workspaceDir, target, user, force) {
66579
66893
  const { adapter, given } = target;
66580
66894
  const root = harnessRootAt(workspaceDir, adapter);
66581
- const file2 = existsSync39(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
66895
+ const file2 = existsSync40(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
66582
66896
  if (file2) context.output.line(`wrote ${file2}`);
66583
- const plugin = given ? { name: given.ref.name, source: user ? isPackageSource(given.ref.source) ? given.ref.source : resolve24(given.ref.source) : given.recorded(root.root), user } : null;
66897
+ const plugin = given ? { name: given.ref.name, source: user ? isPackageSource(given.ref.source) ? given.ref.source : resolve25(given.ref.source) : given.recorded(root.root), user } : null;
66584
66898
  const named2 = plugin ? user ? setUserPlugin(context.env, plugin.name, { source: plugin.source, enabled: true, config: {}, env: [] }) : setPluginEntry(root.file, plugin.name, { source: plugin.source, enabled: true, config: {}, env: [] }) : false;
66585
66899
  if (plugin && named2) context.output.line(user ? `your user config names the plugin ${plugin.name}: source ${plugin.source}` : `${ORBITAL_FILE} names the plugin ${plugin.name}: source ${plugin.source}`);
66586
66900
  const install2 = await adapterInstall(context, adapter, workspaceDir, force);
@@ -66665,6 +66979,12 @@ function followAttach(context, options, signal, inbox, timing = {}) {
66665
66979
  delivery: (event) => {
66666
66980
  const delivery = DeliverySchema.safeParse(event);
66667
66981
  if (delivery.success) inbox.push(delivery.data);
66982
+ const answered = AnsweredSchema.safeParse(event);
66983
+ if (!answered.success) return;
66984
+ for (let index = inbox.length - 1; index >= 0; index -= 1) {
66985
+ const held2 = inbox[index];
66986
+ if (held2.mission === answered.data.mission && (answered.data.question === null || held2.question === answered.data.question)) inbox.splice(index, 1);
66987
+ }
66668
66988
  },
66669
66989
  detached: (reason) => {
66670
66990
  if (kept) kept.ended = true;
@@ -66794,9 +67114,8 @@ function mcpCommand(toolsOf) {
66794
67114
  const joined = await attachment.current();
66795
67115
  return { url: `${joined.nodeUrl}/a2a`, token: joined.token };
66796
67116
  },
66797
- get sessionId() {
66798
- return attachment.sessionId();
66799
- },
67117
+ sessionId: null,
67118
+ attached: async () => (await attachment.current()).sessionId,
66800
67119
  harness,
66801
67120
  follow: true,
66802
67121
  tools: inboxTools(context, attachment.current, inbox)
@@ -66805,8 +67124,8 @@ function mcpCommand(toolsOf) {
66805
67124
  const server = await mcpServer(options, input2.flags.actions === true, toolsOf);
66806
67125
  const transport = new StdioServerTransport();
66807
67126
  await server.connect(transport);
66808
- await new Promise((resolve25) => {
66809
- server.server.onclose = resolve25;
67127
+ await new Promise((resolve26) => {
67128
+ server.server.onclose = resolve26;
66810
67129
  process.stdin.once("end", () => void server.close());
66811
67130
  });
66812
67131
  stop.abort();
@@ -66832,6 +67151,10 @@ function orbitalCommands(options) {
66832
67151
  ...options.commands ?? []
66833
67152
  ];
66834
67153
  }
67154
+ function workspacePluginCommands(options) {
67155
+ const reserved = [.../* @__PURE__ */ new Set([...options.builtIn.map((spec) => spec.path[0]), ...INTROSPECTION_WORDS])];
67156
+ return pluginCommandSpecs({ workspaceDir: options.workspaceDir, env: options.env, reserved, ...options.terminal && { terminal: options.terminal } });
67157
+ }
66835
67158
 
66836
67159
  // src/launch.ts
66837
67160
  import { spawn as spawn9 } from "node:child_process";
@@ -66869,7 +67192,8 @@ export {
66869
67192
  nodeLaunch,
66870
67193
  nodeStartArgv,
66871
67194
  orbitalCommands,
66872
- skillCommands
67195
+ skillCommands,
67196
+ workspacePluginCommands
66873
67197
  };
66874
67198
  /*! Bundled license information:
66875
67199