@kaiba-cloud/orbital 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.6" : "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();
@@ -65754,12 +65831,9 @@ function readDescriptorName(dir) {
65754
65831
  if (!existsSync6(file2)) throw refuse("invalid-input", `${dir} has no ${PLUGIN_PACKAGE_FILE}, so it is not a plugin.`);
65755
65832
  return PluginPackageSchema.parse(JSON.parse(readFileSync7(file2, "utf8"))).name;
65756
65833
  }
65757
- function lastPart(value2) {
65758
- return value2.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() ?? value2;
65759
- }
65760
65834
  function resolvePluginRef(home, ref) {
65761
- if (ref.startsWith("npm:")) return { kind: "npm", name: lastPart(ref.slice(4).replace(/@[^/]*$/, "")), pkg: ref.slice(4) };
65762
- if (ref.startsWith("git:")) return { kind: "git", name: lastPart(ref.slice(4)), url: ref.slice(4) };
65835
+ if (ref.startsWith("npm:")) return { kind: "npm", name: null, pkg: ref.slice(4) };
65836
+ if (ref.startsWith("git:")) return { kind: "git", name: null, url: ref.slice(4) };
65763
65837
  if (isFolder(ref)) return { kind: "folder", name: readDescriptorName(resolve6(ref)), dir: resolve6(ref) };
65764
65838
  const at = ref.lastIndexOf("@");
65765
65839
  if (at <= 0) throw refuse("invalid-input", `"${ref}" is not a plugin reference.`, "Use <name>@<marketplace>, npm:<package>, git:<url>, or a folder such as ./plugins/mine.");
@@ -65791,17 +65865,17 @@ function installPlugin(home, ref, exec4 = run) {
65791
65865
  const installed = Object.keys(JSON.parse(readFileSync7(join10(scratch, "package.json"), "utf8")).dependencies ?? {});
65792
65866
  const pkgDir = join10("node_modules", ...(installed[0] ?? ref.pkg.replace(/(.)@[^/]*$/, "$1")).split("/"));
65793
65867
  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)}
65868
+ writeFileSync5(join10(scratch, PLUGIN_PACKAGE_FILE), `${JSON.stringify({ name: inner.name, command: inner.command, dir: inner.dir ? join10(pkgDir, inner.dir) : pkgDir, env: inner.env, ...Object.keys(inner.config).length > 0 && { config: inner.config }, ...inner.harnesses.length > 0 && { harnesses: inner.harnesses }, ...inner.commands.length > 0 && { commands: inner.commands } }, null, 2)}
65795
65869
  `);
65796
65870
  break;
65797
65871
  }
65798
65872
  }
65799
65873
  const named2 = readDescriptorName(scratch);
65800
- if (named2 !== ref.name) throw refuse("invalid-input", `The plugin calls itself ${named2}, and its reference names it ${ref.name}.`);
65801
- const dir = join10(parent, ref.name);
65874
+ if (ref.name !== null && named2 !== ref.name) throw refuse("invalid-input", `The plugin calls itself ${named2}, and its reference names it ${ref.name}.`);
65875
+ const dir = join10(parent, named2);
65802
65876
  rmSync3(dir, { recursive: true, force: true });
65803
65877
  renameSync(scratch, dir);
65804
- return dir;
65878
+ return { name: named2, dir };
65805
65879
  } finally {
65806
65880
  rmSync3(scratch, { recursive: true, force: true });
65807
65881
  }
@@ -65848,8 +65922,8 @@ function isPackageSource(source) {
65848
65922
  function ensurePluginInstalled(home, name, source, exec4 = run) {
65849
65923
  const dir = join10(pluginsDir(home), name);
65850
65924
  if (existsSync6(join10(dir, PLUGIN_PACKAGE_FILE))) return dir;
65851
- if (source.startsWith("npm:")) return installPlugin(home, { kind: "npm", name, pkg: source.slice(4) }, exec4);
65852
- if (source.startsWith("git:")) return installPlugin(home, { kind: "git", name, url: source.slice(4) }, exec4);
65925
+ if (source.startsWith("npm:")) return installPlugin(home, { kind: "npm", name, pkg: source.slice(4) }, exec4).dir;
65926
+ if (source.startsWith("git:")) return installPlugin(home, { kind: "git", name, url: source.slice(4) }, exec4).dir;
65853
65927
  throw refuse("invalid-input", `The plugin ${name} has the source ${source}, and only an npm: or git: source installs into the plugin cache.`);
65854
65928
  }
65855
65929
 
@@ -65857,15 +65931,22 @@ function ensurePluginInstalled(home, name, source, exec4 = run) {
65857
65931
  function pluginsDir(home) {
65858
65932
  return join11(home, "plugins");
65859
65933
  }
65934
+ function isLocalSource(source) {
65935
+ return source.startsWith(".") || isAbsolute5(source);
65936
+ }
65937
+ function pluginDir(name, source, roots) {
65938
+ if (!isLocalSource(source)) return join11(pluginsDir(roots.home), name);
65939
+ return isAbsolute5(source) ? source : resolve7(roots.harnessRoot ?? process.cwd(), source);
65940
+ }
65860
65941
  function commandOf(name, source, roots, entryEnv = []) {
65861
65942
  if (source.startsWith("exec:")) {
65862
65943
  const [command2, ...args2] = source.slice("exec:".length).trim().split(/\s+/);
65863
65944
  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 };
65945
+ return { command: command2, args: args2, cwd: roots.harnessRoot ?? process.cwd(), env: [...entryEnv], defaults: {}, harnesses: null, commands: null };
65865
65946
  }
65866
65947
  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);
65948
+ const local = isLocalSource(source);
65949
+ const dir = pluginDir(name, source, roots);
65869
65950
  const descriptor = join11(dir, PLUGIN_PACKAGE_FILE);
65870
65951
  if (!local && !existsSync7(descriptor) && /^(npm|git):/.test(source)) ensurePluginInstalled(roots.home, name, source);
65871
65952
  if (!existsSync7(descriptor)) {
@@ -65873,7 +65954,7 @@ function commandOf(name, source, roots, entryEnv = []) {
65873
65954
  }
65874
65955
  const plugin = PluginPackageSchema.parse(JSON.parse(readFileSync8(descriptor, "utf8")));
65875
65956
  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 };
65957
+ 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
65958
  }
65878
65959
  var PLUGIN_ENV = [
65879
65960
  "PATH",
@@ -65918,6 +65999,15 @@ function folderMismatch(manifest, folders) {
65918
65999
  const extra = folders.find((one) => !manifest.provides.harnesses.some((harness) => harness.id === one.id && harness.folder !== null));
65919
66000
  return extra ? `declares the harness ${extra.id} in its ${PLUGIN_PACKAGE_FILE}, and its harnesses port serves no folder for it` : null;
65920
66001
  }
66002
+ function commandMismatch(manifest, declared) {
66003
+ for (const command of manifest.commands) {
66004
+ const same2 = declared.find((one) => one.name === command.name);
66005
+ if (!same2) return `serves the command ${command.name}, and its ${PLUGIN_PACKAGE_FILE} declares no command ${command.name}`;
66006
+ 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`;
66007
+ }
66008
+ const extra = declared.find((one) => !manifest.commands.some((command) => command.name === one.name));
66009
+ return extra ? `declares the command ${extra.name} in its ${PLUGIN_PACKAGE_FILE}, and serves no command ${extra.name}` : null;
66010
+ }
65921
66011
  function manifestEnv(manifest) {
65922
66012
  return manifest.permissions.secrets.filter((name) => PLUGIN_ENV_NAME.test(name));
65923
66013
  }
@@ -65946,15 +66036,16 @@ async function startRemotePlugin(options) {
65946
66036
  let reconnecting = false;
65947
66037
  const connect = async () => {
65948
66038
  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"] });
66039
+ const terminal2 = options.terminal ? [options.terminal.input, options.terminal.output, options.terminal.error] : [];
66040
+ const child = spawn2(start.command, start.args, { cwd: start.cwd, env: pluginProcessEnv(options.env, start.env), stdio: ["pipe", "pipe", "pipe", ...terminal2] });
65950
66041
  try {
65951
- return await initialize(child, start.env, start.defaults, start.harnesses);
66042
+ return await initialize(child, start.env, start.defaults, start.harnesses, start.commands);
65952
66043
  } catch (error62) {
65953
66044
  child.kill("SIGKILL");
65954
66045
  throw error62;
65955
66046
  }
65956
66047
  };
65957
- const initialize = async (child, env3, defaults, folders = null) => {
66048
+ const initialize = async (child, env3, defaults, folders, commands) => {
65958
66049
  const peer = new RpcPeer({ send: (line) => child.stdin?.write(`${line}
65959
66050
  `) });
65960
66051
  createInterface2({ input: child.stdout }).on("line", (line) => peer.receive(line));
@@ -65989,12 +66080,16 @@ async function startRemotePlugin(options) {
65989
66080
  const where2 = options.source.startsWith("exec:") ? `its ${ORBITAL_FILE} entry` : `its ${PLUGIN_PACKAGE_FILE}`;
65990
66081
  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
66082
  }
66083
+ if (commands) {
66084
+ const mismatch = commandMismatch(manifest2, commands);
66085
+ 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.`);
66086
+ }
65992
66087
  if (options.offline && manifest2.permissions.network.length > 0) {
65993
66088
  throw new Error(`The plugin ${options.name} reaches ${manifest2.permissions.network.join(", ")}, and ${ORBITAL_FILE} says offline: true.`);
65994
66089
  }
65995
66090
  await options.policy.check({ kind: "plugin.load", plugin: options.name, network: manifest2.permissions.network, secrets: manifest2.permissions.secrets, models: manifest2.permissions.models });
65996
- child.once("exit", () => {
65997
- peer.close(new Error(`The plugin ${options.name} stopped.`));
66091
+ child.once("exit", (code, signal) => {
66092
+ peer.close(new Error(`The plugin ${options.name} stopped (${signal ? `signal ${signal}` : `exit code ${code}`}).`));
65998
66093
  if (stopped || reconnecting) return;
65999
66094
  current2 = reconnect();
66000
66095
  current2.catch((error62) => options.log("error", error62 instanceof Error ? error62.message : String(error62)));
@@ -66174,7 +66269,7 @@ async function startRemotePlugin(options) {
66174
66269
  endpoint = given;
66175
66270
  void current2.then((connection) => connection.peer.notify(PLUGIN_METHODS.hostEndpoint, given));
66176
66271
  },
66177
- command: async (name, args) => (await call(PLUGIN_METHODS.commandRun, { name, args })).output,
66272
+ command: async (request) => PluginCommandResultSchema.parse(await (await current2).peer.request(PLUGIN_METHODS.commandRun, request, 0)),
66178
66273
  close: async () => {
66179
66274
  stopped = true;
66180
66275
  const connection = await current2.catch(() => null);
@@ -67547,7 +67642,7 @@ var readBodyDirect = (request) => {
67547
67642
  request[bodyBufferKey] = buffered;
67548
67643
  return Promise.resolve(buffered);
67549
67644
  }
67550
- const promise2 = new Promise((resolve25, reject) => {
67645
+ const promise2 = new Promise((resolve27, reject) => {
67551
67646
  const chunks = [];
67552
67647
  let settled = false;
67553
67648
  const finish = (callback) => {
@@ -67565,7 +67660,7 @@ var readBodyDirect = (request) => {
67565
67660
  else if (recovered === void 0) reject(error62 ?? normalizeAbortError(request, incoming));
67566
67661
  else {
67567
67662
  request[bodyBufferKey] = recovered;
67568
- resolve25(recovered);
67663
+ resolve27(recovered);
67569
67664
  }
67570
67665
  });
67571
67666
  return true;
@@ -67577,7 +67672,7 @@ var readBodyDirect = (request) => {
67577
67672
  finish(() => {
67578
67673
  const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
67579
67674
  request[bodyBufferKey] = buffer;
67580
- resolve25(buffer);
67675
+ resolve27(buffer);
67581
67676
  });
67582
67677
  };
67583
67678
  const onError = (error62) => {
@@ -68124,7 +68219,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
68124
68219
  });
68125
68220
  if (!chunk) {
68126
68221
  if (i === 1) {
68127
- await new Promise((resolve25) => setTimeout(resolve25));
68222
+ await new Promise((resolve27) => setTimeout(resolve27));
68128
68223
  maxReadCount = 3;
68129
68224
  continue;
68130
68225
  }
@@ -70393,9 +70488,9 @@ data:
70393
70488
  const initRequest = messages.find((m) => isInitializeRequest(m));
70394
70489
  const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
70395
70490
  if (this._enableJsonResponse) {
70396
- return new Promise((resolve25) => {
70491
+ return new Promise((resolve27) => {
70397
70492
  this._streamMapping.set(streamId, {
70398
- resolveJson: resolve25,
70493
+ resolveJson: resolve27,
70399
70494
  cleanup: () => {
70400
70495
  this._streamMapping.delete(streamId);
70401
70496
  }
@@ -76770,7 +76865,7 @@ var Protocol = class {
76770
76865
  return;
76771
76866
  }
76772
76867
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
76773
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
76868
+ await new Promise((resolve27) => setTimeout(resolve27, pollInterval));
76774
76869
  options?.signal?.throwIfAborted();
76775
76870
  }
76776
76871
  } catch (error62) {
@@ -76787,7 +76882,7 @@ var Protocol = class {
76787
76882
  */
76788
76883
  request(request, resultSchema, options) {
76789
76884
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
76790
- return new Promise((resolve25, reject) => {
76885
+ return new Promise((resolve27, reject) => {
76791
76886
  const earlyReject = (error62) => {
76792
76887
  reject(error62);
76793
76888
  };
@@ -76865,7 +76960,7 @@ var Protocol = class {
76865
76960
  if (!parseResult.success) {
76866
76961
  reject(parseResult.error);
76867
76962
  } else {
76868
- resolve25(parseResult.data);
76963
+ resolve27(parseResult.data);
76869
76964
  }
76870
76965
  } catch (error62) {
76871
76966
  reject(error62);
@@ -77127,12 +77222,12 @@ var Protocol = class {
77127
77222
  }
77128
77223
  } catch {
77129
77224
  }
77130
- return new Promise((resolve25, reject) => {
77225
+ return new Promise((resolve27, reject) => {
77131
77226
  if (signal.aborted) {
77132
77227
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
77133
77228
  return;
77134
77229
  }
77135
- const timeoutId = setTimeout(resolve25, interval);
77230
+ const timeoutId = setTimeout(resolve27, interval);
77136
77231
  signal.addEventListener("abort", () => {
77137
77232
  clearTimeout(timeoutId);
77138
77233
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -78273,7 +78368,7 @@ var McpServer = class {
78273
78368
  let task = createTaskResult.task;
78274
78369
  const pollInterval = task.pollInterval ?? 5e3;
78275
78370
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
78276
- await new Promise((resolve25) => setTimeout(resolve25, pollInterval));
78371
+ await new Promise((resolve27) => setTimeout(resolve27, pollInterval));
78277
78372
  const updatedTask = await extra.taskStore.getTask(taskId);
78278
78373
  if (!updatedTask) {
78279
78374
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -78998,14 +79093,14 @@ function claudeService(env3) {
78998
79093
  if (!dir) return SERVICE;
78999
79094
  return `${SERVICE}-${createHash4("sha256").update(dir.normalize("NFC")).digest("hex").slice(0, 8)}`;
79000
79095
  }
79001
- var findWithSecurity = (args) => new Promise((resolve25) => {
79096
+ var findWithSecurity = (args) => new Promise((resolve27) => {
79002
79097
  const child = spawn3(SECURITY, [...args], { stdio: ["ignore", "pipe", "ignore"], timeout: SECURITY_TIMEOUT_MS });
79003
79098
  let stdout = "";
79004
79099
  child.stdout.setEncoding("utf8").on("data", (chunk) => {
79005
79100
  stdout += chunk;
79006
79101
  });
79007
- child.once("error", () => resolve25({ status: 1, stdout: "" }));
79008
- child.once("close", (code) => resolve25({ status: code ?? 1, stdout }));
79102
+ child.once("error", () => resolve27({ status: 1, stdout: "" }));
79103
+ child.once("close", (code) => resolve27({ status: code ?? 1, stdout }));
79009
79104
  });
79010
79105
  function keychainRead(account, service, find = findWithSecurity) {
79011
79106
  return async () => {
@@ -79026,7 +79121,7 @@ function renewEnv(env3) {
79026
79121
  return Object.fromEntries(Object.entries(env3).filter((entry) => entry[1] !== void 0 && !KEPT_OUT.includes(entry[0])));
79027
79122
  }
79028
79123
  function claudeRenewal(binary, env3) {
79029
- return () => new Promise((resolve25, reject) => {
79124
+ return () => new Promise((resolve27, reject) => {
79030
79125
  const cwd2 = mkdtempSync(join16(tmpdir(), "orbital-claude-renew-"));
79031
79126
  const child = spawn3(binary, RENEW_ARGS, { cwd: cwd2, env: renewEnv(env3), stdio: ["ignore", "ignore", "pipe"], timeout: RENEW_TIMEOUT_MS });
79032
79127
  let stderr = "";
@@ -79036,7 +79131,7 @@ function claudeRenewal(binary, env3) {
79036
79131
  const done = (error62) => {
79037
79132
  rmSync5(cwd2, { recursive: true, force: true });
79038
79133
  if (error62) reject(error62);
79039
- else resolve25();
79134
+ else resolve27();
79040
79135
  };
79041
79136
  child.once("error", done);
79042
79137
  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 +80420,7 @@ function missionOfRow(row2) {
80325
80420
  result: textOf(row2, "result"),
80326
80421
  question: textOf(row2, "question"),
80327
80422
  questions: questionsOf(row2),
80328
- giver: null,
80423
+ receivers: [],
80329
80424
  lead: textOf(row2, "lead"),
80330
80425
  lastActivity: textOf(row2, "activity"),
80331
80426
  claim: textOf(row2, "claim"),
@@ -80479,7 +80574,7 @@ var MissionStore = class {
80479
80574
  source: request.source !== void 0 ? request.source : known?.source ?? null,
80480
80575
  result: request.result !== void 0 ? request.result : known?.result ?? null,
80481
80576
  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,
80577
+ receivers: request.receivers !== void 0 ? request.receivers : known?.receivers ?? [],
80483
80578
  lead: request.lead !== void 0 ? request.lead : known?.lead ?? null,
80484
80579
  claim: request.claim !== void 0 ? request.claim : known?.claim ?? null,
80485
80580
  claimedAt: request.claimedAt !== void 0 ? request.claimedAt : known?.claimedAt ?? null,
@@ -80534,7 +80629,7 @@ function missionRecord(node2, mission, declared, stored) {
80534
80629
  result: stored?.result ?? null,
80535
80630
  question: stored?.question ?? null,
80536
80631
  questions: stored?.questions ?? [],
80537
- giver: stored?.giver ?? null,
80632
+ receivers: stored?.receivers ?? [],
80538
80633
  lead: stored?.lead ?? null,
80539
80634
  lastActivity: null,
80540
80635
  claim: stored?.claim ?? null,
@@ -80642,9 +80737,9 @@ var Telemetry = class {
80642
80737
  async drain(timeoutMs) {
80643
80738
  if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) return true;
80644
80739
  let done = null;
80645
- const empty = new Promise((resolve25) => {
80740
+ const empty = new Promise((resolve27) => {
80646
80741
  done = () => {
80647
- if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve25(true);
80742
+ if (this.queues.every((queue) => !queue.running && queue.items.length === 0)) resolve27(true);
80648
80743
  };
80649
80744
  this.drained.add(done);
80650
80745
  });
@@ -80897,7 +80992,7 @@ var SessionHost = class {
80897
80992
  transition: missionTransition(before?.state ?? "open", after.state, after.question),
80898
80993
  reason: change?.note ?? null,
80899
80994
  by: change?.by ?? "node",
80900
- giver: after.giver,
80995
+ receivers: after.receivers,
80901
80996
  sessionId: after.sessionId,
80902
80997
  lead: after.lead,
80903
80998
  question: after.question,
@@ -80905,7 +81000,7 @@ var SessionHost = class {
80905
81000
  });
80906
81001
  }
80907
81002
  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 });
81003
+ 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
81004
  }
80910
81005
  async snapshot() {
80911
81006
  const definition = await this.definitionOf(this.options.defaultWorkspace);
@@ -80934,7 +81029,7 @@ var SessionHost = class {
80934
81029
  retiring: note?.retiring ?? null
80935
81030
  };
80936
81031
  }), ...[...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 })),
81032
+ 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
81033
  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
81034
  jobs: [...this.openJobs].map(([jobId, open2]) => ({ ...open2.context, jobId, since: new Date(open2.since).toISOString(), via: open2.via })),
80940
81035
  tokens: this.telemetry.tokens(),
@@ -80988,19 +81083,48 @@ var SessionHost = class {
80988
81083
  this.redeliverQuestions(id);
80989
81084
  return info;
80990
81085
  }
80991
- redeliverQuestions(giverId) {
81086
+ redeliverQuestions(sessionId) {
81087
+ const session = this.sessions.get(sessionId);
81088
+ if (!session) return;
80992
81089
  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
- }
81090
+ if (!stored.receivers.some((key2) => this.receives(session, key2))) continue;
81091
+ for (const open2 of this.openQuestions(stored)) if (!this.questionOpen(sessionId, open2.mission, open2.questionId)) this.deliverQuestion(sessionId, open2, stored.sessionId);
80998
81092
  }
80999
81093
  }
81000
- questionOpen(giverId, mission, questionId) {
81001
- if (this.questionsInFlight.has(`${giverId}:${mission}:${questionId ?? ""}`)) return true;
81094
+ openQuestions(stored) {
81095
+ if (!stored.sessionId || stored.state === "paused" || !hasLead(stored.state)) return [];
81096
+ const base2 = { team: stored.team, mission: stored.mission, lead: stored.lead };
81097
+ return [
81098
+ ...stored.state === "waiting" ? [{ ...base2, question: stored.question ?? NO_HANDOFF_TELL, questionId: null }] : [],
81099
+ ...stored.questions.map((asked2) => ({ ...base2, question: asked2.question, questionId: asked2.id }))
81100
+ ];
81101
+ }
81102
+ receiverKey(session) {
81103
+ return session.external?.externalId ? externalReceiver(session.external.externalId) : session.id;
81104
+ }
81105
+ receives(session, key2) {
81106
+ return key2 === session.id || key2 === this.receiverKey(session);
81107
+ }
81108
+ liveReceivers(receivers, except = null) {
81109
+ 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)));
81110
+ }
81111
+ keyOfRemote(receiver) {
81112
+ if (receiver.node !== this.node) return receiverRef(receiver);
81113
+ const session = this.sessions.get(receiver.sessionId);
81114
+ return session ? this.receiverKey(session) : receiver.sessionId;
81115
+ }
81116
+ starterKey(by) {
81117
+ if (!by.startsWith("session:")) return null;
81118
+ const named2 = by.slice("session:".length);
81119
+ const remote = remoteReceiverOf(named2);
81120
+ if (remote) return this.keyOfRemote(remote);
81121
+ const session = this.sessions.get(named2);
81122
+ return session ? this.receiverKey(session) : named2;
81123
+ }
81124
+ questionOpen(sessionId, mission, questionId) {
81125
+ if (this.questionsInFlight.has(`${sessionId}:${mission}:${questionId ?? ""}`)) return true;
81002
81126
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
81003
- if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== giverId) continue;
81127
+ if (asked2.mission !== mission || asked2.questionId !== questionId || this.taskSession.get(taskId) !== sessionId) continue;
81004
81128
  const task = this.tasks.get(taskId);
81005
81129
  if (task && !(task.status && isTerminal(task.status.state))) return true;
81006
81130
  this.questionDeliveries.delete(taskId);
@@ -81032,7 +81156,7 @@ var SessionHost = class {
81032
81156
  async settleExternal(sessionId, settle2) {
81033
81157
  const session = this.sessions.get(sessionId);
81034
81158
  if (!session?.external) throw new SessionHostError("not-found", `There is no attached session ${sessionId} on this node.`);
81035
- const taskId = settle2.taskId ?? session.running;
81159
+ const taskId = settle2.taskId ?? session.running ?? session.external.open.keys().next().value ?? null;
81036
81160
  const task = taskId ? this.tasks.get(taskId) : void 0;
81037
81161
  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
81162
  await this.finish(sessionId, task, "TASK_STATE_COMPLETED", settle2.text);
@@ -81066,23 +81190,21 @@ var SessionHost = class {
81066
81190
  await this.finish(session.id, task, "TASK_STATE_FAILED", `The attached session ${session.id} is detached.`);
81067
81191
  return;
81068
81192
  }
81069
- session.running = task.id;
81193
+ const asked2 = this.questionOf(message);
81194
+ if (asked2 === null) session.running = task.id;
81070
81195
  await this.update(session.id, task, "TASK_STATE_WORKING");
81071
81196
  const fromAgent = message.metadata?.[ORBITAL_META.fromAgent];
81072
81197
  const fromSession = message.metadata?.[ORBITAL_META.fromSession];
81073
- const asked2 = this.questionOf(message);
81074
81198
  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));
81199
+ const settled = new Promise((resolve27) => this.externalWaits.set(task.id, resolve27)).finally(() => {
81200
+ this.externalWaits.delete(task.id);
81201
+ external.open.delete(task.id);
81202
+ if (session.running === task.id) session.running = null;
81203
+ });
81076
81204
  if (asked2 !== null) external.open.set(task.id, delivery);
81077
81205
  if (external.listeners.size === 0) external.waiting.push(delivery);
81078
81206
  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
- }
81207
+ if (asked2 === null) await settled;
81086
81208
  }
81087
81209
  stopFollowing() {
81088
81210
  this.following.abort();
@@ -81097,9 +81219,9 @@ var SessionHost = class {
81097
81219
  if (this.followingHub) return;
81098
81220
  let listener = null;
81099
81221
  try {
81100
- await Promise.race([this.settled(), new Promise((resolve25) => {
81101
- listener = resolve25;
81102
- this.followListeners.add(resolve25);
81222
+ await Promise.race([this.settled(), new Promise((resolve27) => {
81223
+ listener = resolve27;
81224
+ this.followListeners.add(resolve27);
81103
81225
  })]);
81104
81226
  } finally {
81105
81227
  if (listener) this.followListeners.delete(listener);
@@ -81190,7 +81312,7 @@ var SessionHost = class {
81190
81312
  const leadLives = known?.sessionId ? this.sessions.has(known.sessionId) : false;
81191
81313
  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
81314
  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.`);
81315
+ 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
81316
  const ended = known && hasLead(known.state) && !leadLives ? `the lead session ${known.sessionId ?? "(none)"} ended with its node` : null;
81195
81317
  if (known?.state === "done") throw new SessionHostError("invalid", `The mission ${missionId} is done. Give the team a new mission for new work.`);
81196
81318
  const claim2 = declared?.source ?? missionId;
@@ -81201,12 +81323,13 @@ var SessionHost = class {
81201
81323
  const resumable = ended && known?.sessionId && readSessionRecord(this.paths.sessionsDir, known.sessionId) !== null ? known.sessionId : null;
81202
81324
  if (resumable) {
81203
81325
  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 });
81326
+ const starter = this.starterKey(by);
81327
+ 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
81328
  return { task: task2, duplicate: false };
81206
81329
  }
81207
81330
  const task = await this.submit({ message, start: { agent: team, workspaceDir, team, mission } });
81208
81331
  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 });
81332
+ 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
81333
  return { task, duplicate: false };
81211
81334
  }
81212
81335
  pauseInterrupted() {
@@ -81309,7 +81432,7 @@ var SessionHost = class {
81309
81432
  for (const { relay: port } of this.options.ports.missionRelays()) {
81310
81433
  const answered = await port.reply({ missionId, text: text3, ...questionId && { questionId }, by, ...this.claimOfBy(by) });
81311
81434
  if (!answered) continue;
81312
- await this.closeQuestions(missionId, questionId ?? void 0, `answered: ${text3}`);
81435
+ await this.closeQuestions(missionId, questionId ?? void 0, by, text3);
81313
81436
  return answered;
81314
81437
  }
81315
81438
  }
@@ -81320,14 +81443,14 @@ var SessionHost = class {
81320
81443
  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
81444
  const lead = known.sessionId;
81322
81445
  this.missions.change(missionId, { state: "active", by, note: "reply" });
81323
- await this.closeQuestions(missionId, null, `answered: ${text3}`);
81446
+ await this.closeQuestions(missionId, null, by, text3);
81324
81447
  await this.submit({ sessionId: lead, message: { messageId: randomUUID4(), role: "ROLE_USER", parts: [textPart(`[reply to your question on the mission ${missionId}, from ${by}]
81325
81448
  ${text3}`)] } });
81326
81449
  return this.missionOf(missionId);
81327
81450
  }
81328
81451
  async answerAsked(by, missionId, lead, asked2, text3) {
81329
81452
  this.missions.answer(missionId, asked2.id);
81330
- await this.closeQuestions(missionId, asked2.id, `answered: ${text3}`);
81453
+ await this.closeQuestions(missionId, asked2.id, by, text3);
81331
81454
  const session = this.sessions.get(lead);
81332
81455
  if (session?.running && !session.external) {
81333
81456
  this.steering.set(lead, [...this.steering.get(lead) ?? [], { from: by, missionId, text: `reply to your question ${asked2.id}: ${asked2.question}
@@ -81344,7 +81467,7 @@ ${text3}`)] } });
81344
81467
  const missionId = given.mission;
81345
81468
  const known = this.missions.get(missionId);
81346
81469
  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}.`);
81470
+ 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
81471
  this.askedInTurn.add(known.sessionId);
81349
81472
  this.recordQuestion(missionId, known.sessionId, given.question);
81350
81473
  return this.missionOf(missionId);
@@ -81352,8 +81475,8 @@ ${text3}`)] } });
81352
81475
  recordQuestion(missionId, sessionId, question) {
81353
81476
  const asked2 = { id: `q-${randomUUID4().slice(0, 8)}`, question, at: new Date(this.options.context.now()).toISOString() };
81354
81477
  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);
81478
+ 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 });
81479
+ this.tellReceivers(after.receivers, { team: after.team, mission: missionId, lead: after.lead, question: asked2.question, questionId: asked2.id }, sessionId);
81357
81480
  }
81358
81481
  async say(by, target, text3, reader, relay = true) {
81359
81482
  const named2 = this.missions.get(target);
@@ -81409,16 +81532,28 @@ ${text3}`)] } });
81409
81532
  const mission = message.metadata?.[ORBITAL_META.mission];
81410
81533
  return typeof mission === "string" ? mission : null;
81411
81534
  }
81412
- async closeQuestions(mission, questionId, text3) {
81535
+ async closeQuestions(mission, questionId, by, text3) {
81536
+ const answerer = by.startsWith("session:") ? by.slice("session:".length) : null;
81537
+ const told = /* @__PURE__ */ new Set();
81413
81538
  for (const [taskId, asked2] of [...this.questionDeliveries]) {
81414
81539
  if (asked2.mission !== mission || questionId !== void 0 && asked2.questionId !== questionId) continue;
81415
81540
  this.questionDeliveries.delete(taskId);
81416
81541
  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);
81542
+ const receiver = this.taskSession.get(taskId);
81543
+ if (!task || !receiver || task.status && isTerminal(task.status.state)) continue;
81544
+ await this.finish(receiver, task, "TASK_STATE_COMPLETED", `answered by ${by}: ${text3}`);
81420
81545
  this.externalWaits.get(taskId)?.();
81546
+ if (receiver !== answerer) this.tellAnswered(receiver, { kind: "answered", mission, question: asked2.questionId, by, text: text3 }, told);
81421
81547
  }
81548
+ 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);
81549
+ }
81550
+ tellAnswered(sessionId, answered, told) {
81551
+ const external = this.sessions.get(sessionId)?.external;
81552
+ if (!external?.attached || told.has(sessionId)) return;
81553
+ told.add(sessionId);
81554
+ const answers = (delivery) => delivery.mission === answered.mission && (answered.question === null || delivery.question === answered.question);
81555
+ for (let index = external.waiting.length - 1; index >= 0; index -= 1) if (answers(external.waiting[index])) external.waiting.splice(index, 1);
81556
+ for (const listener of external.listeners) listener(answered);
81422
81557
  }
81423
81558
  busyBelow(sessionId) {
81424
81559
  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 +81580,46 @@ ${text3}`)] } });
81445
81580
  }
81446
81581
  this.nudged.delete(sessionId);
81447
81582
  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);
81583
+ this.tellReceivers(known.receivers, { team: known.team, mission: known.mission, lead: known.lead, question: question === "" ? NO_HANDOFF_TELL : question, questionId: null }, sessionId);
81449
81584
  }
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.`);
81585
+ async joinMission(receiver, missionId) {
81586
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
81587
+ if ("sessionId" in receiver && !session) throw new SessionHostError("not-found", `There is no session ${receiver.sessionId} on this node.`);
81453
81588
  const known = this.missions.get(missionId);
81454
- if (!known) {
81589
+ if (!known && session) {
81455
81590
  for (const { relay } of this.options.ports.missionRelays()) {
81456
- const joined = await relay.join({ missionId, giver: { node: this.node, sessionId }, ...this.claimOfSession(sessionId) });
81591
+ const joined = await relay.join({ missionId, receiver: { node: this.node, sessionId: session.id }, ...this.claimOfSession(session.id) });
81457
81592
  if (joined) return joined;
81458
81593
  }
81459
81594
  }
81460
- return this.joinHere(giverRef({ node: this.node, sessionId }), `session:${sessionId}`, missionId);
81595
+ 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.`);
81596
+ if (!session) return this.joinHere(externalReceiver("externalId" in receiver ? receiver.externalId : ""), "cli", missionId, null);
81597
+ return this.joinHere(this.receiverKey(session), `session:${session.id}`, missionId, [session.id]);
81461
81598
  }
81462
81599
  async joinRemote(request) {
81463
- return this.joinHere(giverRef(request.giver), `session:${giverRef(request.giver)}`, request.missionId);
81600
+ const key2 = this.keyOfRemote(request.receiver);
81601
+ const local = remoteReceiverOf(key2) === null ? [request.receiver.sessionId] : null;
81602
+ return this.joinHere(key2, `session:${receiverRef(request.receiver)}`, request.missionId, local);
81464
81603
  }
81465
- async joinHere(giverId, by, missionId) {
81466
- const self = this.node;
81467
- const sessionId = remoteGiverOf(giverId)?.node === self ? remoteGiverOf(giverId).sessionId : giverId;
81604
+ async leaveMission(receiver, missionId) {
81605
+ const known = this.missions.get(missionId);
81606
+ 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.`);
81607
+ const session = "sessionId" in receiver ? this.sessions.get(receiver.sessionId) : void 0;
81608
+ const leaves = (key2) => session ? this.receives(session, key2) : "sessionId" in receiver ? key2 === receiver.sessionId : externalIdOf(key2) === receiver.externalId;
81609
+ const kept = known.receivers.filter((key2) => !leaves(key2));
81610
+ if (kept.length === known.receivers.length) return this.missionOf(missionId);
81611
+ 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 });
81612
+ return this.missionOf(missionId);
81613
+ }
81614
+ async joinHere(key2, by, missionId, deliverTo) {
81468
81615
  const known = this.missions.get(missionId);
81469
81616
  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);
81617
+ 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] });
81618
+ const remote = remoteReceiverOf(key2);
81619
+ const live2 = this.liveReceivers([key2]).filter((session) => deliverTo === null || deliverTo.includes(session.id));
81620
+ for (const open2 of this.openQuestions(stored)) {
81621
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, stored.sessionId);
81622
+ for (const session of live2) if (!this.questionOpen(session.id, open2.mission, open2.questionId)) this.deliverQuestion(session.id, open2, stored.sessionId);
81474
81623
  }
81475
81624
  return this.missionOf(missionId);
81476
81625
  }
@@ -81480,36 +81629,34 @@ ${text3}`)] } });
81480
81629
  missionTeam(missionId) {
81481
81630
  return this.missions.get(missionId)?.team ?? null;
81482
81631
  }
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);
81632
+ async askReceiver(question) {
81633
+ const session = this.sessions.get(question.receiver.sessionId);
81634
+ if (!session || question.receiver.node !== this.node || this.liveReceivers([session.id]).length === 0) return false;
81635
+ 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
81636
  return true;
81488
81637
  }
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;
81638
+ tellReceivers(receivers, open2, leadSessionId) {
81639
+ for (const key2 of receivers) {
81640
+ const remote = remoteReceiverOf(key2);
81641
+ if (remote && remote.node !== this.node) this.askRemote(remote, open2, leadSessionId);
81500
81642
  }
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);
81643
+ for (const session of this.liveReceivers(receivers)) this.deliverQuestion(session.id, open2, leadSessionId);
81644
+ }
81645
+ askRemote(receiver, open2, leadSessionId) {
81646
+ const question = { missionId: open2.mission, node: this.node, lead: open2.lead, leadSessionId, question: open2.question, ...open2.questionId && { questionId: open2.questionId }, receiver, team: open2.team };
81647
+ void (async () => {
81648
+ for (const { relay } of this.options.ports.missionRelays()) if (await relay.ask(question)) return;
81649
+ this.options.context.output.warn(`no plugin carried the question of ${open2.mission} to ${receiverRef(receiver)}`);
81650
+ })().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
81651
  }
81505
- deliverQuestion(giverId, known, sessionId) {
81652
+ deliverQuestion(receiverId, known, sessionId) {
81506
81653
  const missionId = known.mission;
81507
81654
  const question = known.question;
81508
81655
  const questionId = known.questionId;
81509
- const key2 = `${giverId}:${missionId}:${questionId ?? ""}`;
81656
+ const key2 = `${receiverId}:${missionId}:${questionId ?? ""}`;
81510
81657
  this.questionsInFlight.add(key2);
81511
81658
  void this.submit({
81512
- sessionId: giverId,
81659
+ sessionId: receiverId,
81513
81660
  message: {
81514
81661
  messageId: randomUUID4(),
81515
81662
  role: "ROLE_USER",
@@ -81524,7 +81671,7 @@ Answer with missionSay on ${missionId}.`)],
81524
81671
  }
81525
81672
  }).then((task) => {
81526
81673
  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));
81674
+ }).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
81675
  }
81529
81676
  async missionActivity(missionId, limit) {
81530
81677
  const sessionId = this.missions.get(missionId)?.sessionId;
@@ -81742,8 +81889,8 @@ Answer with missionSay on ${missionId}.`)],
81742
81889
  }
81743
81890
  }
81744
81891
  if (signal?.aborted) return;
81745
- await new Promise((resolve25) => {
81746
- wake = resolve25;
81892
+ await new Promise((resolve27) => {
81893
+ wake = resolve27;
81747
81894
  });
81748
81895
  wake = null;
81749
81896
  }
@@ -81819,9 +81966,13 @@ Answer with missionSay on ${missionId}.`)],
81819
81966
  }
81820
81967
  missionRoots(readerSessionId) {
81821
81968
  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));
81969
+ 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
81970
  return new Set(missionRoots);
81824
81971
  }
81972
+ receivesFor(sessionId, receivers) {
81973
+ const session = this.sessions.get(sessionId);
81974
+ return session !== void 0 && receivers.some((key2) => this.receives(session, key2));
81975
+ }
81825
81976
  reachRoots(readerSessionId) {
81826
81977
  const outside = this.sessions.get(readerSessionId)?.external !== void 0;
81827
81978
  const attachedRoots = outside ? [...this.sessions.values()].filter((one) => one.external?.attached === true).map((one) => this.rootOf(one.id)) : [];
@@ -83390,7 +83541,7 @@ var StdioClientTransport = class {
83390
83541
  if (this._process) {
83391
83542
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
83392
83543
  }
83393
- return new Promise((resolve25, reject) => {
83544
+ return new Promise((resolve27, reject) => {
83394
83545
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
83395
83546
  // merge default env with server env because mcp server needs some env vars
83396
83547
  env: {
@@ -83407,7 +83558,7 @@ var StdioClientTransport = class {
83407
83558
  this.onerror?.(error62);
83408
83559
  });
83409
83560
  this._process.on("spawn", () => {
83410
- resolve25();
83561
+ resolve27();
83411
83562
  });
83412
83563
  this._process.on("close", (_code) => {
83413
83564
  this._process = void 0;
@@ -83472,22 +83623,22 @@ var StdioClientTransport = class {
83472
83623
  if (this._process) {
83473
83624
  const processToClose = this._process;
83474
83625
  this._process = void 0;
83475
- const closePromise = new Promise((resolve25) => {
83626
+ const closePromise = new Promise((resolve27) => {
83476
83627
  processToClose.once("close", () => {
83477
- resolve25();
83628
+ resolve27();
83478
83629
  });
83479
83630
  });
83480
83631
  try {
83481
83632
  processToClose.stdin?.end();
83482
83633
  } catch {
83483
83634
  }
83484
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
83635
+ await Promise.race([closePromise, new Promise((resolve27) => setTimeout(resolve27, 2e3).unref())]);
83485
83636
  if (processToClose.exitCode === null) {
83486
83637
  try {
83487
83638
  processToClose.kill("SIGTERM");
83488
83639
  } catch {
83489
83640
  }
83490
- await Promise.race([closePromise, new Promise((resolve25) => setTimeout(resolve25, 2e3).unref())]);
83641
+ await Promise.race([closePromise, new Promise((resolve27) => setTimeout(resolve27, 2e3).unref())]);
83491
83642
  }
83492
83643
  if (processToClose.exitCode === null) {
83493
83644
  try {
@@ -83499,15 +83650,15 @@ var StdioClientTransport = class {
83499
83650
  this._readBuffer.clear();
83500
83651
  }
83501
83652
  send(message) {
83502
- return new Promise((resolve25) => {
83653
+ return new Promise((resolve27) => {
83503
83654
  if (!this._process?.stdin) {
83504
83655
  throw new Error("Not connected");
83505
83656
  }
83506
83657
  const json4 = serializeMessage(message);
83507
83658
  if (this._process.stdin.write(json4)) {
83508
- resolve25();
83659
+ resolve27();
83509
83660
  } else {
83510
- this._process.stdin.once("drain", resolve25);
83661
+ this._process.stdin.once("drain", resolve27);
83511
83662
  }
83512
83663
  });
83513
83664
  }
@@ -83601,12 +83752,12 @@ var StdioServerTransport = class {
83601
83752
  this.onclose?.();
83602
83753
  }
83603
83754
  send(message) {
83604
- return new Promise((resolve25) => {
83755
+ return new Promise((resolve27) => {
83605
83756
  const json4 = serializeMessage(message);
83606
83757
  if (this._stdout.write(json4)) {
83607
- resolve25();
83758
+ resolve27();
83608
83759
  } else {
83609
- this._stdout.once("drain", resolve25);
83760
+ this._stdout.once("drain", resolve27);
83610
83761
  }
83611
83762
  });
83612
83763
  }
@@ -83718,6 +83869,7 @@ function agentTools(options) {
83718
83869
  };
83719
83870
  const send = (message, wait) => node2.send(message, wait);
83720
83871
  const a2a = () => node2.a2a();
83872
+ const own2 = async () => options.attached ? await options.attached() : options.sessionId;
83721
83873
  return [
83722
83874
  defineMcpTool({
83723
83875
  name: "agentList",
@@ -83761,7 +83913,7 @@ function agentTools(options) {
83761
83913
  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
83914
  inputSchema: {},
83763
83915
  run: async () => {
83764
- if (!options.sessionId) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
83916
+ if (!await own2()) throw new Error("agentPeers needs your own session: only an agent the node runs has peers.");
83765
83917
  return await node2.peers();
83766
83918
  }
83767
83919
  }),
@@ -83841,13 +83993,13 @@ function agentTools(options) {
83841
83993
  }),
83842
83994
  defineMcpTool({
83843
83995
  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.",
83996
+ 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
83997
  inputSchema: {},
83846
83998
  run: async () => node2.missions()
83847
83999
  }),
83848
84000
  defineMcpTool({
83849
84001
  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.",
84002
+ 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
84003
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
83852
84004
  run: async (params) => {
83853
84005
  const found = (await node2.missions()).filter((record3) => record3.missionId === params.mission);
@@ -83899,7 +84051,7 @@ function agentTools(options) {
83899
84051
  }),
83900
84052
  defineMcpTool({
83901
84053
  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.",
84054
+ 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
84055
  inputSchema: {
83904
84056
  mission: MissionIdSchema.describe("The mission id."),
83905
84057
  text: external_exports.string().min(1).describe("The answer, in full: the lead has none of your context."),
@@ -83933,13 +84085,22 @@ function agentTools(options) {
83933
84085
  }),
83934
84086
  defineMcpTool({
83935
84087
  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.",
84088
+ 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
84089
  inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
83938
84090
  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.");
84091
+ 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
84092
  return node2.joinMission(params.mission);
83941
84093
  }
83942
84094
  }),
84095
+ defineMcpTool({
84096
+ name: "missionLeave",
84097
+ 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.",
84098
+ inputSchema: { mission: MissionIdSchema.describe("The mission id.") },
84099
+ run: async (params) => {
84100
+ if (!await own2()) throw new Error("missionLeave needs your own session: only a decision receiver leaves a mission.");
84101
+ return node2.leaveMission(params.mission);
84102
+ }
84103
+ }),
83943
84104
  defineMcpTool({
83944
84105
  name: "missionNew",
83945
84106
  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 +84120,7 @@ function agentTools(options) {
83959
84120
  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
84121
  inputSchema: {},
83961
84122
  run: async () => {
83962
- if (!options.sessionId) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
84123
+ if (!await own2()) throw new Error("missionGet needs your own session: only an agent the node runs has a mission.");
83963
84124
  return await node2.mission();
83964
84125
  }
83965
84126
  }),
@@ -83971,7 +84132,7 @@ function agentTools(options) {
83971
84132
  state: external_exports.enum(["done", "failed"]).optional().describe("done when the end state is reached, failed when it cannot be. Default: done.")
83972
84133
  },
83973
84134
  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.");
84135
+ if (!await own2()) throw new Error("missionHandoff needs your own session: only the lead session of a mission hands it off.");
83975
84136
  return await node2.handoff({ result: params.result, state: params.state ?? "done" });
83976
84137
  }
83977
84138
  }),
@@ -83984,18 +84145,18 @@ function agentTools(options) {
83984
84145
  state: external_exports.string().min(1).optional().describe("The state of the linked thing, such as open, merged or closed.")
83985
84146
  },
83986
84147
  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.");
84148
+ if (!await own2()) throw new Error("missionLink needs your own session: only the lead session of a mission adds a link to it.");
83988
84149
  return await node2.link({ label: params.label, url: params.url, state: params.state ?? null });
83989
84150
  }
83990
84151
  }),
83991
84152
  defineMcpTool({
83992
84153
  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.",
84154
+ 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
84155
  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.")
84156
+ 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
84157
  },
83997
84158
  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.");
84159
+ if (!await own2()) throw new Error("missionAsk needs your own session: only the lead session of a mission asks its decision receivers.");
83999
84160
  return await node2.ask({ question: params.question });
84000
84161
  }
84001
84162
  })
@@ -84562,8 +84723,8 @@ function relaySender(plugin, by) {
84562
84723
  function pluginSender(plugin) {
84563
84724
  return { channel: plugin, peer: plugin, from: { kind: "node", name: plugin } };
84564
84725
  }
84565
- function remoteSessionSender(plugin, giver) {
84566
- return { channel: plugin, peer: giver.node, from: { kind: "session", name: giver.sessionId, ade: giver.node } };
84726
+ function remoteSessionSender(plugin, session) {
84727
+ return { channel: plugin, peer: session.node, from: { kind: "session", name: session.sessionId, ade: session.node } };
84567
84728
  }
84568
84729
  function missionTarget(node2, team) {
84569
84730
  return team ? { kind: "team", name: team, ade: node2 } : { kind: "node", name: node2 };
@@ -84702,10 +84863,10 @@ function a2aHandlerFor(host, reader, confined = false, network = null) {
84702
84863
  };
84703
84864
  }
84704
84865
  function readBody(request) {
84705
- return new Promise((resolve25, reject) => {
84866
+ return new Promise((resolve27, reject) => {
84706
84867
  const chunks = [];
84707
84868
  request.on("data", (chunk) => chunks.push(chunk));
84708
- request.on("end", () => resolve25(Buffer.concat(chunks).toString("utf8")));
84869
+ request.on("end", () => resolve27(Buffer.concat(chunks).toString("utf8")));
84709
84870
  request.on("error", reject);
84710
84871
  });
84711
84872
  }
@@ -84769,7 +84930,7 @@ async function serveHostApi(request, response, url2, api) {
84769
84930
  if (request.method === "POST" && url2.pathname === "/orbital/host/missions/ask") {
84770
84931
  const asked2 = MissionQuestionSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
84771
84932
  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) });
84933
+ return json3(response, 200, { delivered: await api.askReceiver(asked2.data) });
84773
84934
  }
84774
84935
  if (request.method === "POST" && url2.pathname === "/orbital/host/settled") {
84775
84936
  const { orFollowing } = JSON.parse(await readBody(request) || "{}");
@@ -84888,11 +85049,16 @@ async function startNodeServer(options) {
84888
85049
  const by = caller.bound ? `session:${caller.bound}` : "cli";
84889
85050
  return json3(response, 200, control[1] === "pause" ? await options.host.pauseMission(by, asked2.data.missionId) : await options.host.resumeMission(by, asked2.data.missionId));
84890
85051
  }
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." });
85052
+ const membership = /^\/orbital\/missions\/(join|leave)$/.exec(url2.pathname);
85053
+ if (request.method === "POST" && membership) {
84893
85054
  const asked2 = MissionJoinSchema.safeParse(JSON.parse(await readBody(request) || "{}"));
84894
85055
  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));
85056
+ const session = caller.bound ?? caller.reader;
85057
+ const holder = caller.presented === caller.token;
85058
+ 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." });
85059
+ 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.` });
85060
+ const receiver = asked2.data.externalId ? { externalId: asked2.data.externalId } : { sessionId: session };
85061
+ return json3(response, 200, membership[1] === "join" ? await options.host.joinMission(receiver, asked2.data.missionId) : await options.host.leaveMission(receiver, asked2.data.missionId));
84896
85062
  }
84897
85063
  if (request.method === "POST" && url2.pathname === "/orbital/missions/new") {
84898
85064
  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 +85096,7 @@ async function startNodeServer(options) {
84930
85096
  const named2 = asked2.data.missionId ?? own2?.missionId ?? null;
84931
85097
  if (!named2) return json3(response, 400, { error: "Name the mission by its id." });
84932
85098
  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." });
85099
+ 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
85100
  return json3(response, 200, await options.host.askMission({ mission: named2, question: asked2.data.question, sessionId: session }));
84935
85101
  }
84936
85102
  const activity = /^\/orbital\/missions\/([^/]+)\/activity$/.exec(url2.pathname);
@@ -84971,6 +85137,20 @@ async function startNodeServer(options) {
84971
85137
  return json3(response, 502, { error: `The panel ${plugin2}/${id} did not answer: ${error62 instanceof Error ? error62.message : String(error62)}` });
84972
85138
  }
84973
85139
  }
85140
+ const commandPath = /^\/orbital\/plugins\/([^/]+)\/commands\/([^/]+)$/.exec(url2.pathname);
85141
+ if (request.method === "POST" && commandPath) {
85142
+ if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret runs a command of a plugin." });
85143
+ const [name, command] = [decodeURIComponent(commandPath[1]), decodeURIComponent(commandPath[2])];
85144
+ const plugin2 = options.ports.plugins.find((one) => one.name === name);
85145
+ if (!plugin2?.command) return json3(response, 404, { error: `No plugin ${name} with commands runs on this node.` });
85146
+ const asked2 = PluginCommandRequestSchema.safeParse(await readBody(request).then((raw) => JSON.parse(raw)).catch(() => null));
85147
+ 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.` });
85148
+ try {
85149
+ return json3(response, 200, await plugin2.command(asked2.data));
85150
+ } catch (error62) {
85151
+ return json3(response, 502, { error: `The command ${name} ${command} failed: ${error62 instanceof Error ? error62.message : String(error62)}` });
85152
+ }
85153
+ }
84974
85154
  if (url2.pathname === "/orbital/telemetry/snapshot" || url2.pathname === "/orbital/telemetry/events") {
84975
85155
  if (request.method === "GET" && url2.pathname === "/orbital/telemetry/snapshot" && (presented === token || bound)) return json3(response, 200, await options.host.snapshot());
84976
85156
  if (presented !== token) return json3(response, 403, { error: "Only a holder of the node secret reads the telemetry stream of the node." });
@@ -85130,7 +85310,7 @@ async function startNodeServer(options) {
85130
85310
  const result2 = await dispatchA2a(await readBody(request), a2aHandlerFor(options.host, reader, bound !== null, httpSender(request.socket.remoteAddress)));
85131
85311
  if (result2.kind === "json") return json3(response, 200, result2.body);
85132
85312
  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));
85313
+ const closed = new Promise((resolve27) => response.on("close", resolve27));
85134
85314
  const iterator = result2.events[Symbol.asyncIterator]();
85135
85315
  for (; ; ) {
85136
85316
  const next = await Promise.race([iterator.next(), closed.then(() => null)]);
@@ -85143,9 +85323,9 @@ async function startNodeServer(options) {
85143
85323
  }
85144
85324
  response.end();
85145
85325
  }
85146
- await new Promise((resolve25, reject) => {
85326
+ await new Promise((resolve27, reject) => {
85147
85327
  server.once("error", reject);
85148
- server.listen(options.port, address, () => resolve25());
85328
+ server.listen(options.port, address, () => resolve27());
85149
85329
  });
85150
85330
  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
85331
  mkdirSync17(nodeDir(options.home, options.name), { recursive: true, mode: 448 });
@@ -85156,7 +85336,7 @@ async function startNodeServer(options) {
85156
85336
  close: async () => {
85157
85337
  if (readNodeInfo(options.home, options.name)?.pid === process.pid) rmSync9(nodeInfoPath(options.home, options.name), { force: true });
85158
85338
  server.closeAllConnections();
85159
- await new Promise((resolve25) => server.close(() => resolve25()));
85339
+ await new Promise((resolve27) => server.close(() => resolve27()));
85160
85340
  }
85161
85341
  };
85162
85342
  }
@@ -86166,7 +86346,7 @@ function hostApiOf(host, plugin, catalog, channels, models) {
86166
86346
  startMission: (start) => host.startMission(plugin, start),
86167
86347
  missions: () => host.missionList(),
86168
86348
  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)));
86349
+ 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
86350
  return host.joinRemote(request);
86171
86351
  },
86172
86352
  replyMission: async (request) => {
@@ -86181,9 +86361,9 @@ function hostApiOf(host, plugin, catalog, channels, models) {
86181
86361
  await relayed(request, `resume the mission ${request.missionId}`);
86182
86362
  return host.resumeMission(request.by, request.missionId, false);
86183
86363
  },
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);
86364
+ askReceiver: async (question) => {
86365
+ 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 })));
86366
+ return host.askReceiver(question);
86187
86367
  },
86188
86368
  sayMission: async (request) => {
86189
86369
  await relayed(request, request.text);
@@ -86399,8 +86579,8 @@ var ChannelHub = class {
86399
86579
  if (state && isTerminal(state)) return;
86400
86580
  }
86401
86581
  if (signal?.aborted) return;
86402
- await new Promise((resolve25) => {
86403
- wake = resolve25;
86582
+ await new Promise((resolve27) => {
86583
+ wake = resolve27;
86404
86584
  });
86405
86585
  wake = null;
86406
86586
  }
@@ -86574,8 +86754,8 @@ Answer yes or no in this thread.`)], metadata: { [ORBITAL_META.channel]: binding
86574
86754
  thread: null
86575
86755
  });
86576
86756
  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() });
86757
+ const answer2 = await new Promise((resolve27, reject) => {
86758
+ this.asks.set(threadKey, { resolve: resolve27, question, channel: binding.channel, since: new Date(this.options.context.now()).toISOString() });
86579
86759
  signal?.addEventListener("abort", () => {
86580
86760
  this.asks.delete(threadKey);
86581
86761
  reject(new Error("The question was withdrawn."));
@@ -87250,8 +87430,9 @@ var panel = {
87250
87430
  var panelCommands = [panel];
87251
87431
 
87252
87432
  // src/core/cli/plugin.ts
87253
- import { readFileSync as readFileSync28 } from "node:fs";
87254
- import { basename as basename9, resolve as resolve14 } from "node:path";
87433
+ var import_yaml8 = __toESM(require_dist(), 1);
87434
+ import { readFileSync as readFileSync29 } from "node:fs";
87435
+ import { basename as basename9, resolve as resolve15 } from "node:path";
87255
87436
 
87256
87437
  // src/sdk/index.ts
87257
87438
  init_protocol();
@@ -87632,13 +87813,148 @@ init_protocol();
87632
87813
 
87633
87814
  // src/core/cli/plugin.ts
87634
87815
  init_protocol();
87816
+
87817
+ // src/core/cli/plugin-commands.ts
87818
+ init_protocol();
87819
+ init_command();
87820
+ import { existsSync as existsSync30, readFileSync as readFileSync28 } from "node:fs";
87821
+ import { join as join37, resolve as resolve14 } from "node:path";
87822
+ var INTROSPECTION_WORDS = ["describe", "schema", "help"];
87823
+ function commandsOf(dir) {
87824
+ const descriptor = join37(dir, PLUGIN_PACKAGE_FILE);
87825
+ if (!existsSync30(descriptor)) return [];
87826
+ try {
87827
+ return PluginPackageSchema.parse(JSON.parse(readFileSync28(descriptor, "utf8"))).commands;
87828
+ } catch {
87829
+ return [];
87830
+ }
87831
+ }
87832
+ function declaredPluginCommands(workspaceDir, env3) {
87833
+ try {
87834
+ const source = definitionSource(workspaceDir, env3);
87835
+ const config2 = source ? workspaceConfig(workspaceDir, env3) : null;
87836
+ if (!source || !config2) return [];
87837
+ return Object.entries(config2.plugins).filter(([, entry]) => entry.enabled && entry.source).flatMap(([plugin, entry]) => {
87838
+ const from = pluginSource(plugin, entry);
87839
+ const commands = from.startsWith("exec:") ? [] : commandsOf(pluginDir(plugin, from, { harnessRoot: source.root, home: orbitalHome(env3) }));
87840
+ return commands.length > 0 ? [{ plugin, commands }] : [];
87841
+ });
87842
+ } catch {
87843
+ return [];
87844
+ }
87845
+ }
87846
+ function workspaceOf(input2, fallback = ".") {
87847
+ return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : fallback);
87848
+ }
87849
+ async function startWorkspacePlugin(context, workspaceDir, name, terminal2) {
87850
+ const root = definitionSource(workspaceDir, context.env)?.root ?? null;
87851
+ const config2 = workspaceConfig(workspaceDir, context.env);
87852
+ const entry = config2?.plugins[name];
87853
+ 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>` });
87854
+ const secrets = new PluginSecrets(context.env);
87855
+ return startRemotePlugin({
87856
+ name,
87857
+ source: pluginSource(name, entry),
87858
+ config: entry.config,
87859
+ entryEnv: entry.env,
87860
+ node: { name: `plugin-${name}`, workspaceDir, harness: defaultHarness(context.env), harnesses: [...RUNNER_HARNESSES], once: true, idleExitMs: null, project: null },
87861
+ roots: { harnessRoot: root, home: orbitalHome(context.env) },
87862
+ offline: config2.offline,
87863
+ policy: new PolicyChain([]),
87864
+ env: context.env,
87865
+ log: (level, message) => level === "info" ? context.output.step(message) : context.output.warn(message),
87866
+ secret: async (secret) => secrets.get(name, secret),
87867
+ restarts: 0,
87868
+ ...terminal2 && { terminal: terminal2 }
87869
+ });
87870
+ }
87871
+ function flagOf(option) {
87872
+ return {
87873
+ name: option.name,
87874
+ type: option.type,
87875
+ summary: option.summary,
87876
+ ...option.required && { required: true },
87877
+ ...option.default !== void 0 && { default: option.default },
87878
+ ...option.values && { enumValues: option.values }
87879
+ };
87880
+ }
87881
+ function requestOf(command, input2, terminal2) {
87882
+ const args = Object.fromEntries(command.args.flatMap((arg) => {
87883
+ const value2 = input2.positionals[arg.name];
87884
+ return value2 === void 0 ? [] : [[arg.name, value2]];
87885
+ }));
87886
+ const options = Object.fromEntries(command.options.flatMap((option) => {
87887
+ const value2 = input2.flags[option.name];
87888
+ if (option.type === "boolean") return [[option.name, value2 === true]];
87889
+ return typeof value2 === "string" || typeof value2 === "number" ? [[option.name, value2]] : [];
87890
+ }));
87891
+ return { name: command.name, args, options, terminal: terminal2 };
87892
+ }
87893
+ async function runOnNode(context, workspaceDir, input2, plugin, request) {
87894
+ const configured = workspaceConfig(workspaceDir, context.env)?.node ?? null;
87895
+ const name = chooseNode({ ...typeof input2.flags.node === "string" && { flag: input2.flags.node }, env: context.env, configured, workspaceDir }).name;
87896
+ const node2 = await liveNode(orbitalHome(context.env), name, context.fetch);
87897
+ 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` });
87898
+ const response = await context.fetch(`${node2.url}/orbital/plugins/${encodeURIComponent(plugin)}/commands/${encodeURIComponent(request.name)}`, {
87899
+ method: "POST",
87900
+ headers: { authorization: `Bearer ${node2.token}`, "content-type": "application/json" },
87901
+ body: JSON.stringify(request)
87902
+ });
87903
+ const body2 = await response.json();
87904
+ 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 });
87905
+ return PluginCommandResultSchema.parse(body2);
87906
+ }
87907
+ async function runInPlugin(context, workspaceDir, input2, plugin, command, terminal2) {
87908
+ if (command.interactive && context.nonInteractive) {
87909
+ 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." });
87910
+ }
87911
+ const given = command.interactive ? { input: terminal2.input, output: context.outputMode === "text" ? terminal2.output : terminal2.error, error: terminal2.error } : void 0;
87912
+ const started = await startWorkspacePlugin(context, workspaceDir, plugin, given);
87913
+ try {
87914
+ return await started.command(requestOf(command, input2, given ? { tty: context.isTty } : null));
87915
+ } catch (error62) {
87916
+ if (error62 instanceof CliError) throw error62;
87917
+ const message = error62 instanceof Error ? error62.message : String(error62);
87918
+ throw new CliError({ code: "internal", message: error62 instanceof RpcError ? `The command ${plugin} ${command.name} failed: ${message}` : `The command ${plugin} ${command.name} got no answer: ${message}`, exitCode: ExitCode.Error });
87919
+ } finally {
87920
+ await started.close?.();
87921
+ }
87922
+ }
87923
+ function specOf(workspaceDir, plugin, command, terminal2) {
87924
+ return {
87925
+ path: [plugin, command.name],
87926
+ summary: command.summary,
87927
+ group: CommandGroup.Packs,
87928
+ credential: "none",
87929
+ flags: [
87930
+ { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` },
87931
+ ...command.node ? [{ name: "node", type: "string", identifier: true, summary: "The node that runs the plugin. Default: the node of this workspace." }] : [],
87932
+ ...command.options.map(flagOf)
87933
+ ],
87934
+ positionals: command.args.map((arg) => ({ name: arg.name, type: "string", summary: arg.summary, ...arg.required && { required: true }, ...arg.variadic && { variadic: true } })),
87935
+ outputShape: { kind: "object", ref: "PluginCommandResult", fields: [{ name: "data", type: "object", summary: `What the command of the plugin ${plugin} answered.` }] },
87936
+ exitCodes: [0, 1, 2, 3, 5],
87937
+ routes: [],
87938
+ examples: [{ command: `orbital ${plugin} ${command.name}`, why: command.summary }],
87939
+ run: async (input2, context) => {
87940
+ const workspace = workspaceOf(input2, workspaceDir);
87941
+ const result2 = command.node ? await runOnNode(context, workspace, input2, plugin, requestOf(command, input2, null)) : await runInPlugin(context, workspace, input2, plugin, command, terminal2);
87942
+ if (result2.text !== null) context.output.line(result2.text);
87943
+ if (result2.text === null && Object.keys(result2.data).length > 0) context.output.line(JSON.stringify(result2.data, null, 2));
87944
+ return { data: result2.data, ...result2.exitCode !== 0 && { exitCode: result2.exitCode } };
87945
+ }
87946
+ };
87947
+ }
87948
+ function pluginCommandSpecs(options) {
87949
+ const terminal2 = options.terminal ?? { input: 0, output: 1, error: 2 };
87950
+ return declaredPluginCommands(options.workspaceDir, options.env).filter(({ plugin }) => !options.reserved.includes(plugin)).flatMap(({ plugin, commands }) => commands.map((command) => specOf(options.workspaceDir, plugin, command, terminal2)));
87951
+ }
87952
+
87953
+ // src/core/cli/plugin.ts
87635
87954
  init_command();
87636
87955
  function workspaceFlag() {
87637
87956
  return { name: "workspace", type: "path", summary: `The project whose ${ORBITAL_FILE} names the plugin. Default: the current directory.` };
87638
87957
  }
87639
- function workspaceOf(input2) {
87640
- return resolve14(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
87641
- }
87642
87958
  function sourceOrRefuse(context, input2) {
87643
87959
  const workspaceDir = workspaceOf(input2);
87644
87960
  const source = definitionSource(workspaceDir, context.env);
@@ -87662,6 +87978,25 @@ async function manifestOf(context, workspaceDir, name, source, root, config2) {
87662
87978
  await plugin.close?.();
87663
87979
  return plugin.manifest;
87664
87980
  }
87981
+ function configOf(input2, known) {
87982
+ const file2 = typeof input2.flags["config-file"] === "string" ? resolve15(input2.flags["config-file"]) : null;
87983
+ const fromFile = file2 ? (0, import_yaml8.parse)(readFileSync29(file2, "utf8")) : {};
87984
+ 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." });
87985
+ const pairs = Array.isArray(input2.flags.config) ? input2.flags.config.map(String) : typeof input2.flags.config === "string" ? [input2.flags.config] : [];
87986
+ const fromPairs = Object.fromEntries(pairs.map((pair) => {
87987
+ const at = pair.indexOf("=");
87988
+ 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." });
87989
+ return [pair.slice(0, at), (0, import_yaml8.parse)(pair.slice(at + 1)) ?? ""];
87990
+ }));
87991
+ return { ...known, ...fromFile, ...fromPairs };
87992
+ }
87993
+ async function checkedManifest(context, workspaceDir, name, source, root, config2) {
87994
+ try {
87995
+ return await manifestOf(context, workspaceDir, name, source, root, config2);
87996
+ } catch (error62) {
87997
+ 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>." });
87998
+ }
87999
+ }
87665
88000
  function permissionLines(manifest) {
87666
88001
  const { network, secrets, models } = manifest.permissions;
87667
88002
  return [
@@ -87677,7 +88012,9 @@ var add = () => ({
87677
88012
  flags: [
87678
88013
  workspaceFlag(),
87679
88014
  { 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.` }
88015
+ { 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.` },
88016
+ { 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.` },
88017
+ { 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
88018
  ],
87682
88019
  positionals: [
87683
88020
  { name: "plugin", type: "string", required: true, summary: "<name>@<marketplace>, npm:<package>, git:<url>, or a folder such as ./plugins/mine." }
@@ -87698,34 +88035,39 @@ var add = () => ({
87698
88035
  examples: [
87699
88036
  { command: "orbital plugin add slack@community", why: "Install the slack plugin from the community marketplace, and use it in this workspace." },
87700
88037
  { 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." }
88038
+ { 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." },
88039
+ { command: "orbital plugin add ./plugins/notify --config channel=general --config retries=3", why: "Add a plugin that checks its config when it starts." }
87702
88040
  ],
87703
88041
  run: async (input2, context) => {
87704
88042
  const home = orbitalHome(context.env);
87705
88043
  const given = String(input2.positionals.plugin);
87706
88044
  const ref = resolvePluginRef(home, given);
87707
88045
  const copied = ref.kind !== "folder" || given.includes("@");
87708
- const installed = copied ? installPlugin(home, ref) : null;
87709
- if (installed) context.output.step(`\u2713 installed ${ref.name} in ${installed}`);
87710
- if (input2.flags["install-only"] === true) return { data: { name: ref.name, source: given, installed, enabled: false, manifest: null } };
88046
+ const install2 = copied ? installPlugin(home, ref) : null;
88047
+ const installed = install2?.dir ?? null;
88048
+ const name = install2?.name ?? ref.name ?? given;
88049
+ if (install2) context.output.step(`\u2713 installed ${name} in ${install2.dir}`);
88050
+ if (input2.flags["install-only"] === true) return { data: { name, source: given, installed, enabled: false, manifest: null } };
87711
88051
  if (input2.flags.user === true) {
87712
88052
  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, {});
87714
- context.output.step(`${ref.name} ${manifest2.version} reaches:`);
88053
+ const config3 = configOf(input2, userPlugins(context.env)[name]?.config ?? {});
88054
+ const manifest2 = await checkedManifest(context, workspaceOf(input2), name, source2, null, config3);
88055
+ context.output.step(`${name} ${manifest2.version} reaches:`);
87715
88056
  for (const line of permissionLines(manifest2)) context.output.step(line);
87716
- setUserPlugin(context.env, ref.name, { source: source2, enabled: true, config: {}, env: [] });
87717
- context.output.step(`\u2713 your user config names ${ref.name}: source ${source2}`);
87718
- return { data: { name: ref.name, source: source2, installed, enabled: true, manifest: manifest2 } };
88057
+ setUserPlugin(context.env, name, { source: source2, enabled: true, config: config3, env: [] });
88058
+ context.output.step(`\u2713 your user config names ${name}: source ${source2}`);
88059
+ return { data: { name, source: source2, installed, enabled: true, manifest: manifest2 } };
87719
88060
  }
87720
88061
  const definition = sourceOrRefuse(context, input2);
87721
88062
  const root = definition.root;
87722
88063
  const source = sourceOf(ref, given, root);
87723
- const manifest = await manifestOf(context, workspaceOf(input2), ref.name, source, root, {});
87724
- context.output.step(`${ref.name} ${manifest.version} reaches:`);
88064
+ const config2 = configOf(input2, sourceConfig(definition).plugins[name]?.config ?? {});
88065
+ const manifest = await checkedManifest(context, workspaceOf(input2), name, source, root, config2);
88066
+ context.output.step(`${name} ${manifest.version} reaches:`);
87725
88067
  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: [] });
87727
- context.output.step(`\u2713 ${basename9(definition.configFile)} names ${ref.name}: source ${source}`);
87728
- return { data: { name: ref.name, source, installed, enabled: true, manifest } };
88068
+ setPluginEntry(definition.configFile, name, { source, enabled: true, config: config2, env: [] });
88069
+ context.output.step(`\u2713 ${basename9(definition.configFile)} names ${name}: source ${source}`);
88070
+ return { data: { name, source, installed, enabled: true, manifest } };
87729
88071
  }
87730
88072
  });
87731
88073
  var list3 = () => ({
@@ -87801,57 +88143,6 @@ var remove = () => ({
87801
88143
  return { data: { name, unnamed, uninstalled } };
87802
88144
  }
87803
88145
  });
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
88146
  var check2 = () => ({
87856
88147
  path: ["plugin", "check"],
87857
88148
  summary: "Run the conformance checks of each port a plugin provides, so any orbital host can use it.",
@@ -87877,8 +88168,8 @@ var check2 = () => ({
87877
88168
  ],
87878
88169
  run: async (input2, context) => {
87879
88170
  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);
88171
+ const fixtures = typeof input2.flags.fixtures === "string" ? JSON.parse(readFileSync29(input2.flags.fixtures, "utf8")) : {};
88172
+ const plugin = await startWorkspacePlugin(context, workspaceOf(input2), name);
87882
88173
  try {
87883
88174
  const results = await runConformance(plugin.ports, fixtures);
87884
88175
  const mark = { pass: "\u2713", fail: "\u2717", skip: "\u25CB" };
@@ -88006,16 +88297,16 @@ var secretRemove = {
88006
88297
  return { data: { plugin, name } };
88007
88298
  }
88008
88299
  };
88009
- var pluginCommands = () => [add(), list3(), remove(), runCommand(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
88300
+ var pluginCommands = () => [add(), list3(), remove(), check2(), marketplaceAdd, marketplaceList, marketplaceUpdate, marketplaceRemove, secretSet, secretRemove];
88010
88301
 
88011
88302
  // src/core/cli/plugin-new.ts
88012
88303
  init_structure();
88013
88304
  init_protocol();
88014
88305
  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";
88306
+ import { existsSync as existsSync31, mkdirSync as mkdirSync22, readdirSync as readdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
88307
+ import { dirname as dirname16, join as join38, relative as relative7, resolve as resolve16 } from "node:path";
88017
88308
  var PLUGIN_PORTS = ["channel", "store", "workspace", "harness", "panel", "policy", "telemetry", "actions", "workflows", "command"];
88018
- var SDK_VERSION = true ? `^${"0.0.3"}` : "*";
88309
+ var SDK_VERSION = true ? `^${"0.0.4"}` : "*";
88019
88310
  var ESBUILD_VERSION = "^0.25.0";
88020
88311
  var TYPES_NODE_VERSION = "^20.19.43";
88021
88312
  var HARNESS_ID = /^[a-z][a-z0-9-]{0,31}$/;
@@ -88394,7 +88685,7 @@ export function plugin(context: PluginContext): PluginImplementation {
88394
88685
  switch (event.kind) {
88395
88686
  case 'mission.state': {
88396
88687
  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 };
88688
+ 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
88689
  graph.missions = [...graph.missions.filter((mission) => mission.missionId !== event.mission), state];
88399
88690
  return;
88400
88691
  }
@@ -88535,7 +88826,7 @@ export function plugin(_context: PluginContext): PluginImplementation {
88535
88826
  case "command":
88536
88827
  return {
88537
88828
  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';
88829
+ 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
88830
 
88540
88831
  export const NAME = '${name}';
88541
88832
 
@@ -88545,17 +88836,19 @@ export function plugin(context: PluginContext): PluginImplementation {
88545
88836
  version: '0.1.0',
88546
88837
  permissions: { network: [], secrets: [], models: [] },
88547
88838
  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
- },
88839
+ commands: [
88840
+ defineCommand({
88841
+ name: 'hello',
88842
+ summary: 'Greet the name given, or the workspace.',
88843
+ args: [{ name: 'name', summary: 'The name to greet.' }],
88844
+ run: async ({ args }) => \`Hello, \${args.name ?? context.init.node.workspaceDir}.\`,
88845
+ }),
88846
+ ],
88554
88847
  };
88555
88848
  }
88556
88849
  `,
88557
88850
  fixtures: {},
88558
- test: ` assert.equal(await implementation.commands?.hello?.run(['Ada']), 'Hello, Ada.');`
88851
+ test: ` assert.equal(await implementation.commands?.[0]?.run({ args: { name: 'Ada' }, options: {}, terminal: null }), 'Hello, Ada.');`
88559
88852
  };
88560
88853
  }
88561
88854
  }
@@ -88589,10 +88882,11 @@ ${template.test ? `${template.test}
88589
88882
  ` : ""}});
88590
88883
  `;
88591
88884
  }
88885
+ var COMMAND_DECLARATION = { name: "hello", summary: "Greet the name given, or the workspace.", args: [{ name: "name", summary: "The name to greet." }] };
88592
88886
  function pluginFiles(name, port) {
88593
88887
  const template = templateOf(name, port);
88594
88888
  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)}
88889
+ "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
88890
  `,
88597
88891
  "package.json": `${JSON.stringify({
88598
88892
  name,
@@ -88650,14 +88944,14 @@ var pluginNewCommand = {
88650
88944
  if (port === "harness" && !HARNESS_ID.test(name)) {
88651
88945
  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
88946
  }
88653
- const dir = resolve15(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
88654
- if (existsSync30(dir) && readdirSync20(dir).length > 0) {
88947
+ const dir = resolve16(typeof input2.flags.dir === "string" ? input2.flags.dir : name);
88948
+ if (existsSync31(dir) && readdirSync20(dir).length > 0) {
88655
88949
  throw new CliError({ code: "conflict", message: `${dir} is not empty.`, exitCode: ExitCode.Validation, hint: "Name a new folder with --dir." });
88656
88950
  }
88657
88951
  const files = pluginFiles(name, port);
88658
88952
  for (const [path3, content] of Object.entries(files)) {
88659
- mkdirSync22(dirname16(join37(dir, path3)), { recursive: true });
88660
- writeFileSync20(join37(dir, path3), content);
88953
+ mkdirSync22(dirname16(join38(dir, path3)), { recursive: true });
88954
+ writeFileSync20(join38(dir, path3), content);
88661
88955
  }
88662
88956
  const program = commandPrefix(context.env);
88663
88957
  const shown = relative7(process.cwd(), dir) || ".";
@@ -88802,8 +89096,8 @@ If the browser does not return here in ${CALLBACK_WAIT_MS / 6e4} minutes, paste
88802
89096
  ${url2}
88803
89097
  Then paste the code, or the full URL the browser ends on.`);
88804
89098
  },
88805
- onManualCodeInput: () => new Promise((resolve25) => {
88806
- timer = setTimeout(() => resolve25(""), opened2 ? CALLBACK_WAIT_MS : 0);
89099
+ onManualCodeInput: () => new Promise((resolve27) => {
89100
+ timer = setTimeout(() => resolve27(""), opened2 ? CALLBACK_WAIT_MS : 0);
88807
89101
  timer.unref();
88808
89102
  }),
88809
89103
  onPrompt: async (prompt) => (await context.ask(`${prompt.message} `))?.trim() ?? "",
@@ -88897,14 +89191,14 @@ function createProviderCommands(signIn = loginAnthropic) {
88897
89191
 
88898
89192
  // src/core/cli/run.ts
88899
89193
  init_command();
88900
- import { resolve as resolve17 } from "node:path";
89194
+ import { resolve as resolve18 } from "node:path";
88901
89195
 
88902
89196
  // src/core/local/local-run.ts
88903
89197
  init_protocol();
88904
89198
  init_command();
88905
89199
  import { randomBytes as randomBytes4, randomUUID as randomUUID8 } from "node:crypto";
88906
89200
  import { mkdirSync as mkdirSync23, rmSync as rmSync12 } from "node:fs";
88907
- import { join as join38, resolve as resolve16 } from "node:path";
89201
+ import { join as join39, resolve as resolve17 } from "node:path";
88908
89202
 
88909
89203
  // src/core/local/interrupt.ts
88910
89204
  var EXIT_CODE = { SIGINT: 130, SIGTERM: 143 };
@@ -88927,7 +89221,7 @@ function cleanupOnInterrupt(cleanup, options = {}) {
88927
89221
  async function runLocal(context, request, plugins) {
88928
89222
  const { workspaceDir } = request;
88929
89223
  const home = orbitalHome(context.env);
88930
- const sessionsDir = join38(home, "sessions");
89224
+ const sessionsDir = join39(home, "sessions");
88931
89225
  mkdirSync23(sessionsDir, { recursive: true, mode: 448 });
88932
89226
  const resumed = request.resumeSessionId ? readSessionRecord(sessionsDir, request.resumeSessionId) : null;
88933
89227
  if (request.resumeSessionId && !resumed) {
@@ -88957,9 +89251,9 @@ async function runLocal(context, request, plugins) {
88957
89251
  let channels = null;
88958
89252
  const asks = new PersonDesk(async (question, name) => await channels?.askPerson(`${name} asks: ${question}`, null) ?? null);
88959
89253
  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 });
89254
+ channels = new ChannelHub({ context, ports, policy, self: nodeName, stateDir: join39(home, "channels", nodeName), plugins: () => definition.config.plugins });
88961
89255
  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 });
89256
+ const actions = nodeActions({ ports: () => ports, policy: () => policy, definition: { workspaceDir: resolve17(workspaceDir), definition }, span: (span2) => host.telemetry.span(span2), now: context.now });
88963
89257
  const agentName = request.agent ?? resumed?.agent ?? null;
88964
89258
  const target = await resolveTarget(definition, agentName, ports);
88965
89259
  const harness = request.harness ?? resumed?.harness ?? defaultHarness(context.env);
@@ -89020,7 +89314,7 @@ async function runLocal(context, request, plugins) {
89020
89314
  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
89315
  if (result2 !== null) context.output.line(result2);
89022
89316
  const cli = commandPrefix(context.env);
89023
- context.output.step(`\u2713 transcript: ${join38(sessionsDir, sessionId, "conversation.jsonl")}`);
89317
+ context.output.step(`\u2713 transcript: ${join39(sessionsDir, sessionId, "conversation.jsonl")}`);
89024
89318
  context.output.step(`\u2713 resume with: ${cli} run --resume ${sessionId}`);
89025
89319
  return {
89026
89320
  sessionId,
@@ -89028,7 +89322,7 @@ async function runLocal(context, request, plugins) {
89028
89322
  done: true,
89029
89323
  harness,
89030
89324
  result: result2,
89031
- transcript: join38(sessionsDir, sessionId, "conversation.jsonl"),
89325
+ transcript: join39(sessionsDir, sessionId, "conversation.jsonl"),
89032
89326
  interactive: request.interactive
89033
89327
  };
89034
89328
  } finally {
@@ -89088,7 +89382,7 @@ async function interactiveTurn(context, request, run3) {
89088
89382
  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
89383
  const opened2 = await openSession(run3.ports, run3.policy, info);
89090
89384
  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());
89385
+ const runDir = join39(run3.home, "runs", randomUUID8());
89092
89386
  mkdirSync23(runDir, { recursive: true, mode: 448 });
89093
89387
  let usage = [];
89094
89388
  try {
@@ -89195,7 +89489,7 @@ function createRunCommand(options) {
89195
89489
  const terminal2 = log ? await options.terminal() : null;
89196
89490
  const data = await runLocal(log ? { ...context, output: log.output(context.output) } : context, {
89197
89491
  agent,
89198
- workspaceDir: resolve17(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89492
+ workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89199
89493
  prompt,
89200
89494
  useDefinition: true,
89201
89495
  follow: false,
@@ -89213,24 +89507,24 @@ function createRunCommand(options) {
89213
89507
  // src/core/cli/session.ts
89214
89508
  init_command();
89215
89509
  init_protocol();
89216
- import { resolve as resolve18 } from "node:path";
89510
+ import { resolve as resolve19 } from "node:path";
89217
89511
 
89218
89512
  // 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";
89513
+ import { existsSync as existsSync33, mkdirSync as mkdirSync24, readFileSync as readFileSync30, writeFileSync as writeFileSync21 } from "node:fs";
89514
+ import { join as join40 } from "node:path";
89221
89515
  function fileOf(home, externalId) {
89222
- return join39(home, "attachments", `${encodeURIComponent(externalId)}.json`);
89516
+ return join40(home, "attachments", `${encodeURIComponent(externalId)}.json`);
89223
89517
  }
89224
89518
  function writeAttachment(home, externalId, node2) {
89225
- mkdirSync24(join39(home, "attachments"), { recursive: true, mode: 448 });
89519
+ mkdirSync24(join40(home, "attachments"), { recursive: true, mode: 448 });
89226
89520
  writeFileSync21(fileOf(home, externalId), `${JSON.stringify({ node: node2 })}
89227
89521
  `, { mode: 384 });
89228
89522
  }
89229
89523
  function readAttachment(home, externalId) {
89230
89524
  const file2 = fileOf(home, externalId);
89231
- if (!existsSync32(file2)) return null;
89525
+ if (!existsSync33(file2)) return null;
89232
89526
  try {
89233
- const parsed = JSON.parse(readFileSync29(file2, "utf8"));
89527
+ const parsed = JSON.parse(readFileSync30(file2, "utf8"));
89234
89528
  return typeof parsed === "object" && parsed !== null && "node" in parsed && typeof parsed.node === "string" ? parsed.node : null;
89235
89529
  } catch {
89236
89530
  return null;
@@ -89298,7 +89592,7 @@ async function joinMission(context, attached, missionId) {
89298
89592
  const body2 = await response.json();
89299
89593
  if (!response.ok) {
89300
89594
  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 });
89595
+ throw new CliError({ code: "invalid-input", message: `The node refused to make this session a decision receiver of ${missionId}: ${error62}`, exitCode: ExitCode.Validation });
89302
89596
  }
89303
89597
  return MissionRecordSchema.parse(body2);
89304
89598
  }
@@ -89378,7 +89672,7 @@ var sessionAttachCommand = {
89378
89672
  { 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
89673
  { name: "team", type: "string", summary: "The team the session joins. Sessions of one team are peers." },
89380
89674
  { 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." }
89675
+ { 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
89676
  ],
89383
89677
  positionals: [],
89384
89678
  outputShape: {
@@ -89395,7 +89689,7 @@ var sessionAttachCommand = {
89395
89689
  examples: [
89396
89690
  { command: "orbital session attach --harness claude-code --output ndjson", why: "Join the node of this folder, and print each delivery as a record." },
89397
89691
  { 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." }
89692
+ { 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
89693
  ],
89400
89694
  run: async (input2, context) => {
89401
89695
  const harness = typeof input2.flags.harness === "string" ? input2.flags.harness : "external";
@@ -89407,7 +89701,7 @@ var sessionAttachCommand = {
89407
89701
  const found = await keepAttached(context, {
89408
89702
  harness,
89409
89703
  agent: typeof input2.flags.agent === "string" ? input2.flags.agent : harness,
89410
- workspaceDir: resolve18(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89704
+ workspaceDir: resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : "."),
89411
89705
  ...typeof input2.flags.node === "string" && { node: input2.flags.node },
89412
89706
  team: typeof input2.flags.team === "string" ? input2.flags.team : null,
89413
89707
  externalId: typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : null,
@@ -89420,6 +89714,8 @@ var sessionAttachCommand = {
89420
89714
  delivery: (event) => {
89421
89715
  const delivery = DeliverySchema.safeParse(event);
89422
89716
  if (delivery.success) context.output.record({ type: StreamRecordType.Event, ...delivery.data });
89717
+ const answered = AnsweredSchema.safeParse(event);
89718
+ if (answered.success) context.output.record({ type: StreamRecordType.Event, ...answered.data });
89423
89719
  },
89424
89720
  detached: (reason) => {
89425
89721
  context.output.record({ type: StreamRecordType.Event, kind: "detached", reason });
@@ -89509,8 +89805,8 @@ var workspaceCommands = [list5, show, remove3];
89509
89805
 
89510
89806
  // src/core/cli/up.ts
89511
89807
  init_command();
89512
- import { existsSync as existsSync33 } from "node:fs";
89513
- import { resolve as resolve19 } from "node:path";
89808
+ import { existsSync as existsSync34 } from "node:fs";
89809
+ import { resolve as resolve20 } from "node:path";
89514
89810
  init_protocol();
89515
89811
 
89516
89812
  // src/core/cli/up-view.ts
@@ -89692,7 +89988,7 @@ async function liveView(context, nodes, signal, terminal2, home) {
89692
89988
  for (const line of events(viewed, states.get(one.node), now)) context.output.line(line);
89693
89989
  states.set(one.node, now);
89694
89990
  }
89695
- await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve25) => signal.addEventListener("abort", () => resolve25(), { once: true }))]);
89991
+ await Promise.race([context.sleep(REFRESH_MS), new Promise((resolve27) => signal.addEventListener("abort", () => resolve27(), { once: true }))]);
89696
89992
  }
89697
89993
  context.output.line(closing);
89698
89994
  }
@@ -89701,7 +89997,7 @@ async function liveView(context, nodes, signal, terminal2, home) {
89701
89997
  var WORKSPACE_FLAG = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
89702
89998
  var TEAMS = { name: "teams", type: "string", variadic: true, summary: "Only the nodes of these teams. Default: every node of the project." };
89703
89999
  async function planOf(context, input2) {
89704
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90000
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
89705
90001
  const { definition, refusals } = await loadDefinitionPartial(workspaceDir, context.env);
89706
90002
  for (const refusal of refusals) context.output.warn(refusal.message);
89707
90003
  const project = projectNodeOf(definition, workspaceDir);
@@ -89848,7 +90144,7 @@ var statusCommand = {
89848
90144
  for (const { charters: _charters, named: _named, ...planned } of nodes) {
89849
90145
  const log = nodeLogPath(home, planned.node);
89850
90146
  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 });
90147
+ if (existsSync34(log)) logs.push({ node: planned.node, log, lastCrash: supervisor?.crashes.at(-1) ?? null, lastExit: supervisor?.lastExit ?? null, gaveUp: supervisor?.gaveUp ?? null });
89852
90148
  const running = await liveNode(home, planned.node, context.fetch);
89853
90149
  const served = running ? await servedTeams(context, running) : null;
89854
90150
  for (const note of served ?? []) if (note.problems.length > 0 || note.retiring) notes.push({ node: planned.node, ...note });
@@ -89881,7 +90177,7 @@ var downCommand = {
89881
90177
  const { workspaceDir, nodes } = await planOf(context, input2);
89882
90178
  const home = orbitalHome(context.env);
89883
90179
  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)));
90180
+ 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
90181
  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
90182
  for (const planned of targets) {
89887
90183
  const running = await liveNode(home, planned.node, context.fetch);
@@ -89940,7 +90236,7 @@ var createJoinCommand = (options) => ({
89940
90236
  }
89941
90237
  let charters = [];
89942
90238
  try {
89943
- const workspaceDir = resolve19(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90239
+ const workspaceDir = resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
89944
90240
  charters = (await loadDefinition(workspaceDir, context.env)).teams;
89945
90241
  } catch {
89946
90242
  charters = [];
@@ -89966,9 +90262,9 @@ function createUpCommands(options) {
89966
90262
  init_command();
89967
90263
  import { spawnSync as spawnSync5 } from "node:child_process";
89968
90264
  import { createHash as createHash10 } from "node:crypto";
89969
- import { existsSync as existsSync34, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
90265
+ import { existsSync as existsSync35, mkdirSync as mkdirSync25, rmSync as rmSync13, writeFileSync as writeFileSync22 } from "node:fs";
89970
90266
  import { homedir as homedir8 } from "node:os";
89971
- import { join as join40, resolve as resolve20 } from "node:path";
90267
+ import { join as join41, resolve as resolve21 } from "node:path";
89972
90268
  var exec = (command, args) => {
89973
90269
  const result2 = spawnSync5(command, args, { encoding: "utf8" });
89974
90270
  return { status: result2.status, output: `${result2.stdout ?? ""}${result2.stderr ?? ""}${result2.error?.message ?? ""}` };
@@ -89986,7 +90282,7 @@ function serviceSpec(env3, workspaceDir) {
89986
90282
  workspaceDir,
89987
90283
  program: [process.execPath, installedProgram(), ...mountPath(), "up", "--detach", "--workspace", workspaceDir],
89988
90284
  env: Object.fromEntries(KEPT_ENV.flatMap((name) => env3[name] ? [[name, env3[name]]] : [])),
89989
- log: join40(orbitalHome(env3), "logs", `service-${hash2}.log`)
90285
+ log: join41(orbitalHome(env3), "logs", `service-${hash2}.log`)
89990
90286
  };
89991
90287
  }
89992
90288
  function xml(text3) {
@@ -90049,7 +90345,7 @@ function userHome(env3) {
90049
90345
  return env3.HOME ?? homedir8();
90050
90346
  }
90051
90347
  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`);
90348
+ return platform2 === "launchd" ? join41(userHome(env3), "Library", "LaunchAgents", `${label}.plist`) : join41(env3.XDG_CONFIG_HOME ?? join41(userHome(env3), ".config"), "systemd", "user", `${label}.service`);
90053
90349
  }
90054
90350
  function domain2() {
90055
90351
  return `gui/${process.getuid?.() ?? 0}`;
@@ -90062,13 +90358,13 @@ function serviceState(platform2, env3, workspaceDir, run3 = exec) {
90062
90358
  const { label } = serviceSpec(env3, workspaceDir);
90063
90359
  const file2 = fileOf2(platform2, env3, label);
90064
90360
  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 };
90361
+ return { platform: platform2, label, file: file2, installed: existsSync35(file2), loaded };
90066
90362
  }
90067
90363
  function installService(platform2, env3, workspaceDir, run3 = exec) {
90068
90364
  const spec = serviceSpec(env3, workspaceDir);
90069
90365
  const file2 = fileOf2(platform2, env3, spec.label);
90070
- mkdirSync25(join40(file2, ".."), { recursive: true });
90071
- mkdirSync25(join40(spec.log, ".."), { recursive: true, mode: 448 });
90366
+ mkdirSync25(join41(file2, ".."), { recursive: true });
90367
+ mkdirSync25(join41(spec.log, ".."), { recursive: true, mode: 448 });
90072
90368
  if (platform2 === "launchd") {
90073
90369
  writeFileSync22(file2, launchdPlist(spec));
90074
90370
  run3("launchctl", ["bootout", `${domain2()}/${spec.label}`]);
@@ -90090,7 +90386,7 @@ function uninstallService(platform2, env3, workspaceDir, run3 = exec) {
90090
90386
  return serviceState(platform2, env3, workspaceDir, run3);
90091
90387
  }
90092
90388
  function workspaceOf2(input2) {
90093
- return resolve20(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90389
+ return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
90094
90390
  }
90095
90391
  function platformOrRefuse() {
90096
90392
  const platform2 = platformOf();
@@ -90165,9 +90461,9 @@ var serviceCommands = [
90165
90461
  ];
90166
90462
 
90167
90463
  // src/core/cli/browser.ts
90168
- import { existsSync as existsSync35, mkdirSync as mkdirSync26, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "node:fs";
90464
+ import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync31, writeFileSync as writeFileSync23 } from "node:fs";
90169
90465
  import { homedir as homedir9 } from "node:os";
90170
- import { dirname as dirname17, join as join41 } from "node:path";
90466
+ import { dirname as dirname17, join as join42 } from "node:path";
90171
90467
 
90172
90468
  // ../../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
90469
  function normalizeHeaders(headers) {
@@ -91782,7 +92078,7 @@ function isChromeDevtools(entry) {
91782
92078
  return isChromeDevtoolsMcp(command, args);
91783
92079
  }
91784
92080
  function claudeJsonPath(env3) {
91785
- return join41(env3.CLAUDE_CONFIG_DIR ?? env3.HOME ?? homedir9(), ".claude.json");
92081
+ return join42(env3.CLAUDE_CONFIG_DIR ?? env3.HOME ?? homedir9(), ".claude.json");
91786
92082
  }
91787
92083
  function chromeServersOf(config2) {
91788
92084
  return Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => isChromeDevtools(entry)).map(([name]) => name);
@@ -91794,21 +92090,21 @@ function sharedBrowserEntry() {
91794
92090
  return { type: "stdio", ...sharedBrowserCommand(), env: {} };
91795
92091
  }
91796
92092
  function backupPath(env3) {
91797
- return join41(browserDir(orbitalHome(env3)), "claude-mcp-backup.json");
92093
+ return join42(browserDir(orbitalHome(env3)), "claude-mcp-backup.json");
91798
92094
  }
91799
92095
  function codexBackupPath(env3) {
91800
- return join41(browserDir(orbitalHome(env3)), "codex-config-backup.json");
92096
+ return join42(browserDir(orbitalHome(env3)), "codex-config-backup.json");
91801
92097
  }
91802
92098
  function codexConfigPath(env3) {
91803
- return join41(env3.CODEX_HOME ?? join41(env3.HOME ?? homedir9(), ".codex"), "config.toml");
92099
+ return join42(env3.CODEX_HOME ?? join42(env3.HOME ?? homedir9(), ".codex"), "config.toml");
91804
92100
  }
91805
92101
  function pointClaudeAtSharedBrowser(env3, entry = sharedBrowserEntry()) {
91806
92102
  const file2 = claudeJsonPath(env3);
91807
- const config2 = existsSync35(file2) ? JSON.parse(readFileSync30(file2, "utf8")) : {};
92103
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
91808
92104
  const found = chromeServersOf(config2);
91809
92105
  const names = found.length > 0 ? found : [SHARED_BROWSER_SERVER];
91810
92106
  const backup = backupPath(env3);
91811
- const previous = existsSync35(backup) ? JSON.parse(readFileSync30(backup, "utf8")) : {};
92107
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
91812
92108
  const saved = { ...Object.fromEntries(names.filter((name) => config2.mcpServers?.[name] !== void 0).map((name) => [name, config2.mcpServers[name]])), ...previous };
91813
92109
  mkdirSync26(browserDir(orbitalHome(env3)), { recursive: true, mode: 448 });
91814
92110
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -91821,9 +92117,9 @@ function pointClaudeAtSharedBrowser(env3, entry = sharedBrowserEntry()) {
91821
92117
  function revertClaudeBrowser(env3) {
91822
92118
  const file2 = claudeJsonPath(env3);
91823
92119
  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")) : {};
92120
+ if (!existsSync36(backup)) return null;
92121
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
92122
+ const config2 = existsSync36(file2) ? JSON.parse(readFileSync31(file2, "utf8")) : {};
91827
92123
  const shared = Object.entries(config2.mcpServers ?? {}).filter(([, entry]) => Array.isArray(entry.args) && entry.args.join(" ").endsWith("browser connect")).map(([name]) => name);
91828
92124
  const servers = { ...config2.mcpServers };
91829
92125
  for (const name of shared) delete servers[name];
@@ -91834,18 +92130,18 @@ function revertClaudeBrowser(env3) {
91834
92130
  }
91835
92131
  function pointCodexAtSharedBrowser(env3, shared = sharedBrowserCommand()) {
91836
92132
  const file2 = codexConfigPath(env3);
91837
- if (!existsSync35(file2) && !existsSync35(dirname17(file2))) return null;
91838
- const text3 = existsSync35(file2) ? readFileSync30(file2, "utf8") : "";
92133
+ if (!existsSync36(file2) && !existsSync36(dirname17(file2))) return null;
92134
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
91839
92135
  const tables = mcpServerTables(text3);
91840
92136
  const found = tables.filter((table2) => isChromeDevtoolsMcp(table2.command, table2.args)).map((table2) => table2.name);
91841
92137
  const connected = tables.filter((table2) => isSharedBrowserConnect(table2.args)).map((table2) => table2.name);
91842
92138
  const names = found.length > 0 ? found : connected.length > 0 ? [] : [SHARED_BROWSER_SERVER];
91843
92139
  const backup = codexBackupPath(env3);
91844
- if (names.length === 0) return { file: file2, servers: connected, backup: existsSync35(backup) ? backup : null };
92140
+ if (names.length === 0) return { file: file2, servers: connected, backup: existsSync36(backup) ? backup : null };
91845
92141
  const block = (name) => stdioServerBlock(name, shared.command, shared.args);
91846
92142
  const replaced = replaceMcpServers(text3, (table2) => names.includes(table2.name) ? block(table2.name) : null);
91847
92143
  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")) : {};
92144
+ const previous = existsSync36(backup) ? JSON.parse(readFileSync31(backup, "utf8")) : {};
91849
92145
  const saved = { ...Object.fromEntries(replaced.replaced.map((one) => [one.name, one.original])), ...previous };
91850
92146
  mkdirSync26(browserDir(orbitalHome(env3)), { recursive: true, mode: 448 });
91851
92147
  writeFileSync23(backup, `${JSON.stringify(saved, null, 2)}
@@ -91857,9 +92153,9 @@ function pointCodexAtSharedBrowser(env3, shared = sharedBrowserCommand()) {
91857
92153
  function revertCodexBrowser(env3) {
91858
92154
  const file2 = codexConfigPath(env3);
91859
92155
  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") : "";
92156
+ if (!existsSync36(backup)) return null;
92157
+ const saved = JSON.parse(readFileSync31(backup, "utf8"));
92158
+ const text3 = existsSync36(file2) ? readFileSync31(file2, "utf8") : "";
91863
92159
  const replaced = replaceMcpServers(text3, (table2) => isSharedBrowserConnect(table2.args) ? saved[table2.name] ?? "" : null);
91864
92160
  const next = Object.entries(saved).filter(([name]) => !replaced.replaced.some((one) => one.name === name)).reduce((current2, [, original]) => appendMcpServer(current2, original), replaced.text);
91865
92161
  mkdirSync26(dirname17(file2), { recursive: true });
@@ -92047,13 +92343,13 @@ var browserCommands = [
92047
92343
  init_command();
92048
92344
  init_structure();
92049
92345
  init_protocol();
92050
- import { existsSync as existsSync37 } from "node:fs";
92051
- import { join as join43, relative as relative8, resolve as resolve21 } from "node:path";
92346
+ import { existsSync as existsSync38 } from "node:fs";
92347
+ import { join as join44, relative as relative8, resolve as resolve22 } from "node:path";
92052
92348
 
92053
92349
  // src/core/local/mission-migrate.ts
92054
92350
  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";
92351
+ 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";
92352
+ import { join as join43 } from "node:path";
92057
92353
  function withTeam(content, team) {
92058
92354
  const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n[\s\S]*)?$/);
92059
92355
  if (!match) return content;
@@ -92065,26 +92361,26 @@ ${head.join("\n")}
92065
92361
  ---${match[2] ?? "\n"}`;
92066
92362
  }
92067
92363
  function emptyDir(dir) {
92068
- if (existsSync36(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
92364
+ if (existsSync37(dir) && readdirSync21(dir).length === 0) rmdirSync2(dir);
92069
92365
  }
92070
92366
  function migrateMissions(root, nodesDir, nodes, dryRun) {
92071
92367
  const moved = [];
92072
92368
  const refused3 = [];
92073
- const teamsDir = join42(root, TEAMS_DIR);
92074
- const target = join42(root, MISSIONS_DIR);
92369
+ const teamsDir = join43(root, TEAMS_DIR);
92370
+ const target = join43(root, MISSIONS_DIR);
92075
92371
  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;
92372
+ for (const team of existsSync37(teamsDir) ? readdirSync21(teamsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : []) {
92373
+ const missionsDir = join43(teamsDir, team.name, MISSIONS_DIR);
92374
+ if (!existsSync37(missionsDir)) continue;
92079
92375
  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);
92376
+ const from = join43(missionsDir, mission.name);
92377
+ const to = join43(target, mission.name);
92082
92378
  const other = seen.get(mission.name);
92083
92379
  if (other) {
92084
92380
  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
92381
  continue;
92086
92382
  }
92087
- if (existsSync36(to)) {
92383
+ if (existsSync37(to)) {
92088
92384
  refused3.push(`${from}: ${to} exists already.`);
92089
92385
  continue;
92090
92386
  }
@@ -92093,22 +92389,22 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92093
92389
  if (dryRun) continue;
92094
92390
  mkdirSync27(target, { recursive: true });
92095
92391
  renameSync8(from, to);
92096
- const file2 = join42(to, MISSION_FILE);
92097
- if (existsSync36(file2)) writeFileSync24(file2, withTeam(readFileSync31(file2, "utf8"), team.name));
92392
+ const file2 = join43(to, MISSION_FILE);
92393
+ if (existsSync37(file2)) writeFileSync24(file2, withTeam(readFileSync32(file2, "utf8"), team.name));
92098
92394
  }
92099
92395
  if (!dryRun) emptyDir(missionsDir);
92100
92396
  }
92101
92397
  for (const node2 of nodes) {
92102
- const nodeDir2 = join42(nodesDir, node2);
92103
- if (!existsSync36(nodeDir2)) continue;
92398
+ const nodeDir2 = join43(nodesDir, node2);
92399
+ if (!existsSync37(nodeDir2)) continue;
92104
92400
  const files = [];
92105
92401
  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 });
92402
+ 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 });
92403
+ 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
92404
  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 });
92405
+ for (const name of readdirSync21(join43(nodeDir2, entry.name))) {
92406
+ if (name.endsWith(".json")) files.push({ from: join43(nodeDir2, entry.name, name), mission: name.slice(0, -".json".length), team: entry.name, backup: false });
92407
+ 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
92408
  }
92113
92409
  }
92114
92410
  files.sort((a, b) => Number(a.backup) - Number(b.backup) || Number(a.team !== null) - Number(b.team !== null));
@@ -92120,13 +92416,13 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92120
92416
  if (!dryRun) rmSync14(file2.from);
92121
92417
  continue;
92122
92418
  }
92123
- if (taken.has(file2.mission) || existsSync36(to)) {
92419
+ if (taken.has(file2.mission) || existsSync37(to)) {
92124
92420
  refused3.push(`${file2.from}: ${to} exists already.`);
92125
92421
  continue;
92126
92422
  }
92127
- const raw = JSON.parse(readFileSync31(file2.from, "utf8"));
92423
+ const raw = JSON.parse(readFileSync32(file2.from, "utf8"));
92128
92424
  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 });
92425
+ const state = MissionStateFileSchema.safeParse({ question: null, lead: null, claim: null, claimedAt: null, ...rest, mission: file2.mission, node: node2, team: rest.team ?? file2.team });
92130
92426
  if (!state.success) {
92131
92427
  refused3.push(`${file2.from}: it is not the state of a mission: ${state.error.issues.map((issue2) => `${issue2.path.join(".")}: ${issue2.message}`).join("; ")}`);
92132
92428
  continue;
@@ -92138,7 +92434,7 @@ function migrateMissions(root, nodesDir, nodes, dryRun) {
92138
92434
  rmSync14(file2.from);
92139
92435
  }
92140
92436
  if (dryRun) continue;
92141
- for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join42(nodeDir2, entry.name));
92437
+ for (const entry of readdirSync21(nodeDir2, { withFileTypes: true })) if (entry.isDirectory()) emptyDir(join43(nodeDir2, entry.name));
92142
92438
  emptyDir(nodeDir2);
92143
92439
  }
92144
92440
  emptyDir(nodesDir);
@@ -92160,13 +92456,13 @@ var MISSION_FIELDS = {
92160
92456
  { name: "result", type: "string", nullable: true, summary: "The hand-off of the mission, when it ended." },
92161
92457
  { name: "summary", type: "string", nullable: true, summary: "The first sentence of the Intent of the brief." },
92162
92458
  { 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." },
92459
+ { 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
92460
  { 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
92461
  { name: "history", type: "array", summary: "Each change of state, with its time and who made it." }
92166
92462
  ]
92167
92463
  };
92168
92464
  function workspaceOf3(input2) {
92169
- return resolve21(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92465
+ return resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92170
92466
  }
92171
92467
  function refused2(message, hint) {
92172
92468
  return new CliError({ code: "invalid-input", message, exitCode: ExitCode.Validation, ...hint && { hint } });
@@ -92248,7 +92544,7 @@ function missionNote(context, record2) {
92248
92544
  }
92249
92545
  }
92250
92546
  function shownState(record2) {
92251
- return waitsOnGiver(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
92547
+ return waitsOnDecision(record2) && record2.state !== "waiting" ? `${record2.state}, waits on you` : record2.state;
92252
92548
  }
92253
92549
  function missionLines2(context, records) {
92254
92550
  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 +92596,7 @@ var missionNew = {
92300
92596
  if (!id.success) throw refused2(`"${String(input2.positionals.id)}" is not a mission id: use lowercase letters, digits and hyphens.`);
92301
92597
  const file2 = missionFile(teamRoot(team), id.data);
92302
92598
  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.");
92599
+ if (existsSync38(file2) && input2.flags.force !== true) throw refused2(`${relative8(workspaceDir, file2)} exists.`, "Pass --force to write over it.");
92304
92600
  const intent = await asked(context, input2, "intent", "Intent: why does the team do this mission? ");
92305
92601
  const endState = await asked(context, input2, "end-state", "End state: what is true when the mission is done? ");
92306
92602
  const limits = await asked(context, input2, "limits", "Limits: what must the team not do or change? ");
@@ -92379,7 +92675,7 @@ var missionShow = {
92379
92675
  if (record2.sessionId) context.output.line(`session: ${record2.sessionId}`);
92380
92676
  if (record2.brief) context.output.line(["", record2.brief].join("\n"));
92381
92677
  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)`);
92678
+ if (record2.receivers.length > 0) context.output.line(`decision receivers: ${record2.receivers.join(", ")} (each question goes to every live one)`);
92383
92679
  if (record2.claim) context.output.line(`claim: ${record2.claim}${record2.claimedAt ? ` since ${record2.claimedAt}` : ""}${record2.stale ? " (stale)" : ""}`);
92384
92680
  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
92681
  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 +92750,31 @@ var missionSay = {
92454
92750
  return { data: { ...said2, node: node2.name } };
92455
92751
  }
92456
92752
  };
92753
+ var CLAUDE_SESSION_ENV = "CLAUDE_CODE_SESSION_ID";
92754
+ function missionMembership(verb, summary, why) {
92755
+ return {
92756
+ path: ["mission", verb],
92757
+ summary,
92758
+ credential: "none",
92759
+ 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.` }],
92760
+ positionals: [MISSION_ID2],
92761
+ outputShape: MISSION_FIELDS,
92762
+ exitCodes: [0, 3],
92763
+ routes: [],
92764
+ examples: [{ command: `orbital mission ${verb} pr-42`, why }],
92765
+ run: async (input2, context) => {
92766
+ const mission = idOf(input2);
92767
+ const externalId = typeof input2.flags["external-id"] === "string" ? input2.flags["external-id"] : context.env[CLAUDE_SESSION_ENV] || null;
92768
+ 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}.`);
92769
+ const node2 = await nodeOfMission(context, mission, () => nodeOfProject(context, workspaceOf3(input2)));
92770
+ const record2 = MissionRecordSchema.parse(await nodeCall(context, node2, `/orbital/missions/${verb}`, { missionId: mission, externalId }));
92771
+ context.output.line(`${record2.missionId} on ${record2.node}: ${record2.state}; decision receivers: ${record2.receivers.join(", ") || "none"}`);
92772
+ return { data: record2 };
92773
+ }
92774
+ };
92775
+ }
92776
+ 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.");
92777
+ 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
92778
  var missionMigrate = {
92458
92779
  path: ["mission", "migrate"],
92459
92780
  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 +92789,11 @@ var missionMigrate = {
92468
92789
  const workspaceDir = workspaceOf3(input2);
92469
92790
  const source = definitionSource(workspaceDir, context.env);
92470
92791
  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));
92792
+ const running = (await liveNodes(orbitalHome(context.env), context.fetch)).filter((node2) => resolve22(node2.workspaceDir) === resolve22(source.projectDir));
92472
92793
  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
92794
  const project = sourceConfig(source).node ?? defaultNodeName(workspaceDir);
92474
92795
  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);
92796
+ const migration = migrateMissions(source.root, join44(orbitalHome(context.env), "missions"), nodes, input2.flags["dry-run"] === true);
92476
92797
  for (const move of migration.moved) context.output.line(`${input2.flags["dry-run"] === true ? "would move" : "moved"} ${move.from} -> ${move.to}`);
92477
92798
  for (const reason of migration.refused) context.output.warn(reason);
92478
92799
  if (migration.moved.length === 0 && migration.refused.length === 0) context.output.line("Nothing to move: the missions are in the new layout.");
@@ -92497,12 +92818,12 @@ var missionReply = {
92497
92818
  return { data: record2 };
92498
92819
  }
92499
92820
  };
92500
- var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
92821
+ var missionCommands = [missionNew, missionStart, missionList, missionShow, missionPause, missionResume, missionSay, missionReply, missionJoin, missionLeave, missionDone, missionMigrate].map((spec) => ({ ...spec, group: CommandGroup.OperatingModel }));
92501
92822
 
92502
92823
  // src/core/cli/check.ts
92503
92824
  init_command();
92504
92825
  init_structure();
92505
- import { resolve as resolve22 } from "node:path";
92826
+ import { resolve as resolve23 } from "node:path";
92506
92827
  var LOAD_RULE = "orbital.load";
92507
92828
  async function loadRefusals(workspaceDir, env3) {
92508
92829
  try {
@@ -92540,7 +92861,7 @@ var checkCommand = {
92540
92861
  { command: "orbital check --annotations", why: "Show the findings on a pull request." }
92541
92862
  ],
92542
92863
  run: async (input2, context) => {
92543
- const workspaceDir = resolve22(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92864
+ const workspaceDir = resolve23(typeof input2.flags.workspace === "string" ? input2.flags.workspace : ".");
92544
92865
  const source = definitionSource(workspaceDir, context.env);
92545
92866
  const findings = await loadRefusals(workspaceDir, context.env);
92546
92867
  for (const finding of findings) context.output.line(`${finding.finding} ${finding.rule} ${finding.path} ${finding.message}`);
@@ -92809,19 +93130,19 @@ function createOrbitalCommands(options) {
92809
93130
  }
92810
93131
 
92811
93132
  // src/core/cli/shipped-skills.ts
92812
- var import_yaml8 = __toESM(require_dist(), 1);
93133
+ var import_yaml9 = __toESM(require_dist(), 1);
92813
93134
  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";
93135
+ 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
93136
  import { createHash as createHash11 } from "node:crypto";
92816
93137
  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";
93138
+ import { dirname as dirname18, join as join45, relative as relative9, resolve as resolve24, sep as sep7 } from "node:path";
92818
93139
  import { fileURLToPath as fileURLToPath3 } from "node:url";
92819
93140
  var HASH_FILE = ".installed-hash";
92820
93141
  function skillsRoot(shipped) {
92821
93142
  let directory = dirname18(fileURLToPath3(shipped.anchor));
92822
93143
  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;
93144
+ const candidate = join45(directory, "skills");
93145
+ if (existsSync39(join45(candidate, shipped.names[0], "SKILL.md"))) return candidate;
92825
93146
  directory = dirname18(directory);
92826
93147
  }
92827
93148
  throw new CliError({
@@ -92833,33 +93154,33 @@ function skillsRoot(shipped) {
92833
93154
  }
92834
93155
  function filesOf2(dir) {
92835
93156
  return readdirSync22(dir, { withFileTypes: true }).flatMap((entry) => {
92836
- const path3 = join44(dir, entry.name);
93157
+ const path3 = join45(dir, entry.name);
92837
93158
  if (entry.isDirectory()) return filesOf2(path3);
92838
93159
  return entry.name === HASH_FILE ? [] : [path3];
92839
93160
  });
92840
93161
  }
92841
93162
  function descriptionOf(skillFile) {
92842
- const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync32(skillFile, "utf8"));
93163
+ const match = /^---\n([\s\S]*?)\n---/.exec(readFileSync33(skillFile, "utf8"));
92843
93164
  if (!match?.[1]) return "";
92844
- const frontmatter = (0, import_yaml8.parse)(match[1]);
93165
+ const frontmatter = (0, import_yaml9.parse)(match[1]);
92845
93166
  return typeof frontmatter?.description === "string" ? frontmatter.description : "";
92846
93167
  }
92847
93168
  function shippedSkills(shipped) {
92848
93169
  const root = skillsRoot(shipped);
92849
93170
  return shipped.names.map((name) => {
92850
- const dir = join44(root, name);
92851
- if (!existsSync38(join44(dir, "SKILL.md"))) {
93171
+ const dir = join45(root, name);
93172
+ if (!existsSync39(join45(dir, "SKILL.md"))) {
92852
93173
  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
93174
  }
92854
93175
  const files = filesOf2(dir).map((file2) => relative9(dir, file2).split(sep7).join("/")).sort();
92855
- return { name, description: descriptionOf(join44(dir, "SKILL.md")), dir, files };
93176
+ return { name, description: descriptionOf(join45(dir, "SKILL.md")), dir, files };
92856
93177
  });
92857
93178
  }
92858
93179
  function folderHash(dir) {
92859
93180
  const hash2 = createHash11("sha256");
92860
93181
  for (const file2 of filesOf2(dir).map((path3) => relative9(dir, path3).split(sep7).join("/")).sort()) {
92861
93182
  hash2.update(`${file2}\0`);
92862
- hash2.update(readFileSync32(join44(dir, file2)));
93183
+ hash2.update(readFileSync33(join45(dir, file2)));
92863
93184
  hash2.update("\0");
92864
93185
  }
92865
93186
  return hash2.digest("hex");
@@ -92879,18 +93200,18 @@ function pick2(all, names) {
92879
93200
  });
92880
93201
  }
92881
93202
  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");
93203
+ if (typeof input2.flags.dir === "string") return resolve24(input2.flags.dir);
93204
+ if (input2.flags.global === true) return join45(homedir11(), ".claude", "skills");
93205
+ return resolve24(".claude", "skills");
92885
93206
  }
92886
93207
  function planOne(skill, base2) {
92887
- const target = join44(base2, skill.name);
93208
+ const target = join45(base2, skill.name);
92888
93209
  const hash2 = folderHash(skill.dir);
92889
- if (!existsSync38(join44(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
93210
+ if (!existsSync39(join45(target, "SKILL.md"))) return { skill, target, hash: hash2, action: "installed", edited: false };
92890
93211
  const onDisk = folderHash(target);
92891
93212
  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() : "";
93213
+ const hashFile = join45(target, HASH_FILE);
93214
+ const recorded = existsSync39(hashFile) ? readFileSync33(hashFile, "utf8").trim() : "";
92894
93215
  return { skill, target, hash: hash2, action: "updated", edited: onDisk !== recorded };
92895
93216
  }
92896
93217
  function writeOne(plan) {
@@ -92898,7 +93219,7 @@ function writeOne(plan) {
92898
93219
  rmSync15(plan.target, { recursive: true, force: true });
92899
93220
  mkdirSync28(plan.target, { recursive: true });
92900
93221
  cpSync4(plan.skill.dir, plan.target, { recursive: true });
92901
- writeFileSync25(join44(plan.target, HASH_FILE), `${plan.hash}
93222
+ writeFileSync25(join45(plan.target, HASH_FILE), `${plan.hash}
92902
93223
  `);
92903
93224
  } catch (error62) {
92904
93225
  throw new CliError({
@@ -92943,7 +93264,7 @@ function createSkillCommands(shipped) {
92943
93264
  context.output.line(skill.dir);
92944
93265
  return { data: { name: skill.name, path: skill.dir, content: null } };
92945
93266
  }
92946
- const content = readFileSync32(join44(skill.dir, "SKILL.md"), "utf8");
93267
+ const content = readFileSync33(join45(skill.dir, "SKILL.md"), "utf8");
92947
93268
  context.output.line(content);
92948
93269
  return { data: { name: skill.name, path: skill.dir, content } };
92949
93270
  }
@@ -93025,21 +93346,21 @@ function createSkillCommands(shipped) {
93025
93346
  // src/init.ts
93026
93347
  init_command();
93027
93348
  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);
93349
+ import { existsSync as existsSync40, mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
93350
+ import { dirname as dirname19, isAbsolute as isAbsolute8, relative as relative10, resolve as resolve25 } from "node:path";
93351
+ var import_yaml10 = __toESM(require_dist(), 1);
93031
93352
  function initHarnessFolder(workspaceDir, adapter = CLAUDE_ADAPTER) {
93032
93353
  const file2 = harnessRootAt(workspaceDir, adapter).file;
93033
- if (existsSync39(file2)) {
93354
+ if (existsSync40(file2)) {
93034
93355
  throw new CliError({ code: "conflict", message: `This project has ${file2} already.`, exitCode: ExitCode.Validation, hint: "A harness folder holds one orbital.yaml." });
93035
93356
  }
93036
93357
  mkdirSync29(dirname19(file2), { recursive: true });
93037
- writeFileSync26(file2, (0, import_yaml9.stringify)({ apiVersion: ORBITAL_API_VERSION }));
93358
+ writeFileSync26(file2, (0, import_yaml10.stringify)({ apiVersion: ORBITAL_API_VERSION }));
93038
93359
  return file2;
93039
93360
  }
93040
93361
  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 });
93362
+ const workspaceDir = resolve25(typeof flag === "string" ? flag : ".");
93363
+ if (!existsSync40(workspaceDir)) throw new CliError({ code: "not-found", message: `${workspaceDir} does not exist.`, exitCode: ExitCode.Validation });
93043
93364
  return workspaceDir;
93044
93365
  }
93045
93366
  var WORKSPACE3 = { name: "workspace", type: "path", summary: "The project. Default: the current directory." };
@@ -93077,9 +93398,9 @@ function pluginRefOf(home, given) {
93077
93398
  return path3.startsWith(".") ? path3 : `./${path3}`;
93078
93399
  } };
93079
93400
  }
93080
- installPlugin(home, resolved);
93401
+ const { name } = installPlugin(home, resolved);
93081
93402
  const source = resolved.kind === "npm" ? `npm:${resolved.pkg}` : resolved.kind === "git" ? `git:${resolved.url}` : spec;
93082
- return { ref: { name: resolved.name, source, base: home }, recorded: () => source };
93403
+ return { ref: { name, source, base: home }, recorded: () => source };
93083
93404
  }
93084
93405
  function knownRef(context, known) {
93085
93406
  const home = orbitalHome(context.env);
@@ -93115,7 +93436,7 @@ function targetOf2(context, workspaceDir, named2, given, adapters) {
93115
93436
  });
93116
93437
  }
93117
93438
  async function chooseTargets(context, workspaceDir, adapters) {
93118
- const present = adapters.filter((adapter) => existsSync39(resolve24(workspaceDir, adapter.folder.folder)));
93439
+ const present = adapters.filter((adapter) => existsSync40(resolve25(workspaceDir, adapter.folder.folder)));
93119
93440
  if (present.length === 1) {
93120
93441
  context.output.step(`installing ${present[0].id}: ${present[0].folder.folder} is the harness folder of this project`);
93121
93442
  return [{ adapter: present[0], given: null }];
@@ -93149,9 +93470,9 @@ async function adapterInstall(context, adapter, workspaceDir, force) {
93149
93470
  async function installTarget(context, workspaceDir, target, user, force) {
93150
93471
  const { adapter, given } = target;
93151
93472
  const root = harnessRootAt(workspaceDir, adapter);
93152
- const file2 = existsSync39(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
93473
+ const file2 = existsSync40(root.file) ? null : initHarnessFolder(workspaceDir, adapter);
93153
93474
  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;
93475
+ 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
93476
  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
93477
  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
93478
  const install2 = await adapterInstall(context, adapter, workspaceDir, force);
@@ -93238,6 +93559,12 @@ function followAttach(context, options, signal, inbox, timing = {}) {
93238
93559
  delivery: (event) => {
93239
93560
  const delivery = DeliverySchema.safeParse(event);
93240
93561
  if (delivery.success) inbox.push(delivery.data);
93562
+ const answered = AnsweredSchema.safeParse(event);
93563
+ if (!answered.success) return;
93564
+ for (let index = inbox.length - 1; index >= 0; index -= 1) {
93565
+ const held2 = inbox[index];
93566
+ if (held2.mission === answered.data.mission && (answered.data.question === null || held2.question === answered.data.question)) inbox.splice(index, 1);
93567
+ }
93241
93568
  },
93242
93569
  detached: (reason) => {
93243
93570
  if (kept) kept.ended = true;
@@ -93367,9 +93694,8 @@ function mcpCommand(toolsOf) {
93367
93694
  const joined = await attachment.current();
93368
93695
  return { url: `${joined.nodeUrl}/a2a`, token: joined.token };
93369
93696
  },
93370
- get sessionId() {
93371
- return attachment.sessionId();
93372
- },
93697
+ sessionId: null,
93698
+ attached: async () => (await attachment.current()).sessionId,
93373
93699
  harness,
93374
93700
  follow: true,
93375
93701
  tools: inboxTools(context, attachment.current, inbox)
@@ -93378,8 +93704,8 @@ function mcpCommand(toolsOf) {
93378
93704
  const server = await mcpServer(options, input2.flags.actions === true, toolsOf);
93379
93705
  const transport = new StdioServerTransport();
93380
93706
  await server.connect(transport);
93381
- await new Promise((resolve25) => {
93382
- server.server.onclose = resolve25;
93707
+ await new Promise((resolve27) => {
93708
+ server.server.onclose = resolve27;
93383
93709
  process.stdin.once("end", () => void server.close());
93384
93710
  });
93385
93711
  stop.abort();
@@ -93405,6 +93731,10 @@ function orbitalCommands(options) {
93405
93731
  ...options.commands ?? []
93406
93732
  ];
93407
93733
  }
93734
+ function workspacePluginCommands(options) {
93735
+ const reserved = [.../* @__PURE__ */ new Set([...options.builtIn.map((spec) => spec.path[0]), ...INTROSPECTION_WORDS])];
93736
+ return pluginCommandSpecs({ workspaceDir: options.workspaceDir, env: options.env, reserved, ...options.terminal && { terminal: options.terminal } });
93737
+ }
93408
93738
 
93409
93739
  // src/launch.ts
93410
93740
  init_zod();
@@ -93437,13 +93767,15 @@ async function nodeLaunch(env3, argv, prefix, run3) {
93437
93767
  }
93438
93768
 
93439
93769
  // src/cli.ts
93440
- var orbital = () => ({
93441
- registry: composeRegistry(orbitalCommands({ terminal: async () => (await init_tui().then(() => tui_exports)).terminal })),
93442
- context: (base2) => base2
93443
- });
93770
+ var orbital = (options) => {
93771
+ const own2 = orbitalCommands({ terminal: async () => (await init_tui().then(() => tui_exports)).terminal });
93772
+ const workspace = peekFlag(options.argv, "workspace");
93773
+ const plugins = workspacePluginCommands({ builtIn: own2, workspaceDir: resolve26(typeof workspace === "string" ? workspace : "."), env: options.env });
93774
+ return { registry: composeRegistry([...own2, ...plugins]), context: (base2) => base2 };
93775
+ };
93444
93776
  async function run2(options) {
93445
93777
  setBrand(ORBITAL_BRAND);
93446
- return runProgram(orbital(), options);
93778
+ return runProgram(orbital(options), options);
93447
93779
  }
93448
93780
  function processRun(argv) {
93449
93781
  return run2({