@kaiba-cloud/orbital 0.0.4 → 0.0.5

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/orbital.js CHANGED
@@ -71,7 +71,7 @@ var VERSION, current;
71
71
  var init_brand = __esm({
72
72
  "src/kit/command/brand.ts"() {
73
73
  "use strict";
74
- VERSION = true ? "0.0.4" : "0.0.0-dev";
74
+ VERSION = true ? "0.0.5" : "0.0.0-dev";
75
75
  current = defineBrand({ program: "orbital" });
76
76
  }
77
77
  });
@@ -1214,19 +1214,19 @@ async function readSecretTerminal(prompt) {
1214
1214
  process.stdin.resume();
1215
1215
  process.stdin.setEncoding("utf8");
1216
1216
  try {
1217
- return await new Promise((resolve25) => {
1217
+ return await new Promise((resolve27) => {
1218
1218
  let typed = "";
1219
1219
  const onData = (chunk) => {
1220
1220
  for (const char of chunk) {
1221
1221
  if (char === "") {
1222
1222
  process.stdin.off("data", onData);
1223
- resolve25(null);
1223
+ resolve27(null);
1224
1224
  return;
1225
1225
  }
1226
1226
  if (char === "\r" || char === "\n") {
1227
1227
  process.stdin.off("data", onData);
1228
1228
  process.stderr.write("\n");
1229
- resolve25(typed || null);
1229
+ resolve27(typed || null);
1230
1230
  return;
1231
1231
  }
1232
1232
  if (char === "\x7F" || char === "\b") {
@@ -1270,8 +1270,10 @@ function toCliError(error62, message) {
1270
1270
  });
1271
1271
  }
1272
1272
  async function runProgram(program, options) {
1273
- const { argv, streams, env: env3, isTty } = options;
1273
+ const { streams, env: env3, isTty } = options;
1274
1274
  const { registry: registry2 } = program;
1275
+ const asksHelp = options.argv[0] === "help" && !registry2.some((spec) => spec.path[0] === "help");
1276
+ const argv = asksHelp ? [...options.argv.slice(1), "--help"] : options.argv;
1275
1277
  const guessed = resolveCommand(argv, registry2);
1276
1278
  const mode = resolveMode(
1277
1279
  typeof peekFlag(argv, "output") === "string" ? String(peekFlag(argv, "output")) : guessed?.defaultOutput,
@@ -1308,7 +1310,7 @@ async function runProgram(program, options) {
1308
1310
  output: commandOutput,
1309
1311
  env: env3,
1310
1312
  now: options.now ?? (() => Date.now()),
1311
- sleep: options.sleep ?? ((ms) => new Promise((resolve25) => setTimeout(resolve25, ms))),
1313
+ sleep: options.sleep ?? ((ms) => new Promise((resolve27) => setTimeout(resolve27, ms))),
1312
1314
  fetch: options.fetch ?? globalThis.fetch,
1313
1315
  quiet,
1314
1316
  nonInteractive: peekFlag(argv, "non-interactive") === true || !isTty,
@@ -6046,7 +6048,7 @@ function isRef(value2) {
6046
6048
  function cloneIssues(issues2) {
6047
6049
  return issues2.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
6048
6050
  }
6049
- function isRecursive(inst, stack, resolve25) {
6051
+ function isRecursive(inst, stack, resolve27) {
6050
6052
  const cached2 = recursive.get(inst);
6051
6053
  if (cached2 !== void 0)
6052
6054
  return cached2 ? PROVEN : NONE;
@@ -6056,7 +6058,7 @@ function isRecursive(inst, stack, resolve25) {
6056
6058
  let result2 = NONE;
6057
6059
  const check4 = (child) => {
6058
6060
  if (result2 !== PROVEN && child?._zod) {
6059
- const answer2 = isRecursive(child, stack, resolve25);
6061
+ const answer2 = isRecursive(child, stack, resolve27);
6060
6062
  if (answer2 > result2)
6061
6063
  result2 = answer2;
6062
6064
  }
@@ -6067,7 +6069,7 @@ function isRecursive(inst, stack, resolve25) {
6067
6069
  const desc = Object.getOwnPropertyDescriptor(sh, key2);
6068
6070
  if (spread && !desc.enumerable)
6069
6071
  continue;
6070
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve25) : NONE;
6072
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve27) : NONE;
6071
6073
  if (child > answer2)
6072
6074
  answer2 = child;
6073
6075
  }
@@ -6131,7 +6133,7 @@ function isRecursive(inst, stack, resolve25) {
6131
6133
  break;
6132
6134
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
6133
6135
  case "lazy": {
6134
- const inner = def._cachedInner ?? (resolve25 ? inst._zod.innerType : void 0);
6136
+ const inner = def._cachedInner ?? (resolve27 ? inst._zod.innerType : void 0);
6135
6137
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
6136
6138
  break;
6137
6139
  }
@@ -29932,7 +29934,7 @@ function leadFailure(mission) {
29932
29934
  const last = mission.history.at(-1);
29933
29935
  return mission.state === "paused" && last?.state === "paused" && last.note?.startsWith(LEAD_FAILED) ? last.note : null;
29934
29936
  }
29935
- function waitsOnGiver(mission) {
29937
+ function waitsOnDecision(mission) {
29936
29938
  return mission.state === "waiting" || mission.state === "active" && mission.questions.length > 0;
29937
29939
  }
29938
29940
  function sourceLink(source) {
@@ -29969,14 +29971,21 @@ function steerParts(text3) {
29969
29971
  const match = /^\[orbital\] message during your turn, from (.+?)(?: on the mission [^:\n]+)?:\n([\s\S]*)$/.exec(text3);
29970
29972
  return match ? { from: match[1], text: match[2] } : { from: "the person", text: text3 };
29971
29973
  }
29972
- function giverRef(giver) {
29973
- return `${giver.sessionId}@${giver.node}`;
29974
+ function externalReceiver(externalId) {
29975
+ return `${EXTERNAL_RECEIVER}${externalId}`;
29974
29976
  }
29975
- function remoteGiverOf(giver) {
29976
- const at = giver.lastIndexOf("@");
29977
- return at > 0 && at < giver.length - 1 ? { sessionId: giver.slice(0, at), node: giver.slice(at + 1) } : null;
29977
+ function externalIdOf(receiver) {
29978
+ return receiver.startsWith(EXTERNAL_RECEIVER) ? receiver.slice(EXTERNAL_RECEIVER.length) : null;
29978
29979
  }
29979
- var MISSION_STATES, MissionStateSchema, MissionChangeSchema, LEAD_FAILED, MISSION_STATE_FILE, MissionLinkSchema, MissionAskedSchema, MISSION_QUESTION_META, GITHUB_SOURCE, HEADING, MissionStateFileSchema, MISSION_ID, MissionIdSchema, MissionRecordSchema, MissionListSchema, MissionOutcomeSchema, MissionHandoffSchema, MissionLinkRequestSchema, MissionAskRequestSchema, MissionStartRequestSchema, MissionNewRequestSchema, MissionWrittenSchema, MissionReplySchema, RemoteGiverSchema, SENDER_FIELDS, RelayJoinSchema, RelayReplySchema, MissionControlSchema, STEER_MARK, SayRequestSchema, SaidSchema, RelayMessageSchema, RelayControlSchema, MissionQuestionSchema, MissionJoinSchema;
29980
+ function receiverRef(receiver) {
29981
+ return `${receiver.sessionId}@${receiver.node}`;
29982
+ }
29983
+ function remoteReceiverOf(receiver) {
29984
+ if (externalIdOf(receiver) !== null) return null;
29985
+ const at = receiver.lastIndexOf("@");
29986
+ return at > 0 && at < receiver.length - 1 ? { sessionId: receiver.slice(0, at), node: receiver.slice(at + 1) } : null;
29987
+ }
29988
+ var MISSION_STATES, MissionStateSchema, MissionChangeSchema, LEAD_FAILED, MISSION_STATE_FILE, MissionLinkSchema, MissionAskedSchema, MISSION_QUESTION_META, GITHUB_SOURCE, HEADING, MissionStateFileSchema, MISSION_ID, MissionIdSchema, MissionRecordSchema, MissionListSchema, MissionOutcomeSchema, MissionHandoffSchema, MissionLinkRequestSchema, MissionAskRequestSchema, MissionStartRequestSchema, MissionNewRequestSchema, MissionWrittenSchema, MissionReplySchema, RemoteReceiverSchema, SENDER_FIELDS, RelayJoinSchema, RelayReplySchema, MissionControlSchema, STEER_MARK, SayRequestSchema, SaidSchema, RelayMessageSchema, RelayControlSchema, MissionQuestionSchema, EXTERNAL_RECEIVER, MissionJoinSchema;
29980
29989
  var init_missions = __esm({
29981
29990
  "src/protocol/missions.ts"() {
29982
29991
  "use strict";
@@ -30014,7 +30023,7 @@ var init_missions = __esm({
30014
30023
  source: external_exports.string().nullable(),
30015
30024
  result: external_exports.string().nullable(),
30016
30025
  question: external_exports.string().nullable(),
30017
- giver: external_exports.string().nullable(),
30026
+ receivers: external_exports.array(external_exports.string().min(1)).default([]),
30018
30027
  lead: external_exports.string().nullable(),
30019
30028
  claim: external_exports.string().nullable(),
30020
30029
  claimedAt: external_exports.string().nullable(),
@@ -30041,7 +30050,7 @@ var init_missions = __esm({
30041
30050
  result: external_exports.string().nullable(),
30042
30051
  question: external_exports.string().nullable(),
30043
30052
  questions: external_exports.array(MissionAskedSchema).default([]),
30044
- giver: external_exports.string().nullable(),
30053
+ receivers: external_exports.array(external_exports.string().min(1)).default([]),
30045
30054
  lead: external_exports.string().nullable(),
30046
30055
  lastActivity: external_exports.string().nullable().default(null),
30047
30056
  claim: external_exports.string().nullable().default(null),
@@ -30083,9 +30092,9 @@ var init_missions = __esm({
30083
30092
  text: external_exports.string().min(1),
30084
30093
  questionId: external_exports.string().min(1).optional()
30085
30094
  });
30086
- RemoteGiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
30095
+ RemoteReceiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
30087
30096
  SENDER_FIELDS = { team: external_exports.string().min(1).nullable().default(null), agent: external_exports.string().min(1).nullable().default(null) };
30088
- RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, giver: RemoteGiverSchema, ...SENDER_FIELDS });
30097
+ RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, receiver: RemoteReceiverSchema, ...SENDER_FIELDS });
30089
30098
  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 });
30090
30099
  MissionControlSchema = external_exports.object({ missionId: MissionIdSchema });
30091
30100
  STEER_MARK = "[orbital] message during your turn";
@@ -30107,11 +30116,13 @@ var init_missions = __esm({
30107
30116
  leadSessionId: external_exports.string().min(1),
30108
30117
  question: external_exports.string(),
30109
30118
  questionId: external_exports.string().min(1).optional(),
30110
- giver: RemoteGiverSchema,
30119
+ receiver: RemoteReceiverSchema,
30111
30120
  team: external_exports.string().min(1).nullable().default(null)
30112
30121
  });
30122
+ EXTERNAL_RECEIVER = "external:";
30113
30123
  MissionJoinSchema = external_exports.object({
30114
- missionId: MissionIdSchema
30124
+ missionId: MissionIdSchema,
30125
+ externalId: external_exports.string().min(1).optional()
30115
30126
  });
30116
30127
  }
30117
30128
  });
@@ -30266,7 +30277,7 @@ var init_telemetry = __esm({
30266
30277
  transition: MissionTransitionSchema,
30267
30278
  reason: external_exports.string().nullable(),
30268
30279
  by: external_exports.string(),
30269
- giver: external_exports.string().nullable(),
30280
+ receivers: external_exports.array(external_exports.string()).default([]),
30270
30281
  sessionId: external_exports.string().nullable(),
30271
30282
  lead: external_exports.string().nullable(),
30272
30283
  question: external_exports.string().nullable(),
@@ -30320,7 +30331,7 @@ var init_telemetry = __esm({
30320
30331
  state: MissionStateSchema,
30321
30332
  sessionId: external_exports.string().nullable(),
30322
30333
  lead: external_exports.string().nullable(),
30323
- giver: external_exports.string().nullable(),
30334
+ receivers: external_exports.array(external_exports.string()).default([]),
30324
30335
  givenBy: external_exports.string().nullable(),
30325
30336
  question: external_exports.string().nullable(),
30326
30337
  questions: external_exports.array(MissionAskedSchema).default([]),
@@ -30960,6 +30971,9 @@ var init_node_client = __esm({
30960
30971
  async joinMission(missionId) {
30961
30972
  return MissionRecordSchema.parse(await this.request("/orbital/missions/join", { method: "POST", body: { missionId } }));
30962
30973
  }
30974
+ async leaveMission(missionId) {
30975
+ return MissionRecordSchema.parse(await this.request("/orbital/missions/leave", { method: "POST", body: { missionId } }));
30976
+ }
30963
30977
  async handoff(handoff2) {
30964
30978
  return MissionRecordSchema.parse(await this.request("/orbital/missions/handoff", { method: "POST", body: handoff2 }));
30965
30979
  }
@@ -31620,7 +31634,7 @@ var init_definition_files = __esm({
31620
31634
  });
31621
31635
 
31622
31636
  // src/protocol/attach.ts
31623
- var AttachRequestSchema, AttachAnswerSchema, DeliverySchema, SettleSchema, ToolCheckRequestSchema, ToolDecisionSchema, ToolCheckAnswerSchema;
31637
+ var AttachRequestSchema, AttachAnswerSchema, DeliverySchema, AnsweredSchema, SettleSchema, ToolCheckRequestSchema, ToolDecisionSchema, ToolCheckAnswerSchema;
31624
31638
  var init_attach = __esm({
31625
31639
  "src/protocol/attach.ts"() {
31626
31640
  "use strict";
@@ -31648,6 +31662,13 @@ var init_attach = __esm({
31648
31662
  question: external_exports.string().nullable().default(null),
31649
31663
  text: external_exports.string()
31650
31664
  });
31665
+ AnsweredSchema = external_exports.object({
31666
+ kind: external_exports.literal("answered"),
31667
+ mission: external_exports.string().min(1),
31668
+ question: external_exports.string().nullable(),
31669
+ by: external_exports.string().min(1),
31670
+ text: external_exports.string()
31671
+ });
31651
31672
  SettleSchema = external_exports.object({
31652
31673
  taskId: external_exports.string().min(1).optional(),
31653
31674
  text: external_exports.string()
@@ -31900,7 +31921,7 @@ var init_history = __esm({
31900
31921
  });
31901
31922
 
31902
31923
  // src/protocol/plugin-protocol.ts
31903
- var PORTS_VERSION, PLUGIN_METHODS, PORT_METHODS, PluginManifestSchema, PluginInitSchema, HostEndpointSchema, PluginPackageSchema, PLUGIN_PACKAGE_FILE, MarketplaceSchema, MARKETPLACE_FILE, RPC_ERRORS;
31924
+ var PORTS_VERSION, PLUGIN_METHODS, PORT_METHODS, PLUGIN_COMMAND_RESERVED_OPTIONS, PluginCommandOptionSchema, PluginCommandArgSchema, PluginCommandSchema, PluginCommandsSchema, PluginCommandRequestSchema, PluginCommandResultSchema, PluginManifestSchema, PluginInitSchema, HostEndpointSchema, PluginPackageSchema, PLUGIN_PACKAGE_FILE, MarketplaceSchema, MARKETPLACE_FILE, RPC_ERRORS;
31904
31925
  var init_plugin_protocol = __esm({
31905
31926
  "src/protocol/plugin-protocol.ts"() {
31906
31927
  "use strict";
@@ -31936,6 +31957,60 @@ var init_plugin_protocol = __esm({
31936
31957
  reasoner: ["extractModule", "classify", "align", "checkConsistency"],
31937
31958
  missions: ["join", "reply", "pause", "resume", "ask", "say"]
31938
31959
  };
31960
+ PLUGIN_COMMAND_RESERVED_OPTIONS = ["output", "quiet", "non-interactive", "confirm", "timeout", "help", "version", "workspace", "node"];
31961
+ PluginCommandOptionSchema = external_exports.object({
31962
+ 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(", ")}` }),
31963
+ type: external_exports.enum(["string", "boolean", "integer", "enum", "path"]),
31964
+ summary: external_exports.string().min(1),
31965
+ values: external_exports.array(external_exports.string().min(1)).min(1).optional(),
31966
+ required: external_exports.boolean().default(false),
31967
+ default: external_exports.union([external_exports.string(), external_exports.number().int(), external_exports.boolean()]).optional()
31968
+ }).strict().superRefine((option, issues2) => {
31969
+ 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` });
31970
+ if (option.type === "boolean" && option.required) issues2.addIssue({ code: "custom", message: `the boolean option ${option.name} cannot be required` });
31971
+ if (option.default === void 0) return;
31972
+ 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);
31973
+ 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}` });
31974
+ });
31975
+ PluginCommandArgSchema = external_exports.object({
31976
+ name: external_exports.string().regex(NAME_SHAPE),
31977
+ summary: external_exports.string().min(1),
31978
+ required: external_exports.boolean().default(false),
31979
+ variadic: external_exports.boolean().default(false)
31980
+ }).strict();
31981
+ PluginCommandSchema = external_exports.object({
31982
+ name: external_exports.string().regex(NAME_SHAPE),
31983
+ summary: external_exports.string().min(1),
31984
+ args: external_exports.array(PluginCommandArgSchema).default([]),
31985
+ options: external_exports.array(PluginCommandOptionSchema).default([]),
31986
+ interactive: external_exports.boolean().default(false),
31987
+ node: external_exports.boolean().default(false)
31988
+ }).strict().superRefine((command, issues2) => {
31989
+ const twice = (names) => names.find((name, index) => names.indexOf(name) !== index);
31990
+ const arg = twice(command.args.map((one) => one.name));
31991
+ if (arg) issues2.addIssue({ code: "custom", message: `the command ${command.name} names the argument ${arg} twice` });
31992
+ const option = twice(command.options.map((one) => one.name));
31993
+ if (option) issues2.addIssue({ code: "custom", message: `the command ${command.name} names the option ${option} twice` });
31994
+ 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` });
31995
+ 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` });
31996
+ 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` });
31997
+ });
31998
+ PluginCommandsSchema = external_exports.array(PluginCommandSchema).default([]).superRefine((commands, issues2) => {
31999
+ const names = commands.map((one) => one.name);
32000
+ const twice = names.find((name, index) => names.indexOf(name) !== index);
32001
+ if (twice) issues2.addIssue({ code: "custom", message: `the command ${twice} is declared twice` });
32002
+ });
32003
+ PluginCommandRequestSchema = external_exports.object({
32004
+ name: external_exports.string().regex(NAME_SHAPE),
32005
+ args: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.array(external_exports.string())])).default({}),
32006
+ options: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).default({}),
32007
+ terminal: external_exports.object({ tty: external_exports.boolean() }).nullable().default(null)
32008
+ }).strict();
32009
+ PluginCommandResultSchema = external_exports.object({
32010
+ 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),
32011
+ text: external_exports.string().nullable().default(null),
32012
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).default({})
32013
+ }).strict();
31939
32014
  PluginManifestSchema = external_exports.object({
31940
32015
  name: external_exports.string().regex(NAME_SHAPE),
31941
32016
  version: external_exports.string().min(1),
@@ -31965,7 +32040,7 @@ var init_plugin_protocol = __esm({
31965
32040
  secrets: external_exports.array(external_exports.string().min(1)).default([]),
31966
32041
  models: external_exports.array(external_exports.string().regex(/^[a-z0-9-]+:.+$/, "a model pattern is <provider>:<model>, such as anthropic:*")).default([])
31967
32042
  }).prefault({}),
31968
- commands: external_exports.array(external_exports.object({ name: external_exports.string().regex(NAME_SHAPE), summary: external_exports.string() })).default([]),
32043
+ commands: PluginCommandsSchema,
31969
32044
  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([])
31970
32045
  }).strict();
31971
32046
  PluginInitSchema = external_exports.object({
@@ -31988,7 +32063,8 @@ var init_plugin_protocol = __esm({
31988
32063
  dir: external_exports.string().min(1).optional(),
31989
32064
  env: external_exports.array(external_exports.string().regex(PLUGIN_ENV_NAME, "is not the name of an environment variable")).default([]),
31990
32065
  config: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
31991
- harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([])
32066
+ harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([]),
32067
+ commands: PluginCommandsSchema
31992
32068
  }).strict();
31993
32069
  PLUGIN_PACKAGE_FILE = "orbital-plugin.json";
31994
32070
  MarketplaceSchema = external_exports.object({
@@ -32041,12 +32117,12 @@ var init_rpc_peer = __esm({
32041
32117
  request(method, params, timeoutMs = this.timeoutMs) {
32042
32118
  if (this.closedWith) return Promise.reject(this.closedWith);
32043
32119
  const id = this.nextId++;
32044
- return new Promise((resolve25, reject) => {
32120
+ return new Promise((resolve27, reject) => {
32045
32121
  const timer = timeoutMs > 0 ? setTimeout(() => {
32046
32122
  this.pending.delete(id);
32047
32123
  reject(new RpcError(RPC_ERRORS.internal, `${method} got no answer in ${timeoutMs} ms`));
32048
32124
  }, timeoutMs) : null;
32049
- this.pending.set(id, { resolve: resolve25, reject, timer });
32125
+ this.pending.set(id, { resolve: resolve27, reject, timer });
32050
32126
  this.write({ jsonrpc: "2.0", id, method, ...params !== void 0 && { params } });
32051
32127
  });
32052
32128
  }
@@ -35234,7 +35310,7 @@ var require_compile = __commonJS({
35234
35310
  const schOrFunc = root.refs[ref];
35235
35311
  if (schOrFunc)
35236
35312
  return schOrFunc;
35237
- let _sch = resolve25.call(this, root, ref);
35313
+ let _sch = resolve27.call(this, root, ref);
35238
35314
  if (_sch === void 0) {
35239
35315
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
35240
35316
  const { schemaId } = this.opts;
@@ -35261,7 +35337,7 @@ var require_compile = __commonJS({
35261
35337
  function sameSchemaEnv(s1, s2) {
35262
35338
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
35263
35339
  }
35264
- function resolve25(root, ref) {
35340
+ function resolve27(root, ref) {
35265
35341
  let sch;
35266
35342
  while (typeof (sch = this.refs[ref]) == "string")
35267
35343
  ref = sch;
@@ -36125,7 +36201,7 @@ var require_fast_uri = __commonJS({
36125
36201
  }
36126
36202
  return uri;
36127
36203
  }
36128
- function resolve25(baseURI, relativeURI, options) {
36204
+ function resolve27(baseURI, relativeURI, options) {
36129
36205
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
36130
36206
  const {
36131
36207
  parsed: baseParsed,
@@ -36516,7 +36592,7 @@ var require_fast_uri = __commonJS({
36516
36592
  var fastUri = {
36517
36593
  SCHEMES,
36518
36594
  normalize,
36519
- resolve: resolve25,
36595
+ resolve: resolve27,
36520
36596
  resolveComponent,
36521
36597
  equal,
36522
36598
  serialize,
@@ -39611,12 +39687,12 @@ var require_isexe = __commonJS({
39611
39687
  if (typeof Promise !== "function") {
39612
39688
  throw new TypeError("callback not provided");
39613
39689
  }
39614
- return new Promise(function(resolve25, reject) {
39690
+ return new Promise(function(resolve27, reject) {
39615
39691
  isexe(path3, options || {}, function(er, is) {
39616
39692
  if (er) {
39617
39693
  reject(er);
39618
39694
  } else {
39619
- resolve25(is);
39695
+ resolve27(is);
39620
39696
  }
39621
39697
  });
39622
39698
  });
@@ -39682,27 +39758,27 @@ var require_which = __commonJS({
39682
39758
  opt = {};
39683
39759
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
39684
39760
  const found = [];
39685
- const step = (i) => new Promise((resolve25, reject) => {
39761
+ const step = (i) => new Promise((resolve27, reject) => {
39686
39762
  if (i === pathEnv.length)
39687
- return opt.all && found.length ? resolve25(found) : reject(getNotFoundError(cmd));
39763
+ return opt.all && found.length ? resolve27(found) : reject(getNotFoundError(cmd));
39688
39764
  const ppRaw = pathEnv[i];
39689
39765
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
39690
39766
  const pCmd = path3.join(pathPart, cmd);
39691
39767
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
39692
- resolve25(subStep(p, i, 0));
39768
+ resolve27(subStep(p, i, 0));
39693
39769
  });
39694
- const subStep = (p, i, ii) => new Promise((resolve25, reject) => {
39770
+ const subStep = (p, i, ii) => new Promise((resolve27, reject) => {
39695
39771
  if (ii === pathExt.length)
39696
- return resolve25(step(i + 1));
39772
+ return resolve27(step(i + 1));
39697
39773
  const ext = pathExt[ii];
39698
39774
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
39699
39775
  if (!er && is) {
39700
39776
  if (opt.all)
39701
39777
  found.push(p + ext);
39702
39778
  else
39703
- return resolve25(p + ext);
39779
+ return resolve27(p + ext);
39704
39780
  }
39705
- return resolve25(subStep(p, i, ii + 1));
39781
+ return resolve27(subStep(p, i, ii + 1));
39706
39782
  });
39707
39783
  });
39708
39784
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -40029,7 +40105,7 @@ function groupOf(state) {
40029
40105
  }
40030
40106
  }
40031
40107
  function missionGroup(mission) {
40032
- return waitsOnGiver(mission) ? "waiting" : groupOf(mission.state);
40108
+ return waitsOnDecision(mission) ? "waiting" : groupOf(mission.state);
40033
40109
  }
40034
40110
  function isSelectable(row2) {
40035
40111
  return row2.kind === "node" || row2.kind === "mission" || row2.kind === "member" || row2.kind === "session" || row2.kind === "tasks" || row2.kind === "subagent" || row2.kind === "task" && row2.task.transcript;
@@ -45193,8 +45269,8 @@ var require_react_reconciler_production = __commonJS({
45193
45269
  currentEntangledActionThenable = {
45194
45270
  status: "pending",
45195
45271
  value: void 0,
45196
- then: function(resolve25) {
45197
- entangledListeners.push(resolve25);
45272
+ then: function(resolve27) {
45273
+ entangledListeners.push(resolve27);
45198
45274
  }
45199
45275
  };
45200
45276
  }
@@ -45217,8 +45293,8 @@ var require_react_reconciler_production = __commonJS({
45217
45293
  status: "pending",
45218
45294
  value: null,
45219
45295
  reason: null,
45220
- then: function(resolve25) {
45221
- listeners.push(resolve25);
45296
+ then: function(resolve27) {
45297
+ listeners.push(resolve27);
45222
45298
  }
45223
45299
  };
45224
45300
  thenable.then(
@@ -61894,8 +61970,8 @@ var init_ink = __esm({
61894
61970
  }
61895
61971
  }
61896
61972
  async waitUntilExit() {
61897
- this.exitPromise ||= new Promise((resolve25, reject) => {
61898
- this.resolveExitPromise = resolve25;
61973
+ this.exitPromise ||= new Promise((resolve27, reject) => {
61974
+ this.resolveExitPromise = resolve27;
61899
61975
  this.rejectExitPromise = reject;
61900
61976
  });
61901
61977
  if (!this.beforeExitHandler) {
@@ -63219,8 +63295,8 @@ var init_chat = __esm({
63219
63295
  ask(question, policy) {
63220
63296
  if (this.closed) return Promise.resolve(null);
63221
63297
  const id = this.nextAsk++;
63222
- return new Promise((resolve25) => {
63223
- this.answers.set(id, resolve25);
63298
+ return new Promise((resolve27) => {
63299
+ this.answers.set(id, resolve27);
63224
63300
  this.set({ asks: [...this.state.asks, { id, question, policy }] });
63225
63301
  });
63226
63302
  }
@@ -64688,6 +64764,7 @@ var init_tui = __esm({
64688
64764
 
64689
64765
  // src/cli.ts
64690
64766
  init_command();
64767
+ import { resolve as resolve26 } from "node:path";
64691
64768
 
64692
64769
  // src/core/config/env.ts
64693
64770
  init_command();
@@ -65791,7 +65868,7 @@ function installPlugin(home, ref, exec4 = run) {
65791
65868
  const installed = Object.keys(JSON.parse(readFileSync7(join10(scratch, "package.json"), "utf8")).dependencies ?? {});
65792
65869
  const pkgDir = join10("node_modules", ...(installed[0] ?? ref.pkg.replace(/(.)@[^/]*$/, "$1")).split("/"));
65793
65870
  const inner = PluginPackageSchema.parse(JSON.parse(readFileSync7(join10(scratch, pkgDir, PLUGIN_PACKAGE_FILE), "utf8")));
65794
- 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)}
65871
+ 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 }, ...inner.commands.length > 0 && { commands: inner.commands } }, null, 2)}
65795
65872
  `);
65796
65873
  break;
65797
65874
  }
@@ -65857,15 +65934,22 @@ function ensurePluginInstalled(home, name, source, exec4 = run) {
65857
65934
  function pluginsDir(home) {
65858
65935
  return join11(home, "plugins");
65859
65936
  }
65937
+ function isLocalSource(source) {
65938
+ return source.startsWith(".") || isAbsolute5(source);
65939
+ }
65940
+ function pluginDir(name, source, roots) {
65941
+ if (!isLocalSource(source)) return join11(pluginsDir(roots.home), name);
65942
+ return isAbsolute5(source) ? source : resolve7(roots.harnessRoot ?? process.cwd(), source);
65943
+ }
65860
65944
  function commandOf(name, source, roots, entryEnv = []) {
65861
65945
  if (source.startsWith("exec:")) {
65862
65946
  const [command2, ...args2] = source.slice("exec:".length).trim().split(/\s+/);
65863
65947
  if (!command2) throw new Error(`The plugin ${name}'s source "${source}" names no command.`);
65864
- return { command: command2, args: args2, cwd: roots.harnessRoot ?? process.cwd(), env: [...entryEnv], defaults: {}, harnesses: null };
65948
+ return { command: command2, args: args2, cwd: roots.harnessRoot ?? process.cwd(), env: [...entryEnv], defaults: {}, harnesses: null, commands: null };
65865
65949
  }
65866
65950
  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.`);
65867
- const local = source.startsWith(".") || isAbsolute5(source);
65868
- const dir = local ? isAbsolute5(source) ? source : resolve7(roots.harnessRoot ?? process.cwd(), source) : join11(pluginsDir(roots.home), name);
65951
+ const local = isLocalSource(source);
65952
+ const dir = pluginDir(name, source, roots);
65869
65953
  const descriptor = join11(dir, PLUGIN_PACKAGE_FILE);
65870
65954
  if (!local && !existsSync7(descriptor) && /^(npm|git):/.test(source)) ensurePluginInstalled(roots.home, name, source);
65871
65955
  if (!existsSync7(descriptor)) {
@@ -65873,7 +65957,7 @@ function commandOf(name, source, roots, entryEnv = []) {
65873
65957
  }
65874
65958
  const plugin = PluginPackageSchema.parse(JSON.parse(readFileSync8(descriptor, "utf8")));
65875
65959
  const [command, ...args] = plugin.command;
65876
- 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 };
65960
+ 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 };
65877
65961
  }
65878
65962
  var PLUGIN_ENV = [
65879
65963
  "PATH",
@@ -65918,6 +66002,15 @@ function folderMismatch(manifest, folders) {
65918
66002
  const extra = folders.find((one) => !manifest.provides.harnesses.some((harness) => harness.id === one.id && harness.folder !== null));
65919
66003
  return extra ? `declares the harness ${extra.id} in its ${PLUGIN_PACKAGE_FILE}, and its harnesses port serves no folder for it` : null;
65920
66004
  }
66005
+ function commandMismatch(manifest, declared) {
66006
+ for (const command of manifest.commands) {
66007
+ const same2 = declared.find((one) => one.name === command.name);
66008
+ if (!same2) return `serves the command ${command.name}, and its ${PLUGIN_PACKAGE_FILE} declares no command ${command.name}`;
66009
+ 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`;
66010
+ }
66011
+ const extra = declared.find((one) => !manifest.commands.some((command) => command.name === one.name));
66012
+ return extra ? `declares the command ${extra.name} in its ${PLUGIN_PACKAGE_FILE}, and serves no command ${extra.name}` : null;
66013
+ }
65921
66014
  function manifestEnv(manifest) {
65922
66015
  return manifest.permissions.secrets.filter((name) => PLUGIN_ENV_NAME.test(name));
65923
66016
  }
@@ -65946,15 +66039,16 @@ async function startRemotePlugin(options) {
65946
66039
  let reconnecting = false;
65947
66040
  const connect = async () => {
65948
66041
  const start = commandOf(options.name, options.source, options.roots, options.entryEnv);
65949
- const child = spawn2(start.command, start.args, { cwd: start.cwd, env: pluginProcessEnv(options.env, start.env), stdio: ["pipe", "pipe", "pipe"] });
66042
+ const terminal2 = options.terminal ? [options.terminal.input, options.terminal.output, options.terminal.error] : [];
66043
+ const child = spawn2(start.command, start.args, { cwd: start.cwd, env: pluginProcessEnv(options.env, start.env), stdio: ["pipe", "pipe", "pipe", ...terminal2] });
65950
66044
  try {
65951
- return await initialize(child, start.env, start.defaults, start.harnesses);
66045
+ return await initialize(child, start.env, start.defaults, start.harnesses, start.commands);
65952
66046
  } catch (error62) {
65953
66047
  child.kill("SIGKILL");
65954
66048
  throw error62;
65955
66049
  }
65956
66050
  };
65957
- const initialize = async (child, env3, defaults, folders = null) => {
66051
+ const initialize = async (child, env3, defaults, folders, commands) => {
65958
66052
  const peer = new RpcPeer({ send: (line) => child.stdin?.write(`${line}
65959
66053
  `) });
65960
66054
  createInterface2({ input: child.stdout }).on("line", (line) => peer.receive(line));
@@ -65989,6 +66083,10 @@ async function startRemotePlugin(options) {
65989
66083
  const where2 = options.source.startsWith("exec:") ? `its ${ORBITAL_FILE} entry` : `its ${PLUGIN_PACKAGE_FILE}`;
65990
66084
  throw new Error(`The plugin ${options.name} declares the variables [${declared.join(", ")}] as secrets, and ${where2} declares env: [${env3.join(", ")}]. Make them the same.`);
65991
66085
  }
66086
+ if (commands) {
66087
+ const mismatch = commandMismatch(manifest2, commands);
66088
+ 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.`);
66089
+ }
65992
66090
  if (options.offline && manifest2.permissions.network.length > 0) {
65993
66091
  throw new Error(`The plugin ${options.name} reaches ${manifest2.permissions.network.join(", ")}, and ${ORBITAL_FILE} says offline: true.`);
65994
66092
  }
@@ -66174,7 +66272,7 @@ async function startRemotePlugin(options) {
66174
66272
  endpoint = given;
66175
66273
  void current2.then((connection) => connection.peer.notify(PLUGIN_METHODS.hostEndpoint, given));
66176
66274
  },
66177
- command: async (name, args) => (await call(PLUGIN_METHODS.commandRun, { name, args })).output,
66275
+ command: async (request) => PluginCommandResultSchema.parse(await call(PLUGIN_METHODS.commandRun, request)),
66178
66276
  close: async () => {
66179
66277
  stopped = true;
66180
66278
  const connection = await current2.catch(() => null);
@@ -67547,7 +67645,7 @@ var readBodyDirect = (request) => {
67547
67645
  request[bodyBufferKey] = buffered;
67548
67646
  return Promise.resolve(buffered);
67549
67647
  }
67550
- const promise2 = new Promise((resolve25, reject) => {
67648
+ const promise2 = new Promise((resolve27, reject) => {
67551
67649
  const chunks = [];
67552
67650
  let settled = false;
67553
67651
  const finish = (callback) => {
@@ -67565,7 +67663,7 @@ var readBodyDirect = (request) => {
67565
67663
  else if (recovered === void 0) reject(error62 ?? normalizeAbortError(request, incoming));
67566
67664
  else {
67567
67665
  request[bodyBufferKey] = recovered;
67568
- resolve25(recovered);
67666
+ resolve27(recovered);
67569
67667
  }
67570
67668
  });
67571
67669
  return true;
@@ -67577,7 +67675,7 @@ var readBodyDirect = (request) => {
67577
67675
  finish(() => {
67578
67676
  const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
67579
67677
  request[bodyBufferKey] = buffer;
67580
- resolve25(buffer);
67678
+ resolve27(buffer);
67581
67679
  });
67582
67680
  };
67583
67681
  const onError = (error62) => {
@@ -68124,7 +68222,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
68124
68222
  });
68125
68223
  if (!chunk) {
68126
68224
  if (i === 1) {
68127
- await new Promise((resolve25) => setTimeout(resolve25));
68225
+ await new Promise((resolve27) => setTimeout(resolve27));
68128
68226
  maxReadCount = 3;
68129
68227
  continue;
68130
68228
  }
@@ -70393,9 +70491,9 @@ data:
70393
70491
  const initRequest = messages.find((m) => isInitializeRequest(m));
70394
70492
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
70395
70493
  if (this._enableJsonResponse) {
70396
- return new Promise((resolve25) => {
70494
+ return new Promise((resolve27) => {
70397
70495
  this._streamMapping.set(streamId, {
70398
- resolveJson: resolve25,
70496
+ resolveJson: resolve27,
70399
70497
  cleanup: () => {
70400
70498
  this._streamMapping.delete(streamId);
70401
70499
  }
@@ -76770,7 +76868,7 @@ var Protocol = class {
76770
76868
  return;
76771
76869
  }
76772
76870
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
76773
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
76871
+ await new Promise((resolve27) => setTimeout(resolve27, pollInterval));
76774
76872
  options?.signal?.throwIfAborted();
76775
76873
  }
76776
76874
  } catch (error62) {
@@ -76787,7 +76885,7 @@ var Protocol = class {
76787
76885
  */
76788
76886
  request(request, resultSchema, options) {
76789
76887
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
76790
- return new Promise((resolve25, reject) => {
76888
+ return new Promise((resolve27, reject) => {
76791
76889
  const earlyReject = (error62) => {
76792
76890
  reject(error62);
76793
76891
  };
@@ -76865,7 +76963,7 @@ var Protocol = class {
76865
76963
  if (!parseResult.success) {
76866
76964
  reject(parseResult.error);
76867
76965
  } else {
76868
- resolve25(parseResult.data);
76966
+ resolve27(parseResult.data);
76869
76967
  }
76870
76968
  } catch (error62) {
76871
76969
  reject(error62);
@@ -77127,12 +77225,12 @@ var Protocol = class {
77127
77225
  }
77128
77226
  } catch {
77129
77227
  }
77130
- return new Promise((resolve25, reject) => {
77228
+ return new Promise((resolve27, reject) => {
77131
77229
  if (signal.aborted) {
77132
77230
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
77133
77231
  return;
77134
77232
  }
77135
- const timeoutId = setTimeout(resolve25, interval);
77233
+ const timeoutId = setTimeout(resolve27, interval);
77136
77234
  signal.addEventListener("abort", () => {
77137
77235
  clearTimeout(timeoutId);
77138
77236
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -78273,7 +78371,7 @@ var McpServer = class {
78273
78371
  let task = createTaskResult.task;
78274
78372
  const pollInterval = task.pollInterval ?? 5e3;
78275
78373
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
78276
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
78374
+ await new Promise((resolve27) => setTimeout(resolve27, pollInterval));
78277
78375
  const updatedTask = await extra.taskStore.getTask(taskId);
78278
78376
  if (!updatedTask) {
78279
78377
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -78998,14 +79096,14 @@ function claudeService(env3) {
78998
79096
  if (!dir) return SERVICE;
78999
79097
  return `${SERVICE}-${createHash4("sha256").update(dir.normalize("NFC")).digest("hex").slice(0, 8)}`;
79000
79098
  }
79001
- var findWithSecurity = (args) => new Promise((resolve25) => {
79099
+ var findWithSecurity = (args) => new Promise((resolve27) => {
79002
79100
  const child = spawn3(SECURITY, [...args], { stdio: ["ignore", "pipe", "ignore"], timeout: SECURITY_TIMEOUT_MS });
79003
79101
  let stdout = "";
79004
79102
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
79005
79103
  stdout += chunk;
79006
79104
  });
79007
- child.once("error", () => resolve25({ status: 1, stdout: "" }));
79008
- child.once("close", (code) => resolve25({ status: code ?? 1, stdout }));
79105
+ child.once("error", () => resolve27({ status: 1, stdout: "" }));
79106
+ child.once("close", (code) => resolve27({ status: code ?? 1, stdout }));
79009
79107
  });
79010
79108
  function keychainRead(account, service, find = findWithSecurity) {
79011
79109
  return async () => {
@@ -79026,7 +79124,7 @@ function renewEnv(env3) {
79026
79124
  return Object.fromEntries(Object.entries(env3).filter((entry) => entry[1] !== void 0 && !KEPT_OUT.includes(entry[0])));
79027
79125
  }
79028
79126
  function claudeRenewal(binary, env3) {
79029
- return () => new Promise((resolve25, reject) => {
79127
+ return () => new Promise((resolve27, reject) => {
79030
79128
  const cwd2 = mkdtempSync(join16(tmpdir(), "orbital-claude-renew-"));
79031
79129
  const child = spawn3(binary, RENEW_ARGS, { cwd: cwd2, env: renewEnv(env3), stdio: ["ignore", "ignore", "pipe"], timeout: RENEW_TIMEOUT_MS });
79032
79130
  let stderr = "";
@@ -79036,7 +79134,7 @@ function claudeRenewal(binary, env3) {
79036
79134
  const done = (error62) => {
79037
79135
  rmSync5(cwd2, { recursive: true, force: true });
79038
79136
  if (error62) reject(error62);
79039
- else resolve25();
79137
+ else resolve27();
79040
79138
  };
79041
79139
  child.once("error", done);
79042
79140
  child.once("close", (code) => done(code === 0 ? null : new Error(`claude exited ${code ?? "on a signal"}${stderr.trim() ? `: ${stderr.trim().split("\n")[0]}` : ""}`)));
@@ -80325,7 +80423,7 @@ function missionOfRow(row2) {
80325
80423
  result: textOf(row2, "result"),
80326
80424
  question: textOf(row2, "question"),
80327
80425
  questions: questionsOf(row2),
80328
- giver: null,
80426
+ receivers: [],
80329
80427
  lead: textOf(row2, "lead"),
80330
80428
  lastActivity: textOf(row2, "activity"),
80331
80429
  claim: textOf(row2, "claim"),
@@ -80479,7 +80577,7 @@ var MissionStore = class {
80479
80577
  source: request.source !== void 0 ? request.source : known?.source ?? null,
80480
80578
  result: request.result !== void 0 ? request.result : known?.result ?? null,
80481
80579
  question: request.question !== void 0 ? request.question : request.state === "waiting" ? known?.question ?? null : null,
80482
- giver: request.giver !== void 0 ? request.giver : known?.giver ?? null,
80580
+ receivers: request.receivers !== void 0 ? request.receivers : known?.receivers ?? [],
80483
80581
  lead: request.lead !== void 0 ? request.lead : known?.lead ?? null,
80484
80582
  claim: request.claim !== void 0 ? request.claim : known?.claim ?? null,
80485
80583
  claimedAt: request.claimedAt !== void 0 ? request.claimedAt : known?.claimedAt ?? null,
@@ -80534,7 +80632,7 @@ function missionRecord(node2, mission, declared, stored) {
80534
80632
  result: stored?.result ?? null,
80535
80633
  question: stored?.question ?? null,
80536
80634
  questions: stored?.questions ?? [],
80537
- giver: stored?.giver ?? null,
80635
+ receivers: stored?.receivers ?? [],
80538
80636
  lead: stored?.lead ?? null,
80539
80637
  lastActivity: null,
80540
80638
  claim: stored?.claim ?? null,
@@ -80642,9 +80740,9 @@ var Telemetry = class {
80642
80740
  async drain(timeoutMs) {
80643
80741
  if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) return true;
80644
80742
  let done = null;
80645
- const empty = new Promise((resolve25) => {
80743
+ const empty = new Promise((resolve27) => {
80646
80744
  done = () => {
80647
- if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve25(true);
80745
+ if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve27(true);
80648
80746
  };
80649
80747
  this.drained.add(done);
80650
80748
  });
@@ -80897,7 +80995,7 @@ var SessionHost = class {
80897
80995
  transition: missionTransition(before?.state ?? "open", after.state, after.question),
80898
80996
  reason: change?.note ?? null,
80899
80997
  by: change?.by ?? "node",
80900
- giver: after.giver,
80998
+ receivers: after.receivers,
80901
80999
  sessionId: after.sessionId,
80902
81000
  lead: after.lead,
80903
81001
  question: after.question,
@@ -80905,7 +81003,7 @@ var SessionHost = class {
80905
81003
  });
80906
81004
  }
80907
81005
  missionWritten(mission, team, by) {
80908
- 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 });
81006
+ 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 });
80909
81007
  }
80910
81008
  async snapshot() {
80911
81009
  const definition = await this.definitionOf(this.options.defaultWorkspace);
@@ -80934,7 +81032,7 @@ var SessionHost = class {
80934
81032
  retiring: note?.retiring ?? null
80935
81033
  };
80936
81034
  }), ...[...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 }))],
80937
- 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 })),
81035
+ 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 })),
80938
81036
  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 })),
80939
81037
  jobs: [...this.openJobs].map(([jobId, open2]) => ({ ...open2.context, jobId, since: new Date(open2.since).toISOString(), via: open2.via })),
80940
81038
  tokens: this.telemetry.tokens(),
@@ -80988,19 +81086,48 @@ var SessionHost = class {
80988
81086
  this.redeliverQuestions(id);
80989
81087
  return info;
80990
81088
  }
80991
- redeliverQuestions(giverId) {
81089
+ redeliverQuestions(sessionId) {
81090
+ const session = this.sessions.get(sessionId);
81091
+ if (!session) return;
80992
81092
  for (const stored of this.missions.all()) {
80993
- if (stored.giver !== giverId || !stored.sessionId || stored.state === "paused" || !hasLead(stored.state)) continue;
80994
- 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);
80995
- for (const asked2 of stored.questions) {
80996
- 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);
80997
- }
81093
+ if (!stored.receivers.some((key2) => this.receives(session, key2))) continue;
81094
+ for (const open2 of this.openQuestions(stored)) if (!this.questionOpen(sessionId, open2.mission, open2.questionId)) this.deliverQuestion(sessionId, open2, stored.sessionId);
80998
81095
  }
80999
81096
  }
81000
- questionOpen(giverId, mission, questionId) {
81001
- if (this.questionsInFlight.has(`${giverId}:${mission}:${questionId ?? ""}`)) return true;
81097
+ openQuestions(stored) {
81098
+ if (!stored.sessionId || stored.state === "paused" || !hasLead(stored.state)) return [];
81099
+ const base2 = { team: stored.team, mission: stored.mission, lead: stored.lead };
81100
+ return [
81101
+ ...stored.state === "waiting" ? [{ ...base2, question: stored.question ?? NO_HANDOFF_TELL, questionId: null }] : [],
81102
+ ...stored.questions.map((asked2) => ({ ...base2, question: asked2.question, questionId: asked2.id }))
81103
+ ];
81104
+ }
81105
+ receiverKey(session) {
81106
+ return session.external?.externalId ? externalReceiver(session.external.externalId) : session.id;
81107
+ }
81108
+ receives(session, key2) {
81109
+ return key2 === session.id || key2 === this.receiverKey(session);
81110
+ }
81111
+ liveReceivers(receivers, except = null) {
81112
+ 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)));
81113
+ }
81114
+ keyOfRemote(receiver) {
81115
+ if (receiver.node !== this.node) return receiverRef(receiver);
81116
+ const session = this.sessions.get(receiver.sessionId);
81117
+ return session ? this.receiverKey(session) : receiver.sessionId;
81118
+ }
81119
+ starterKey(by) {
81120
+ if (!by.startsWith("session:")) return null;
81121
+ const named2 = by.slice("session:".length);
81122
+ const remote = remoteReceiverOf(named2);
81123
+ if (remote) return this.keyOfRemote(remote);
81124
+ const session = this.sessions.get(named2);
81125
+ return session ? this.receiverKey(session) : named2;
81126
+ }
81127
+ questionOpen(sessionId, mission, questionId) {
81128
+ if (this.questionsInFlight.has(`${sessionId}:${mission}:${questionId ?? ""}`)) return true;
81002
81129
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
81003
- if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== giverId) continue;
81130
+ if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== sessionId) continue;
81004
81131
  const task = this.tasks.get(taskId);
81005
81132
  if (task && !(task.status && isTerminal(task.status.state))) return true;
81006
81133
  this.questionDeliveries.delete(taskId);
@@ -81032,7 +81159,7 @@ var SessionHost = class {
81032
81159
  async settleExternal(sessionId, settle2) {
81033
81160
  const session = this.sessions.get(sessionId);
81034
81161
  if (!session?.external) throw new SessionHostError("not-found", `There is no attached session ${sessionId} on this node.`);
81035
- const taskId = settle2.taskId ?? session.running;
81162
+ const taskId = settle2.taskId ?? session.running ?? session.external.open.keys().next().value ?? null;
81036
81163
  const task = taskId ? this.tasks.get(taskId) : void 0;
81037
81164
  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.`);
81038
81165
  await this.finish(sessionId, task, "TASK_STATE_COMPLETED", settle2.text);
@@ -81066,23 +81193,21 @@ var SessionHost = class {
81066
81193
  await this.finish(session.id, task, "TASK_STATE_FAILED", `The attached session ${session.id} is detached.`);
81067
81194
  return;
81068
81195
  }
81069
- session.running = task.id;
81196
+ const asked2 = this.questionOf(message);
81197
+ if (asked2 === null) session.running = task.id;
81070
81198
  await this.update(session.id, task, "TASK_STATE_WORKING");
81071
81199
  const fromAgent = message.metadata?.[ORBITAL_META.fromAgent];
81072
81200
  const fromSession = message.metadata?.[ORBITAL_META.fromSession];
81073
- const asked2 = this.questionOf(message);
81074
81201
  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) };
81075
- const settled = new Promise((resolve25) => this.externalWaits.set(task.id, resolve25));
81202
+ const settled = new Promise((resolve27) => this.externalWaits.set(task.id, resolve27)).finally(() => {
81203
+ this.externalWaits.delete(task.id);
81204
+ external.open.delete(task.id);
81205
+ if (session.running === task.id) session.running = null;
81206
+ });
81076
81207
  if (asked2 !== null) external.open.set(task.id, delivery);
81077
81208
  if (external.listeners.size === 0) external.waiting.push(delivery);
81078
81209
  for (const listener of external.listeners) listener(delivery);
81079
- try {
81080
- await settled;
81081
- } finally {
81082
- this.externalWaits.delete(task.id);
81083
- external.open.delete(task.id);
81084
- session.running = null;
81085
- }
81210
+ if (asked2 === null) await settled;
81086
81211
  }
81087
81212
  stopFollowing() {
81088
81213
  this.following.abort();
@@ -81097,9 +81222,9 @@ var SessionHost = class {
81097
81222
  if (this.followingHub) return;
81098
81223
  let listener = null;
81099
81224
  try {
81100
- await Promise.race([this.settled(), new Promise((resolve25) => {
81101
- listener = resolve25;
81102
- this.followListeners.add(resolve25);
81225
+ await Promise.race([this.settled(), new Promise((resolve27) => {
81226
+ listener = resolve27;
81227
+ this.followListeners.add(resolve27);
81103
81228
  })]);
81104
81229
  } finally {
81105
81230
  if (listener) this.followListeners.delete(listener);
@@ -81190,7 +81315,7 @@ var SessionHost = class {
81190
81315
  const leadLives = known?.sessionId ? this.sessions.has(known.sessionId) : false;
81191
81316
  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}.`);
81192
81317
  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.`);
81193
- 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.`);
81318
+ 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.`);
81194
81319
  const ended = known && hasLead(known.state) && !leadLives ? `the lead session ${known.sessionId ?? "(none)"} ended with its node` : null;
81195
81320
  if (known?.state === "done") throw new SessionHostError("invalid", `The mission ${missionId} is done. Give the team a new mission for new work.`);
81196
81321
  const claim2 = declared?.source ?? missionId;
@@ -81201,12 +81326,13 @@ var SessionHost = class {
81201
81326
  const resumable = ended && known?.sessionId && readSessionRecord(this.paths.sessionsDir, known.sessionId) !== null ? known.sessionId : null;
81202
81327
  if (resumable) {
81203
81328
  const task2 = await this.submit({ sessionId: resumable, message });
81204
- 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 });
81329
+ const starter = this.starterKey(by);
81330
+ 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] : []])] });
81205
81331
  return { task: task2, duplicate: false };
81206
81332
  }
81207
81333
  const task = await this.submit({ message, start: { agent: team, workspaceDir, team, mission } });
81208
81334
  const takenOver = held2.map(claimHolder);
81209
- 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 });
81335
+ 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 });
81210
81336
  return { task, duplicate: false };
81211
81337
  }
81212
81338
  pauseInterrupted() {
@@ -81309,7 +81435,7 @@ var SessionHost = class {
81309
81435
  for (const { relay: port } of this.options.ports.missionRelays()) {
81310
81436
  const answered = await port.reply({ missionId, text: text3, ...questionId && { questionId }, by, ...this.claimOfBy(by) });
81311
81437
  if (!answered) continue;
81312
- await this.closeQuestions(missionId, questionId ?? void 0, `answered: ${text3}`);
81438
+ await this.closeQuestions(missionId, questionId ?? void 0, by, text3);
81313
81439
  return answered;
81314
81440
  }
81315
81441
  }
@@ -81320,14 +81446,14 @@ var SessionHost = class {
81320
81446
  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.`);
81321
81447
  const lead = known.sessionId;
81322
81448
  this.missions.change(missionId, { state: "active", by, note: "reply" });
81323
- await this.closeQuestions(missionId, null, `answered: ${text3}`);
81449
+ await this.closeQuestions(missionId, null, by, text3);
81324
81450
  await this.submit({ sessionId: lead, message: { messageId: randomUUID4(), role: "ROLE_USER", parts: [textPart(`[reply to your question on the mission ${missionId}, from ${by}]
81325
81451
  ${text3}`)] } });
81326
81452
  return this.missionOf(missionId);
81327
81453
  }
81328
81454
  async answerAsked(by, missionId, lead, asked2, text3) {
81329
81455
  this.missions.answer(missionId, asked2.id);
81330
- await this.closeQuestions(missionId, asked2.id, `answered: ${text3}`);
81456
+ await this.closeQuestions(missionId, asked2.id, by, text3);
81331
81457
  const session = this.sessions.get(lead);
81332
81458
  if (session?.running && !session.external) {
81333
81459
  this.steering.set(lead, [...this.steering.get(lead) ?? [], { from: by, missionId, text: `reply to your question ${asked2.id}: ${asked2.question}
@@ -81344,7 +81470,7 @@ ${text3}`)] } });
81344
81470
  const missionId = given.mission;
81345
81471
  const known = this.missions.get(missionId);
81346
81472
  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.`);
81347
- 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}.`);
81473
+ 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}.`);
81348
81474
  this.askedInTurn.add(known.sessionId);
81349
81475
  this.recordQuestion(missionId, known.sessionId, given.question);
81350
81476
  return this.missionOf(missionId);
@@ -81352,8 +81478,8 @@ ${text3}`)] } });
81352
81478
  recordQuestion(missionId, sessionId, question) {
81353
81479
  const asked2 = { id: `q-${randomUUID4().slice(0, 8)}`, question, at: new Date(this.options.context.now()).toISOString() };
81354
81480
  const after = this.missions.ask(missionId, asked2);
81355
- 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 });
81356
- this.tellGiver({ team: after.team, mission: missionId, giver: after.giver, lead: after.lead, question: asked2.question, questionId: asked2.id }, sessionId);
81481
+ 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 });
81482
+ this.tellReceivers(after.receivers, { team: after.team, mission: missionId, lead: after.lead, question: asked2.question, questionId: asked2.id }, sessionId);
81357
81483
  }
81358
81484
  async say(by, target, text3, reader, relay = true) {
81359
81485
  const named2 = this.missions.get(target);
@@ -81409,16 +81535,28 @@ ${text3}`)] } });
81409
81535
  const mission = message.metadata?.[ORBITAL_META.mission];
81410
81536
  return typeof mission === "string" ? mission : null;
81411
81537
  }
81412
- async closeQuestions(mission, questionId, text3) {
81538
+ async closeQuestions(mission, questionId, by, text3) {
81539
+ const answerer = by.startsWith("session:") ? by.slice("session:".length) : null;
81540
+ const told = /* @__PURE__ */ new Set();
81413
81541
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
81414
81542
  if (asked2.mission !== mission || questionId !== void 0 && asked2.questionId !== questionId) continue;
81415
81543
  this.questionDeliveries.delete(taskId);
81416
81544
  const task = this.tasks.get(taskId);
81417
- const giver = this.taskSession.get(taskId);
81418
- if (!task || !giver || task.status && isTerminal(task.status.state)) continue;
81419
- await this.finish(giver, task, "TASK_STATE_COMPLETED", text3);
81545
+ const receiver = this.taskSession.get(taskId);
81546
+ if (!task || !receiver || task.status && isTerminal(task.status.state)) continue;
81547
+ await this.finish(receiver, task, "TASK_STATE_COMPLETED", `answered by ${by}: ${text3}`);
81420
81548
  this.externalWaits.get(taskId)?.();
81549
+ if (receiver !== answerer) this.tellAnswered(receiver, { kind: "answered", mission, question: asked2.questionId, by, text: text3 }, told);
81421
81550
  }
81551
+ 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);
81552
+ }
81553
+ tellAnswered(sessionId, answered, told) {
81554
+ const external = this.sessions.get(sessionId)?.external;
81555
+ if (!external?.attached || told.has(sessionId)) return;
81556
+ told.add(sessionId);
81557
+ const answers = (delivery) => delivery.mission === answered.mission && (answered.question === null || delivery.question === answered.question);
81558
+ for (let index = external.waiting.length - 1; index >= 0; index -= 1) if (answers(external.waiting[index])) external.waiting.splice(index, 1);
81559
+ for (const listener of external.listeners) listener(answered);
81422
81560
  }
81423
81561
  busyBelow(sessionId) {
81424
81562
  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);
@@ -81445,32 +81583,46 @@ ${text3}`)] } });
81445
81583
  }
81446
81584
  this.nudged.delete(sessionId);
81447
81585
  this.missions.change(known.mission, { state: "waiting", by: `session:${sessionId}`, note: question === "" ? NO_HANDOFF_NOTE : stateWord(state), question: question === "" ? null : question });
81448
- this.tellGiver({ ...known, question: question === "" ? NO_HANDOFF_TELL : question, questionId: null }, sessionId);
81586
+ this.tellReceivers(known.receivers, { team: known.team, mission: known.mission, lead: known.lead, question: question === "" ? NO_HANDOFF_TELL : question, questionId: null }, sessionId);
81449
81587
  }
81450
- async joinMission(sessionId, missionId) {
81451
- const session = this.sessions.get(sessionId);
81452
- if (!session?.external) throw new SessionHostError("invalid", `Only an attached session joins a mission as its giver, and ${sessionId} is not one.`);
81588
+ async joinMission(receiver, missionId) {
81589
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
81590
+ if ("sessionId" in receiver && !session) throw new SessionHostError("not-found", `There is no session ${receiver.sessionId} on this node.`);
81453
81591
  const known = this.missions.get(missionId);
81454
- if (!known) {
81592
+ if (!known && session) {
81455
81593
  for (const { relay } of this.options.ports.missionRelays()) {
81456
- const joined = await relay.join({ missionId, giver: { node: this.node, sessionId }, ...this.claimOfSession(sessionId) });
81594
+ const joined = await relay.join({ missionId, receiver: { node: this.node, sessionId: session.id }, ...this.claimOfSession(session.id) });
81457
81595
  if (joined) return joined;
81458
81596
  }
81459
81597
  }
81460
- return this.joinHere(giverRef({ node: this.node, sessionId }), `session:${sessionId}`, missionId);
81598
+ 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.`);
81599
+ if (!session) return this.joinHere(externalReceiver("externalId" in receiver ? receiver.externalId : ""), "cli", missionId, null);
81600
+ return this.joinHere(this.receiverKey(session), `session:${session.id}`, missionId, [session.id]);
81461
81601
  }
81462
81602
  async joinRemote(request) {
81463
- return this.joinHere(giverRef(request.giver), `session:${giverRef(request.giver)}`, request.missionId);
81603
+ const key2 = this.keyOfRemote(request.receiver);
81604
+ const local = remoteReceiverOf(key2) === null ? [request.receiver.sessionId] : null;
81605
+ return this.joinHere(key2, `session:${receiverRef(request.receiver)}`, request.missionId, local);
81464
81606
  }
81465
- async joinHere(giverId, by, missionId) {
81466
- const self = this.node;
81467
- const sessionId = remoteGiverOf(giverId)?.node === self ? remoteGiverOf(giverId).sessionId : giverId;
81607
+ async leaveMission(receiver, missionId) {
81608
+ const known = this.missions.get(missionId);
81609
+ 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.`);
81610
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
81611
+ const leaves = (key2) => session ? this.receives(session, key2) : "sessionId" in receiver ? key2 === receiver.sessionId : externalIdOf(key2) === receiver.externalId;
81612
+ const kept = known.receivers.filter((key2) => !leaves(key2));
81613
+ if (kept.length === known.receivers.length) return this.missionOf(missionId);
81614
+ 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 });
81615
+ return this.missionOf(missionId);
81616
+ }
81617
+ async joinHere(key2, by, missionId, deliverTo) {
81468
81618
  const known = this.missions.get(missionId);
81469
81619
  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.`);
81470
- if (known.giver !== sessionId) {
81471
- const changed = this.missions.change(missionId, { state: known.state, by, note: `joined as giver${known.giver ? `, in place of session ${known.giver}` : ""}`, giver: sessionId });
81472
- if (changed.state === "waiting") this.tellGiver({ ...changed, question: changed.question ?? NO_HANDOFF_TELL, questionId: null }, changed.sessionId);
81473
- if (changed.state !== "paused") for (const asked2 of changed.questions) this.tellGiver({ ...changed, question: asked2.question, questionId: asked2.id }, changed.sessionId);
81620
+ 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] });
81621
+ const remote = remoteReceiverOf(key2);
81622
+ const live2 = this.liveReceivers([key2]).filter((session) => deliverTo === null || deliverTo.includes(session.id));
81623
+ for (const open2 of this.openQuestions(stored)) {
81624
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, stored.sessionId);
81625
+ for (const session of live2) if (!this.questionOpen(session.id, open2.mission, open2.questionId)) this.deliverQuestion(session.id, open2, stored.sessionId);
81474
81626
  }
81475
81627
  return this.missionOf(missionId);
81476
81628
  }
@@ -81480,36 +81632,34 @@ ${text3}`)] } });
81480
81632
  missionTeam(missionId) {
81481
81633
  return this.missions.get(missionId)?.team ?? null;
81482
81634
  }
81483
- async askGiver(question) {
81484
- const giver = this.sessions.get(question.giver.sessionId);
81485
- if (!giver?.external || question.giver.node !== this.node) return false;
81486
- 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);
81635
+ async askReceiver(question) {
81636
+ const session = this.sessions.get(question.receiver.sessionId);
81637
+ if (!session || question.receiver.node !== this.node || this.liveReceivers([session.id]).length === 0) return false;
81638
+ 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);
81487
81639
  return true;
81488
81640
  }
81489
- tellGiver(known, sessionId) {
81490
- const missionId = known.mission;
81491
- const remote = known.giver ? remoteGiverOf(known.giver) : null;
81492
- const self = this.node;
81493
- if (remote && remote.node !== self) {
81494
- const question = { missionId, node: self, lead: known.lead, leadSessionId: sessionId, question: known.question, ...known.questionId && { questionId: known.questionId }, giver: remote, team: known.team };
81495
- void (async () => {
81496
- for (const { relay } of this.options.ports.missionRelays()) if (await relay.ask(question)) return;
81497
- this.options.context.output.warn(`no plugin carried the question of ${missionId} to ${known.giver}`);
81498
- })().catch((error62) => this.options.context.output.warn(`could not tell the giver of ${missionId}: ${error62 instanceof Error ? error62.message : String(error62)}`));
81499
- return;
81641
+ tellReceivers(receivers, open2, leadSessionId) {
81642
+ for (const key2 of receivers) {
81643
+ const remote = remoteReceiverOf(key2);
81644
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, leadSessionId);
81500
81645
  }
81501
- const giver = known.giver ? this.sessions.get(remote ? remote.sessionId : known.giver) : void 0;
81502
- if (!giver) return;
81503
- this.deliverQuestion(giver.id, known, sessionId);
81646
+ for (const session of this.liveReceivers(receivers)) this.deliverQuestion(session.id, open2, leadSessionId);
81647
+ }
81648
+ askRemote(receiver, open2, leadSessionId) {
81649
+ const question = { missionId: open2.mission, node: this.node, lead: open2.lead, leadSessionId, question: open2.question, ...open2.questionId && { questionId: open2.questionId }, receiver, team: open2.team };
81650
+ void (async () => {
81651
+ for (const { relay } of this.options.ports.missionRelays()) if (await relay.ask(question)) return;
81652
+ this.options.context.output.warn(`no plugin carried the question of ${open2.mission} to ${receiverRef(receiver)}`);
81653
+ })().catch((error62) => this.options.context.output.warn(`could not tell ${receiverRef(receiver)} the question of ${open2.mission}: ${error62 instanceof Error ? error62.message : String(error62)}`));
81504
81654
  }
81505
- deliverQuestion(giverId, known, sessionId) {
81655
+ deliverQuestion(receiverId, known, sessionId) {
81506
81656
  const missionId = known.mission;
81507
81657
  const question = known.question;
81508
81658
  const questionId = known.questionId;
81509
- const key2 = `${giverId}:${missionId}:${questionId ?? ""}`;
81659
+ const key2 = `${receiverId}:${missionId}:${questionId ?? ""}`;
81510
81660
  this.questionsInFlight.add(key2);
81511
81661
  void this.submit({
81512
- sessionId: giverId,
81662
+ sessionId: receiverId,
81513
81663
  message: {
81514
81664
  messageId: randomUUID4(),
81515
81665
  role: "ROLE_USER",
@@ -81524,7 +81674,7 @@ Answer with missionSay on ${missionId}.`)],
81524
81674
  }
81525
81675
  }).then((task) => {
81526
81676
  this.questionDeliveries.set(task.id, { mission: known.mission, questionId });
81527
- }).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));
81677
+ }).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));
81528
81678
  }
81529
81679
  async missionActivity(missionId, limit) {
81530
81680
  const sessionId = this.missions.get(missionId)?.sessionId;
@@ -81742,8 +81892,8 @@ Answer with missionSay on ${missionId}.`)],
81742
81892
  }
81743
81893
  }
81744
81894
  if (signal?.aborted) return;
81745
- await new Promise((resolve25) => {
81746
- wake = resolve25;
81895
+ await new Promise((resolve27) => {
81896
+ wake = resolve27;
81747
81897
  });
81748
81898
  wake = null;
81749
81899
  }
@@ -81819,9 +81969,13 @@ Answer with missionSay on ${missionId}.`)],
81819
81969
  }
81820
81970
  missionRoots(readerSessionId) {
81821
81971
  const outside = this.sessions.get(readerSessionId)?.external !== void 0;
81822
- 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));
81972
+ 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));
81823
81973
  return new Set(missionRoots);
81824
81974
  }
81975
+ receivesFor(sessionId, receivers) {
81976
+ const session = this.sessions.get(sessionId);
81977
+ return session !== void 0 && receivers.some((key2) => this.receives(session, key2));
81978
+ }
81825
81979
  reachRoots(readerSessionId) {
81826
81980
  const outside = this.sessions.get(readerSessionId)?.external !== void 0;
81827
81981
  const attachedRoots = outside ? [...this.sessions.values()].filter((one) => one.external?.attached === true).map((one) => this.rootOf(one.id)) : [];
@@ -83390,7 +83544,7 @@ var StdioClientTransport = class {
83390
83544
  if (this._process) {
83391
83545
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
83392
83546
  }
83393
- return new Promise((resolve25, reject) => {
83547
+ return new Promise((resolve27, reject) => {
83394
83548
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
83395
83549
  // merge default env with server env because mcp server needs some env vars
83396
83550
  env: {
@@ -83407,7 +83561,7 @@ var StdioClientTransport = class {
83407
83561
  this.onerror?.(error62);
83408
83562
  });
83409
83563
  this._process.on("spawn", () => {
83410
- resolve25();
83564
+ resolve27();
83411
83565
  });
83412
83566
  this._process.on("close", (_code) => {
83413
83567
  this._process = void 0;
@@ -83472,22 +83626,22 @@ var StdioClientTransport = class {
83472
83626
  if (this._process) {
83473
83627
  const processToClose = this._process;
83474
83628
  this._process = void 0;
83475
- const closePromise = new Promise((resolve25) => {
83629
+ const closePromise = new Promise((resolve27) => {
83476
83630
  processToClose.once("close", () => {
83477
- resolve25();
83631
+ resolve27();
83478
83632
  });
83479
83633
  });
83480
83634
  try {
83481
83635
  processToClose.stdin?.end();
83482
83636
  } catch {
83483
83637
  }
83484
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
83638
+ await Promise.race([closePromise, new Promise((resolve27) => setTimeout(resolve27, 2e3).unref())]);
83485
83639
  if (processToClose.exitCode === null) {
83486
83640
  try {
83487
83641
  processToClose.kill("SIGTERM");
83488
83642
  } catch {
83489
83643
  }
83490
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
83644
+ await Promise.race([closePromise, new Promise((resolve27) => setTimeout(resolve27, 2e3).unref())]);
83491
83645
  }
83492
83646
  if (processToClose.exitCode === null) {
83493
83647
  try {
@@ -83499,15 +83653,15 @@ var StdioClientTransport = class {
83499
83653
  this._readBuffer.clear();
83500
83654
  }
83501
83655
  send(message) {
83502
- return new Promise((resolve25) => {
83656
+ return new Promise((resolve27) => {
83503
83657
  if (!this._process?.stdin) {
83504
83658
  throw new Error("Not connected");
83505
83659
  }
83506
83660
  const json4 = serializeMessage(message);
83507
83661
  if (this._process.stdin.write(json4)) {
83508
- resolve25();
83662
+ resolve27();
83509
83663
  } else {
83510
- this._process.stdin.once("drain", resolve25);
83664
+ this._process.stdin.once("drain", resolve27);
83511
83665
  }
83512
83666
  });
83513
83667
  }
@@ -83601,12 +83755,12 @@ var StdioServerTransport = class {
83601
83755
  this.onclose?.();
83602
83756
  }
83603
83757
  send(message) {
83604
- return new Promise((resolve25) => {
83758
+ return new Promise((resolve27) => {
83605
83759
  const json4 = serializeMessage(message);
83606
83760
  if (this._stdout.write(json4)) {
83607
- resolve25();
83761
+ resolve27();
83608
83762
  } else {
83609
- this._stdout.once("drain", resolve25);
83763
+ this._stdout.once("drain", resolve27);
83610
83764
  }
83611
83765
  });
83612
83766
  }
@@ -83718,6 +83872,7 @@ function agentTools(options) {
83718
83872
  };
83719
83873
  const send = (message, wait) => node2.send(message, wait);
83720
83874
  const a2a = () => node2.a2a();
83875
+ const own2 = async () => options.attached ? await options.attached() : options.sessionId;
83721
83876
  return [
83722
83877
  defineMcpTool({
83723
83878
  name: "agentList",
@@ -83761,7 +83916,7 @@ function agentTools(options) {
83761
83916
  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.",
83762
83917
  inputSchema: {},
83763
83918
  run: async () => {
83764
- if (!options.sessionId) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
83919
+ if (!await own2()) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
83765
83920
  return await node2.peers();
83766
83921
  }
83767
83922
  }),
@@ -83841,13 +83996,13 @@ function agentTools(options) {
83841
83996
  }),
83842
83997
  defineMcpTool({
83843
83998
  name: "missionList",
83844
- 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.",
83999
+ 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.",
83845
84000
  inputSchema: {},
83846
84001
  run: async () => node2.missions()
83847
84002
  }),
83848
84003
  defineMcpTool({
83849
84004
  name: "missionShow",
83850
- 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.",
84005
+ 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.",
83851
84006
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
83852
84007
  run: async (params) => {
83853
84008
  const found = (await node2.missions()).filter((record3) => record3.missionId === params.mission);
@@ -83899,7 +84054,7 @@ function agentTools(options) {
83899
84054
  }),
83900
84055
  defineMcpTool({
83901
84056
  name: "missionReply",
83902
- 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.",
84057
+ 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.",
83903
84058
  inputSchema: {
83904
84059
  mission: MissionIdSchema.describe("The mission id."),
83905
84060
  text: external_exports.string().min(1).describe("The answer, in full: the lead has none of your context."),
@@ -83933,13 +84088,22 @@ function agentTools(options) {
83933
84088
  }),
83934
84089
  defineMcpTool({
83935
84090
  name: "missionJoin",
83936
- 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.",
84091
+ 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.",
83937
84092
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
83938
84093
  run: async (params) => {
83939
- if (!options.sessionId) throw new Error("missionJoin needs your own session: only a session of the node becomes the giver of a mission.");
84094
+ 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.");
83940
84095
  return node2.joinMission(params.mission);
83941
84096
  }
83942
84097
  }),
84098
+ defineMcpTool({
84099
+ name: "missionLeave",
84100
+ 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.",
84101
+ inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
84102
+ run: async (params) => {
84103
+ if (!await own2()) throw new Error("missionLeave needs your own session: only a decision receiver leaves a mission.");
84104
+ return node2.leaveMission(params.mission);
84105
+ }
84106
+ }),
83943
84107
  defineMcpTool({
83944
84108
  name: "missionNew",
83945
84109
  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.",
@@ -83959,7 +84123,7 @@ function agentTools(options) {
83959
84123
  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.",
83960
84124
  inputSchema: {},
83961
84125
  run: async () => {
83962
- if (!options.sessionId) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
84126
+ if (!await own2()) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
83963
84127
  return await node2.mission();
83964
84128
  }
83965
84129
  }),
@@ -83971,7 +84135,7 @@ function agentTools(options) {
83971
84135
  state: external_exports.enum(["done", "failed"]).optional().describe("done when the end state is reached, failed when it cannot be. Default: done.")
83972
84136
  },
83973
84137
  run: async (params) => {
83974
- if (!options.sessionId) throw new Error("missionHandoff needs your own session: only the lead session of a mission hands it off.");
84138
+ if (!await own2()) throw new Error("missionHandoff needs your own session: only the lead session of a mission hands it off.");
83975
84139
  return await node2.handoff({ result: params.result, state: params.state ?? "done" });
83976
84140
  }
83977
84141
  }),
@@ -83984,18 +84148,18 @@ function agentTools(options) {
83984
84148
  state: external_exports.string().min(1).optional().describe("The state of the linked thing, such as open, merged or closed.")
83985
84149
  },
83986
84150
  run: async (params) => {
83987
- if (!options.sessionId) throw new Error("missionLink needs your own session: only the lead session of a mission adds a link to it.");
84151
+ if (!await own2()) throw new Error("missionLink needs your own session: only the lead session of a mission adds a link to it.");
83988
84152
  return await node2.link({ label: params.label, url: params.url, state: params.state ?? null });
83989
84153
  }
83990
84154
  }),
83991
84155
  defineMcpTool({
83992
84156
  name: "missionAsk",
83993
- 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.",
84157
+ 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.",
83994
84158
  inputSchema: {
83995
- 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.")
84159
+ 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.")
83996
84160
  },
83997
84161
  run: async (params) => {
83998
- if (!options.sessionId) throw new Error("missionAsk needs your own session: only the lead session of a mission asks its giver.");
84162
+ if (!await own2()) throw new Error("missionAsk needs your own session: only the lead session of a mission asks its decision receivers.");
83999
84163
  return await node2.ask({ question: params.question });
84000
84164
  }
84001
84165
  })
@@ -84562,8 +84726,8 @@ function relaySender(plugin, by) {
84562
84726
  function pluginSender(plugin) {
84563
84727
  return { channel: plugin, peer: plugin, from: { kind: "node", name: plugin } };
84564
84728
  }
84565
- function remoteSessionSender(plugin, giver) {
84566
- return { channel: plugin, peer: giver.node, from: { kind: "session", name: giver.sessionId, ade: giver.node } };
84729
+ function remoteSessionSender(plugin, session) {
84730
+ return { channel: plugin, peer: session.node, from: { kind: "session", name: session.sessionId, ade: session.node } };
84567
84731
  }
84568
84732
  function missionTarget(node2, team) {
84569
84733
  return team ? { kind: "team", name: team, ade: node2 } : { kind: "node", name: node2 };
@@ -84702,10 +84866,10 @@ function a2aHandlerFor(host, reader, confined = false, network = null) {
84702
84866
  };
84703
84867
  }
84704
84868
  function readBody(request) {
84705
- return new Promise((resolve25, reject) => {
84869
+ return new Promise((resolve27, reject) => {
84706
84870
  const chunks = [];
84707
84871
  request.on("data", (chunk) => chunks.push(chunk));
84708
- request.on("end", () => resolve25(Buffer.concat(chunks).toString("utf8")));
84872
+ request.on("end", () => resolve27(Buffer.concat(chunks).toString("utf8")));
84709
84873
  request.on("error", reject);
84710
84874
  });
84711
84875
  }
@@ -84769,7 +84933,7 @@ async function serveHostApi(request, response, url2, api) {
84769
84933
  if (request.method === "POST" && url2.pathname === "/orbital/host/missions/ask") {
84770
84934
  const asked2 = MissionQuestionSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
84771
84935
  if (!asked2.success) return json3(response, 400, { error: asked2.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ") });
84772
- return json3(response, 200, { delivered: await api.askGiver(asked2.data) });
84936
+ return json3(response, 200, { delivered: await api.askReceiver(asked2.data) });
84773
84937
  }
84774
84938
  if (request.method === "POST" && url2.pathname === "/orbital/host/settled") {
84775
84939
  const { orFollowing } = JSON.parse(await readBody(request) || "{}");
@@ -84888,11 +85052,16 @@ async function startNodeServer(options) {
84888
85052
  const by = caller.bound ? `session:${caller.bound}` : "cli";
84889
85053
  return json3(response, 200, control[1] === "pause" ? await options.host.pauseMission(by, asked2.data.missionId) : await options.host.resumeMission(by, asked2.data.missionId));
84890
85054
  }
84891
- if (request.method === "POST" && url2.pathname === "/orbital/missions/join") {
84892
- if (!caller.bound) return json3(response, 403, { error: "Only an attached session joins a mission as its giver: present its session token." });
85055
+ const membership = /^\/orbital\/missions\/(join|leave)$/.exec(url2.pathname);
85056
+ if (request.method === "POST" && membership) {
84893
85057
  const asked2 = MissionJoinSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
84894
85058
  if (!asked2.success) return json3(response, 400, { error: asked2.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ") });
84895
- return json3(response, 200, await options.host.joinMission(caller.bound, asked2.data.missionId));
85059
+ const session = caller.bound ?? caller.reader;
85060
+ const holder = caller.presented === caller.token;
85061
+ 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." });
85062
+ 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.` });
85063
+ const receiver = asked2.data.externalId ? { externalId: asked2.data.externalId } : { sessionId: session };
85064
+ return json3(response, 200, membership[1] === "join" ? await options.host.joinMission(receiver, asked2.data.missionId) : await options.host.leaveMission(receiver, asked2.data.missionId));
84896
85065
  }
84897
85066
  if (request.method === "POST" && url2.pathname === "/orbital/missions/new") {
84898
85067
  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." });
@@ -84930,7 +85099,7 @@ async function startNodeServer(options) {
84930
85099
  const named2 = asked2.data.missionId ?? own2?.missionId ?? null;
84931
85100
  if (!named2) return json3(response, 400, { error: "Name the mission by its id." });
84932
85101
  if (own2 && own2.missionId !== named2) return json3(response, 403, { error: `The session ${session} works on the mission ${own2.missionId}, not on ${named2}.` });
84933
- 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." });
85102
+ 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." });
84934
85103
  return json3(response, 200, await options.host.askMission({ mission: named2, question: asked2.data.question, sessionId: session }));
84935
85104
  }
84936
85105
  const activity = /^\/orbital\/missions\/([^/]+)\/activity$/.exec(url2.pathname);
@@ -84971,6 +85140,20 @@ async function startNodeServer(options) {
84971
85140
  return json3(response, 502, { error: `The panel ${plugin2}/${id} did not answer: ${error62 instanceof Error ? error62.message : String(error62)}` });
84972
85141
  }
84973
85142
  }
85143
+ const commandPath = /^\/orbital\/plugins\/([^/]+)\/commands\/([^/]+)$/.exec(url2.pathname);
85144
+ if (request.method === "POST" && commandPath) {
85145
+ if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret runs a command of a plugin." });
85146
+ const [name, command] = [decodeURIComponent(commandPath[1]), decodeURIComponent(commandPath[2])];
85147
+ const plugin2 = options.ports.plugins.find((one) => one.name === name);
85148
+ if (!plugin2?.command) return json3(response, 404, { error: `No plugin ${name} with commands runs on this node.` });
85149
+ const asked2 = PluginCommandRequestSchema.safeParse(await readBody(request).then((raw) => JSON.parse(raw)).catch(() => null));
85150
+ 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.` });
85151
+ try {
85152
+ return json3(response, 200, await plugin2.command(asked2.data));
85153
+ } catch (error62) {
85154
+ return json3(response, 502, { error: `The command ${name} ${command} failed: ${error62 instanceof Error ? error62.message : String(error62)}` });
85155
+ }
85156
+ }
84974
85157
  if (url2.pathname === "/orbital/telemetry/snapshot" || url2.pathname === "/orbital/telemetry/events") {
84975
85158
  if (request.method === "GET" && url2.pathname === "/orbital/telemetry/snapshot" && (presented === token || bound)) return json3(response, 200, await options.host.snapshot());
84976
85159
  if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret reads the telemetry stream of the node." });
@@ -85130,7 +85313,7 @@ async function startNodeServer(options) {
85130
85313
  const result2 = await dispatchA2a(await readBody(request), a2aHandlerFor(options.host, reader, bound !== null, httpSender(request.socket.remoteAddress)));
85131
85314
  if (result2.kind === "json") return json3(response, 200, result2.body);
85132
85315
  response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
85133
- const closed = new Promise((resolve25) => response.on("close", resolve25));
85316
+ const closed = new Promise((resolve27) => response.on("close", resolve27));
85134
85317
  const iterator = result2.events[Symbol.asyncIterator]();
85135
85318
  for (; ; ) {
85136
85319
  const next = await Promise.race([iterator.next(), closed.then(() => null)]);
@@ -85143,9 +85326,9 @@ async function startNodeServer(options) {
85143
85326
  }
85144
85327
  response.end();
85145
85328
  }
85146
- await new Promise((resolve25, reject) => {
85329
+ await new Promise((resolve27, reject) => {
85147
85330
  server.once("error", reject);
85148
- server.listen(options.port, address, () => resolve25());
85331
+ server.listen(options.port, address, () => resolve27());
85149
85332
  });
85150
85333
  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() };
85151
85334
  mkdirSync17(nodeDir(options.home, options.name), { recursive: true, mode: 448 });
@@ -85156,7 +85339,7 @@ async function startNodeServer(options) {
85156
85339
  close: async () => {
85157
85340
  if (readNodeInfo(options.home, options.name)?.pid === process.pid) rmSync9(nodeInfoPath(options.home, options.name), { force: true });
85158
85341
  server.closeAllConnections();
85159
- await new Promise((resolve25) => server.close(() => resolve25()));
85342
+ await new Promise((resolve27) => server.close(() => resolve27()));
85160
85343
  }
85161
85344
  };
85162
85345
  }
@@ -86166,7 +86349,7 @@ function hostApiOf(host, plugin, catalog, channels, models) {
86166
86349
  startMission: (start) => host.startMission(plugin, start),
86167
86350
  missions: () => host.missionList(),
86168
86351
  joinMission: async (request) => {
86169
- 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)));
86352
+ 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)));
86170
86353
  return host.joinRemote(request);
86171
86354
  },
86172
86355
  replyMission: async (request) => {
@@ -86181,9 +86364,9 @@ function hostApiOf(host, plugin, catalog, channels, models) {
86181
86364
  await relayed(request, `resume the mission ${request.missionId}`);
86182
86365
  return host.resumeMission(request.by, request.missionId, false);
86183
86366
  },
86184
- askGiver: async (question) => {
86185
- 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 })));
86186
- return host.askGiver(question);
86367
+ askReceiver: async (question) => {
86368
+ 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 })));
86369
+ return host.askReceiver(question);
86187
86370
  },
86188
86371
  sayMission: async (request) => {
86189
86372
  await relayed(request, request.text);
@@ -86399,8 +86582,8 @@ var ChannelHub = class {
86399
86582
  if (state && isTerminal(state)) return;
86400
86583
  }
86401
86584
  if (signal?.aborted) return;
86402
- await new Promise((resolve25) => {
86403
- wake = resolve25;
86585
+ await new Promise((resolve27) => {
86586
+ wake = resolve27;
86404
86587
  });
86405
86588
  wake = null;
86406
86589
  }
@@ -86574,8 +86757,8 @@ Answer yes or no in this thread.`)], metadata: { [ORBITAL_META.channel]: binding
86574
86757
  thread: null
86575
86758
  });
86576
86759
  const threadKey = `${binding.channel}|${binding.id}|${sent.thread ?? sent.ref}`;
86577
- const answer2 = await new Promise((resolve25, reject) => {
86578
- this.asks.set(threadKey, { resolve: resolve25, question, channel: binding.channel, since: new Date(this.options.context.now()).toISOString() });
86760
+ const answer2 = await new Promise((resolve27, reject) => {
86761
+ this.asks.set(threadKey, { resolve: resolve27, question, channel: binding.channel, since: new Date(this.options.context.now()).toISOString() });
86579
86762
  signal?.addEventListener("abort", () => {
86580
86763
  this.asks.delete(threadKey);
86581
86764
  reject(new Error("The question was withdrawn."));
@@ -87250,8 +87433,9 @@ var panel = {
87250
87433
  var panelCommands = [panel];
87251
87434
 
87252
87435
  // src/core/cli/plugin.ts
87253
- import { readFileSync as readFileSync28 } from "node:fs";
87254
- import { basename as basename9, resolve as resolve14 } from "node:path";
87436
+ var import_yaml8 = __toESM(require_dist(), 1);
87437
+ import { readFileSync as readFileSync29 } from "node:fs";
87438
+ import { basename as basename9, resolve as resolve15 } from "node:path";
87255
87439
 
87256
87440
  // src/sdk/index.ts
87257
87441
  init_protocol();
@@ -87632,13 +87816,144 @@ init_protocol();
87632
87816
 
87633
87817
  // src/core/cli/plugin.ts
87634
87818
  init_protocol();
87819
+
87820
+ // src/core/cli/plugin-commands.ts
87821
+ init_protocol();
87822
+ init_command();
87823
+ import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
87824
+ import { join as join37, resolve as resolve14 } from "node:path";
87825
+ var INTROSPECTION_WORDS = ["describe", "schema", "help"];
87826
+ function commandsOf(dir) {
87827
+ const descriptor = join37(dir, PLUGIN_PACKAGE_FILE);
87828
+ if (!existsSync30(descriptor)) return [];
87829
+ try {
87830
+ return PluginPackageSchema.parse(JSON.parse(readFileSync28(descriptor, "utf8"))).commands;
87831
+ } catch {
87832
+ return [];
87833
+ }
87834
+ }
87835
+ function declaredPluginCommands(workspaceDir, env3) {
87836
+ try {
87837
+ const source = definitionSource(workspaceDir, env3);
87838
+ const config2 = source ? workspaceConfig(workspaceDir, env3) : null;
87839
+ if (!source || !config2) return [];
87840
+ return Object.entries(config2.plugins).filter(([, entry]) => entry.enabled && entry.source).flatMap(([plugin, entry]) => {
87841
+ const from = pluginSource(plugin, entry);
87842
+ const commands = from.startsWith("exec:") ? [] : commandsOf(pluginDir(plugin, from, { harnessRoot: source.root, home: orbitalHome(env3) }));
87843
+ return commands.length > 0 ? [{ plugin, commands }] : [];
87844
+ });
87845
+ } catch {
87846
+ return [];
87847
+ }
87848
+ }
87849
+ function workspaceOf(input2, fallback = ".") {
87850
+ return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : fallback);
87851
+ }
87852
+ async function startWorkspacePlugin(context, workspaceDir, name, terminal2) {
87853
+ const root = definitionSource(workspaceDir, context.env)?.root ?? null;
87854
+ const config2 = workspaceConfig(workspaceDir, context.env);
87855
+ const entry = config2?.plugins[name];
87856
+ 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>` });
87857
+ const secrets = new PluginSecrets(context.env);
87858
+ return startRemotePlugin({
87859
+ name,
87860
+ source: pluginSource(name, entry),
87861
+ config: entry.config,
87862
+ entryEnv: entry.env,
87863
+ node: { name: `plugin-${name}`, workspaceDir, harness: defaultHarness(context.env), harnesses: [...RUNNER_HARNESSES], once: true, idleExitMs: null, project: null },
87864
+ roots: { harnessRoot: root, home: orbitalHome(context.env) },
87865
+ offline: config2.offline,
87866
+ policy: new PolicyChain([]),
87867
+ env: context.env,
87868
+ log: (level, message) => level === "info" ? context.output.step(message) : context.output.warn(message),
87869
+ secret: async (secret) => secrets.get(name, secret),
87870
+ restarts: 0,
87871
+ ...terminal2 && { terminal: terminal2 }
87872
+ });
87873
+ }
87874
+ function flagOf(option) {
87875
+ return {
87876
+ name: option.name,
87877
+ type: option.type,
87878
+ summary: option.summary,
87879
+ ...option.required && { required: true },
87880
+ ...option.default !== void 0 && { default: option.default },
87881
+ ...option.values && { enumValues: option.values }
87882
+ };
87883
+ }
87884
+ function requestOf(command, input2, terminal2) {
87885
+ const args = Object.fromEntries(command.args.flatMap((arg) => {
87886
+ const value2 = input2.positionals[arg.name];
87887
+ return value2 === void 0 ? [] : [[arg.name, value2]];
87888
+ }));
87889
+ const options = Object.fromEntries(command.options.flatMap((option) => {
87890
+ const value2 = input2.flags[option.name];
87891
+ if (option.type === "boolean") return [[option.name, value2 === true]];
87892
+ return typeof value2 === "string" || typeof value2 === "number" ? [[option.name, value2]] : [];
87893
+ }));
87894
+ return { name: command.name, args, options, terminal: terminal2 };
87895
+ }
87896
+ async function runOnNode(context, workspaceDir, input2, plugin, request) {
87897
+ const configured = workspaceConfig(workspaceDir, context.env)?.node ?? null;
87898
+ const name = chooseNode({ ...typeof input2.flags.node === "string" && { flag: input2.flags.node }, env: context.env, configured, workspaceDir }).name;
87899
+ const node2 = await liveNode(orbitalHome(context.env), name, context.fetch);
87900
+ 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` });
87901
+ const response = await context.fetch(`${node2.url}/orbital/plugins/${encodeURIComponent(plugin)}/commands/${encodeURIComponent(request.name)}`, {
87902
+ method: "POST",
87903
+ headers: { authorization: `Bearer ${node2.token}`, "content-type": "application/json" },
87904
+ body: JSON.stringify(request)
87905
+ });
87906
+ const body2 = await response.json();
87907
+ 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 });
87908
+ return PluginCommandResultSchema.parse(body2);
87909
+ }
87910
+ async function runInPlugin(context, workspaceDir, input2, plugin, command, terminal2) {
87911
+ if (command.interactive && context.nonInteractive) {
87912
+ 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." });
87913
+ }
87914
+ const given = command.interactive ? { input: terminal2.input, output: context.outputMode === "text" ? terminal2.output : terminal2.error, error: terminal2.error } : void 0;
87915
+ const started = await startWorkspacePlugin(context, workspaceDir, plugin, given);
87916
+ try {
87917
+ return await started.command(requestOf(command, input2, given ? { tty: context.isTty } : null));
87918
+ } finally {
87919
+ await started.close?.();
87920
+ }
87921
+ }
87922
+ function specOf(workspaceDir, plugin, command, terminal2) {
87923
+ return {
87924
+ path: [plugin, command.name],
87925
+ summary: command.summary,
87926
+ group: CommandGroup.Packs,
87927
+ credential: "none",
87928
+ flags: [
87929
+ { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` },
87930
+ ...command.node ? [{ name: "node", type: "string", identifier: true, summary: "The node that runs the plugin. Default: the node of this workspace." }] : [],
87931
+ ...command.options.map(flagOf)
87932
+ ],
87933
+ positionals: command.args.map((arg) => ({ name: arg.name, type: "string", summary: arg.summary, ...arg.required && { required: true }, ...arg.variadic && { variadic: true } })),
87934
+ outputShape: { kind: "object", ref: "PluginCommandResult", fields: [{ name: "data", type: "object", summary: `What the command of the plugin ${plugin} answered.` }] },
87935
+ exitCodes: [0, 1, 2, 3, 5],
87936
+ routes: [],
87937
+ examples: [{ command: `orbital ${plugin} ${command.name}`, why: command.summary }],
87938
+ run: async (input2, context) => {
87939
+ const workspace = workspaceOf(input2, workspaceDir);
87940
+ const result2 = command.node ? await runOnNode(context, workspace, input2, plugin, requestOf(command, input2, null)) : await runInPlugin(context, workspace, input2, plugin, command, terminal2);
87941
+ if (result2.text !== null) context.output.line(result2.text);
87942
+ if (result2.text === null && Object.keys(result2.data).length > 0) context.output.line(JSON.stringify(result2.data, null, 2));
87943
+ return { data: result2.data, ...result2.exitCode !== 0 && { exitCode: result2.exitCode } };
87944
+ }
87945
+ };
87946
+ }
87947
+ function pluginCommandSpecs(options) {
87948
+ const terminal2 = options.terminal ?? { input: 0, output: 1, error: 2 };
87949
+ return declaredPluginCommands(options.workspaceDir, options.env).filter(({ plugin }) => !options.reserved.includes(plugin)).flatMap(({ plugin, commands }) => commands.map((command) => specOf(options.workspaceDir, plugin, command, terminal2)));
87950
+ }
87951
+
87952
+ // src/core/cli/plugin.ts
87635
87953
  init_command();
87636
87954
  function workspaceFlag() {
87637
87955
  return { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` };
87638
87956
  }
87639
- function workspaceOf(input2) {
87640
- return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
87641
- }
87642
87957
  function sourceOrRefuse(context, input2) {
87643
87958
  const workspaceDir = workspaceOf(input2);
87644
87959
  const source = definitionSource(workspaceDir, context.env);
@@ -87662,6 +87977,25 @@ async function manifestOf(context, workspaceDir, name, source, root, config2) {
87662
87977
  await plugin.close?.();
87663
87978
  return plugin.manifest;
87664
87979
  }
87980
+ function configOf(input2, known) {
87981
+ const file2 = typeof input2.flags["config-file"] === "string" ? resolve15(input2.flags["config-file"]) : null;
87982
+ const fromFile = file2 ? (0, import_yaml8.parse)(readFileSync29(file2, "utf8")) : {};
87983
+ 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." });
87984
+ const pairs = Array.isArray(input2.flags.config) ? input2.flags.config.map(String) : typeof input2.flags.config === "string" ? [input2.flags.config] : [];
87985
+ const fromPairs = Object.fromEntries(pairs.map((pair) => {
87986
+ const at = pair.indexOf("=");
87987
+ 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." });
87988
+ return [pair.slice(0, at), (0, import_yaml8.parse)(pair.slice(at + 1)) ?? ""];
87989
+ }));
87990
+ return { ...known, ...fromFile, ...fromPairs };
87991
+ }
87992
+ async function checkedManifest(context, workspaceDir, name, source, root, config2) {
87993
+ try {
87994
+ return await manifestOf(context, workspaceDir, name, source, root, config2);
87995
+ } catch (error62) {
87996
+ 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>." });
87997
+ }
87998
+ }
87665
87999
  function permissionLines(manifest) {
87666
88000
  const { network, secrets, models } = manifest.permissions;
87667
88001
  return [
@@ -87677,7 +88011,9 @@ var add = () => ({
87677
88011
  flags: [
87678
88012
  workspaceFlag(),
87679
88013
  { name: "install-only", type: "boolean", summary: `Install the plugin on this machine only: do not name it in ${ORBITAL_FILE}.` },
87680
- { 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.` }
88014
+ { 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.` },
88015
+ { 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.` },
88016
+ { 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." }
87681
88017
  ],
87682
88018
  positionals: [
87683
88019
  { name: "plugin", type: "string", required: true, summary: "<name>@<marketplace>, npm:<package>, git:<url>, or a folder such as ./plugins/mine." }
@@ -87698,7 +88034,8 @@ var add = () => ({
87698
88034
  examples: [
87699
88035
  { command: "orbital plugin add slack@community", why: "Install the slack plugin from the community marketplace, and use it in this workspace." },
87700
88036
  { command: "orbital plugin add ./plugins/mine", why: `Use a plugin you write in this project: ${ORBITAL_FILE} names it by its path.` },
87701
- { 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." }
88037
+ { 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." },
88038
+ { command: "orbital plugin add ./plugins/notify --config channel=general --config retries=3", why: "Add a plugin that checks its config when it starts." }
87702
88039
  ],
87703
88040
  run: async (input2, context) => {
87704
88041
  const home = orbitalHome(context.env);
@@ -87710,20 +88047,22 @@ var add = () => ({
87710
88047
  if (input2.flags["install-only"] === true) return { data: { name: ref.name, source: given, installed, enabled: false, manifest: null } };
87711
88048
  if (input2.flags.user === true) {
87712
88049
  const source2 = ref.kind === "folder" && !given.includes("@") ? ref.dir : ref.kind === "npm" ? `npm:${ref.pkg}` : ref.kind === "git" ? `git:${ref.url}` : given;
87713
- const manifest2 = await manifestOf(context, workspaceOf(input2), ref.name, source2, null, {});
88050
+ const config3 = configOf(input2, userPlugins(context.env)[ref.name]?.config ?? {});
88051
+ const manifest2 = await checkedManifest(context, workspaceOf(input2), ref.name, source2, null, config3);
87714
88052
  context.output.step(`${ref.name} ${manifest2.version} reaches:`);
87715
88053
  for (const line of permissionLines(manifest2)) context.output.step(line);
87716
- setUserPlugin(context.env, ref.name, { source: source2, enabled: true, config: {}, env: [] });
88054
+ setUserPlugin(context.env, ref.name, { source: source2, enabled: true, config: config3, env: [] });
87717
88055
  context.output.step(`\u2713 your user config names ${ref.name}: source ${source2}`);
87718
88056
  return { data: { name: ref.name, source: source2, installed, enabled: true, manifest: manifest2 } };
87719
88057
  }
87720
88058
  const definition = sourceOrRefuse(context, input2);
87721
88059
  const root = definition.root;
87722
88060
  const source = sourceOf(ref, given, root);
87723
- const manifest = await manifestOf(context, workspaceOf(input2), ref.name, source, root, {});
88061
+ const config2 = configOf(input2, sourceConfig(definition).plugins[ref.name]?.config ?? {});
88062
+ const manifest = await checkedManifest(context, workspaceOf(input2), ref.name, source, root, config2);
87724
88063
  context.output.step(`${ref.name} ${manifest.version} reaches:`);
87725
88064
  for (const line of permissionLines(manifest)) context.output.step(line);
87726
- setPluginEntry(definition.configFile, ref.name, { source, enabled: true, config: sourceConfig(definition).plugins[ref.name]?.config ?? {}, env: [] });
88065
+ setPluginEntry(definition.configFile, ref.name, { source, enabled: true, config: config2, env: [] });
87727
88066
  context.output.step(`\u2713 ${basename9(definition.configFile)} names ${ref.name}: source ${source}`);
87728
88067
  return { data: { name: ref.name, source, installed, enabled: true, manifest } };
87729
88068
  }
@@ -87801,57 +88140,6 @@ var remove = () => ({
87801
88140
  return { data: { name, unnamed, uninstalled } };
87802
88141
  }
87803
88142
  });
87804
- var runCommand = () => ({
87805
- path: ["plugin", "run"],
87806
- summary: "Run one of a plugin's own commands, such as a login, and print what it answers.",
87807
- credential: "none",
87808
- flags: [workspaceFlag()],
87809
- positionals: [
87810
- { name: "plugin", type: "string", required: true, identifier: true, summary: `A plugin ${ORBITAL_FILE} names.` },
87811
- { name: "command", type: "string", required: true, summary: "The plugin's command." },
87812
- { name: "args", type: "string", summary: "Arguments for the command, after `--`.", passthrough: true }
87813
- ],
87814
- outputShape: { kind: "object", ref: "PluginCommand", fields: [{ name: "output", type: "string", summary: "What the command answered." }] },
87815
- exitCodes: [0, 1, 3],
87816
- routes: [],
87817
- examples: [{ command: "orbital plugin run slack login", why: "Run the slack plugin's login command." }],
87818
- run: async (input2, context) => {
87819
- const name = String(input2.positionals.plugin);
87820
- const plugin = await startNamed(context, input2, name);
87821
- try {
87822
- const command = String(input2.positionals.command);
87823
- if (!plugin.manifest.commands.some((one) => one.name === command)) {
87824
- 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." });
87825
- }
87826
- const output2 = await plugin.command(command, [...input2.passthrough]);
87827
- context.output.line(output2);
87828
- return { data: { output: output2 } };
87829
- } finally {
87830
- await plugin.close?.();
87831
- }
87832
- }
87833
- });
87834
- async function startNamed(context, input2, name) {
87835
- const root = definitionSource(workspaceOf(input2), context.env)?.root ?? null;
87836
- const config2 = workspaceConfig(workspaceOf(input2), context.env);
87837
- const entry = config2?.plugins[name];
87838
- 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>` });
87839
- const secrets = new PluginSecrets(context.env);
87840
- return startRemotePlugin({
87841
- name,
87842
- source: pluginSource(name, entry),
87843
- config: entry.config,
87844
- entryEnv: entry.env,
87845
- node: { name: `plugin-${name}`, workspaceDir: workspaceOf(input2), harness: defaultHarness(context.env), harnesses: [...RUNNER_HARNESSES], once: true, idleExitMs: null, project: null },
87846
- roots: { harnessRoot: root, home: orbitalHome(context.env) },
87847
- offline: config2.offline,
87848
- policy: new PolicyChain([]),
87849
- env: context.env,
87850
- log: (level, message) => level === "info" ? context.output.step(message) : context.output.warn(message),
87851
- secret: async (secret) => secrets.get(name, secret),
87852
- restarts: 0
87853
- });
87854
- }
87855
88143
  var check2 = () => ({
87856
88144
  path: ["plugin", "check"],
87857
88145
  summary: "Run the conformance checks of each port a plugin provides, so any orbital host can use it.",
@@ -87877,8 +88165,8 @@ var check2 = () => ({
87877
88165
  ],
87878
88166
  run: async (input2, context) => {
87879
88167
  const name = String(input2.positionals.plugin);
87880
- const fixtures = typeof input2.flags.fixtures === "string" ? JSON.parse(readFileSync28(input2.flags.fixtures, "utf8")) : {};
87881
- const plugin = await startNamed(context, input2, name);
88168
+ const fixtures = typeof input2.flags.fixtures === "string" ? JSON.parse(readFileSync29(input2.flags.fixtures, "utf8")) : {};
88169
+ const plugin = await startWorkspacePlugin(context, workspaceOf(input2), name);
87882
88170
  try {
87883
88171
  const results = await runConformance(plugin.ports, fixtures);
87884
88172
  const mark = { pass: "\u2713", fail: "\u2717", skip: "\u25CB" };
@@ -88006,16 +88294,16 @@ var secretRemove = {
88006
88294
  return { data: { plugin, name } };
88007
88295
  }
88008
88296
  };
88009
- var pluginCommands = () => [add(), list3(), remove(), runCommand(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
88297
+ var pluginCommands = () => [add(), list3(), remove(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
88010
88298
 
88011
88299
  // src/core/cli/plugin-new.ts
88012
88300
  init_structure();
88013
88301
  init_protocol();
88014
88302
  init_command();
88015
- import { existsSync as existsSync30, mkdirSync as mkdirSync22, readdirSync as readdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
88016
- import { dirname as dirname16, join as join37, relative as relative7, resolve as resolve15 } from "node:path";
88303
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readdirSync as readdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
88304
+ import { dirname as dirname16, join as join38, relative as relative7, resolve as resolve16 } from "node:path";
88017
88305
  var PLUGIN_PORTS = ["channel", "store", "workspace", "harness", "panel", "policy", "telemetry", "actions", "workflows", "command"];
88018
- var SDK_VERSION = true ? `^${"0.0.3"}` : "*";
88306
+ var SDK_VERSION = true ? `^${"0.0.4"}` : "*";
88019
88307
  var ESBUILD_VERSION = "^0.25.0";
88020
88308
  var TYPES_NODE_VERSION = "^20.19.43";
88021
88309
  var HARNESS_ID = /^[a-z][a-z0-9-]{0,31}$/;
@@ -88394,7 +88682,7 @@ export function plugin(context: PluginContext): PluginImplementation {
88394
88682
  switch (event.kind) {
88395
88683
  case 'mission.state': {
88396
88684
  const known = graph.missions.find((mission) => mission.missionId === event.mission);
88397
- 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 };
88685
+ 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 };
88398
88686
  graph.missions = [...graph.missions.filter((mission) => mission.missionId !== event.mission), state];
88399
88687
  return;
88400
88688
  }
@@ -88535,7 +88823,7 @@ export function plugin(_context: PluginContext): PluginImplementation {
88535
88823
  case "command":
88536
88824
  return {
88537
88825
  summary: `An orbital plugin with a command: ${name}.`,
88538
- plugin: `${header("one command. A person runs it with `orbital plugin run " + name + " hello [name]`.")}import type { PluginContext, PluginImplementation } from '@kaiba-cloud/orbital-sdk';
88826
+ 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';
88539
88827
 
88540
88828
  export const NAME = '${name}';
88541
88829
 
@@ -88545,17 +88833,19 @@ export function plugin(context: PluginContext): PluginImplementation {
88545
88833
  version: '0.1.0',
88546
88834
  permissions: { network: [], secrets: [], models: [] },
88547
88835
  ports: {},
88548
- commands: {
88549
- hello: {
88550
- summary: 'Greet the name given, or the node.',
88551
- run: async (args) => \`Hello, \${args.join(' ') || context.init.node.name}.\`,
88552
- },
88553
- },
88836
+ commands: [
88837
+ defineCommand({
88838
+ name: 'hello',
88839
+ summary: 'Greet the name given, or the workspace.',
88840
+ args: [{ name: 'name', summary: 'The name to greet.' }],
88841
+ run: async ({ args }) => \`Hello, \${args.name ?? context.init.node.workspaceDir}.\`,
88842
+ }),
88843
+ ],
88554
88844
  };
88555
88845
  }
88556
88846
  `,
88557
88847
  fixtures: {},
88558
- test: ` assert.equal(await implementation.commands?.hello?.run(['Ada']), 'Hello, Ada.');`
88848
+ test: ` assert.equal(await implementation.commands?.[0]?.run({ args: { name: 'Ada' }, options: {}, terminal: null }), 'Hello, Ada.');`
88559
88849
  };
88560
88850
  }
88561
88851
  }
@@ -88589,10 +88879,11 @@ ${template.test ? `${template.test}
88589
88879
  ` : ""}});
88590
88880
  `;
88591
88881
  }
88882
+ var COMMAND_DECLARATION = { name: "hello", summary: "Greet the name given, or the workspace.", args: [{ name: "name", summary: "The name to greet." }] };
88592
88883
  function pluginFiles(name, port) {
88593
88884
  const template = templateOf(name, port);
88594
88885
  const files = {
88595
- "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)}
88886
+ "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)}
88596
88887
  `,
88597
88888
  "package.json": `${JSON.stringify({
88598
88889
  name,
@@ -88650,14 +88941,14 @@ var pluginNewCommand = {
88650
88941
  if (port === "harness" && !HARNESS_ID.test(name)) {
88651
88942
  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." });
88652
88943
  }
88653
- const dir = resolve15(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
88654
- if (existsSync30(dir) && readdirSync20(dir).length > 0) {
88944
+ const dir = resolve16(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
88945
+ if (existsSync31(dir) && readdirSync20(dir).length > 0) {
88655
88946
  throw new CliError({ code: "conflict", message: `${dir} is not empty.`, exitCode: ExitCode.Validation, hint: "Name a new folder with --dir." });
88656
88947
  }
88657
88948
  const files = pluginFiles(name, port);
88658
88949
  for (const [path3, content] of Object.entries(files)) {
88659
- mkdirSync22(dirname16(join37(dir, path3)), { recursive: true });
88660
- writeFileSync20(join37(dir, path3), content);
88950
+ mkdirSync22(dirname16(join38(dir, path3)), { recursive: true });
88951
+ writeFileSync20(join38(dir, path3), content);
88661
88952
  }
88662
88953
  const program = commandPrefix(context.env);
88663
88954
  const shown = relative7(process.cwd(), dir) || ".";
@@ -88802,8 +89093,8 @@ If the browser does not return here in ${CALLBACK_WAIT_MS / 6e4} minutes, paste
88802
89093
  ${url2}
88803
89094
  Then paste the code, or the full URL the browser ends on.`);
88804
89095
  },
88805
- onManualCodeInput: () => new Promise((resolve25) => {
88806
- timer = setTimeout(() => resolve25(""), opened2 ? CALLBACK_WAIT_MS : 0);
89096
+ onManualCodeInput: () => new Promise((resolve27) => {
89097
+ timer = setTimeout(() => resolve27(""), opened2 ? CALLBACK_WAIT_MS : 0);
88807
89098
  timer.unref();
88808
89099
  }),
88809
89100
  onPrompt: async (prompt) => (await context.ask(`${prompt.message} `))?.trim() ?? "",
@@ -88897,14 +89188,14 @@ function createProviderCommands(signIn = loginAnthropic) {
88897
89188
 
88898
89189
  // src/core/cli/run.ts
88899
89190
  init_command();
88900
- import { resolve as resolve17 } from "node:path";
89191
+ import { resolve as resolve18 } from "node:path";
88901
89192
 
88902
89193
  // src/core/local/local-run.ts
88903
89194
  init_protocol();
88904
89195
  init_command();
88905
89196
  import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
88906
89197
  import { mkdirSync as mkdirSync23, rmSync as rmSync12 } from "node:fs";
88907
- import { join as join38, resolve as resolve16 } from "node:path";
89198
+ import { join as join39, resolve as resolve17 } from "node:path";
88908
89199
 
88909
89200
  // src/core/local/interrupt.ts
88910
89201
  var EXIT_CODE = { SIGINT: 130, SIGTERM: 143 };
@@ -88927,7 +89218,7 @@ function cleanupOnInterrupt(cleanup, options = {}) {
88927
89218
  async function runLocal(context, request, plugins) {
88928
89219
  const { workspaceDir } = request;
88929
89220
  const home = orbitalHome(context.env);
88930
- const sessionsDir = join38(home, "sessions");
89221
+ const sessionsDir = join39(home, "sessions");
88931
89222
  mkdirSync23(sessionsDir, { recursive: true, mode: 448 });
88932
89223
  const resumed = request.resumeSessionId ? readSessionRecord(sessionsDir, request.resumeSessionId) : null;
88933
89224
  if (request.resumeSessionId && !resumed) {
@@ -88957,9 +89248,9 @@ async function runLocal(context, request, plugins) {
88957
89248
  let channels = null;
88958
89249
  const asks = new PersonDesk(async (question, name) => await channels?.askPerson(`${name} asks: ${question}`, null) ?? null);
88959
89250
  const policy = new PolicyChain(ports.policies(), asks.ask);
88960
- channels = new ChannelHub({ context, ports, policy, self: nodeName, stateDir: join38(home, "channels", nodeName), plugins: () => definition.config.plugins });
89251
+ channels = new ChannelHub({ context, ports, policy, self: nodeName, stateDir: join39(home, "channels", nodeName), plugins: () => definition.config.plugins });
88961
89252
  const models = modelGatewayOf(context, policy, remote);
88962
- const actions = nodeActions({ ports: () => ports, policy: () => policy, definition: { workspaceDir: resolve16(workspaceDir), definition }, span: (span2) => host.telemetry.span(span2), now: context.now });
89253
+ const actions = nodeActions({ ports: () => ports, policy: () => policy, definition: { workspaceDir: resolve17(workspaceDir), definition }, span: (span2) => host.telemetry.span(span2), now: context.now });
88963
89254
  const agentName = request.agent ?? resumed?.agent ?? null;
88964
89255
  const target = await resolveTarget(definition, agentName, ports);
88965
89256
  const harness = request.harness ?? resumed?.harness ?? defaultHarness(context.env);
@@ -89020,7 +89311,7 @@ async function runLocal(context, request, plugins) {
89020
89311
  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 });
89021
89312
  if (result2 !== null) context.output.line(result2);
89022
89313
  const cli = commandPrefix(context.env);
89023
- context.output.step(`\u2713 transcript: ${join38(sessionsDir, sessionId, "conversation.jsonl")}`);
89314
+ context.output.step(`\u2713 transcript: ${join39(sessionsDir, sessionId, "conversation.jsonl")}`);
89024
89315
  context.output.step(`\u2713 resume with: ${cli} run --resume ${sessionId}`);
89025
89316
  return {
89026
89317
  sessionId,
@@ -89028,7 +89319,7 @@ async function runLocal(context, request, plugins) {
89028
89319
  done: true,
89029
89320
  harness,
89030
89321
  result: result2,
89031
- transcript: join38(sessionsDir, sessionId, "conversation.jsonl"),
89322
+ transcript: join39(sessionsDir, sessionId, "conversation.jsonl"),
89032
89323
  interactive: request.interactive
89033
89324
  };
89034
89325
  } finally {
@@ -89088,7 +89379,7 @@ async function interactiveTurn(context, request, run3) {
89088
89379
  const info = sessionInfoOf(run3.sessionId, run3.target, { node: run3.host.node, agent: run3.target.local?.name ?? run3.target.ref.name, parentSessionId: null, workspaceDir: request.workspaceDir, harness: run3.harness, team: null, mission: null, definition: request.useDefinition ? run3.definition : null });
89089
89380
  const opened2 = await openSession(run3.ports, run3.policy, info);
89090
89381
  const record2 = run3.host.attach(info, opened2.sinks, { agent: info.agent, harness: run3.harness, workspaceDir: request.workspaceDir });
89091
- const runDir = join38(run3.home, "runs", randomUUID8());
89382
+ const runDir = join39(run3.home, "runs", randomUUID8());
89092
89383
  mkdirSync23(runDir, { recursive: true, mode: 448 });
89093
89384
  let usage = [];
89094
89385
  try {
@@ -89195,7 +89486,7 @@ function createRunCommand(options) {
89195
89486
  const terminal2 = log ? await options.terminal() : null;
89196
89487
  const data = await runLocal(log ? { ...context, output: log.output(context.output) } : context, {
89197
89488
  agent,
89198
- workspaceDir: resolve17(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89489
+ workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89199
89490
  prompt,
89200
89491
  useDefinition: true,
89201
89492
  follow: false,
@@ -89213,24 +89504,24 @@ function createRunCommand(options) {
89213
89504
  // src/core/cli/session.ts
89214
89505
  init_command();
89215
89506
  init_protocol();
89216
- import { resolve as resolve18 } from "node:path";
89507
+ import { resolve as resolve19 } from "node:path";
89217
89508
 
89218
89509
  // src/core/local/attachments.ts
89219
- import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync21 } from "node:fs";
89220
- import { join as join39 } from "node:path";
89510
+ import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync21 } from "node:fs";
89511
+ import { join as join40 } from "node:path";
89221
89512
  function fileOf(home, externalId) {
89222
- return join39(home, "attachments", `${encodeURIComponent(externalId)}.json`);
89513
+ return join40(home, "attachments", `${encodeURIComponent(externalId)}.json`);
89223
89514
  }
89224
89515
  function writeAttachment(home, externalId, node2) {
89225
- mkdirSync24(join39(home, "attachments"), { recursive: true, mode: 448 });
89516
+ mkdirSync24(join40(home, "attachments"), { recursive: true, mode: 448 });
89226
89517
  writeFileSync21(fileOf(home, externalId), `${JSON.stringify({ node: node2 })}
89227
89518
  `, { mode: 384 });
89228
89519
  }
89229
89520
  function readAttachment(home, externalId) {
89230
89521
  const file2 = fileOf(home, externalId);
89231
- if (!existsSync32(file2)) return null;
89522
+ if (!existsSync33(file2)) return null;
89232
89523
  try {
89233
- const parsed = JSON.parse(readFileSync29(file2, "utf8"));
89524
+ const parsed = JSON.parse(readFileSync30(file2, "utf8"));
89234
89525
  return typeof parsed === "object" && parsed !== null && "node" in parsed && typeof parsed.node === "string" ? parsed.node : null;
89235
89526
  } catch {
89236
89527
  return null;
@@ -89298,7 +89589,7 @@ async function joinMission(context, attached, missionId) {
89298
89589
  const body2 = await response.json();
89299
89590
  if (!response.ok) {
89300
89591
  const error62 = typeof body2 === "object" && body2 !== null && "error" in body2 ? String(body2.error) : `The node answered ${response.status}.`;
89301
- throw new CliError({ code: "invalid-input", message: `The node refused to make this session the giver of ${missionId}: ${error62}`, exitCode: ExitCode.Validation });
89592
+ throw new CliError({ code: "invalid-input", message: `The node refused to make this session a decision receiver of ${missionId}: ${error62}`, exitCode: ExitCode.Validation });
89302
89593
  }
89303
89594
  return MissionRecordSchema.parse(body2);
89304
89595
  }
@@ -89378,7 +89669,7 @@ var sessionAttachCommand = {
89378
89669
  { 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." },
89379
89670
  { name: "team", type: "string", summary: "The team the session joins. Sessions of one team are peers." },
89380
89671
  { name: "external-id", type: "string", summary: "The id the outside program gives the session. With it, the command attaches again after a node restarts." },
89381
- { 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." }
89672
+ { 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." }
89382
89673
  ],
89383
89674
  positionals: [],
89384
89675
  outputShape: {
@@ -89395,7 +89686,7 @@ var sessionAttachCommand = {
89395
89686
  examples: [
89396
89687
  { command: "orbital session attach --harness claude-code --output ndjson", why: "Join the node of this folder, and print each delivery as a record." },
89397
89688
  { command: "orbital session attach --node shared --team review --output ndjson", why: "Join the node shared, as a member of the team review." },
89398
- { 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." }
89689
+ { 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." }
89399
89690
  ],
89400
89691
  run: async (input2, context) => {
89401
89692
  const harness = typeof input2.flags.harness === "string" ? input2.flags.harness : "external";
@@ -89407,7 +89698,7 @@ var sessionAttachCommand = {
89407
89698
  const found = await keepAttached(context, {
89408
89699
  harness,
89409
89700
  agent: typeof input2.flags.agent === "string" ? input2.flags.agent : harness,
89410
- workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89701
+ workspaceDir: resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89411
89702
  ...typeof input2.flags.node === "string" && { node: input2.flags.node },
89412
89703
  team: typeof input2.flags.team === "string" ? input2.flags.team : null,
89413
89704
  externalId: typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : null,
@@ -89420,6 +89711,8 @@ var sessionAttachCommand = {
89420
89711
  delivery: (event) => {
89421
89712
  const delivery = DeliverySchema.safeParse(event);
89422
89713
  if (delivery.success) context.output.record({ type: StreamRecordType.Event, ...delivery.data });
89714
+ const answered = AnsweredSchema.safeParse(event);
89715
+ if (answered.success) context.output.record({ type: StreamRecordType.Event, ...answered.data });
89423
89716
  },
89424
89717
  detached: (reason) => {
89425
89718
  context.output.record({ type: StreamRecordType.Event, kind: "detached", reason });
@@ -89509,8 +89802,8 @@ var workspaceCommands = [list5, show, remove3];
89509
89802
 
89510
89803
  // src/core/cli/up.ts
89511
89804
  init_command();
89512
- import { existsSync as existsSync33 } from "node:fs";
89513
- import { resolve as resolve19 } from "node:path";
89805
+ import { existsSync as existsSync34 } from "node:fs";
89806
+ import { resolve as resolve20 } from "node:path";
89514
89807
  init_protocol();
89515
89808
 
89516
89809
  // src/core/cli/up-view.ts
@@ -89692,7 +89985,7 @@ async function liveView(context, nodes, signal, terminal2, home) {
89692
89985
  for (const line of events(viewed, states.get(one.node), now)) context.output.line(line);
89693
89986
  states.set(one.node, now);
89694
89987
  }
89695
- await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve25) => signal.addEventListener("abort", () => resolve25(), { once: true }))]);
89988
+ await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve27) => signal.addEventListener("abort", () => resolve27(), { once: true }))]);
89696
89989
  }
89697
89990
  context.output.line(closing);
89698
89991
  }
@@ -89701,7 +89994,7 @@ async function liveView(context, nodes, signal, terminal2, home) {
89701
89994
  var WORKSPACE_FLAG = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
89702
89995
  var TEAMS = { name: "teams", type: "string", variadic: true, summary: "Only the nodes of these teams. Default: every node of the project." };
89703
89996
  async function planOf(context, input2) {
89704
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
89997
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
89705
89998
  const { definition, refusals } = await loadDefinitionPartial(workspaceDir, context.env);
89706
89999
  for (const refusal of refusals) context.output.warn(refusal.message);
89707
90000
  const project = projectNodeOf(definition, workspaceDir);
@@ -89848,7 +90141,7 @@ var statusCommand = {
89848
90141
  for (const { charters: _charters, named: _named, ...planned } of nodes) {
89849
90142
  const log = nodeLogPath(home, planned.node);
89850
90143
  const supervisor = readSupervisor(home, planned.node);
89851
- if (existsSync33(log)) logs.push({ node: planned.node, log, lastCrash: supervisor?.crashes.at(-1) ?? null, lastExit: supervisor?.lastExit ?? null, gaveUp: supervisor?.gaveUp ?? null });
90144
+ if (existsSync34(log)) logs.push({ node: planned.node, log, lastCrash: supervisor?.crashes.at(-1) ?? null, lastExit: supervisor?.lastExit ?? null, gaveUp: supervisor?.gaveUp ?? null });
89852
90145
  const running = await liveNode(home, planned.node, context.fetch);
89853
90146
  const served = running ? await servedTeams(context, running) : null;
89854
90147
  for (const note of served ?? []) if (note.problems.length > 0 || note.retiring) notes.push({ node: planned.node, ...note });
@@ -89881,7 +90174,7 @@ var downCommand = {
89881
90174
  const { workspaceDir, nodes } = await planOf(context, input2);
89882
90175
  const home = orbitalHome(context.env);
89883
90176
  const rows = [];
89884
- 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)));
90177
+ 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)));
89885
90178
  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 ?? [] }))];
89886
90179
  for (const planned of targets) {
89887
90180
  const running = await liveNode(home, planned.node, context.fetch);
@@ -89940,7 +90233,7 @@ var createJoinCommand = (options) => ({
89940
90233
  }
89941
90234
  let charters = [];
89942
90235
  try {
89943
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90236
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
89944
90237
  charters = (await loadDefinition(workspaceDir, context.env)).teams;
89945
90238
  } catch {
89946
90239
  charters = [];
@@ -89966,9 +90259,9 @@ function createUpCommands(options) {
89966
90259
  init_command();
89967
90260
  import { spawnSync as spawnSync5 } from "node:child_process";
89968
90261
  import { createHash as createHash10 } from "node:crypto";
89969
- import { existsSync as existsSync34, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
90262
+ import { existsSync as existsSync35, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
89970
90263
  import { homedir as homedir8 } from "node:os";
89971
- import { join as join40, resolve as resolve20 } from "node:path";
90264
+ import { join as join41, resolve as resolve21 } from "node:path";
89972
90265
  var exec = (command, args) => {
89973
90266
  const result2 = spawnSync5(command, args, { encoding: "utf8" });
89974
90267
  return { status: result2.status, output: `${result2.stdout ?? ""}${result2.stderr ?? ""}${result2.error?.message ?? ""}` };
@@ -89986,7 +90279,7 @@ function serviceSpec(env3, workspaceDir) {
89986
90279
  workspaceDir,
89987
90280
  program: [process.execPath, installedProgram(), ...mountPath(), "up", "--detach", "--workspace", workspaceDir],
89988
90281
  env: Object.fromEntries(KEPT_ENV.flatMap((name) => env3[name] ? [[name, env3[name]]] : [])),
89989
- log: join40(orbitalHome(env3), "logs", `service-${hash2}.log`)
90282
+ log: join41(orbitalHome(env3), "logs", `service-${hash2}.log`)
89990
90283
  };
89991
90284
  }
89992
90285
  function xml(text3) {
@@ -90049,7 +90342,7 @@ function userHome(env3) {
90049
90342
  return env3.HOME ?? homedir8();
90050
90343
  }
90051
90344
  function fileOf2(platform2, env3, label) {
90052
- return platform2 === "launchd" ? join40(userHome(env3), "Library", "LaunchAgents", `${label}.plist`) : join40(env3.XDG_CONFIG_HOME ?? join40(userHome(env3), ".config"), "systemd", "user", `${label}.service`);
90345
+ return platform2 === "launchd" ? join41(userHome(env3), "Library", "LaunchAgents", `${label}.plist`) : join41(env3.XDG_CONFIG_HOME ?? join41(userHome(env3), ".config"), "systemd", "user", `${label}.service`);
90053
90346
  }
90054
90347
  function domain2() {
90055
90348
  return `gui/${process.getuid?.() ?? 0}`;
@@ -90062,13 +90355,13 @@ function serviceState(platform2, env3, workspaceDir, run3 = exec) {
90062
90355
  const { label } = serviceSpec(env3, workspaceDir);
90063
90356
  const file2 = fileOf2(platform2, env3, label);
90064
90357
  const loaded = platform2 === "launchd" ? run3("launchctl", ["print", `${domain2()}/${label}`]).status === 0 : run3("systemctl", ["--user", "is-enabled", `${label}.service`]).status === 0;
90065
- return { platform: platform2, label, file: file2, installed: existsSync34(file2), loaded };
90358
+ return { platform: platform2, label, file: file2, installed: existsSync35(file2), loaded };
90066
90359
  }
90067
90360
  function installService(platform2, env3, workspaceDir, run3 = exec) {
90068
90361
  const spec = serviceSpec(env3, workspaceDir);
90069
90362
  const file2 = fileOf2(platform2, env3, spec.label);
90070
- mkdirSync25(join40(file2, ".."), { recursive: true });
90071
- mkdirSync25(join40(spec.log, ".."), { recursive: true, mode: 448 });
90363
+ mkdirSync25(join41(file2, ".."), { recursive: true });
90364
+ mkdirSync25(join41(spec.log, ".."), { recursive: true, mode: 448 });
90072
90365
  if (platform2 === "launchd") {
90073
90366
  writeFileSync22(file2, launchdPlist(spec));
90074
90367
  run3("launchctl", ["bootout", `${domain2()}/${spec.label}`]);
@@ -90090,7 +90383,7 @@ function uninstallService(platform2, env3, workspaceDir, run3 = exec) {
90090
90383
  return serviceState(platform2, env3, workspaceDir, run3);
90091
90384
  }
90092
90385
  function workspaceOf2(input2) {
90093
- return resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90386
+ return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90094
90387
  }
90095
90388
  function platformOrRefuse() {
90096
90389
  const platform2 = platformOf();
@@ -90165,9 +90458,9 @@ var serviceCommands = [
90165
90458
  ];
90166
90459
 
90167
90460
  // src/core/cli/browser.ts
90168
- import { existsSync as existsSync35, mkdirSync as mkdirSync26, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "node:fs";
90461
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync31, writeFileSync as writeFileSync23 } from "node:fs";
90169
90462
  import { homedir as homedir9 } from "node:os";
90170
- import { dirname as dirname17, join as join41 } from "node:path";
90463
+ import { dirname as dirname17, join as join42 } from "node:path";
90171
90464
 
90172
90465
  // ../../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
90173
90466
  function normalizeHeaders(headers) {
@@ -91782,7 +92075,7 @@ function isChromeDevtools(entry) {
91782
92075
  return isChromeDevtoolsMcp(command, args);
91783
92076
  }
91784
92077
  function claudeJsonPath(env3) {
91785
- return join41(env3.CLAUDE_CONFIG_DIR ?? env3.HOME ?? homedir9(), ".claude.json");
92078
+ return join42(env3.CLAUDE_CONFIG_DIR ?? env3.HOME ?? homedir9(), ".claude.json");
91786
92079
  }
91787
92080
  function chromeServersOf(config2) {
91788
92081
  return Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => isChromeDevtools(entry)).map(([name]) => name);
@@ -91794,21 +92087,21 @@ function sharedBrowserEntry() {
91794
92087
  return { type: "stdio", ...sharedBrowserCommand(), env: {} };
91795
92088
  }
91796
92089
  function backupPath(env3) {
91797
- return join41(browserDir(orbitalHome(env3)), "claude-mcp-backup.json");
92090
+ return join42(browserDir(orbitalHome(env3)), "claude-mcp-backup.json");
91798
92091
  }
91799
92092
  function codexBackupPath(env3) {
91800
- return join41(browserDir(orbitalHome(env3)), "codex-config-backup.json");
92093
+ return join42(browserDir(orbitalHome(env3)), "codex-config-backup.json");
91801
92094
  }
91802
92095
  function codexConfigPath(env3) {
91803
- return join41(env3.CODEX_HOME ?? join41(env3.HOME ?? homedir9(), ".codex"), "config.toml");
92096
+ return join42(env3.CODEX_HOME ?? join42(env3.HOME ?? homedir9(), ".codex"), "config.toml");
91804
92097
  }
91805
92098
  function pointClaudeAtSharedBrowser(env3, entry = sharedBrowserEntry()) {
91806
92099
  const file2 = claudeJsonPath(env3);
91807
- const config2 = existsSync35(file2) ? JSON.parse(readFileSync30(file2, "utf8")) : {};
92100
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
91808
92101
  const found = chromeServersOf(config2);
91809
92102
  const names = found.length > 0 ? found : [SHARED_BROWSER_SERVER];
91810
92103
  const backup = backupPath(env3);
91811
- const previous = existsSync35(backup) ? JSON.parse(readFileSync30(backup, "utf8")) : {};
92104
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
91812
92105
  const saved = { ...Object.fromEntries(names.filter((name) => config2.mcpServers?.[name] !== void 0).map((name) => [name, config2.mcpServers[name]])), ...previous };
91813
92106
  mkdirSync26(browserDir(orbitalHome(env3)), { recursive: true, mode: 448 });
91814
92107
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -91821,9 +92114,9 @@ function pointClaudeAtSharedBrowser(env3, entry = sharedBrowserEntry()) {
91821
92114
  function revertClaudeBrowser(env3) {
91822
92115
  const file2 = claudeJsonPath(env3);
91823
92116
  const backup = backupPath(env3);
91824
- if (!existsSync35(backup)) return null;
91825
- const saved = JSON.parse(readFileSync30(backup, "utf8"));
91826
- const config2 = existsSync35(file2) ? JSON.parse(readFileSync30(file2, "utf8")) : {};
92117
+ if (!existsSync36(backup)) return null;
92118
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
92119
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
91827
92120
  const shared = Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => Array.isArray(entry.args) && entry.args.join(" ").endsWith("browser connect")).map(([name]) => name);
91828
92121
  const servers = { ...config2.mcpServers };
91829
92122
  for (const name of shared) delete servers[name];
@@ -91834,18 +92127,18 @@ function revertClaudeBrowser(env3) {
91834
92127
  }
91835
92128
  function pointCodexAtSharedBrowser(env3, shared = sharedBrowserCommand()) {
91836
92129
  const file2 = codexConfigPath(env3);
91837
- if (!existsSync35(file2) && !existsSync35(dirname17(file2))) return null;
91838
- const text3 = existsSync35(file2) ? readFileSync30(file2, "utf8") : "";
92130
+ if (!existsSync36(file2) && !existsSync36(dirname17(file2))) return null;
92131
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
91839
92132
  const tables = mcpServerTables(text3);
91840
92133
  const found = tables.filter((table2) => isChromeDevtoolsMcp(table2.command, table2.args)).map((table2) => table2.name);
91841
92134
  const connected = tables.filter((table2) => isSharedBrowserConnect(table2.args)).map((table2) => table2.name);
91842
92135
  const names = found.length > 0 ? found : connected.length > 0 ? [] : [SHARED_BROWSER_SERVER];
91843
92136
  const backup = codexBackupPath(env3);
91844
- if (names.length === 0) return { file: file2, servers: connected, backup: existsSync35(backup) ? backup : null };
92137
+ if (names.length === 0) return { file: file2, servers: connected, backup: existsSync36(backup) ? backup : null };
91845
92138
  const block = (name) => stdioServerBlock(name, shared.command, shared.args);
91846
92139
  const replaced = replaceMcpServers(text3, (table2) => names.includes(table2.name) ? block(table2.name) : null);
91847
92140
  const next = names.filter((name) => !replaced.replaced.some((one) => one.name === name)).reduce((current2, name) => appendMcpServer(current2, block(name)), replaced.text);
91848
- const previous = existsSync35(backup) ? JSON.parse(readFileSync30(backup, "utf8")) : {};
92141
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
91849
92142
  const saved = { ...Object.fromEntries(replaced.replaced.map((one) => [one.name, one.original])), ...previous };
91850
92143
  mkdirSync26(browserDir(orbitalHome(env3)), { recursive: true, mode: 448 });
91851
92144
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -91857,9 +92150,9 @@ function pointCodexAtSharedBrowser(env3, shared = sharedBrowserCommand()) {
91857
92150
  function revertCodexBrowser(env3) {
91858
92151
  const file2 = codexConfigPath(env3);
91859
92152
  const backup = codexBackupPath(env3);
91860
- if (!existsSync35(backup)) return null;
91861
- const saved = JSON.parse(readFileSync30(backup, "utf8"));
91862
- const text3 = existsSync35(file2) ? readFileSync30(file2, "utf8") : "";
92153
+ if (!existsSync36(backup)) return null;
92154
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
92155
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
91863
92156
  const replaced = replaceMcpServers(text3, (table2) => isSharedBrowserConnect(table2.args) ? saved[table2.name] ?? "" : null);
91864
92157
  const next = Object.entries(saved).filter(([name]) => !replaced.replaced.some((one) => one.name === name)).reduce((current2, [, original]) => appendMcpServer(current2, original), replaced.text);
91865
92158
  mkdirSync26(dirname17(file2), { recursive: true });
@@ -92047,13 +92340,13 @@ var browserCommands = [
92047
92340
  init_command();
92048
92341
  init_structure();
92049
92342
  init_protocol();
92050
- import { existsSync as existsSync37 } from "node:fs";
92051
- import { join as join43, relative as relative8, resolve as resolve21 } from "node:path";
92343
+ import { existsSync as existsSync38 } from "node:fs";
92344
+ import { join as join44, relative as relative8, resolve as resolve22 } from "node:path";
92052
92345
 
92053
92346
  // src/core/local/mission-migrate.ts
92054
92347
  init_protocol();
92055
- 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";
92056
- import { join as join42 } from "node:path";
92348
+ 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";
92349
+ import { join as join43 } from "node:path";
92057
92350
  function withTeam(content, team) {
92058
92351
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n[\s\S]*)?$/);
92059
92352
  if (!match) return content;
@@ -92065,26 +92358,26 @@ ${head.join("\n")}
92065
92358
  ---${match[2] ?? "\n"}`;
92066
92359
  }
92067
92360
  function emptyDir(dir) {
92068
- if (existsSync36(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
92361
+ if (existsSync37(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
92069
92362
  }
92070
92363
  function migrateMissions(root, nodesDir, nodes, dryRun) {
92071
92364
  const moved = [];
92072
92365
  const refused3 = [];
92073
- const teamsDir = join42(root, TEAMS_DIR);
92074
- const target = join42(root, MISSIONS_DIR);
92366
+ const teamsDir = join43(root, TEAMS_DIR);
92367
+ const target = join43(root, MISSIONS_DIR);
92075
92368
  const seen = /* @__PURE__ */ new Map();
92076
- for (const team of existsSync36(teamsDir) ? readdirSync21(teamsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : []) {
92077
- const missionsDir = join42(teamsDir, team.name, MISSIONS_DIR);
92078
- if (!existsSync36(missionsDir)) continue;
92369
+ for (const team of existsSync37(teamsDir) ? readdirSync21(teamsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : []) {
92370
+ const missionsDir = join43(teamsDir, team.name, MISSIONS_DIR);
92371
+ if (!existsSync37(missionsDir)) continue;
92079
92372
  for (const mission of readdirSync21(missionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory())) {
92080
- const from = join42(missionsDir, mission.name);
92081
- const to = join42(target, mission.name);
92373
+ const from = join43(missionsDir, mission.name);
92374
+ const to = join43(target, mission.name);
92082
92375
  const other = seen.get(mission.name);
92083
92376
  if (other) {
92084
92377
  refused3.push(`${from}: the team ${other} has a mission ${mission.name} too. Rename one folder and its mission: key, then run the migration again.`);
92085
92378
  continue;
92086
92379
  }
92087
- if (existsSync36(to)) {
92380
+ if (existsSync37(to)) {
92088
92381
  refused3.push(`${from}: ${to} exists already.`);
92089
92382
  continue;
92090
92383
  }
@@ -92093,22 +92386,22 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92093
92386
  if (dryRun) continue;
92094
92387
  mkdirSync27(target, { recursive: true });
92095
92388
  renameSync8(from, to);
92096
- const file2 = join42(to, MISSION_FILE);
92097
- if (existsSync36(file2)) writeFileSync24(file2, withTeam(readFileSync31(file2, "utf8"), team.name));
92389
+ const file2 = join43(to, MISSION_FILE);
92390
+ if (existsSync37(file2)) writeFileSync24(file2, withTeam(readFileSync32(file2, "utf8"), team.name));
92098
92391
  }
92099
92392
  if (!dryRun) emptyDir(missionsDir);
92100
92393
  }
92101
92394
  for (const node2 of nodes) {
92102
- const nodeDir2 = join42(nodesDir, node2);
92103
- if (!existsSync36(nodeDir2)) continue;
92395
+ const nodeDir2 = join43(nodesDir, node2);
92396
+ if (!existsSync37(nodeDir2)) continue;
92104
92397
  const files = [];
92105
92398
  for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) {
92106
- 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 });
92107
- 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 });
92399
+ 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 });
92400
+ 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 });
92108
92401
  if (!entry.isDirectory()) continue;
92109
- for (const name of readdirSync21(join42(nodeDir2, entry.name))) {
92110
- if (name.endsWith(".json")) files.push({ from: join42(nodeDir2, entry.name, name), mission: name.slice(0, -".json".length), team: entry.name, backup: false });
92111
- 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 });
92402
+ for (const name of readdirSync21(join43(nodeDir2, entry.name))) {
92403
+ if (name.endsWith(".json")) files.push({ from: join43(nodeDir2, entry.name, name), mission: name.slice(0, -".json".length), team: entry.name, backup: false });
92404
+ 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 });
92112
92405
  }
92113
92406
  }
92114
92407
  files.sort((a, b) => Number(a.backup) - Number(b.backup) || Number(a.team !== null) - Number(b.team !== null));
@@ -92120,13 +92413,13 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92120
92413
  if (!dryRun) rmSync14(file2.from);
92121
92414
  continue;
92122
92415
  }
92123
- if (taken.has(file2.mission) || existsSync36(to)) {
92416
+ if (taken.has(file2.mission) || existsSync37(to)) {
92124
92417
  refused3.push(`${file2.from}: ${to} exists already.`);
92125
92418
  continue;
92126
92419
  }
92127
- const raw = JSON.parse(readFileSync31(file2.from, "utf8"));
92420
+ const raw = JSON.parse(readFileSync32(file2.from, "utf8"));
92128
92421
  const { file: _file2, ...rest } = raw;
92129
- 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 });
92422
+ const state = MissionStateFileSchema.safeParse({ question: null, lead: null, claim: null, claimedAt: null, ...rest, mission: file2.mission, node: node2, team: rest.team ?? file2.team });
92130
92423
  if (!state.success) {
92131
92424
  refused3.push(`${file2.from}: it is not the state of a mission: ${state.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ")}`);
92132
92425
  continue;
@@ -92138,7 +92431,7 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92138
92431
  rmSync14(file2.from);
92139
92432
  }
92140
92433
  if (dryRun) continue;
92141
- for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join42(nodeDir2, entry.name));
92434
+ for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join43(nodeDir2, entry.name));
92142
92435
  emptyDir(nodeDir2);
92143
92436
  }
92144
92437
  emptyDir(nodesDir);
@@ -92160,13 +92453,13 @@ var MISSION_FIELDS = {
92160
92453
  { name: "result", type: "string", nullable: true, summary: "The hand-off of the mission, when it ended." },
92161
92454
  { name: "summary", type: "string", nullable: true, summary: "The first sentence of the Intent of the brief." },
92162
92455
  { name: "question", type: "string", nullable: true, summary: "The question a waiting lead ended its turn with." },
92163
- { 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." },
92456
+ { 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." },
92164
92457
  { 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." },
92165
92458
  { name: "history", type: "array", summary: "Each change of state, with its time and who made it." }
92166
92459
  ]
92167
92460
  };
92168
92461
  function workspaceOf3(input2) {
92169
- return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92462
+ return resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92170
92463
  }
92171
92464
  function refused2(message, hint) {
92172
92465
  return new CliError({ code: "invalid-input", message, exitCode: ExitCode.Validation, ...hint && { hint } });
@@ -92248,7 +92541,7 @@ function missionNote(context, record2) {
92248
92541
  }
92249
92542
  }
92250
92543
  function shownState(record2) {
92251
- return waitsOnGiver(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
92544
+ return waitsOnDecision(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
92252
92545
  }
92253
92546
  function missionLines2(context, records) {
92254
92547
  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)));
@@ -92300,7 +92593,7 @@ var missionNew = {
92300
92593
  if (!id.success) throw refused2(`"${String(input2.positionals.id)}" is not a mission id: use lowercase letters, digits and hyphens.`);
92301
92594
  const file2 = missionFile(teamRoot(team), id.data);
92302
92595
  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.`);
92303
- if (existsSync37(file2) && input2.flags.force !== true) throw refused2(`${relative8(workspaceDir, file2)} exists.`, "Pass --force to write over it.");
92596
+ if (existsSync38(file2) && input2.flags.force !== true) throw refused2(`${relative8(workspaceDir, file2)} exists.`, "Pass --force to write over it.");
92304
92597
  const intent = await asked(context, input2, "intent", "Intent: why does the team do this mission? ");
92305
92598
  const endState = await asked(context, input2, "end-state", "End state: what is true when the mission is done? ");
92306
92599
  const limits = await asked(context, input2, "limits", "Limits: what must the team not do or change? ");
@@ -92379,7 +92672,7 @@ var missionShow = {
92379
92672
  if (record2.sessionId) context.output.line(`session: ${record2.sessionId}`);
92380
92673
  if (record2.brief) context.output.line(["", record2.brief].join("\n"));
92381
92674
  if (record2.lead) context.output.line(`lead: ${record2.lead}`);
92382
- if (record2.giver) context.output.line(`giver: session ${record2.giver} (its questions go to that session)`);
92675
+ if (record2.receivers.length > 0) context.output.line(`decision receivers: ${record2.receivers.join(", ")} (each question goes to every live one)`);
92383
92676
  if (record2.claim) context.output.line(`claim: ${record2.claim}${record2.claimedAt ? ` since ${record2.claimedAt}` : ""}${record2.stale ? " (stale)" : ""}`);
92384
92677
  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"));
92385
92678
  if (record2.question && record2.state === "waiting") context.output.line(["", "question:", record2.question, "", `Answer with: ${commandPrefix(context.env)} mission reply ${record2.missionId} "<answer>"`].join("\n"));
@@ -92454,6 +92747,31 @@ var missionSay = {
92454
92747
  return { data: { ...said2, node: node2.name } };
92455
92748
  }
92456
92749
  };
92750
+ var CLAUDE_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
92751
+ function missionMembership(verb, summary, why) {
92752
+ return {
92753
+ path: ["mission", verb],
92754
+ summary,
92755
+ credential: "none",
92756
+ 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.` }],
92757
+ positionals: [MISSION_ID2],
92758
+ outputShape: MISSION_FIELDS,
92759
+ exitCodes: [0, 3],
92760
+ routes: [],
92761
+ examples: [{ command: `orbital mission ${verb} pr-42`, why }],
92762
+ run: async (input2, context) => {
92763
+ const mission = idOf(input2);
92764
+ const externalId = typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : context.env[CLAUDE_SESSION_ENV] || null;
92765
+ 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}.`);
92766
+ const node2 = await nodeOfMission(context, mission, () => nodeOfProject(context, workspaceOf3(input2)));
92767
+ const record2 = MissionRecordSchema.parse(await nodeCall(context, node2, `/orbital/missions/${verb}`, { missionId: mission, externalId }));
92768
+ context.output.line(`${record2.missionId} on ${record2.node}: ${record2.state}; decision receivers: ${record2.receivers.join(", ") || "none"}`);
92769
+ return { data: record2 };
92770
+ }
92771
+ };
92772
+ }
92773
+ 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.");
92774
+ 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.");
92457
92775
  var missionMigrate = {
92458
92776
  path: ["mission", "migrate"],
92459
92777
  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.`,
@@ -92468,11 +92786,11 @@ var missionMigrate = {
92468
92786
  const workspaceDir = workspaceOf3(input2);
92469
92787
  const source = definitionSource(workspaceDir, context.env);
92470
92788
  if (!source) throw refused2(`${workspaceDir} has no definition of orbital.`);
92471
- const running = (await liveNodes(orbitalHome(context.env), context.fetch)).filter((node2) => resolve21(node2.workspaceDir) === resolve21(source.projectDir));
92789
+ const running = (await liveNodes(orbitalHome(context.env), context.fetch)).filter((node2) => resolve22(node2.workspaceDir) === resolve22(source.projectDir));
92472
92790
  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.`);
92473
92791
  const project = sourceConfig(source).node ?? defaultNodeName(workspaceDir);
92474
92792
  const nodes = teamNodes(readTeams(source.root).teams, project);
92475
- const migration = migrateMissions(source.root, join43(orbitalHome(context.env), "missions"), nodes, input2.flags["dry-run"] === true);
92793
+ const migration = migrateMissions(source.root, join44(orbitalHome(context.env), "missions"), nodes, input2.flags["dry-run"] === true);
92476
92794
  for (const move of migration.moved) context.output.line(`${input2.flags["dry-run"] === true ? "would move" : "moved"} ${move.from} -> ${move.to}`);
92477
92795
  for (const reason of migration.refused) context.output.warn(reason);
92478
92796
  if (migration.moved.length === 0 && migration.refused.length === 0) context.output.line("Nothing to move: the missions are in the new layout.");
@@ -92497,12 +92815,12 @@ var missionReply = {
92497
92815
  return { data: record2 };
92498
92816
  }
92499
92817
  };
92500
- var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
92818
+ var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionJoin, missionLeave, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
92501
92819
 
92502
92820
  // src/core/cli/check.ts
92503
92821
  init_command();
92504
92822
  init_structure();
92505
- import { resolve as resolve22 } from "node:path";
92823
+ import { resolve as resolve23 } from "node:path";
92506
92824
  var LOAD_RULE = "orbital.load";
92507
92825
  async function loadRefusals(workspaceDir, env3) {
92508
92826
  try {
@@ -92540,7 +92858,7 @@ var checkCommand = {
92540
92858
  { command: "orbital check --annotations", why: "Show the findings on a pull request." }
92541
92859
  ],
92542
92860
  run: async (input2, context) => {
92543
- const workspaceDir = resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92861
+ const workspaceDir = resolve23(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92544
92862
  const source = definitionSource(workspaceDir, context.env);
92545
92863
  const findings = await loadRefusals(workspaceDir, context.env);
92546
92864
  for (const finding of findings) context.output.line(`${finding.finding} ${finding.rule} ${finding.path} ${finding.message}`);
@@ -92809,19 +93127,19 @@ function createOrbitalCommands(options) {
92809
93127
  }
92810
93128
 
92811
93129
  // src/core/cli/shipped-skills.ts
92812
- var import_yaml8 = __toESM(require_dist(), 1);
93130
+ var import_yaml9 = __toESM(require_dist(), 1);
92813
93131
  init_command();
92814
- 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";
93132
+ 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";
92815
93133
  import { createHash as createHash11 } from "node:crypto";
92816
93134
  import { homedir as homedir11 } from "node:os";
92817
- import { dirname as dirname18, join as join44, relative as relative9, resolve as resolve23, sep as sep7 } from "node:path";
93135
+ import { dirname as dirname18, join as join45, relative as relative9, resolve as resolve24, sep as sep7 } from "node:path";
92818
93136
  import { fileURLToPath as fileURLToPath3 } from "node:url";
92819
93137
  var HASH_FILE = ".installed-hash";
92820
93138
  function skillsRoot(shipped) {
92821
93139
  let directory = dirname18(fileURLToPath3(shipped.anchor));
92822
93140
  for (let depth = 0; depth < 5; depth += 1) {
92823
- const candidate = join44(directory, "skills");
92824
- if (existsSync38(join44(candidate, shipped.names[0], "SKILL.md"))) return candidate;
93141
+ const candidate = join45(directory, "skills");
93142
+ if (existsSync39(join45(candidate, shipped.names[0], "SKILL.md"))) return candidate;
92825
93143
  directory = dirname18(directory);
92826
93144
  }
92827
93145
  throw new CliError({
@@ -92833,33 +93151,33 @@ function skillsRoot(shipped) {
92833
93151
  }
92834
93152
  function filesOf2(dir) {
92835
93153
  return readdirSync22(dir, { withFileTypes: true }).flatMap((entry) => {
92836
- const path3 = join44(dir, entry.name);
93154
+ const path3 = join45(dir, entry.name);
92837
93155
  if (entry.isDirectory()) return filesOf2(path3);
92838
93156
  return entry.name === HASH_FILE ? [] : [path3];
92839
93157
  });
92840
93158
  }
92841
93159
  function descriptionOf(skillFile) {
92842
- const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync32(skillFile, "utf8"));
93160
+ const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync33(skillFile, "utf8"));
92843
93161
  if (!match?.[1]) return "";
92844
- const frontmatter = (0, import_yaml8.parse)(match[1]);
93162
+ const frontmatter = (0, import_yaml9.parse)(match[1]);
92845
93163
  return typeof frontmatter?.description === "string" ? frontmatter.description : "";
92846
93164
  }
92847
93165
  function shippedSkills(shipped) {
92848
93166
  const root = skillsRoot(shipped);
92849
93167
  return shipped.names.map((name) => {
92850
- const dir = join44(root, name);
92851
- if (!existsSync38(join44(dir, "SKILL.md"))) {
93168
+ const dir = join45(root, name);
93169
+ if (!existsSync39(join45(dir, "SKILL.md"))) {
92852
93170
  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." });
92853
93171
  }
92854
93172
  const files = filesOf2(dir).map((file2) => relative9(dir, file2).split(sep7).join("/")).sort();
92855
- return { name, description: descriptionOf(join44(dir, "SKILL.md")), dir, files };
93173
+ return { name, description: descriptionOf(join45(dir, "SKILL.md")), dir, files };
92856
93174
  });
92857
93175
  }
92858
93176
  function folderHash(dir) {
92859
93177
  const hash2 = createHash11("sha256");
92860
93178
  for (const file2 of filesOf2(dir).map((path3) => relative9(dir, path3).split(sep7).join("/")).sort()) {
92861
93179
  hash2.update(`${file2}\0`);
92862
- hash2.update(readFileSync32(join44(dir, file2)));
93180
+ hash2.update(readFileSync33(join45(dir, file2)));
92863
93181
  hash2.update("\0");
92864
93182
  }
92865
93183
  return hash2.digest("hex");
@@ -92879,18 +93197,18 @@ function pick2(all, names) {
92879
93197
  });
92880
93198
  }
92881
93199
  function installBase(input2) {
92882
- if (typeof input2.flags.dir === "string") return resolve23(input2.flags.dir);
92883
- if (input2.flags.global === true) return join44(homedir11(), ".claude", "skills");
92884
- return resolve23(".claude", "skills");
93200
+ if (typeof input2.flags.dir === "string") return resolve24(input2.flags.dir);
93201
+ if (input2.flags.global === true) return join45(homedir11(), ".claude", "skills");
93202
+ return resolve24(".claude", "skills");
92885
93203
  }
92886
93204
  function planOne(skill, base2) {
92887
- const target = join44(base2, skill.name);
93205
+ const target = join45(base2, skill.name);
92888
93206
  const hash2 = folderHash(skill.dir);
92889
- if (!existsSync38(join44(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
93207
+ if (!existsSync39(join45(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
92890
93208
  const onDisk = folderHash(target);
92891
93209
  if (onDisk === hash2) return { skill, target, hash: hash2, action: "unchanged", edited: false };
92892
- const hashFile = join44(target, HASH_FILE);
92893
- const recorded = existsSync38(hashFile) ? readFileSync32(hashFile, "utf8").trim() : "";
93210
+ const hashFile = join45(target, HASH_FILE);
93211
+ const recorded = existsSync39(hashFile) ? readFileSync33(hashFile, "utf8").trim() : "";
92894
93212
  return { skill, target, hash: hash2, action: "updated", edited: onDisk !== recorded };
92895
93213
  }
92896
93214
  function writeOne(plan) {
@@ -92898,7 +93216,7 @@ function writeOne(plan) {
92898
93216
  rmSync15(plan.target, { recursive: true, force: true });
92899
93217
  mkdirSync28(plan.target, { recursive: true });
92900
93218
  cpSync4(plan.skill.dir, plan.target, { recursive: true });
92901
- writeFileSync25(join44(plan.target, HASH_FILE), `${plan.hash}
93219
+ writeFileSync25(join45(plan.target, HASH_FILE), `${plan.hash}
92902
93220
  `);
92903
93221
  } catch (error62) {
92904
93222
  throw new CliError({
@@ -92943,7 +93261,7 @@ function createSkillCommands(shipped) {
92943
93261
  context.output.line(skill.dir);
92944
93262
  return { data: { name: skill.name, path: skill.dir, content: null } };
92945
93263
  }
92946
- const content = readFileSync32(join44(skill.dir, "SKILL.md"), "utf8");
93264
+ const content = readFileSync33(join45(skill.dir, "SKILL.md"), "utf8");
92947
93265
  context.output.line(content);
92948
93266
  return { data: { name: skill.name, path: skill.dir, content } };
92949
93267
  }
@@ -93025,21 +93343,21 @@ function createSkillCommands(shipped) {
93025
93343
  // src/init.ts
93026
93344
  init_command();
93027
93345
  init_protocol();
93028
- import { existsSync as existsSync39, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
93029
- import { dirname as dirname19, isAbsolute as isAbsolute8, relative as relative10, resolve as resolve24 } from "node:path";
93030
- var import_yaml9 = __toESM(require_dist(), 1);
93346
+ import { existsSync as existsSync40, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
93347
+ import { dirname as dirname19, isAbsolute as isAbsolute8, relative as relative10, resolve as resolve25 } from "node:path";
93348
+ var import_yaml10 = __toESM(require_dist(), 1);
93031
93349
  function initHarnessFolder(workspaceDir, adapter = CLAUDE_ADAPTER) {
93032
93350
  const file2 = harnessRootAt(workspaceDir, adapter).file;
93033
- if (existsSync39(file2)) {
93351
+ if (existsSync40(file2)) {
93034
93352
  throw new CliError({ code: "conflict", message: `This project has ${file2} already.`, exitCode: ExitCode.Validation, hint: "A harness folder holds one orbital.yaml." });
93035
93353
  }
93036
93354
  mkdirSync29(dirname19(file2), { recursive: true });
93037
- writeFileSync26(file2, (0, import_yaml9.stringify)({ apiVersion: ORBITAL_API_VERSION }));
93355
+ writeFileSync26(file2, (0, import_yaml10.stringify)({ apiVersion: ORBITAL_API_VERSION }));
93038
93356
  return file2;
93039
93357
  }
93040
93358
  function workspaceOf4(flag) {
93041
- const workspaceDir = resolve24(typeof flag === "string" ? flag : ".");
93042
- if (!existsSync39(workspaceDir)) throw new CliError({ code: "not-found", message: `${workspaceDir} does not exist.`, exitCode: ExitCode.Validation });
93359
+ const workspaceDir = resolve25(typeof flag === "string" ? flag : ".");
93360
+ if (!existsSync40(workspaceDir)) throw new CliError({ code: "not-found", message: `${workspaceDir} does not exist.`, exitCode: ExitCode.Validation });
93043
93361
  return workspaceDir;
93044
93362
  }
93045
93363
  var WORKSPACE3 = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
@@ -93115,7 +93433,7 @@ function targetOf2(context, workspaceDir, named2, given, adapters) {
93115
93433
  });
93116
93434
  }
93117
93435
  async function chooseTargets(context, workspaceDir, adapters) {
93118
- const present = adapters.filter((adapter) => existsSync39(resolve24(workspaceDir, adapter.folder.folder)));
93436
+ const present = adapters.filter((adapter) => existsSync40(resolve25(workspaceDir, adapter.folder.folder)));
93119
93437
  if (present.length === 1) {
93120
93438
  context.output.step(`installing ${present[0].id}: ${present[0].folder.folder} is the harness folder of this project`);
93121
93439
  return [{ adapter: present[0], given: null }];
@@ -93149,9 +93467,9 @@ async function adapterInstall(context, adapter, workspaceDir, force) {
93149
93467
  async function installTarget(context, workspaceDir, target, user, force) {
93150
93468
  const { adapter, given } = target;
93151
93469
  const root = harnessRootAt(workspaceDir, adapter);
93152
- const file2 = existsSync39(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
93470
+ const file2 = existsSync40(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
93153
93471
  if (file2) context.output.line(`wrote ${file2}`);
93154
- 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;
93472
+ 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;
93155
93473
  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;
93156
93474
  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}`);
93157
93475
  const install2 = await adapterInstall(context, adapter, workspaceDir, force);
@@ -93238,6 +93556,12 @@ function followAttach(context, options, signal, inbox, timing = {}) {
93238
93556
  delivery: (event) => {
93239
93557
  const delivery = DeliverySchema.safeParse(event);
93240
93558
  if (delivery.success) inbox.push(delivery.data);
93559
+ const answered = AnsweredSchema.safeParse(event);
93560
+ if (!answered.success) return;
93561
+ for (let index = inbox.length - 1; index >= 0; index -= 1) {
93562
+ const held2 = inbox[index];
93563
+ if (held2.mission === answered.data.mission && (answered.data.question === null || held2.question === answered.data.question)) inbox.splice(index, 1);
93564
+ }
93241
93565
  },
93242
93566
  detached: (reason) => {
93243
93567
  if (kept) kept.ended = true;
@@ -93367,9 +93691,8 @@ function mcpCommand(toolsOf) {
93367
93691
  const joined = await attachment.current();
93368
93692
  return { url: `${joined.nodeUrl}/a2a`, token: joined.token };
93369
93693
  },
93370
- get sessionId() {
93371
- return attachment.sessionId();
93372
- },
93694
+ sessionId: null,
93695
+ attached: async () => (await attachment.current()).sessionId,
93373
93696
  harness,
93374
93697
  follow: true,
93375
93698
  tools: inboxTools(context, attachment.current, inbox)
@@ -93378,8 +93701,8 @@ function mcpCommand(toolsOf) {
93378
93701
  const server = await mcpServer(options, input2.flags.actions === true, toolsOf);
93379
93702
  const transport = new StdioServerTransport();
93380
93703
  await server.connect(transport);
93381
- await new Promise((resolve25) => {
93382
- server.server.onclose = resolve25;
93704
+ await new Promise((resolve27) => {
93705
+ server.server.onclose = resolve27;
93383
93706
  process.stdin.once("end", () => void server.close());
93384
93707
  });
93385
93708
  stop.abort();
@@ -93405,6 +93728,10 @@ function orbitalCommands(options) {
93405
93728
  ...options.commands ?? []
93406
93729
  ];
93407
93730
  }
93731
+ function workspacePluginCommands(options) {
93732
+ const reserved = [.../* @__PURE__ */ new Set([...options.builtIn.map((spec) => spec.path[0]), ...INTROSPECTION_WORDS])];
93733
+ return pluginCommandSpecs({ workspaceDir: options.workspaceDir, env: options.env, reserved, ...options.terminal && { terminal: options.terminal } });
93734
+ }
93408
93735
 
93409
93736
  // src/launch.ts
93410
93737
  init_zod();
@@ -93437,13 +93764,15 @@ async function nodeLaunch(env3, argv, prefix, run3) {
93437
93764
  }
93438
93765
 
93439
93766
  // src/cli.ts
93440
- var orbital = () => ({
93441
- registry: composeRegistry(orbitalCommands({ terminal: async () => (await init_tui().then(() => tui_exports)).terminal })),
93442
- context: (base2) => base2
93443
- });
93767
+ var orbital = (options) => {
93768
+ const own2 = orbitalCommands({ terminal: async () => (await init_tui().then(() => tui_exports)).terminal });
93769
+ const workspace = peekFlag(options.argv, "workspace");
93770
+ const plugins = workspacePluginCommands({ builtIn: own2, workspaceDir: resolve26(typeof workspace === "string" ? workspace : "."), env: options.env });
93771
+ return { registry: composeRegistry([...own2, ...plugins]), context: (base2) => base2 };
93772
+ };
93444
93773
  async function run2(options) {
93445
93774
  setBrand(ORBITAL_BRAND);
93446
- return runProgram(orbital(), options);
93775
+ return runProgram(orbital(options), options);
93447
93776
  }
93448
93777
  function processRun(argv) {
93449
93778
  return run2({