@kaiba-cloud/orbital 0.0.3 → 0.0.4

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/runner.js CHANGED
@@ -45510,7 +45510,7 @@ function handleIntersectionResults(result, left, right) {
45510
45510
  const unrecKeys = /* @__PURE__ */ new Map();
45511
45511
  let unrecIssue;
45512
45512
  const keyIssues = /* @__PURE__ */ new Map();
45513
- const collect = (iss, side) => {
45513
+ const collect2 = (iss, side) => {
45514
45514
  let keys;
45515
45515
  if (iss.code === "unrecognized_keys" && !iss.path?.length) {
45516
45516
  unrecIssue ?? (unrecIssue = iss);
@@ -45531,11 +45531,11 @@ function handleIntersectionResults(result, left, right) {
45531
45531
  return true;
45532
45532
  };
45533
45533
  for (const iss of left.issues) {
45534
- if (!collect(iss, "l"))
45534
+ if (!collect2(iss, "l"))
45535
45535
  result.issues.push(iss);
45536
45536
  }
45537
45537
  for (const iss of right.issues) {
45538
- if (!collect(iss, "r"))
45538
+ if (!collect2(iss, "r"))
45539
45539
  result.issues.push(iss);
45540
45540
  }
45541
45541
  const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);
@@ -61386,6 +61386,7 @@ var ORBITAL_META = {
61386
61386
  requestedModel: "orbital/requestedModel",
61387
61387
  fromAgent: "orbital/fromAgent",
61388
61388
  fromSession: "orbital/fromSession",
61389
+ fromTeam: "orbital/fromTeam",
61389
61390
  peerHops: "orbital/peerHops",
61390
61391
  entry: "orbital/entry",
61391
61392
  block: "orbital/block",
@@ -61645,6 +61646,16 @@ var MissionChangeSchema = external_exports.object({
61645
61646
  by: external_exports.string(),
61646
61647
  note: external_exports.string().nullable()
61647
61648
  });
61649
+ var MissionLinkSchema = external_exports.object({
61650
+ label: external_exports.string().min(1),
61651
+ url: external_exports.string().url().refine((url2) => /^https?:\/\//i.test(url2), "a link is an http or https URL"),
61652
+ state: external_exports.string().min(1).nullable().default(null)
61653
+ });
61654
+ var MissionAskedSchema = external_exports.object({
61655
+ id: external_exports.string().min(1),
61656
+ question: external_exports.string().min(1),
61657
+ at: external_exports.string()
61658
+ });
61648
61659
  var MissionStateFileSchema = external_exports.object({
61649
61660
  mission: external_exports.string().min(1),
61650
61661
  node: external_exports.string().min(1),
@@ -61659,6 +61670,8 @@ var MissionStateFileSchema = external_exports.object({
61659
61670
  lead: external_exports.string().nullable(),
61660
61671
  claim: external_exports.string().nullable(),
61661
61672
  claimedAt: external_exports.string().nullable(),
61673
+ links: external_exports.array(MissionLinkSchema).default([]),
61674
+ questions: external_exports.array(MissionAskedSchema).default([]),
61662
61675
  history: external_exports.array(MissionChangeSchema)
61663
61676
  });
61664
61677
  var MISSION_ID = /^[a-z0-9][a-z0-9-]{0,62}$/;
@@ -61672,11 +61685,14 @@ var MissionRecordSchema = external_exports.object({
61672
61685
  declared: external_exports.boolean(),
61673
61686
  givenBy: external_exports.string().nullable(),
61674
61687
  brief: external_exports.string().nullable(),
61688
+ summary: external_exports.string().nullable().default(null),
61689
+ links: external_exports.array(MissionLinkSchema).default([]),
61675
61690
  sessionId: external_exports.string().nullable(),
61676
61691
  taskId: external_exports.string().nullable(),
61677
61692
  source: external_exports.string().nullable(),
61678
61693
  result: external_exports.string().nullable(),
61679
61694
  question: external_exports.string().nullable(),
61695
+ questions: external_exports.array(MissionAskedSchema).default([]),
61680
61696
  giver: external_exports.string().nullable(),
61681
61697
  lead: external_exports.string().nullable(),
61682
61698
  lastActivity: external_exports.string().nullable().default(null),
@@ -61693,6 +61709,11 @@ var MissionHandoffSchema = external_exports.object({
61693
61709
  state: MissionOutcomeSchema.default("done"),
61694
61710
  result: external_exports.string().min(1)
61695
61711
  });
61712
+ var MissionLinkRequestSchema = MissionLinkSchema.extend({ missionId: MissionIdSchema.optional() });
61713
+ var MissionAskRequestSchema = external_exports.object({
61714
+ missionId: MissionIdSchema.optional(),
61715
+ question: external_exports.string().min(1)
61716
+ });
61696
61717
  var MissionStartRequestSchema = external_exports.object({
61697
61718
  mission: MissionIdSchema,
61698
61719
  team: external_exports.string().min(1).nullable().default(null),
@@ -61711,11 +61732,13 @@ var MissionNewRequestSchema = external_exports.object({
61711
61732
  var MissionWrittenSchema = external_exports.object({ missionId: external_exports.string().min(1), file: external_exports.string().min(1) });
61712
61733
  var MissionReplySchema = external_exports.object({
61713
61734
  missionId: MissionIdSchema,
61714
- text: external_exports.string().min(1)
61735
+ text: external_exports.string().min(1),
61736
+ questionId: external_exports.string().min(1).optional()
61715
61737
  });
61716
61738
  var RemoteGiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
61717
- var RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, giver: RemoteGiverSchema });
61718
- var RelayReplySchema = external_exports.object({ missionId: MissionIdSchema, text: external_exports.string().min(1), by: external_exports.string().min(1) });
61739
+ var SENDER_FIELDS = { team: external_exports.string().min(1).nullable().default(null), agent: external_exports.string().min(1).nullable().default(null) };
61740
+ var RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, giver: RemoteGiverSchema, ...SENDER_FIELDS });
61741
+ var RelayReplySchema = external_exports.object({ missionId: MissionIdSchema, text: external_exports.string().min(1), questionId: external_exports.string().min(1).optional(), by: external_exports.string().min(1), ...SENDER_FIELDS });
61719
61742
  var MissionControlSchema = external_exports.object({ missionId: MissionIdSchema });
61720
61743
  var SayRequestSchema = external_exports.object({
61721
61744
  target: external_exports.string().min(1),
@@ -61726,15 +61749,17 @@ var SaidSchema = external_exports.object({
61726
61749
  missionId: external_exports.string().nullable(),
61727
61750
  delivery: external_exports.enum(["reply", "steer", "turn"])
61728
61751
  });
61729
- var RelayMessageSchema = external_exports.object({ missionId: MissionIdSchema, text: external_exports.string().min(1), by: external_exports.string().min(1) });
61730
- var RelayControlSchema = external_exports.object({ missionId: MissionIdSchema, by: external_exports.string().min(1) });
61752
+ var RelayMessageSchema = external_exports.object({ missionId: MissionIdSchema, text: external_exports.string().min(1), by: external_exports.string().min(1), ...SENDER_FIELDS });
61753
+ var RelayControlSchema = external_exports.object({ missionId: MissionIdSchema, by: external_exports.string().min(1), ...SENDER_FIELDS });
61731
61754
  var MissionQuestionSchema = external_exports.object({
61732
61755
  missionId: MissionIdSchema,
61733
61756
  node: external_exports.string().min(1),
61734
61757
  lead: external_exports.string().nullable(),
61735
61758
  leadSessionId: external_exports.string().min(1),
61736
61759
  question: external_exports.string(),
61737
- giver: RemoteGiverSchema
61760
+ questionId: external_exports.string().min(1).optional(),
61761
+ giver: RemoteGiverSchema,
61762
+ team: external_exports.string().min(1).nullable().default(null)
61738
61763
  });
61739
61764
  var MissionJoinSchema = external_exports.object({
61740
61765
  missionId: MissionIdSchema
@@ -61868,6 +61893,7 @@ var TelemetrySnapshotSchema = external_exports.object({
61868
61893
  giver: external_exports.string().nullable(),
61869
61894
  givenBy: external_exports.string().nullable(),
61870
61895
  question: external_exports.string().nullable(),
61896
+ questions: external_exports.array(MissionAskedSchema).default([]),
61871
61897
  result: external_exports.string().nullable(),
61872
61898
  updatedAt: external_exports.string().nullable()
61873
61899
  })),
@@ -62258,6 +62284,12 @@ var NodeClient = class {
62258
62284
  async handoff(handoff2) {
62259
62285
  return MissionRecordSchema.parse(await this.request("/orbital/missions/handoff", { method: "POST", body: handoff2 }));
62260
62286
  }
62287
+ async link(link) {
62288
+ return MissionRecordSchema.parse(await this.request("/orbital/missions/link", { method: "POST", body: link }));
62289
+ }
62290
+ async ask(ask) {
62291
+ return MissionRecordSchema.parse(await this.request("/orbital/missions/ask", { method: "POST", body: ask }));
62292
+ }
62261
62293
  async transcript(sessionId, limit) {
62262
62294
  return SessionTranscriptSchema.parse(await this.request(`/orbital/sessions/${encodeURIComponent(sessionId)}/transcript?limit=${limit}`));
62263
62295
  }
@@ -62482,6 +62514,7 @@ var DeliverySchema = external_exports.object({
62482
62514
  fromAgent: external_exports.string().nullable(),
62483
62515
  fromSession: external_exports.string().nullable(),
62484
62516
  mission: external_exports.string().nullable().default(null),
62517
+ question: external_exports.string().nullable().default(null),
62485
62518
  text: external_exports.string()
62486
62519
  });
62487
62520
  var SettleSchema = external_exports.object({
@@ -62532,6 +62565,7 @@ var HarnessFolderSchema = external_exports.strictObject({
62532
62565
  var HarnessAdapterDeclarationSchema = HarnessFolderSchema.extend({ id: external_exports.string().regex(/^[a-z][a-z0-9-]{0,31}$/) });
62533
62566
  var HarnessInstallResultSchema = external_exports.object({
62534
62567
  written: external_exports.array(external_exports.string()).default([]),
62568
+ removed: external_exports.array(external_exports.string()).default([]),
62535
62569
  notes: external_exports.array(external_exports.string()).default([])
62536
62570
  });
62537
62571
  var HarnessAgentFileSchema = external_exports.object({
@@ -62720,12 +62754,6 @@ function localAgentSystemPrompt(systemPrompt, promptBlock) {
62720
62754
  ${promptBlock}` : systemPrompt;
62721
62755
  }
62722
62756
 
62723
- // src/protocol/keychain.ts
62724
- var KEYCHAIN_PATH = "/orbital/keychain";
62725
- var KEYCHAIN_MODE = "keychain";
62726
- var KeychainCallSchema = external_exports.object({ args: external_exports.array(external_exports.string()), input: external_exports.string().default("") });
62727
- var KeychainAnswerSchema = external_exports.object({ status: external_exports.number().int(), stdout: external_exports.string(), stderr: external_exports.string() });
62728
-
62729
62757
  // src/runner/runtime/harness/claude-browser.ts
62730
62758
  import { existsSync, readFileSync } from "node:fs";
62731
62759
  import { homedir } from "node:os";
@@ -75023,7 +75051,8 @@ var ClaudeCodeHarness = class {
75023
75051
  ...QUIET_ENV,
75024
75052
  ...this.args.toolEnvironment?.(),
75025
75053
  ...credential && { CLAUDE_CONFIG_DIR: this.configDir },
75026
- ...credential && (isOAuth ? { CLAUDE_CODE_OAUTH_TOKEN: credential.payload } : { ANTHROPIC_API_KEY: credential.payload })
75054
+ ...credential && (isOAuth ? { CLAUDE_CODE_OAUTH_TOKEN: credential.payload } : { ANTHROPIC_API_KEY: credential.payload }),
75055
+ ...!credential && this.host.login && { CLAUDE_CODE_OAUTH_TOKEN: this.host.login }
75027
75056
  };
75028
75057
  delete env.NODE_OPTIONS;
75029
75058
  return env;
@@ -81317,6 +81346,8 @@ function createAgentBridgeTools(options) {
81317
81346
  if (messaging.peers) host.registerTool(buildAgentPeersTool(messaging, options));
81318
81347
  if (messaging.mission) host.registerTool(buildMissionGetTool(messaging, options));
81319
81348
  if (messaging.handoffMission) host.registerTool(buildMissionHandoffTool(messaging, options));
81349
+ if (messaging.linkMission) host.registerTool(buildMissionLinkTool(messaging, options));
81350
+ if (messaging.askMission) host.registerTool(buildMissionAskTool(messaging, options));
81320
81351
  host.registerTool(buildAgentStopTool(jobs, messaging, options, log2));
81321
81352
  host.registerTool(buildAgentKillTool(jobs, ledger, messaging, options, log2));
81322
81353
  });
@@ -81658,6 +81689,48 @@ function buildMissionHandoffTool(messaging, options) {
81658
81689
  }
81659
81690
  };
81660
81691
  }
81692
+ var MissionLinkParams = Type.Object({
81693
+ label: Type.String({ minLength: 1, description: "A short label, such as PR #42." }),
81694
+ url: Type.String({ minLength: 1, description: "The http or https URL of the link." }),
81695
+ state: Type.Optional(Type.String({ minLength: 1, description: "The state of the linked thing, such as open, merged or closed." }))
81696
+ });
81697
+ function buildMissionLinkTool(messaging, options) {
81698
+ return {
81699
+ name: "missionLink",
81700
+ label: "Link Mission",
81701
+ description: "Add a link to the mission of your session, as its lead, or change the state of a link it has: such as the pull request you opened, with its state open, merged or closed. One URL or one label is one link, so a call with the same URL replaces it. The node keeps the links with the mission, and the person sees them in the live view.",
81702
+ promptSnippet: "missionLink: add a link to your mission, such as the PR you opened, with its state",
81703
+ parameters: MissionLinkParams,
81704
+ executionMode: "sequential",
81705
+ async execute(_toolCallId, params) {
81706
+ try {
81707
+ return textResult(await messaging.linkMission(options.callerSessionId, { label: params.label, url: params.url, state: params.state ?? null }));
81708
+ } catch (err) {
81709
+ return errorResult(`missionLink failed: ${err instanceof Error ? err.message : String(err)}`);
81710
+ }
81711
+ }
81712
+ };
81713
+ }
81714
+ var MissionAskParams = Type.Object({
81715
+ question: Type.String({ minLength: 1, description: "The question, in full, with the options you see and what each one means: the giver has none of your context." })
81716
+ });
81717
+ function buildMissionAskTool(messaging, options) {
81718
+ return {
81719
+ name: "missionAsk",
81720
+ label: "Ask Mission Giver",
81721
+ 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.",
81722
+ promptSnippet: "missionAsk: ask the giver of your mission a question at any moment, also while your workers run",
81723
+ parameters: MissionAskParams,
81724
+ executionMode: "sequential",
81725
+ async execute(_toolCallId, params) {
81726
+ try {
81727
+ return textResult(await messaging.askMission(options.callerSessionId, { question: params.question }));
81728
+ } catch (err) {
81729
+ return errorResult(`missionAsk failed: ${err instanceof Error ? err.message : String(err)}`);
81730
+ }
81731
+ }
81732
+ };
81733
+ }
81661
81734
  function buildAgentMessageTool(jobs, ledger, messaging, options, log2) {
81662
81735
  return {
81663
81736
  name: "agentMessage",
@@ -83119,8 +83192,9 @@ var RunSpecSchema = external_exports.object({
83119
83192
  harness: external_exports.enum(RUNNER_HARNESSES).default("claude"),
83120
83193
  claude: external_exports.object({
83121
83194
  settings: external_exports.string().nullable(),
83122
- args: external_exports.array(external_exports.string())
83123
- }).default({ settings: null, args: [] }),
83195
+ args: external_exports.array(external_exports.string()),
83196
+ login: external_exports.string().nullable().default(null)
83197
+ }).default({ settings: null, args: [], login: null }),
83124
83198
  agent: external_exports.discriminatedUnion("kind", [
83125
83199
  external_exports.object({ kind: external_exports.literal("directory"), id: external_exports.string(), bootstrap: external_exports.record(external_exports.string(), external_exports.unknown()) }),
83126
83200
  external_exports.object({ kind: external_exports.literal("local"), definition: LocalAgentSchema })
@@ -83139,19 +83213,88 @@ function readRunSpec(path10) {
83139
83213
  }
83140
83214
 
83141
83215
  // src/runner/local-store.ts
83142
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
83216
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
83143
83217
  import path7 from "node:path";
83218
+
83219
+ // src/runner/jsonl-log.ts
83220
+ import { appendFileSync, closeSync, existsSync as existsSync4, openSync, readSync, renameSync as renameSync2, statSync } from "node:fs";
83221
+ var CHUNK = 64 * 1024;
83222
+ function rotatedPath(file2) {
83223
+ return file2.replace(/\.jsonl$/, ".1.jsonl");
83224
+ }
83225
+ function appendCapped(file2, text, cap) {
83226
+ if (existsSync4(file2) && statSync(file2).size + Buffer.byteLength(text) > cap) renameSync2(file2, rotatedPath(file2));
83227
+ appendFileSync(file2, text);
83228
+ }
83229
+ function backward(file2, take, into) {
83230
+ if (!existsSync4(file2)) return true;
83231
+ const fd = openSync(file2, "r");
83232
+ try {
83233
+ let position = statSync(file2).size;
83234
+ let rest = Buffer.alloc(0);
83235
+ while (position > 0) {
83236
+ const length = Math.min(CHUNK, position);
83237
+ position -= length;
83238
+ const chunk = Buffer.alloc(length);
83239
+ readSync(fd, chunk, 0, length, position);
83240
+ const joined = Buffer.concat([chunk, rest]);
83241
+ let end = joined.length;
83242
+ for (let at = joined.lastIndexOf(10, end - 1); at !== -1; at = end > 0 ? joined.lastIndexOf(10, end - 1) : -1) {
83243
+ const line = joined.subarray(at + 1, end).toString("utf8");
83244
+ end = at;
83245
+ if (line.trim() === "") continue;
83246
+ if (!take(line)) return false;
83247
+ into.push(line);
83248
+ }
83249
+ rest = joined.subarray(0, end);
83250
+ }
83251
+ const first = rest.toString("utf8");
83252
+ if (first.trim() !== "") {
83253
+ if (!take(first)) return false;
83254
+ into.push(first);
83255
+ }
83256
+ return true;
83257
+ } finally {
83258
+ closeSync(fd);
83259
+ }
83260
+ }
83261
+ function collect(file2, take) {
83262
+ const lines = [];
83263
+ if (backward(file2, take, lines)) backward(rotatedPath(file2), take, lines);
83264
+ return lines.reverse();
83265
+ }
83266
+ function tailLines(file2, count, maxBytes = Number.POSITIVE_INFINITY) {
83267
+ let taken = 0;
83268
+ let bytes = 0;
83269
+ return count <= 0 ? [] : collect(file2, (line) => {
83270
+ if (taken >= count || taken > 0 && bytes + line.length > maxBytes) return false;
83271
+ taken += 1;
83272
+ bytes += line.length;
83273
+ return true;
83274
+ });
83275
+ }
83276
+ function sequenceOf(line) {
83277
+ const found = /"sequence":(\d+)/.exec(line);
83278
+ return found ? Number(found[1]) : 0;
83279
+ }
83280
+ function lastSequenceOf(file2) {
83281
+ const last = tailLines(file2, 1)[0];
83282
+ return last ? sequenceOf(last) : 0;
83283
+ }
83284
+
83285
+ // src/runner/local-store.ts
83144
83286
  var RUNNER_LOG = "runner.jsonl";
83287
+ var RUNNER_LOG_CAP = 32 * 1024 * 1024;
83145
83288
  var HISTORY_DIR = "history";
83146
83289
  function folderUnit(dir) {
83147
83290
  const walk = (folder) => readdirSync2(folder).flatMap((name) => {
83148
83291
  const full = path7.join(folder, name);
83149
- return statSync(full).isDirectory() ? walk(full) : [full];
83292
+ return statSync2(full).isDirectory() ? walk(full) : [full];
83150
83293
  });
83151
83294
  return {
83152
83295
  kinds: ["history"],
83153
83296
  async list() {
83154
- if (!existsSync4(dir)) return null;
83297
+ if (!existsSync5(dir)) return null;
83155
83298
  return Promise.all(walk(dir).map(async (full) => {
83156
83299
  const bytes = readFileSync4(full);
83157
83300
  return { path: path7.relative(dir, full).split(path7.sep).join("/"), hash: await sha256Hex(bytes), size: bytes.length };
@@ -83159,7 +83302,7 @@ function folderUnit(dir) {
83159
83302
  },
83160
83303
  async read(_ref, file2) {
83161
83304
  const full = path7.join(dir, ...file2.split("/"));
83162
- return existsSync4(full) ? { path: file2, contentBase64: readFileSync4(full).toString("base64") } : null;
83305
+ return existsSync5(full) ? { path: file2, contentBase64: readFileSync4(full).toString("base64") } : null;
83163
83306
  },
83164
83307
  async write() {
83165
83308
  throw new Error("The runner reads a session's history unit; the host writes it.");
@@ -83185,8 +83328,8 @@ var LocalSessionStore = class {
83185
83328
  async persistEvent(sessionId, event) {
83186
83329
  const sequence = (this.sequences.get(sessionId) ?? 0) + 1;
83187
83330
  this.sequences.set(sessionId, sequence);
83188
- appendFileSync(path7.join(this.recordDir(sessionId), RUNNER_LOG), `${JSON.stringify({ sequence, ...event })}
83189
- `);
83331
+ appendCapped(path7.join(this.recordDir(sessionId), RUNNER_LOG), `${JSON.stringify({ sequence, ...event })}
83332
+ `, RUNNER_LOG_CAP);
83190
83333
  this.observer.event?.(sessionId, { ...event, sequence });
83191
83334
  }
83192
83335
  async seedSequence(sessionId, sequence) {
@@ -83196,9 +83339,7 @@ var LocalSessionStore = class {
83196
83339
  this.sequences.delete(sessionId);
83197
83340
  }
83198
83341
  async lastEventSequence(sessionId) {
83199
- const file2 = path7.join(this.sessionsDir, sessionId, RUNNER_LOG);
83200
- const lines = existsSync4(file2) ? readFileSync4(file2, "utf8").trim().split("\n").filter(Boolean) : [];
83201
- const logged = lines.length === 0 ? 0 : JSON.parse(lines[lines.length - 1]).sequence;
83342
+ const logged = lastSequenceOf(path7.join(this.sessionsDir, sessionId, RUNNER_LOG));
83202
83343
  return Math.max(logged, (await this.history(sessionId))?.record.watermark ?? 0);
83203
83344
  }
83204
83345
  async readConversation(sessionId) {
@@ -83238,7 +83379,7 @@ function createLocalBootstrapSource(options) {
83238
83379
  mcps: []
83239
83380
  };
83240
83381
  }
83241
- return { organizationId: LOCAL_ORGANIZATION, ...spec.agent.bootstrap };
83382
+ return { organizationId: LOCAL_ORGANIZATION, ...spec.agent.bootstrap, systemPrompt: localAgentSystemPrompt(String(spec.agent.bootstrap.systemPrompt ?? ""), spec.promptBlock) };
83242
83383
  }
83243
83384
  return {
83244
83385
  async fetchBootstrap(agentId) {
@@ -83352,6 +83493,12 @@ var NodeMessaging = class {
83352
83493
  async handoffMission(callerSessionId, handoff2) {
83353
83494
  return this.as(callerSessionId).handoff(handoff2);
83354
83495
  }
83496
+ async linkMission(callerSessionId, link) {
83497
+ return this.as(callerSessionId).link(link);
83498
+ }
83499
+ async askMission(callerSessionId, ask) {
83500
+ return this.as(callerSessionId).ask(ask);
83501
+ }
83355
83502
  };
83356
83503
 
83357
83504
  // src/runner/node-gateway.ts
@@ -83415,40 +83562,6 @@ function localLayout(runDir, workspaceDir) {
83415
83562
  };
83416
83563
  }
83417
83564
 
83418
- // src/runner/keychain-client.ts
83419
- async function keychainClient(specPath, args, streams, fetchImpl = fetch) {
83420
- const spec = readRunSpec(specPath);
83421
- const input2 = args.length === 1 && args[0] === "-i" ? await streams.stdin() : "";
83422
- try {
83423
- const response = await fetchImpl(`${spec.node.url.replace(/\/a2a$/, "")}${KEYCHAIN_PATH}`, {
83424
- method: "POST",
83425
- headers: { authorization: `Bearer ${spec.node.token}`, [NODE_SESSION_HEADER]: spec.sessionId, "content-type": "application/json" },
83426
- body: JSON.stringify({ args, input: input2 })
83427
- });
83428
- if (!response.ok) {
83429
- streams.stderr(`security: the node refused the keychain call (${response.status}): ${(await response.text()).slice(0, 300)}
83430
- `);
83431
- return 1;
83432
- }
83433
- const answer = KeychainAnswerSchema.parse(await response.json());
83434
- streams.stdout(answer.stdout);
83435
- streams.stderr(answer.stderr);
83436
- return answer.status;
83437
- } catch (error62) {
83438
- streams.stderr(`security: the node did not answer the keychain call: ${error62 instanceof Error ? error62.message : String(error62)}
83439
- `);
83440
- return 1;
83441
- }
83442
- }
83443
- function readStdin() {
83444
- return new Promise((resolve2, reject) => {
83445
- const chunks = [];
83446
- process.stdin.on("data", (chunk) => chunks.push(chunk));
83447
- process.stdin.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
83448
- process.stdin.on("error", reject);
83449
- });
83450
- }
83451
-
83452
83565
  // src/runner/main.ts
83453
83566
  function emit(record2) {
83454
83567
  process.stdout.write(`${JSON.stringify(record2)}
@@ -83461,7 +83574,8 @@ function claudeOnThisMachine(spec) {
83461
83574
  settingSources: ["user", "project", "local"],
83462
83575
  subAgents: true,
83463
83576
  ...spec.claude.settings && { settings: spec.claude.settings },
83464
- extraArgs: spec.claude.args
83577
+ extraArgs: spec.claude.args,
83578
+ login: spec.claude.login
83465
83579
  };
83466
83580
  }
83467
83581
  function startLocalRun(spec, fetchImpl = fetch) {
@@ -83577,31 +83691,7 @@ async function runToCompletion(run, spec, interactiveMode = interactive) {
83577
83691
  await run.sessions.closeAllSessions();
83578
83692
  }
83579
83693
  }
83580
- async function keychain() {
83581
- const [specPath, ...args] = process.argv.slice(3);
83582
- if (!specPath) throw new Error(`usage: runner ${KEYCHAIN_MODE} <spec.json> <security arguments>`);
83583
- installProxyDispatcher();
83584
- let out = "";
83585
- let err = "";
83586
- const code = await keychainClient(specPath, args, { stdin: readStdin, stdout: (text) => {
83587
- out += text;
83588
- }, stderr: (text) => {
83589
- err += text;
83590
- } });
83591
- await new Promise((done) => process.stdout.write(out, () => done()));
83592
- await new Promise((done) => process.stderr.write(err, () => done()));
83593
- return code;
83594
- }
83595
- var entered = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href;
83596
- if (entered && process.argv[2] === KEYCHAIN_MODE) {
83597
- keychain().then(
83598
- (code) => process.exit(code),
83599
- (err) => {
83600
- process.stderr.write(`security: ${err instanceof Error ? err.message : String(err)}
83601
- `, () => process.exit(1));
83602
- }
83603
- );
83604
- } else if (entered) {
83694
+ if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href) {
83605
83695
  main().then(
83606
83696
  () => process.exit(0),
83607
83697
  (err) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kaiba-cloud/orbital",
3
3
  "description": "orbital: the control plane lite for agents on any harness: the node, its runner, its terminal UI, its protocol and its Claude Code mod, in one command line",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "engines": {
@@ -297,6 +297,32 @@
297
297
  "null"
298
298
  ]
299
299
  },
300
+ "questions": {
301
+ "default": [],
302
+ "type": "array",
303
+ "items": {
304
+ "type": "object",
305
+ "properties": {
306
+ "id": {
307
+ "type": "string",
308
+ "minLength": 1
309
+ },
310
+ "question": {
311
+ "type": "string",
312
+ "minLength": 1
313
+ },
314
+ "at": {
315
+ "type": "string"
316
+ }
317
+ },
318
+ "required": [
319
+ "id",
320
+ "question",
321
+ "at"
322
+ ],
323
+ "additionalProperties": false
324
+ }
325
+ },
300
326
  "result": {
301
327
  "type": [
302
328
  "string",
@@ -319,6 +345,7 @@
319
345
  "giver",
320
346
  "givenBy",
321
347
  "question",
348
+ "questions",
322
349
  "result",
323
350
  "updatedAt"
324
351
  ],
@@ -16,7 +16,7 @@ plain object for each port.
16
16
  | [references/ports.md](references/ports.md) | Each port: what it is for, when the node calls it, the config, the interface, the remote methods, an example. |
17
17
  | [references/host-api.md](references/host-api.md) | Each method of the host API: `submit`, `deliver`, `completeModel`, `missions`, `snapshot` and the others. |
18
18
  | [references/manifest.md](references/manifest.md) | `orbital-plugin.json`, the `orbital.yaml` entry, the manifest, permissions, secrets, `context.init`. |
19
- | [references/recipes.md](references/recipes.md) | Whole plugins: a cross-machine missions relay with a directory and placements, a telemetry dashboard, a Slack channel, a policy engine, a gatekeeper for messages from the network. |
19
+ | [references/recipes.md](references/recipes.md) | Whole plugins: a cross-machine missions relay with a directory and placements, a telemetry dashboard, a Slack channel, a policy engine, a harness. |
20
20
 
21
21
  ## Ports
22
22
 
@@ -113,8 +113,7 @@ npm test # build, then the conformance checks and your own checks
113
113
  - Test your own logic next to the checks: call the port methods with a fake
114
114
  `PluginContext`, as the template's test does.
115
115
  - For a test against a real node, start the plugin with `startRemotePlugin`
116
- and a node with `runNode`, as `plugins/channel-slack/__tests__` does in the
117
- source repository.
116
+ and a node with `runNode`.
118
117
 
119
118
  ### 4. Check through the real protocol
120
119
 
@@ -215,15 +214,5 @@ start stops the start with "A plugin did not start: …".
215
214
 
216
215
  ## Worked examples
217
216
 
218
- In the source repository of orbital:
219
-
220
- - `plugins/channel-slack`: a channel with a Socket Mode service, `host.deliver`, idempotent send, secrets, a panel and a command.
221
- - `plugins/policy-judge`: a policy that calls a model through `host.completeModel`.
222
- - `plugins/gatekeeper`: a policy that asks an agent of the receiving team through `host.askAgent` if a message from the network may reach the team.
223
- - `plugins/store-object`: a store in an object store.
224
- - `plugins/reasoner-open-ontologies`: a reasoner.
225
- - `packages/orbital/src/core/node/static-directory.ts`: a directory, a `sessions.create` hook and a placement that send `<agent>@<node>` to another node.
226
- - `packages/orbital/__tests__/core/fixtures/echo-plugin`: session hooks, a directory, a hosted tool, a panel, a service that submits a turn, and a command.
227
- - `packages/orbital/__tests__/core/fixtures/echo-harness`: a harness and its turn program.
228
- - `packages/orbital-harness-pi`: the adapter of the harness `pi`: its folder `.pi`, its install, and a turn program on the Pi coding agent.
229
- - `packages/orbital-harness-codex`: the adapter of the harness `codex`: its folder `.codex`, its install in `config.toml`, and a turn program on the OpenAI Codex CLI.
217
+ - `@kaiba-cloud/orbital-harness-pi` on npm: the adapter of the harness `pi`: its folder `.pi`, its install, and a turn program on the Pi coding agent.
218
+ - `@kaiba-cloud/orbital-harness-codex` on npm: the adapter of the harness `codex`: its folder `.codex`, its install in `config.toml`, and a turn program on the OpenAI Codex CLI.
@@ -209,12 +209,12 @@ holds the mission calls them when the same plugin on another node sends it a
209
209
  request. See [ports.md](ports.md#missions).
210
210
 
211
211
  ```ts
212
- interface RelayJoin { missionId: string; giver: { node: string; sessionId: string } }
213
- interface RelayReply { missionId: string; text: string; by: string }
214
- interface RelayControl { missionId: string; by: string }
215
- interface RelayMessage { missionId: string; text: string; by: string }
212
+ interface RelayJoin { missionId: string; giver: { node: string; sessionId: string }; team: string | null; agent: string | null }
213
+ interface RelayReply { missionId: string; text: string; by: string; team: string | null; agent: string | null }
214
+ interface RelayControl { missionId: string; by: string; team: string | null; agent: string | null }
215
+ interface RelayMessage { missionId: string; text: string; by: string; team: string | null; agent: string | null }
216
216
  interface Said { sessionId: string; missionId: string | null; delivery: 'reply' | 'steer' | 'turn' }
217
- interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; giver: { node: string; sessionId: string } }
217
+ interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; giver: { node: string; sessionId: string }; team: string | null }
218
218
  ```
219
219
 
220
220
  - `joinMission` makes the remote session the giver of the mission. When the
@@ -229,6 +229,10 @@ interface MissionQuestion { missionId: string; node: string; lead: string | null
229
229
  paused one.
230
230
  - `askGiver` gives the question to the giver session on this node. It answers
231
231
  false when the giver is not an attached session of this node.
232
+ - `team` and `agent` name the team and the agent of the session that sent the
233
+ request, or are null. In a question, `team` is the team of the mission and
234
+ `lead` is the agent. The node gives them to the `channel.receive` policy
235
+ action as `origin.team` and `origin.agent`, as a claim of the sender.
232
236
  - Each of these acts only on this node. None relays again.
233
237
 
234
238
  ### snapshot
@@ -185,7 +185,7 @@ engine can govern each message that reaches the node over the network.
185
185
  type ChannelTrust = 'verified' | 'unverified';
186
186
  type ChannelOrigin =
187
187
  | { kind: 'local' }
188
- | { kind: 'network'; peer: string; address: Address; trust: ChannelTrust };
188
+ | { kind: 'network'; peer: string; address: Address; team: string | null; agent: string | null; trust: ChannelTrust };
189
189
  type ChannelReceive = {
190
190
  kind: 'channel.receive';
191
191
  channel: string;
@@ -205,6 +205,12 @@ type ChannelReceive = {
205
205
  `ade`. Else it is the name of the sender, or the name that the carrying
206
206
  plugin gives.
207
207
  - `origin.address` is the address of the sender.
208
+ - `origin.team` and `origin.agent` name the team and the agent of the session
209
+ that sent the message from its node. They are null when no session of a team
210
+ sent it, for example a person, a plain node or the CLI. The sending node puts
211
+ them in the message metadata (`orbital/fromTeam`, `orbital/fromAgent`) or in
212
+ the relay request (`team`, `agent`). They are as trusted as the channel: with
213
+ `trust: unverified` they are only the claim of the sender.
208
214
  - `origin.trust` is `verified` only when the channel declares
209
215
  `authenticated: true`. Every other path gives `unverified`.
210
216
  - `origin.kind` is `local` only for a message on a channel that declares
@@ -1029,6 +1035,11 @@ transport, then calls the host API of its node:
1029
1035
  | `resume(request)` | `host.resumeMission(request)` |
1030
1036
  | `ask(question)` | `host.askGiver(question)` (true when delivered) |
1031
1037
 
1038
+ The near node fills `team` and `agent` with the team and the agent of the
1039
+ session that sends the request, or null. In a question, `team` is the team of
1040
+ the mission and `lead` is the agent. Carry the request whole: the far node
1041
+ gives these fields to its policy as `origin.team` and `origin.agent`.
1042
+
1032
1043
  The far node does not relay a request again. To find which node holds a
1033
1044
  mission, read `host.missions()` on each node: it gives the missions of that
1034
1045
  node only. To show the missions of other nodes in the mission list, and for
@@ -1048,12 +1059,12 @@ interface MissionRelayPort {
1048
1059
  say(request: RelayMessage): Promise<Said | null>;
1049
1060
  }
1050
1061
  interface RemoteGiver { node: string; sessionId: string }
1051
- interface RelayJoin { missionId: string; giver: RemoteGiver }
1052
- interface RelayReply { missionId: string; text: string; by: string }
1053
- interface RelayControl { missionId: string; by: string }
1054
- interface RelayMessage { missionId: string; text: string; by: string }
1062
+ interface RelayJoin { missionId: string; giver: RemoteGiver; team: string | null; agent: string | null }
1063
+ interface RelayReply { missionId: string; text: string; by: string; team: string | null; agent: string | null }
1064
+ interface RelayControl { missionId: string; by: string; team: string | null; agent: string | null }
1065
+ interface RelayMessage { missionId: string; text: string; by: string; team: string | null; agent: string | null }
1055
1066
  interface Said { sessionId: string; missionId: string | null; delivery: 'reply' | 'steer' | 'turn' }
1056
- interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; giver: RemoteGiver }
1067
+ interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; giver: RemoteGiver; team: string | null }
1057
1068
  interface MissionRecord {
1058
1069
  missionId: string; team: string | null; node: string; file: string | null;
1059
1070
  state: 'open' | 'active' | 'waiting' | 'paused' | 'done' | 'failed';