@holon-run/agentinbox 1.0.4 → 1.1.0

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 CHANGED
@@ -13,8 +13,9 @@ In practice, that means `AgentInbox` can:
13
13
 
14
14
  - share one GitHub or Feishu source across multiple local agents
15
15
  - materialize those events into durable inboxes
16
- - wake or drive agent sessions running in `tmux` or `iTerm2`, even when the
17
- agent runtime does not expose a notification API
16
+ - activate terminal-backed sessions in `tmux` or `iTerm2`, or Holon agents
17
+ through runtime-provided webhook triggers, without embedding connector logic
18
+ in the agent runtime
18
19
 
19
20
  ## Event Flow
20
21
 
@@ -111,7 +112,7 @@ node dist/src/cli.js --help
111
112
 
112
113
  ## Recommended Onboarding
113
114
 
114
- If you are using Codex or Claude Code, start with the bundled AgentInbox skill:
115
+ If you are using Codex, Claude Code, or Holon, start with the bundled AgentInbox skill:
115
116
 
116
117
  - repo copy: [`skills/agentinbox/SKILL.md`](./skills/agentinbox/SKILL.md)
117
118
  - docs site copy: `https://agentinbox.holon.run/skills/agentinbox/SKILL`
@@ -120,7 +121,7 @@ If you use the community `skills` installer, you can install the bundled skill
120
121
  directly:
121
122
 
122
123
  ```bash
123
- npx skills add holon-run/agentinbox --skill agentinbox -a codex -a claude-code
124
+ npx skills add holon-run/agentinbox --skill agentinbox -a codex -a claude-code -a holon
124
125
  ```
125
126
 
126
127
  That skill is the recommended onboarding path. It can guide the agent through:
@@ -128,7 +129,8 @@ That skill is the recommended onboarding path. It can guide the agent through:
128
129
  - checking or installing `agentinbox`
129
130
  - checking or installing `uxc`
130
131
  - importing GitHub auth from the local `gh` CLI via `uxc auth credential import github --from gh`
131
- - registering the current terminal session as an agent
132
+ - registering the current runtime/session as an agent, including Holon webhook
133
+ activation when running in Holon
132
134
  - adding GitHub sources and standing subscriptions using the docs-site examples
133
135
 
134
136
  ## Quick Start
@@ -139,15 +141,20 @@ Start the local daemon:
139
141
  agentinbox daemon start
140
142
  ```
141
143
 
142
- Register the current terminal session:
144
+ Register the current runtime/session:
143
145
 
144
146
  ```bash
145
147
  agentinbox agent register
146
148
  agentinbox agent register --agent-id agent-alpha
149
+ agentinbox agent register --agent-id agent-alpha --webhook-url <external-trigger-url>
147
150
  agentinbox agent register --notify-lease-ms 600000 --min-unacked-items 5
148
151
  agentinbox agent current
149
152
  ```
150
153
 
154
+ Terminal-backed runtimes attach a terminal activation target. In Holon, the
155
+ no-arg form uses `HOLON_AGENT_ID` and `HOLON_EXTERNAL_TRIGGER_URL` when present;
156
+ otherwise pass `--webhook-url` explicitly.
157
+
151
158
  `notifyLeaseMs` and `minUnackedItems` are target-facing notification policy:
152
159
  they control how often a target may be reminded and how many unacked items must
153
160
  accumulate before AgentInbox notifies it. Service-wide activation batching
@@ -197,7 +197,7 @@ class AdapterRegistry {
197
197
  async listDeliveryOperations(source, handle) {
198
198
  const module = await this.resolveDeliveryModule(source, handle);
199
199
  if (!module?.listDeliveryOperations) {
200
- return [];
200
+ return null;
201
201
  }
202
202
  return module.listDeliveryOperations({ handle, source });
203
203
  }
@@ -208,6 +208,18 @@ class AdapterRegistry {
208
208
  }
209
209
  return module.invokeDeliveryOperation({ handle, operation, input, attempt, source });
210
210
  }
211
+ async listSourceOperations(source) {
212
+ if (source.sourceType === "local_event") {
213
+ return [];
214
+ }
215
+ return this.remoteSource.listSourceOperations(source);
216
+ }
217
+ async invokeSourceOperation(source, operation, input) {
218
+ if (source.sourceType === "local_event") {
219
+ throw new Error(`source operations are not supported for source type ${source.sourceType}`);
220
+ }
221
+ return this.remoteSource.invokeSourceOperation(source, operation, input);
222
+ }
211
223
  status() {
212
224
  return {
213
225
  remote: this.remoteSource.status?.() ?? {},
package/dist/src/cli.js CHANGED
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const commander_1 = require("commander");
9
10
  const adapters_1 = require("./adapters");
10
11
  const client_1 = require("./client");
11
12
  const control_server_1 = require("./control_server");
@@ -24,11 +25,16 @@ async function main() {
24
25
  const normalized = shouldTreatJsonFlagAsNoop(detectedArgs)
25
26
  ? stripNoopJsonFlag(detectedArgs)
26
27
  : detectedArgs;
28
+ const shell = parseCommanderShell(normalized);
27
29
  const command = normalized[0];
28
30
  if (!command || command === "help" || command === "--help" || command === "-h") {
29
31
  printHelp(normalized.slice(1));
30
32
  return;
31
33
  }
34
+ if (shell.isBareGroup) {
35
+ printHelp([command]);
36
+ return;
37
+ }
32
38
  if (command === "version" || command === "--version" || command === "-v") {
33
39
  if (hasHelpFlag(normalized.slice(1))) {
34
40
  printHelp(["version"]);
@@ -343,6 +349,27 @@ async function main() {
343
349
  await printRemote(client, `/sources/${encodeURIComponent(sourceRef)}/schema`, undefined, "GET");
344
350
  return;
345
351
  }
352
+ if (command === "source" && normalized[1] === "actions") {
353
+ const sourceId = normalized[2];
354
+ if (!sourceId) {
355
+ throw new Error("usage: agentinbox source actions <sourceId>");
356
+ }
357
+ await printRemote(client, `/sources/${encodeURIComponent(sourceId)}/actions`, undefined, "GET");
358
+ return;
359
+ }
360
+ if (command === "source" && normalized[1] === "invoke") {
361
+ const sourceId = normalized[2];
362
+ const operation = takeFlagValue(normalized, "--operation");
363
+ const inputJson = takeFlagValue(normalized, "--input-json");
364
+ if (!sourceId || !operation || !inputJson) {
365
+ throw new Error("usage: agentinbox source invoke <sourceId> --operation NAME --input-json JSON");
366
+ }
367
+ await printRemote(client, `/sources/${encodeURIComponent(sourceId)}/invoke`, {
368
+ operation,
369
+ input: (0, util_1.parseJsonArg)(inputJson, "--input-json"),
370
+ });
371
+ return;
372
+ }
346
373
  if (command === "source" && normalized[1] === "poll") {
347
374
  const sourceId = normalized[2];
348
375
  if (!sourceId) {
@@ -878,6 +905,53 @@ async function main() {
878
905
  }
879
906
  throw new Error(`unknown command: ${normalized.join(" ")}`);
880
907
  }
908
+ const COMMAND_SHELL_SPECS = [
909
+ { name: "serve", group: false, summary: "Run the AgentInbox control server." },
910
+ { name: "daemon", group: true, summary: "Manage the local AgentInbox daemon." },
911
+ { name: "host", group: true, summary: "Manage source hosts." },
912
+ { name: "stream", group: true, summary: "Manage source streams." },
913
+ { name: "follow", group: true, summary: "Create high-level source subscriptions." },
914
+ { name: "source", group: true, summary: "Manage source streams through the source alias." },
915
+ { name: "agent", group: true, summary: "Manage local agent registrations and activation targets." },
916
+ { name: "timer", group: true, summary: "Manage agent reminder timers." },
917
+ { name: "subscription", group: true, summary: "Manage source subscriptions." },
918
+ { name: "inbox", group: true, summary: "Read and manage agent inboxes." },
919
+ { name: "gc", group: false, summary: "Run garbage collection." },
920
+ { name: "deliver", group: true, summary: "Send outbound delivery actions." },
921
+ { name: "status", group: false, summary: "Show daemon status." },
922
+ { name: "version", group: false, summary: "Show CLI version." },
923
+ ];
924
+ const COMMANDER_SHELL = createCommanderShell();
925
+ function createCommanderShell() {
926
+ const program = new commander_1.Command();
927
+ program
928
+ .name("agentinbox")
929
+ .allowUnknownOption(true)
930
+ .allowExcessArguments(true)
931
+ .exitOverride()
932
+ .configureOutput({
933
+ writeOut: () => { },
934
+ writeErr: () => { },
935
+ });
936
+ for (const spec of COMMAND_SHELL_SPECS) {
937
+ program
938
+ .command(`${spec.name} [args...]`)
939
+ .description(spec.summary)
940
+ .allowUnknownOption(true)
941
+ .allowExcessArguments(true);
942
+ }
943
+ return program;
944
+ }
945
+ function parseCommanderShell(args) {
946
+ const command = args[0] ?? null;
947
+ if (!command || command === "help" || command === "--help" || command === "-h") {
948
+ return { isBareGroup: false };
949
+ }
950
+ const spec = COMMANDER_SHELL.commands.find((item) => item.name() === command);
951
+ return {
952
+ isBareGroup: Boolean(COMMAND_SHELL_SPECS.find((item) => item.name === spec?.name())?.group && args.length === 1),
953
+ };
954
+ }
881
955
  async function runServe(args) {
882
956
  const port = parseOptionalNumber(takeFlagValue(args, "--port"));
883
957
  const homeOverride = takeFlagValue(args, "--home");
@@ -1345,10 +1419,9 @@ function unexpectedFlags(args, allowedFlags) {
1345
1419
  const allowed = new Set(allowedFlags);
1346
1420
  return args.filter((token) => token.startsWith("--") && !allowed.has(token));
1347
1421
  }
1348
- function printHelp(path = []) {
1349
- const key = path[0] ?? "root";
1350
- const helpByKey = {
1351
- root: `agentinbox
1422
+ function renderRootHelp() {
1423
+ const commands = COMMAND_SHELL_SPECS.map((spec) => ` ${spec.name}`).join("\n");
1424
+ return `agentinbox
1352
1425
 
1353
1426
  Usage:
1354
1427
  agentinbox <command> [options]
@@ -1357,19 +1430,13 @@ Usage:
1357
1430
  agentinbox --version, -v
1358
1431
 
1359
1432
  Commands:
1360
- serve
1361
- daemon
1362
- follow
1363
- source
1364
- agent
1365
- timer
1366
- subscription
1367
- inbox
1368
- gc
1369
- deliver
1370
- status
1371
- version
1372
- `,
1433
+ ${commands}
1434
+ `;
1435
+ }
1436
+ function printHelp(path = []) {
1437
+ const key = path[0] ?? "root";
1438
+ const helpByKey = {
1439
+ root: renderRootHelp(),
1373
1440
  serve: `agentinbox serve
1374
1441
 
1375
1442
  Usage:
@@ -1415,6 +1482,7 @@ Examples:
1415
1482
  agentinbox follow github repo --agent-id <agentId> --arg owner=holon-run --arg repo=agentinbox
1416
1483
  agentinbox follow github pr --agent-id <agentId> --arg owner=holon-run --arg repo=agentinbox --arg number=87 --arg withCi=true
1417
1484
  agentinbox follow github issue --agent-id <agentId> --arg owner=holon-run --arg repo=agentinbox --arg number=180
1485
+ agentinbox follow feishu mentions --agent-id <agentId> --config-json '{"uxcAuth":"feishu-default"}'
1418
1486
  `,
1419
1487
  source: `agentinbox source
1420
1488
 
@@ -1427,6 +1495,8 @@ Usage:
1427
1495
  agentinbox source pause <remoteSourceId>
1428
1496
  agentinbox source resume <remoteSourceId>
1429
1497
  agentinbox source schema <sourceId>
1498
+ agentinbox source actions <sourceId>
1499
+ agentinbox source invoke <sourceId> --operation NAME --input-json JSON
1430
1500
  agentinbox source poll <sourceId>
1431
1501
  agentinbox source event <sourceId> --native-id ID --event EVENT [--occurred-at ISO8601] [--metadata-json JSON] [--payload-json JSON]
1432
1502
  `,
package/dist/src/http.js CHANGED
@@ -381,6 +381,60 @@ function buildFastifyServer(service) {
381
381
  const params = request.params;
382
382
  return service.getResolvedSourceSchema(decodeURIComponent(params.sourceId));
383
383
  });
384
+ app.get("/sources/:sourceId/actions", {
385
+ schema: {
386
+ tags: ["sources"],
387
+ params: {
388
+ type: "object",
389
+ required: ["sourceId"],
390
+ properties: {
391
+ sourceId: { type: "string", minLength: 1 },
392
+ },
393
+ },
394
+ response: {
395
+ 200: jsonObjectSchema,
396
+ 400: errorResponseSchema,
397
+ 404: errorResponseSchema,
398
+ },
399
+ },
400
+ }, async (request) => {
401
+ const params = request.params;
402
+ return service.listSourceOperations(decodeURIComponent(params.sourceId));
403
+ });
404
+ app.post("/sources/:sourceId/invoke", {
405
+ schema: {
406
+ tags: ["sources"],
407
+ params: {
408
+ type: "object",
409
+ required: ["sourceId"],
410
+ properties: {
411
+ sourceId: { type: "string", minLength: 1 },
412
+ },
413
+ },
414
+ body: {
415
+ type: "object",
416
+ additionalProperties: false,
417
+ required: ["operation", "input"],
418
+ properties: {
419
+ operation: { type: "string", minLength: 1 },
420
+ input: jsonObjectSchema,
421
+ },
422
+ },
423
+ response: {
424
+ 200: jsonObjectSchema,
425
+ 400: errorResponseSchema,
426
+ 404: errorResponseSchema,
427
+ },
428
+ },
429
+ }, async (request) => {
430
+ const params = request.params;
431
+ const body = request.body;
432
+ return service.invokeSourceOperation({
433
+ sourceId: decodeURIComponent(params.sourceId),
434
+ operation: body.operation,
435
+ input: body.input,
436
+ });
437
+ });
384
438
  app.patch("/streams/:streamId", {
385
439
  schema: {
386
440
  tags: ["sources"],
@@ -1816,10 +1870,15 @@ function isBadRequestError(message) {
1816
1870
  message.startsWith("deliver send is not supported") ||
1817
1871
  message.startsWith("deliveryHandle requires") ||
1818
1872
  message.startsWith("delivery operations are not supported") ||
1873
+ message.startsWith("unsupported delivery surface") ||
1874
+ message.startsWith("unsupported delivery operation") ||
1875
+ message.startsWith("source operations are not supported") ||
1819
1876
  message.startsWith("unknown GitHub delivery operation") ||
1820
1877
  message.startsWith("unknown Feishu delivery operation") ||
1878
+ message.startsWith("unknown Feishu source operation") ||
1821
1879
  message.startsWith("invalid GitHub targetRef") ||
1822
- message.includes("requires input.text") ||
1880
+ message.startsWith("get_message_context requires") ||
1881
+ message.includes("requires input.") ||
1823
1882
  message.includes("requires input.body") ||
1824
1883
  message.startsWith("subscriptions/reset requires") ||
1825
1884
  message.startsWith("agents requires") ||
@@ -282,6 +282,22 @@ class AgentInboxService {
282
282
  const source = this.getSource(sourceId);
283
283
  return this.adapters.resolveSourceSchema(source);
284
284
  }
285
+ async listSourceOperations(sourceId) {
286
+ const source = this.getSource(sourceId);
287
+ return {
288
+ sourceId: source.sourceId,
289
+ operations: await this.adapters.listSourceOperations(source),
290
+ };
291
+ }
292
+ async invokeSourceOperation(request) {
293
+ const source = this.getSource(request.sourceId);
294
+ const data = await this.adapters.invokeSourceOperation(source, request.operation, request.input);
295
+ return {
296
+ sourceId: source.sourceId,
297
+ operation: request.operation,
298
+ data,
299
+ };
300
+ }
285
301
  async previewSourceSchema(input) {
286
302
  try {
287
303
  const source = buildPreviewSource(input);
@@ -1604,6 +1620,12 @@ class AgentInboxService {
1604
1620
  const handle = resolveDeliveryHandle(request);
1605
1621
  const source = resolveDeliverySource(this.store, request.sourceId, handle);
1606
1622
  const operations = await this.adapters.listDeliveryOperations(source, handle);
1623
+ if (operations === null) {
1624
+ throw new Error(`delivery operations are not supported for provider ${handle.provider}`);
1625
+ }
1626
+ if (operations.length === 0) {
1627
+ throw new Error(noDeliveryOperationsMessage(handle));
1628
+ }
1607
1629
  return {
1608
1630
  sourceId: source?.sourceId ?? null,
1609
1631
  handle,
@@ -1613,6 +1635,15 @@ class AgentInboxService {
1613
1635
  async invokeDelivery(request) {
1614
1636
  const handle = resolveDeliveryHandle(request);
1615
1637
  const source = resolveDeliverySource(this.store, request.sourceId, handle);
1638
+ const operations = await this.adapters.listDeliveryOperations(source, handle);
1639
+ if (operations !== null) {
1640
+ if (operations.length === 0) {
1641
+ throw new Error(noDeliveryOperationsMessage(handle));
1642
+ }
1643
+ if (!operations.some((operation) => operation.name === request.operation)) {
1644
+ throw new Error(unsupportedDeliveryOperationMessage(handle, request.operation, operations));
1645
+ }
1646
+ }
1616
1647
  const attempt = {
1617
1648
  deliveryId: (0, util_1.generateCanonicalId)("dlv"),
1618
1649
  provider: handle.provider,
@@ -1632,9 +1663,9 @@ class AgentInboxService {
1632
1663
  }
1633
1664
  async sendCanonicalDeliveryViaModule(source, handle, payload, attempt) {
1634
1665
  const operations = await this.adapters.listDeliveryOperations(source, handle);
1635
- const canonicalOperation = operations.find((operation) => operation.canonicalTextAlias);
1666
+ const canonicalOperation = operations?.find((operation) => operation.canonicalTextAlias);
1636
1667
  if (!canonicalOperation) {
1637
- throw new Error(`deliver send is not supported for ${handle.provider}/${handle.surface}; use deliver invoke`);
1668
+ throw new Error(deliverSendUnsupportedMessage(handle, operations));
1638
1669
  }
1639
1670
  const input = payload.text != null ? { ...payload, body: String(payload.text) } : payload;
1640
1671
  return this.adapters.invokeDeliveryOperation(source, handle, canonicalOperation.name, input, attempt);
@@ -3869,6 +3900,43 @@ function listHostStreamKinds(hostType) {
3869
3900
  return ["default"];
3870
3901
  }
3871
3902
  }
3903
+ const KNOWN_DELIVERY_SURFACES = {
3904
+ feishu: ["message_reply", "chat_message"],
3905
+ github: ["issue_comment", "pull_request_comment", "review_comment"],
3906
+ };
3907
+ function unsupportedDeliverySurfaceMessage(handle) {
3908
+ const supported = KNOWN_DELIVERY_SURFACES[handle.provider];
3909
+ const supportedHint = supported?.length
3910
+ ? ` Supported surfaces for ${handle.provider}: ${supported.join(", ")}.`
3911
+ : "";
3912
+ return `unsupported delivery surface for ${handle.provider}: ${handle.surface}.${supportedHint} ${deliveryActionsHelp(handle)}`;
3913
+ }
3914
+ function noDeliveryOperationsMessage(handle) {
3915
+ if (!isKnownDeliverySurface(handle)) {
3916
+ return unsupportedDeliverySurfaceMessage(handle);
3917
+ }
3918
+ return `delivery operations are not supported for ${handle.provider}/${handle.surface}. ${deliveryActionsHelp(handle)}`;
3919
+ }
3920
+ function unsupportedDeliveryOperationMessage(handle, operation, operations) {
3921
+ const names = operations.map((item) => item.name).join(", ");
3922
+ return `unsupported delivery operation for ${handle.provider}/${handle.surface}: ${operation}. Available operations: ${names}. ${deliveryActionsHelp(handle)}`;
3923
+ }
3924
+ function deliverSendUnsupportedMessage(handle, operations) {
3925
+ if (!operations || operations.length === 0) {
3926
+ if (!isKnownDeliverySurface(handle)) {
3927
+ return unsupportedDeliverySurfaceMessage(handle);
3928
+ }
3929
+ return `deliver send is not supported for ${handle.provider}/${handle.surface}. ${deliveryActionsHelp(handle)}`;
3930
+ }
3931
+ const names = operations.map((item) => item.name).join(", ");
3932
+ return `deliver send is not supported for ${handle.provider}/${handle.surface}; use deliver invoke with one of: ${names}. ${deliveryActionsHelp(handle)}`;
3933
+ }
3934
+ function deliveryActionsHelp(handle) {
3935
+ return `Run \`agentinbox deliver actions --provider ${handle.provider} --surface ${handle.surface} --target ${handle.targetRef}\` to inspect supported operations and input schemas.`;
3936
+ }
3937
+ function isKnownDeliverySurface(handle) {
3938
+ return KNOWN_DELIVERY_SURFACES[handle.provider]?.includes(handle.surface) === true;
3939
+ }
3872
3940
  function getHostConfigFields(hostType) {
3873
3941
  switch (hostType) {
3874
3942
  case "github":
@@ -3877,9 +3945,7 @@ function getHostConfigFields(hostType) {
3877
3945
  ];
3878
3946
  case "feishu":
3879
3947
  return [
3880
- { name: "appId", type: "string", description: "Feishu app ID.", required: true },
3881
- { name: "appSecret", type: "string", description: "Feishu app secret.", required: true },
3882
- { name: "uxcAuth", type: "string", description: "Optional shared Feishu auth/runtime profile.", required: false },
3948
+ { name: "uxcAuth", type: "string", description: "Optional shared Feishu/Lark UXC auth profile.", required: false },
3883
3949
  ];
3884
3950
  case "local_event":
3885
3951
  return [];
@@ -32,13 +32,11 @@ function resolveSourceRegistration(input) {
32
32
  if (input.sourceType === "feishu_bot") {
33
33
  return {
34
34
  hostType: "feishu",
35
- hostKey: `app:${stringOrDefault(config.appId, input.configRef ?? input.sourceKey)}`,
35
+ hostKey: `uxcAuth:${stringOrDefault(config.uxcAuth, input.configRef ?? input.sourceKey)}`,
36
36
  hostConfig: {
37
- ...(valueOrUndefined(config.appId) ? { appId: config.appId } : {}),
38
- ...(valueOrUndefined(config.appSecret) ? { appSecret: config.appSecret } : {}),
37
+ ...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
39
38
  ...(valueOrUndefined(config.schemaUrl) ? { schemaUrl: config.schemaUrl } : {}),
40
39
  ...(valueOrUndefined(config.replyInThread) ? { replyInThread: config.replyInThread } : {}),
41
- ...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
42
40
  },
43
41
  streamKind: "message_events",
44
42
  streamKey: input.sourceKey,
@@ -146,13 +146,11 @@ const SOURCE_SCHEMAS = {
146
146
  ],
147
147
  eventVariantExamples: ["im.message.receive_v1.text"],
148
148
  configFields: [
149
- { name: "appId", type: "string", required: true, description: "Feishu app ID." },
150
- { name: "appSecret", type: "string", required: true, description: "Feishu app secret." },
149
+ { name: "uxcAuth", type: "string", required: false, description: "Optional UXC auth profile for the Feishu/Lark app." },
151
150
  { name: "eventTypes", type: "string[]", required: false, description: "Optional Feishu event type allowlist." },
152
151
  { name: "chatIds", type: "string[]", required: false, description: "Optional Feishu chat allowlist." },
153
152
  { name: "schemaUrl", type: "string", required: false, description: "Optional Feishu OpenAPI schema URL." },
154
153
  { name: "replyInThread", type: "boolean", required: false, description: "Reply in thread when sending outbound messages." },
155
- { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
156
154
  ],
157
155
  },
158
156
  };