@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/README.md +8 -0
- package/dist/index.js +749 -425
- package/dist/mod.json +1 -1
- package/dist/orbital.js +777 -445
- package/dist/runner.js +79 -13
- package/package.json +6 -7
- package/schemas/telemetry-event.schema.json +7 -6
- package/schemas/telemetry-snapshot.schema.json +7 -6
- package/skills/orbital-plugin-builder/SKILL.md +8 -5
- package/skills/orbital-plugin-builder/references/host-api.md +10 -10
- package/skills/orbital-plugin-builder/references/manifest.md +7 -2
- package/skills/orbital-plugin-builder/references/ports.md +72 -19
- package/skills/orbital-plugin-builder/references/recipes.md +4 -4
package/dist/runner.js
CHANGED
|
@@ -61666,7 +61666,7 @@ var MissionStateFileSchema = external_exports.object({
|
|
|
61666
61666
|
source: external_exports.string().nullable(),
|
|
61667
61667
|
result: external_exports.string().nullable(),
|
|
61668
61668
|
question: external_exports.string().nullable(),
|
|
61669
|
-
|
|
61669
|
+
receivers: external_exports.array(external_exports.string().min(1)).default([]),
|
|
61670
61670
|
lead: external_exports.string().nullable(),
|
|
61671
61671
|
claim: external_exports.string().nullable(),
|
|
61672
61672
|
claimedAt: external_exports.string().nullable(),
|
|
@@ -61693,7 +61693,7 @@ var MissionRecordSchema = external_exports.object({
|
|
|
61693
61693
|
result: external_exports.string().nullable(),
|
|
61694
61694
|
question: external_exports.string().nullable(),
|
|
61695
61695
|
questions: external_exports.array(MissionAskedSchema).default([]),
|
|
61696
|
-
|
|
61696
|
+
receivers: external_exports.array(external_exports.string().min(1)).default([]),
|
|
61697
61697
|
lead: external_exports.string().nullable(),
|
|
61698
61698
|
lastActivity: external_exports.string().nullable().default(null),
|
|
61699
61699
|
claim: external_exports.string().nullable().default(null),
|
|
@@ -61735,9 +61735,9 @@ var MissionReplySchema = external_exports.object({
|
|
|
61735
61735
|
text: external_exports.string().min(1),
|
|
61736
61736
|
questionId: external_exports.string().min(1).optional()
|
|
61737
61737
|
});
|
|
61738
|
-
var
|
|
61738
|
+
var RemoteReceiverSchema = external_exports.object({ node: external_exports.string().min(1), sessionId: external_exports.string().min(1) });
|
|
61739
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,
|
|
61740
|
+
var RelayJoinSchema = external_exports.object({ missionId: MissionIdSchema, receiver: RemoteReceiverSchema, ...SENDER_FIELDS });
|
|
61741
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 });
|
|
61742
61742
|
var MissionControlSchema = external_exports.object({ missionId: MissionIdSchema });
|
|
61743
61743
|
var SayRequestSchema = external_exports.object({
|
|
@@ -61758,11 +61758,12 @@ var MissionQuestionSchema = external_exports.object({
|
|
|
61758
61758
|
leadSessionId: external_exports.string().min(1),
|
|
61759
61759
|
question: external_exports.string(),
|
|
61760
61760
|
questionId: external_exports.string().min(1).optional(),
|
|
61761
|
-
|
|
61761
|
+
receiver: RemoteReceiverSchema,
|
|
61762
61762
|
team: external_exports.string().min(1).nullable().default(null)
|
|
61763
61763
|
});
|
|
61764
61764
|
var MissionJoinSchema = external_exports.object({
|
|
61765
|
-
missionId: MissionIdSchema
|
|
61765
|
+
missionId: MissionIdSchema,
|
|
61766
|
+
externalId: external_exports.string().min(1).optional()
|
|
61766
61767
|
});
|
|
61767
61768
|
|
|
61768
61769
|
// src/protocol/charter.ts
|
|
@@ -61836,7 +61837,7 @@ var MissionStateEventSchema = external_exports.object({
|
|
|
61836
61837
|
transition: MissionTransitionSchema,
|
|
61837
61838
|
reason: external_exports.string().nullable(),
|
|
61838
61839
|
by: external_exports.string(),
|
|
61839
|
-
|
|
61840
|
+
receivers: external_exports.array(external_exports.string()).default([]),
|
|
61840
61841
|
sessionId: external_exports.string().nullable(),
|
|
61841
61842
|
lead: external_exports.string().nullable(),
|
|
61842
61843
|
question: external_exports.string().nullable(),
|
|
@@ -61890,7 +61891,7 @@ var TelemetrySnapshotSchema = external_exports.object({
|
|
|
61890
61891
|
state: MissionStateSchema,
|
|
61891
61892
|
sessionId: external_exports.string().nullable(),
|
|
61892
61893
|
lead: external_exports.string().nullable(),
|
|
61893
|
-
|
|
61894
|
+
receivers: external_exports.array(external_exports.string()).default([]),
|
|
61894
61895
|
givenBy: external_exports.string().nullable(),
|
|
61895
61896
|
question: external_exports.string().nullable(),
|
|
61896
61897
|
questions: external_exports.array(MissionAskedSchema).default([]),
|
|
@@ -62281,6 +62282,9 @@ var NodeClient = class {
|
|
|
62281
62282
|
async joinMission(missionId) {
|
|
62282
62283
|
return MissionRecordSchema.parse(await this.request("/orbital/missions/join", { method: "POST", body: { missionId } }));
|
|
62283
62284
|
}
|
|
62285
|
+
async leaveMission(missionId) {
|
|
62286
|
+
return MissionRecordSchema.parse(await this.request("/orbital/missions/leave", { method: "POST", body: { missionId } }));
|
|
62287
|
+
}
|
|
62284
62288
|
async handoff(handoff2) {
|
|
62285
62289
|
return MissionRecordSchema.parse(await this.request("/orbital/missions/handoff", { method: "POST", body: handoff2 }));
|
|
62286
62290
|
}
|
|
@@ -62517,6 +62521,13 @@ var DeliverySchema = external_exports.object({
|
|
|
62517
62521
|
question: external_exports.string().nullable().default(null),
|
|
62518
62522
|
text: external_exports.string()
|
|
62519
62523
|
});
|
|
62524
|
+
var AnsweredSchema = external_exports.object({
|
|
62525
|
+
kind: external_exports.literal("answered"),
|
|
62526
|
+
mission: external_exports.string().min(1),
|
|
62527
|
+
question: external_exports.string().nullable(),
|
|
62528
|
+
by: external_exports.string().min(1),
|
|
62529
|
+
text: external_exports.string()
|
|
62530
|
+
});
|
|
62520
62531
|
var SettleSchema = external_exports.object({
|
|
62521
62532
|
taskId: external_exports.string().min(1).optional(),
|
|
62522
62533
|
text: external_exports.string()
|
|
@@ -62668,6 +62679,60 @@ var LeaseSchema = external_exports.object({ holder: external_exports.string(), e
|
|
|
62668
62679
|
|
|
62669
62680
|
// src/protocol/plugin-protocol.ts
|
|
62670
62681
|
var PORTS_VERSION = "orbital/v1alpha1";
|
|
62682
|
+
var PLUGIN_COMMAND_RESERVED_OPTIONS = ["output", "quiet", "non-interactive", "confirm", "timeout", "help", "version", "workspace", "node"];
|
|
62683
|
+
var PluginCommandOptionSchema = external_exports.object({
|
|
62684
|
+
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(", ")}` }),
|
|
62685
|
+
type: external_exports.enum(["string", "boolean", "integer", "enum", "path"]),
|
|
62686
|
+
summary: external_exports.string().min(1),
|
|
62687
|
+
values: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
62688
|
+
required: external_exports.boolean().default(false),
|
|
62689
|
+
default: external_exports.union([external_exports.string(), external_exports.number().int(), external_exports.boolean()]).optional()
|
|
62690
|
+
}).strict().superRefine((option, issues) => {
|
|
62691
|
+
if (option.type === "enum" !== (option.values !== void 0)) issues.addIssue({ code: "custom", message: `the option ${option.name} names values only when its type is enum` });
|
|
62692
|
+
if (option.type === "boolean" && option.required) issues.addIssue({ code: "custom", message: `the boolean option ${option.name} cannot be required` });
|
|
62693
|
+
if (option.default === void 0) return;
|
|
62694
|
+
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);
|
|
62695
|
+
if (!fits) issues.addIssue({ code: "custom", message: `the default of the option ${option.name} is not a ${option.type === "enum" ? `value of ${option.values.join(", ")}` : option.type}` });
|
|
62696
|
+
});
|
|
62697
|
+
var PluginCommandArgSchema = external_exports.object({
|
|
62698
|
+
name: external_exports.string().regex(NAME_SHAPE),
|
|
62699
|
+
summary: external_exports.string().min(1),
|
|
62700
|
+
required: external_exports.boolean().default(false),
|
|
62701
|
+
variadic: external_exports.boolean().default(false)
|
|
62702
|
+
}).strict();
|
|
62703
|
+
var PluginCommandSchema = external_exports.object({
|
|
62704
|
+
name: external_exports.string().regex(NAME_SHAPE),
|
|
62705
|
+
summary: external_exports.string().min(1),
|
|
62706
|
+
args: external_exports.array(PluginCommandArgSchema).default([]),
|
|
62707
|
+
options: external_exports.array(PluginCommandOptionSchema).default([]),
|
|
62708
|
+
interactive: external_exports.boolean().default(false),
|
|
62709
|
+
node: external_exports.boolean().default(false)
|
|
62710
|
+
}).strict().superRefine((command, issues) => {
|
|
62711
|
+
const twice = (names) => names.find((name, index) => names.indexOf(name) !== index);
|
|
62712
|
+
const arg = twice(command.args.map((one) => one.name));
|
|
62713
|
+
if (arg) issues.addIssue({ code: "custom", message: `the command ${command.name} names the argument ${arg} twice` });
|
|
62714
|
+
const option = twice(command.options.map((one) => one.name));
|
|
62715
|
+
if (option) issues.addIssue({ code: "custom", message: `the command ${command.name} names the option ${option} twice` });
|
|
62716
|
+
if (command.args.slice(0, -1).some((one) => one.variadic)) issues.addIssue({ code: "custom", message: `only the last argument of the command ${command.name} can be variadic` });
|
|
62717
|
+
if (command.args.some((one, index) => one.required && command.args.slice(0, index).some((before) => !before.required))) issues.addIssue({ code: "custom", message: `a required argument of the command ${command.name} cannot come after an optional one` });
|
|
62718
|
+
if (command.interactive && command.node) issues.addIssue({ code: "custom", message: `the command ${command.name} cannot be interactive and need the node: the node has no terminal to give it` });
|
|
62719
|
+
});
|
|
62720
|
+
var PluginCommandsSchema = external_exports.array(PluginCommandSchema).default([]).superRefine((commands, issues) => {
|
|
62721
|
+
const names = commands.map((one) => one.name);
|
|
62722
|
+
const twice = names.find((name, index) => names.indexOf(name) !== index);
|
|
62723
|
+
if (twice) issues.addIssue({ code: "custom", message: `the command ${twice} is declared twice` });
|
|
62724
|
+
});
|
|
62725
|
+
var PluginCommandRequestSchema = external_exports.object({
|
|
62726
|
+
name: external_exports.string().regex(NAME_SHAPE),
|
|
62727
|
+
args: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.array(external_exports.string())])).default({}),
|
|
62728
|
+
options: external_exports.record(external_exports.string(), external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).default({}),
|
|
62729
|
+
terminal: external_exports.object({ tty: external_exports.boolean() }).nullable().default(null)
|
|
62730
|
+
}).strict();
|
|
62731
|
+
var PluginCommandResultSchema = external_exports.object({
|
|
62732
|
+
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),
|
|
62733
|
+
text: external_exports.string().nullable().default(null),
|
|
62734
|
+
data: external_exports.record(external_exports.string(), external_exports.unknown()).default({})
|
|
62735
|
+
}).strict();
|
|
62671
62736
|
var PluginManifestSchema = external_exports.object({
|
|
62672
62737
|
name: external_exports.string().regex(NAME_SHAPE),
|
|
62673
62738
|
version: external_exports.string().min(1),
|
|
@@ -62697,7 +62762,7 @@ var PluginManifestSchema = external_exports.object({
|
|
|
62697
62762
|
secrets: external_exports.array(external_exports.string().min(1)).default([]),
|
|
62698
62763
|
models: external_exports.array(external_exports.string().regex(/^[a-z0-9-]+:.+$/, "a model pattern is <provider>:<model>, such as anthropic:*")).default([])
|
|
62699
62764
|
}).prefault({}),
|
|
62700
|
-
commands:
|
|
62765
|
+
commands: PluginCommandsSchema,
|
|
62701
62766
|
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([])
|
|
62702
62767
|
}).strict();
|
|
62703
62768
|
var PluginInitSchema = external_exports.object({
|
|
@@ -62720,7 +62785,8 @@ var PluginPackageSchema = external_exports.object({
|
|
|
62720
62785
|
dir: external_exports.string().min(1).optional(),
|
|
62721
62786
|
env: external_exports.array(external_exports.string().regex(PLUGIN_ENV_NAME, "is not the name of an environment variable")).default([]),
|
|
62722
62787
|
config: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
|
|
62723
|
-
harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([])
|
|
62788
|
+
harnesses: external_exports.array(HarnessAdapterDeclarationSchema).default([]),
|
|
62789
|
+
commands: PluginCommandsSchema
|
|
62724
62790
|
}).strict();
|
|
62725
62791
|
var MarketplaceSchema = external_exports.object({
|
|
62726
62792
|
name: external_exports.string().regex(NAME_SHAPE),
|
|
@@ -81712,14 +81778,14 @@ function buildMissionLinkTool(messaging, options) {
|
|
|
81712
81778
|
};
|
|
81713
81779
|
}
|
|
81714
81780
|
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
|
|
81781
|
+
question: Type.String({ minLength: 1, description: "The question, in full, with the options you see and what each one means: the receivers have none of your context." })
|
|
81716
81782
|
});
|
|
81717
81783
|
function buildMissionAskTool(messaging, options) {
|
|
81718
81784
|
return {
|
|
81719
81785
|
name: "missionAsk",
|
|
81720
81786
|
label: "Ask Mission Giver",
|
|
81721
|
-
description: "Ask the
|
|
81722
|
-
promptSnippet: "missionAsk: ask the
|
|
81787
|
+
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.",
|
|
81788
|
+
promptSnippet: "missionAsk: ask the decision receivers of your mission a question at any moment, also while your workers run",
|
|
81723
81789
|
parameters: MissionAskParams,
|
|
81724
81790
|
executionMode: "sequential",
|
|
81725
81791
|
async execute(_toolCallId, params) {
|
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.
|
|
4
|
+
"version": "0.0.6",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -11,17 +11,16 @@
|
|
|
11
11
|
"orbital": "dist/orbital.js"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
|
-
".": "./
|
|
15
|
-
"./
|
|
16
|
-
"./sdk": "./src/sdk/index.ts",
|
|
17
|
-
"./tui": "./src/tui/index.tsx",
|
|
18
|
-
"./*": "./src/*.ts"
|
|
14
|
+
".": "./dist/index.js",
|
|
15
|
+
"./package.json": "./package.json"
|
|
19
16
|
},
|
|
20
17
|
"scripts": {
|
|
21
18
|
"build": "node scripts/build.mjs",
|
|
22
19
|
"check-types": "tsc -p tsconfig.json",
|
|
23
20
|
"schemas": "node scripts/schemas.ts",
|
|
24
|
-
"test": "vitest run"
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"prepack": "node ../../scripts/pack-manifest.mjs prepack",
|
|
23
|
+
"postpack": "node ../../scripts/pack-manifest.mjs postpack"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.295",
|
|
@@ -566,11 +566,12 @@
|
|
|
566
566
|
"by": {
|
|
567
567
|
"type": "string"
|
|
568
568
|
},
|
|
569
|
-
"
|
|
570
|
-
"
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
569
|
+
"receivers": {
|
|
570
|
+
"default": [],
|
|
571
|
+
"type": "array",
|
|
572
|
+
"items": {
|
|
573
|
+
"type": "string"
|
|
574
|
+
}
|
|
574
575
|
},
|
|
575
576
|
"sessionId": {
|
|
576
577
|
"type": [
|
|
@@ -611,7 +612,7 @@
|
|
|
611
612
|
"transition",
|
|
612
613
|
"reason",
|
|
613
614
|
"by",
|
|
614
|
-
"
|
|
615
|
+
"receivers",
|
|
615
616
|
"sessionId",
|
|
616
617
|
"lead",
|
|
617
618
|
"question",
|
|
@@ -279,11 +279,12 @@
|
|
|
279
279
|
"null"
|
|
280
280
|
]
|
|
281
281
|
},
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
282
|
+
"receivers": {
|
|
283
|
+
"default": [],
|
|
284
|
+
"type": "array",
|
|
285
|
+
"items": {
|
|
286
|
+
"type": "string"
|
|
287
|
+
}
|
|
287
288
|
},
|
|
288
289
|
"givenBy": {
|
|
289
290
|
"type": [
|
|
@@ -342,7 +343,7 @@
|
|
|
342
343
|
"state",
|
|
343
344
|
"sessionId",
|
|
344
345
|
"lead",
|
|
345
|
-
"
|
|
346
|
+
"receivers",
|
|
346
347
|
"givenBy",
|
|
347
348
|
"question",
|
|
348
349
|
"questions",
|
|
@@ -37,7 +37,7 @@ plain object for each port.
|
|
|
37
37
|
| workflows | `ports.workflows` | run workflow files on an engine | [workflows](references/ports.md#workflows) |
|
|
38
38
|
| reasoner | `ports.reasoner` | reason over an OWL ontology | [reasoner](references/ports.md#reasoner) |
|
|
39
39
|
| missions | `ports.missions` | join, say, reply, pause, resume and ask on a mission of another node | [missions](references/ports.md#missions) |
|
|
40
|
-
| commands | `commands` (beside `ports`)
|
|
40
|
+
| commands | `commands` (beside `ports`), and `commands` in `orbital-plugin.json` | `orbital <plugin> <command>`, such as a login | [commands](references/ports.md#commands) |
|
|
41
41
|
| service | `service` (beside `ports`) | background work while the node serves | [service](references/ports.md#service) |
|
|
42
42
|
|
|
43
43
|
## Workflow
|
|
@@ -126,7 +126,10 @@ orbital plugin check <name> --fixtures <name>/fixtures.json
|
|
|
126
126
|
|
|
127
127
|
`plugin add` prints the permissions, and names a folder in `orbital.yaml` by its
|
|
128
128
|
path. It does not copy it. Rebuild, and the next start of the node uses the new
|
|
129
|
-
build.
|
|
129
|
+
build. A plugin that checks its config when it starts gets it at the add:
|
|
130
|
+
`orbital plugin add ./<name> --config channel=general`, or `--config-file <file>`.
|
|
131
|
+
The add starts the plugin with that config, and writes it to `orbital.yaml` only
|
|
132
|
+
when the plugin starts. `plugin check` starts the built plugin with the config of its
|
|
130
133
|
`orbital.yaml` entry and the secrets of `orbital plugin secret set`.
|
|
131
134
|
|
|
132
135
|
### 5. Add to the config
|
|
@@ -148,9 +151,9 @@ how. Then `orbital node start`.
|
|
|
148
151
|
|
|
149
152
|
### 6. Publish
|
|
150
153
|
|
|
151
|
-
- **npm.** Publish `orbital-plugin.json` and `dist/`.
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
+
- **npm.** Publish `orbital-plugin.json` and `dist/`. The package can have any
|
|
155
|
+
name: `orbital plugin add npm:@acme/orbital-discord` names the plugin by the
|
|
156
|
+
`name` of its `orbital-plugin.json`, such as `discord`.
|
|
154
157
|
- **A marketplace.** A Git repository with a `marketplace.json`. A person adds
|
|
155
158
|
it with `orbital plugin marketplace add <git-url>`, then runs
|
|
156
159
|
`orbital plugin add discord@<marketplace>`.
|
|
@@ -43,7 +43,7 @@ message. Catch it where a failure must not stop the plugin.
|
|
|
43
43
|
| `sayMission(request)` | `Said` | Far side of `missions.say`. |
|
|
44
44
|
| `pauseMission(request)` | `MissionRecord` | Far side of `missions.pause`. |
|
|
45
45
|
| `resumeMission(request)` | `MissionRecord` | Far side of `missions.resume`. |
|
|
46
|
-
| `
|
|
46
|
+
| `askReceiver(question)` | `boolean` | Far side of `missions.ask`. |
|
|
47
47
|
| `snapshot()` | `TelemetrySnapshot` | Read the telemetry graph of the node now. |
|
|
48
48
|
|
|
49
49
|
### submit
|
|
@@ -199,26 +199,26 @@ interface MissionStarted { missionId: string; sessionId: string; taskId: string;
|
|
|
199
199
|
### missions
|
|
200
200
|
|
|
201
201
|
`missions()` answers the missions that this node holds, with their state,
|
|
202
|
-
lead, question,
|
|
202
|
+
lead, question, decision receivers and history. It never includes the missions of other
|
|
203
203
|
nodes, so a relay can ask each node which missions it holds.
|
|
204
204
|
|
|
205
|
-
### joinMission, replyMission, sayMission, pauseMission, resumeMission,
|
|
205
|
+
### joinMission, replyMission, sayMission, pauseMission, resumeMission, askReceiver
|
|
206
206
|
|
|
207
207
|
These are the far side of the `missions` port. A relay plugin on the node that
|
|
208
208
|
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;
|
|
212
|
+
interface RelayJoin { missionId: string; receiver: { node: string; sessionId: string }; team: string | null; agent: string | null }
|
|
213
213
|
interface RelayReply { missionId: string; text: string; by: string; team: string | null; agent: string | null }
|
|
214
214
|
interface RelayControl { missionId: string; by: string; team: string | null; agent: string | null }
|
|
215
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;
|
|
217
|
+
interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; receiver: { node: string; sessionId: string }; team: string | null }
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
- `joinMission`
|
|
221
|
-
mission
|
|
220
|
+
- `joinMission` adds the remote session to the decision receivers of the
|
|
221
|
+
mission. The node sends each open question to the new receiver at once.
|
|
222
222
|
- `replyMission` answers a waiting mission. It fails when the mission is
|
|
223
223
|
paused, or is not waiting.
|
|
224
224
|
- `sayMission` gives the words to the lead of the mission. `delivery` says
|
|
@@ -227,8 +227,8 @@ interface MissionQuestion { missionId: string; node: string; lead: string | null
|
|
|
227
227
|
lead.
|
|
228
228
|
- `pauseMission` pauses an active or waiting mission. `resumeMission` resumes a
|
|
229
229
|
paused one.
|
|
230
|
-
- `
|
|
231
|
-
false when the
|
|
230
|
+
- `askReceiver` gives the question to the receiver session on this node. It
|
|
231
|
+
answers false when the receiver is not a live session of this node.
|
|
232
232
|
- `team` and `agent` name the team and the agent of the session that sent the
|
|
233
233
|
request, or are null. In a question, `team` is the team of the mission and
|
|
234
234
|
`lead` is the agent. The node gives them to the `channel.receive` policy
|
|
@@ -243,7 +243,7 @@ interface MissionQuestion { missionId: string; node: string; lead: string | null
|
|
|
243
243
|
interface TelemetrySnapshot {
|
|
244
244
|
v: 'orbital.telemetry/v1'; node: string; sequence: number; at: string; startedAt: string;
|
|
245
245
|
teams: Array<{ name: string; node: string; purpose: string | null; lead: string | null; members: Array<{ agent: string; role: 'lead' | 'member'; responsibility: string | null; harness: string | null; placement: string | null; state: 'working' | 'idle'; lastActivityAt: string | null; work: MemberWork[] }> }>;
|
|
246
|
-
missions: Array<{ missionId: string; team: string | null; state: MissionState; sessionId: string | null; lead: string | null;
|
|
246
|
+
missions: Array<{ missionId: string; team: string | null; state: MissionState; sessionId: string | null; lead: string | null; receivers: string[]; givenBy: string | null; question: string | null; result: string | null; updatedAt: string | null }>;
|
|
247
247
|
sessions: Array<{ sessionId: string; parentSessionId: string | null; agent: string; harness: string; team: string | null; mission: string | null; role: string; placement: string; running: boolean; since: string | null }>;
|
|
248
248
|
jobs: Array<{ jobId: string; sessionId: string; parentSessionId: string | null; agent: string; harness: string; team: string | null; mission: string | null; since: string }>;
|
|
249
249
|
tokens: Array<{ team: string | null; mission: string | null; provider: string; model: string; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; costUsd: number; llmCalls: number; turns: number }>;
|
|
@@ -16,7 +16,8 @@ A plugin has two descriptions:
|
|
|
16
16
|
"command": ["node", "dist/plugin.js"],
|
|
17
17
|
"dir": ".",
|
|
18
18
|
"env": ["LAN_SHARED_KEY"],
|
|
19
|
-
"config": { "port": 7420 }
|
|
19
|
+
"config": { "port": 7420 },
|
|
20
|
+
"commands": [{ "name": "status", "summary": "Show the peers." }]
|
|
20
21
|
}
|
|
21
22
|
```
|
|
22
23
|
|
|
@@ -27,6 +28,7 @@ A plugin has two descriptions:
|
|
|
27
28
|
| `dir` | no | The working folder, relative to the plugin's folder. Default: the plugin's folder. |
|
|
28
29
|
| `env` | no | Environment variables the node passes to the plugin. It must list the same names as the secrets in the manifest that have the form of a variable (`[A-Z][A-Z0-9_]*`). |
|
|
29
30
|
| `config` | no | Default config. The `config` of the `orbital.yaml` entry overrides it key by key. |
|
|
31
|
+
| `commands` | no | The CLI commands of the plugin, the same as its `commands` in code. See [commands](ports.md#commands). |
|
|
30
32
|
|
|
31
33
|
No other field is allowed. A `orbital.yaml` source `exec:<command> <args>`
|
|
32
34
|
starts a program on PATH with no `orbital-plugin.json`. Only such an entry can
|
|
@@ -49,6 +51,9 @@ plugins:
|
|
|
49
51
|
|
|
50
52
|
`orbital plugin add <source>` writes the entry. A folder source stays where it
|
|
51
53
|
is. Any other source is installed in `<orbital home>/plugins/<name>`.
|
|
54
|
+
`--config key=value` (repeatable) and `--config-file <file>` give the config:
|
|
55
|
+
the add starts the plugin with it, and writes the entry only when the start
|
|
56
|
+
succeeds.
|
|
52
57
|
|
|
53
58
|
## The manifest
|
|
54
59
|
|
|
@@ -75,7 +80,7 @@ interface PluginManifest {
|
|
|
75
80
|
missions: boolean;
|
|
76
81
|
};
|
|
77
82
|
permissions: { network: string[]; secrets: string[]; models: string[] };
|
|
78
|
-
commands: Array<{ name: string; summary: string }>;
|
|
83
|
+
commands: Array<{ name: string; summary: string; args: PluginCommandArg[]; options: PluginCommandOption[]; interactive: boolean; node: boolean }>;
|
|
79
84
|
panels: Array<{ id: string; title: string; kind: 'list' | 'tree' | 'transcript' | 'form' }>;
|
|
80
85
|
}
|
|
81
86
|
```
|
|
@@ -227,7 +227,7 @@ reason to the sender:
|
|
|
227
227
|
| Path | The sender gets |
|
|
228
228
|
|---|---|
|
|
229
229
|
| `host.deliver`: work, a cancel, the answer of a person to a question | `DeliveryAnswer` with `accepted: false` and the `reason`. For refused work, the node also sends a reply over the same channel (see below) |
|
|
230
|
-
| `host.joinMission`, `replyMission`, `sayMission`, `pauseMission`, `resumeMission`, `
|
|
230
|
+
| `host.joinMission`, `replyMission`, `sayMission`, `pauseMission`, `resumeMission`, `askReceiver` | an error with the reason |
|
|
231
231
|
| `host.a2a`, and a request to `/a2a` from a non-loopback address | a JSON-RPC error with the reason |
|
|
232
232
|
|
|
233
233
|
The answer of a remote node to a request of this node is not checked: this
|
|
@@ -1007,8 +1007,8 @@ LAN or cross-machine plugin implements.
|
|
|
1007
1007
|
itself. It asks each plugin with the port, in plugin order. The first answer
|
|
1008
1008
|
that is not null (or true) wins.
|
|
1009
1009
|
|
|
1010
|
-
- `join(request)`:
|
|
1011
|
-
|
|
1010
|
+
- `join(request)`: a session on this node joins a mission as a decision
|
|
1011
|
+
receiver (`missionJoin`). `request.receiver` is `{ node, sessionId }` of this node.
|
|
1012
1012
|
- `say(request)`: a person or a session talks to the lead of a mission
|
|
1013
1013
|
(`mission say`, `missionSay`). The far node gives the words to the lead: in
|
|
1014
1014
|
its running turn, as its next turn, or as the answer to its question. When
|
|
@@ -1017,7 +1017,7 @@ that is not null (or true) wins.
|
|
|
1017
1017
|
mission (`mission reply`, `missionReply`).
|
|
1018
1018
|
- `pause(request)`, `resume(request)`: `mission pause`, `mission resume`.
|
|
1019
1019
|
- `ask(question)`: the lead of a mission on this node waits with a question,
|
|
1020
|
-
and
|
|
1020
|
+
and a decision receiver is a session of another node. Answer true when you carried the
|
|
1021
1021
|
question to that node. When no plugin answers true, the node warns.
|
|
1022
1022
|
|
|
1023
1023
|
Answer null (or false) when you do not know the mission or the node. Else
|
|
@@ -1033,7 +1033,7 @@ transport, then calls the host API of its node:
|
|
|
1033
1033
|
| `reply(request)` | `host.replyMission(request)` |
|
|
1034
1034
|
| `pause(request)` | `host.pauseMission(request)` |
|
|
1035
1035
|
| `resume(request)` | `host.resumeMission(request)` |
|
|
1036
|
-
| `ask(question)` | `host.
|
|
1036
|
+
| `ask(question)` | `host.askReceiver(question)` (true when delivered) |
|
|
1037
1037
|
|
|
1038
1038
|
The near node fills `team` and `agent` with the team and the agent of the
|
|
1039
1039
|
session that sends the request, or null. In a question, `team` is the team of
|
|
@@ -1058,18 +1058,18 @@ interface MissionRelayPort {
|
|
|
1058
1058
|
ask(question: MissionQuestion): Promise<boolean>;
|
|
1059
1059
|
say(request: RelayMessage): Promise<Said | null>;
|
|
1060
1060
|
}
|
|
1061
|
-
interface
|
|
1062
|
-
interface RelayJoin { missionId: string;
|
|
1061
|
+
interface RemoteReceiver { node: string; sessionId: string }
|
|
1062
|
+
interface RelayJoin { missionId: string; receiver: RemoteReceiver; team: string | null; agent: string | null }
|
|
1063
1063
|
interface RelayReply { missionId: string; text: string; by: string; team: string | null; agent: string | null }
|
|
1064
1064
|
interface RelayControl { missionId: string; by: string; team: string | null; agent: string | null }
|
|
1065
1065
|
interface RelayMessage { missionId: string; text: string; by: string; team: string | null; agent: string | null }
|
|
1066
1066
|
interface Said { sessionId: string; missionId: string | null; delivery: 'reply' | 'steer' | 'turn' }
|
|
1067
|
-
interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string;
|
|
1067
|
+
interface MissionQuestion { missionId: string; node: string; lead: string | null; leadSessionId: string; question: string; receiver: RemoteReceiver; team: string | null }
|
|
1068
1068
|
interface MissionRecord {
|
|
1069
1069
|
missionId: string; team: string | null; node: string; file: string | null;
|
|
1070
1070
|
state: 'open' | 'active' | 'waiting' | 'paused' | 'done' | 'failed';
|
|
1071
1071
|
declared: boolean; givenBy: string | null; brief: string | null; sessionId: string | null; taskId: string | null;
|
|
1072
|
-
source: string | null; result: string | null; question: string | null;
|
|
1072
|
+
source: string | null; result: string | null; question: string | null; receivers: string[]; lead: string | null;
|
|
1073
1073
|
lastActivity: string | null; claim: string | null; claimedAt: string | null; stale: boolean; updatedAt: string | null;
|
|
1074
1074
|
history: Array<{ state: MissionState; at: string; by: string; note: string | null }>;
|
|
1075
1075
|
}
|
|
@@ -1090,7 +1090,7 @@ ports: {
|
|
|
1090
1090
|
reply: async (request) => peer.ask('reply', request),
|
|
1091
1091
|
pause: async (request) => peer.ask('pause', request),
|
|
1092
1092
|
resume: async (request) => peer.ask('resume', request),
|
|
1093
|
-
ask: async (question) => (await peer.send(question.
|
|
1093
|
+
ask: async (question) => (await peer.send(question.receiver.node, 'ask', question)) === true,
|
|
1094
1094
|
say: async (request) => peer.ask('say', request),
|
|
1095
1095
|
},
|
|
1096
1096
|
},
|
|
@@ -1103,19 +1103,72 @@ test it with two nodes.
|
|
|
1103
1103
|
|
|
1104
1104
|
## `commands`
|
|
1105
1105
|
|
|
1106
|
-
`commands` sits beside `ports`, not in it.
|
|
1107
|
-
`orbital
|
|
1106
|
+
`commands` sits beside `ports`, not in it. Each command adds a command to the
|
|
1107
|
+
CLI: a person runs `orbital <plugin> <command> [args] [--options]`.
|
|
1108
|
+
`orbital help` and `orbital <plugin> --help` list the commands.
|
|
1109
|
+
|
|
1110
|
+
Declare each command two times, with the same fields:
|
|
1111
|
+
|
|
1112
|
+
- In `orbital-plugin.json`, so that orbital lists the command without a start
|
|
1113
|
+
of the plugin.
|
|
1114
|
+
- In the code, with `defineCommand`. The SDK types `args` and `options` from
|
|
1115
|
+
the declaration.
|
|
1116
|
+
|
|
1117
|
+
The node refuses a plugin when the two lists are not the same.
|
|
1118
|
+
|
|
1119
|
+
```json
|
|
1120
|
+
{ "name": "acme", "command": ["node", "dist/plugin.js"],
|
|
1121
|
+
"commands": [
|
|
1122
|
+
{ "name": "login", "summary": "Sign in to acme in the browser.", "interactive": true },
|
|
1123
|
+
{ "name": "status", "summary": "Show the account.", "options": [{ "name": "verbose", "type": "boolean", "summary": "Show more." }] }
|
|
1124
|
+
] }
|
|
1125
|
+
```
|
|
1108
1126
|
|
|
1109
1127
|
```ts
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1128
|
+
import { defineCommand } from '@kaiba-cloud/orbital-sdk';
|
|
1129
|
+
|
|
1130
|
+
commands: [
|
|
1131
|
+
defineCommand({
|
|
1132
|
+
name: 'login',
|
|
1133
|
+
summary: 'Sign in to acme in the browser.',
|
|
1134
|
+
interactive: true,
|
|
1135
|
+
run: async ({ terminal }) => {
|
|
1136
|
+
terminal!.output.write(`Open ${url} and type the code.\n`);
|
|
1137
|
+
return { text: 'Signed in.', data: { account } };
|
|
1138
|
+
},
|
|
1139
|
+
}),
|
|
1140
|
+
defineCommand({
|
|
1141
|
+
name: 'status',
|
|
1142
|
+
summary: 'Show the account.',
|
|
1143
|
+
options: { verbose: { type: 'boolean', summary: 'Show more.' } },
|
|
1144
|
+
run: async ({ options }) => ({ text: options.verbose ? detail : account, data: { account } }),
|
|
1145
|
+
}),
|
|
1146
|
+
],
|
|
1116
1147
|
```
|
|
1117
1148
|
|
|
1118
|
-
|
|
1149
|
+
| Field | Meaning |
|
|
1150
|
+
|---|---|
|
|
1151
|
+
| `name`, `summary` | The command word and its line in the help. A plugin whose name is a built-in command, such as `plugin` or `check`, gets no command. |
|
|
1152
|
+
| `args` | Positional arguments: `name`, `summary`, `required`, `variadic` (the last only). |
|
|
1153
|
+
| `options` | Flags: `name`, `type` (`string`, `boolean`, `integer`, `enum`, `path`), `summary`, `values` (enum only), `required`, `default`. `output`, `quiet`, `non-interactive`, `confirm`, `timeout`, `help`, `version`, `workspace` and `node` belong to orbital. |
|
|
1154
|
+
| `interactive` | The command needs a terminal, such as a browser or device login. `terminal` gives it the person's stdin, stdout and stderr. With `--output json`, its stdout goes to stderr. Without a terminal, orbital refuses the command. |
|
|
1155
|
+
| `node` | The command runs in the plugin of the running node, not in a new process. Without a node, orbital refuses it. It cannot be interactive. |
|
|
1156
|
+
|
|
1157
|
+
orbital starts the plugin for the command only, with its config from
|
|
1158
|
+
`orbital.yaml` and the workspace in `context.init`. No node needs to run.
|
|
1159
|
+
|
|
1160
|
+
`run` answers `{ text?, data?, exitCode? }`, or a string as `text`. Text mode
|
|
1161
|
+
prints `text`. `--output json` prints `data` in the envelope. `exitCode` is 0,
|
|
1162
|
+
1, 2 (no credential), 3 (bad input) or 5, and orbital exits with it.
|
|
1163
|
+
|
|
1164
|
+
In an interactive command, write to `terminal.output`, never to
|
|
1165
|
+
`process.stdout`: stdout carries the protocol.
|
|
1166
|
+
|
|
1167
|
+
The commands are for a person at the CLI. Agents do not see them: give agents
|
|
1168
|
+
a tool with `ports.tools`.
|
|
1169
|
+
|
|
1170
|
+
**Remote method.** `command.run {name, args, options, terminal}`, answered
|
|
1171
|
+
with `{ exitCode, text, data }`.
|
|
1119
1172
|
|
|
1120
1173
|
## `service`
|
|
1121
1174
|
|
|
@@ -18,7 +18,7 @@ key. Then:
|
|
|
18
18
|
- the agents of each node list the teams and agents of the other nodes
|
|
19
19
|
(`directory`);
|
|
20
20
|
- a person on one node joins, talks to, answers, pauses and resumes a mission
|
|
21
|
-
whose lead runs on another node, and the lead's question reaches
|
|
21
|
+
whose lead runs on another node, and the lead's question reaches each decision receiver on its own
|
|
22
22
|
node (`missions`);
|
|
23
23
|
- `orbital mission list` and the claims see the missions of every node, and
|
|
24
24
|
`orbital up` shows the peers (`panels`).
|
|
@@ -130,7 +130,7 @@ export function plugin(context: PluginContext): PluginImplementation {
|
|
|
130
130
|
}
|
|
131
131
|
const body = await read(request);
|
|
132
132
|
if (path === '/a2a') return a2a(host, body, response);
|
|
133
|
-
if (path === '/missions/ask') return send(response, 200, { delivered: await host.
|
|
133
|
+
if (path === '/missions/ask') return send(response, 200, { delivered: await host.askReceiver(JSON.parse(body)) });
|
|
134
134
|
const relay = /^\/missions\/(join|say|reply|pause|resume)$/.exec(path);
|
|
135
135
|
if (!relay) return send(response, 404, { error: `No route ${path}.` });
|
|
136
136
|
const asked = JSON.parse(body);
|
|
@@ -184,7 +184,7 @@ export function plugin(context: PluginContext): PluginImplementation {
|
|
|
184
184
|
pause: async (request) => firstAnswer<MissionRecord>('/missions/pause', request),
|
|
185
185
|
resume: async (request) => firstAnswer<MissionRecord>('/missions/resume', request),
|
|
186
186
|
ask: async (question) => {
|
|
187
|
-
const peer = peers.find((one) => one.name === question.
|
|
187
|
+
const peer = peers.find((one) => one.name === question.receiver.node);
|
|
188
188
|
if (!peer) return false;
|
|
189
189
|
return (await call<{ delivered: boolean }>(peer, '/missions/ask', question).catch(() => null))?.delivered === true;
|
|
190
190
|
},
|
|
@@ -236,7 +236,7 @@ How it works:
|
|
|
236
236
|
its node. See the table in [ports.md](ports.md#missions).
|
|
237
237
|
- **Missions.** `join`, `say`, `reply`, `pause` and `resume` ask each peer. A peer
|
|
238
238
|
answers 404 when `host.missions()` does not hold the mission, and the next
|
|
239
|
-
peer is asked. `ask` goes to the peer that `question.
|
|
239
|
+
peer is asked. `ask` goes to the peer that `question.receiver.node` names.
|
|
240
240
|
- **Placements.** The node of the far agent never shows its own secret. The
|
|
241
241
|
plugin serves A2A at `/a2a` and passes each call to `host.a2a`. The host API
|
|
242
242
|
does not stream, so the plugin answers `SubscribeToTask` as a stream of
|