@holon-run/agentinbox 0.2.0 → 0.4.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
@@ -136,12 +136,40 @@ Create a local source and publish an event:
136
136
  agentinbox source add local_event local-demo
137
137
  agentinbox subscription add <source_id>
138
138
  agentinbox subscription add <source_id> --agent-id <agent_id>
139
+ agentinbox source schema <source_id>
140
+ agentinbox subscription add <source_id> --shortcut pr --shortcut-args-json '{"number":373}'
139
141
  agentinbox subscription add <source_id> --tracked-resource-ref pr:373 --cleanup-policy-json '{"mode":"manual"}'
140
142
  agentinbox subscription add <source_id> --filter-file ./filter.json
141
143
  cat filter.json | agentinbox subscription add <source_id> --filter-stdin
142
144
  agentinbox source event <source_id> --native-id demo-1 --event local.demo
143
145
  agentinbox inbox read
144
146
  agentinbox inbox read --agent-id <agent_id>
147
+ agentinbox inbox ack --agent-id <agent_id> --through <last_item_id>
148
+ ```
149
+
150
+ When acking a reviewed batch, prefer `--through <last_item_id>` over `--all`.
151
+ That preserves the boundary of the items you actually inspected. Use
152
+ `ack --all` only when you have intentionally verified that every current
153
+ unacked item should be cleared.
154
+
155
+ Send a direct text message into an agent inbox:
156
+
157
+ ```bash
158
+ agentinbox inbox send --agent-id <agent_id> --message "Please review PR #87"
159
+ agentinbox inbox send --agent-id <agent_id> --message "CI failed on main" --sender operator
160
+ ```
161
+
162
+ Schedule reminder messages with timers:
163
+
164
+ ```bash
165
+ agentinbox timer add --agent-id <agent_id> --at <RFC3339_TIMESTAMP> --message "Check the morning build"
166
+ agentinbox timer add --agent-id <agent_id> --every 24h --message "Review today's open PRs"
167
+ agentinbox timer add --agent-id <agent_id> --cron "0 8 * * *" --timezone Asia/Shanghai --message "Daily triage"
168
+ agentinbox timer list
169
+ agentinbox timer list --agent-id <agent_id>
170
+ agentinbox timer pause <schedule_id>
171
+ agentinbox timer resume <schedule_id>
172
+ agentinbox timer remove <schedule_id>
145
173
  ```
146
174
 
147
175
  Update an existing source in place:
@@ -164,6 +192,16 @@ Remove a task-specific subscription without deleting the whole agent:
164
192
  agentinbox subscription remove <subscription_id>
165
193
  ```
166
194
 
195
+ For implementation-backed sources, inspect resolved subscription capabilities
196
+ before adding task-scoped subscriptions:
197
+
198
+ ```bash
199
+ agentinbox source schema <source_id>
200
+ ```
201
+
202
+ If the source advertises subscription shortcuts, prefer them over manually
203
+ repeating the equivalent filter, tracked resource ref, and cleanup policy.
204
+
167
205
  ## Docs
168
206
 
169
207
  Public docs live in the mdorigin site under [`docs/site`](./docs/site).
package/dist/src/cli.js CHANGED
@@ -180,6 +180,7 @@ async function main() {
180
180
  backend: detected.backend,
181
181
  runtimeKind: detected.runtimeKind,
182
182
  runtimeSessionId: detected.runtimeSessionId ?? undefined,
183
+ runtimePid: detected.runtimePid ?? undefined,
183
184
  tmuxPaneId: detected.tmuxPaneId ?? undefined,
184
185
  tty: detected.tty ?? undefined,
185
186
  termProgram: detected.termProgram ?? undefined,
@@ -212,6 +213,14 @@ async function main() {
212
213
  await printRemote(client, `/agents/${encodeURIComponent(agentId)}`, undefined, "DELETE");
213
214
  return;
214
215
  }
216
+ if (command === "agent" && normalized[1] === "resume") {
217
+ const agentId = normalized[2];
218
+ if (!agentId) {
219
+ throw new Error("usage: agentinbox agent resume <agentId>");
220
+ }
221
+ await printRemote(client, `/agents/${encodeURIComponent(agentId)}/resume`, {});
222
+ return;
223
+ }
215
224
  if (command === "agent" && normalized[1] === "target" && normalized[2] === "add" && normalized[3] === "webhook") {
216
225
  const agentId = normalized[4];
217
226
  const url = takeFlagValue(normalized, "--url");
@@ -242,6 +251,14 @@ async function main() {
242
251
  await printRemote(client, `/agents/${encodeURIComponent(agentId)}/targets/${encodeURIComponent(targetId)}`, undefined, "DELETE");
243
252
  return;
244
253
  }
254
+ if (command === "agent" && normalized[1] === "target" && normalized[2] === "resume") {
255
+ const [agentId, targetId] = normalized.slice(3, 5);
256
+ if (!agentId || !targetId) {
257
+ throw new Error("usage: agentinbox agent target resume <agentId> <targetId>");
258
+ }
259
+ await printRemote(client, `/agents/${encodeURIComponent(agentId)}/targets/${encodeURIComponent(targetId)}/resume`, {});
260
+ return;
261
+ }
245
262
  if (command === "subscription" && normalized[1] === "add") {
246
263
  const args = normalized.slice(2);
247
264
  const positionals = positionalArgs(args, [
@@ -344,6 +361,62 @@ async function main() {
344
361
  });
345
362
  return;
346
363
  }
364
+ if (command === "timer" && normalized[1] === "add") {
365
+ const args = normalized.slice(2);
366
+ const allowedFlags = ["--agent-id", "--at", "--every", "--cron", "--timezone", "--message", "--sender"];
367
+ if (positionalArgs(args, ["--agent-id", "--at", "--every", "--cron", "--timezone", "--message", "--sender"]).length > 0 || unexpectedFlags(args, allowedFlags).length > 0) {
368
+ throw new Error("usage: agentinbox timer add --agent-id ID (--at ISO8601 | --every DURATION | --cron EXPR) --message TEXT [--timezone TZ] [--sender SENDER]");
369
+ }
370
+ const agentId = takeFlagValue(normalized, "--agent-id");
371
+ const message = takeFlagValue(normalized, "--message");
372
+ const at = takeFlagValue(normalized, "--at");
373
+ const every = takeFlagValue(normalized, "--every");
374
+ const cron = takeFlagValue(normalized, "--cron");
375
+ if (!agentId || !message || [at, every, cron].filter(Boolean).length !== 1) {
376
+ throw new Error("usage: agentinbox timer add --agent-id ID (--at ISO8601 | --every DURATION | --cron EXPR) --message TEXT [--timezone TZ] [--sender SENDER]");
377
+ }
378
+ await printRemote(client, "/timers", {
379
+ agentId,
380
+ at: at ?? undefined,
381
+ every: every ? parseDurationMs(every) : undefined,
382
+ cron: cron ?? undefined,
383
+ timezone: takeFlagValue(normalized, "--timezone") ?? undefined,
384
+ message,
385
+ sender: takeFlagValue(normalized, "--sender") ?? undefined,
386
+ });
387
+ return;
388
+ }
389
+ if (command === "timer" && normalized[1] === "list") {
390
+ const query = buildQuery({
391
+ agent_id: takeFlagValue(normalized, "--agent-id"),
392
+ });
393
+ await printRemote(client, `/timers${query}`, undefined, "GET");
394
+ return;
395
+ }
396
+ if (command === "timer" && normalized[1] === "pause") {
397
+ const scheduleId = normalized[2];
398
+ if (!scheduleId) {
399
+ throw new Error("usage: agentinbox timer pause <scheduleId>");
400
+ }
401
+ await printRemote(client, `/timers/${encodeURIComponent(scheduleId)}/pause`, {});
402
+ return;
403
+ }
404
+ if (command === "timer" && normalized[1] === "resume") {
405
+ const scheduleId = normalized[2];
406
+ if (!scheduleId) {
407
+ throw new Error("usage: agentinbox timer resume <scheduleId>");
408
+ }
409
+ await printRemote(client, `/timers/${encodeURIComponent(scheduleId)}/resume`, {});
410
+ return;
411
+ }
412
+ if (command === "timer" && normalized[1] === "remove") {
413
+ const scheduleId = normalized[2];
414
+ if (!scheduleId) {
415
+ throw new Error("usage: agentinbox timer remove <scheduleId>");
416
+ }
417
+ await printRemote(client, `/timers/${encodeURIComponent(scheduleId)}`, undefined, "DELETE");
418
+ return;
419
+ }
347
420
  if (command === "inbox" && normalized[1] === "list") {
348
421
  await printRemote(client, "/agents", undefined, "GET");
349
422
  return;
@@ -374,6 +447,23 @@ async function main() {
374
447
  console.log((0, util_1.jsonResponse)(withCommandMetadata(response.data, selection)));
375
448
  return;
376
449
  }
450
+ if (command === "inbox" && normalized[1] === "send") {
451
+ const args = normalized.slice(2);
452
+ const allowedFlags = ["--agent-id", "--message", "--sender"];
453
+ if (positionalArgs(args, ["--agent-id", "--message", "--sender"]).length > 0 || unexpectedFlags(args, allowedFlags).length > 0) {
454
+ throw new Error("usage: agentinbox inbox send --agent-id ID --message TEXT [--sender SENDER]");
455
+ }
456
+ const agentId = takeFlagValue(normalized, "--agent-id");
457
+ const message = takeFlagValue(normalized, "--message");
458
+ if (!agentId || !message) {
459
+ throw new Error("usage: agentinbox inbox send --agent-id ID --message TEXT [--sender SENDER]");
460
+ }
461
+ await printRemote(client, `/agents/${encodeURIComponent(agentId)}/inbox/items`, {
462
+ message,
463
+ sender: takeFlagValue(normalized, "--sender") ?? undefined,
464
+ });
465
+ return;
466
+ }
377
467
  if (command === "inbox" && normalized[1] === "watch") {
378
468
  const args = normalized.slice(2);
379
469
  if (positionalArgs(args, ["--agent-id", "--after-item", "--heartbeat-ms"]).length > 0) {
@@ -592,6 +682,7 @@ async function selectAgentForCommand(client, options) {
592
682
  backend: contextForCurrent.backend,
593
683
  runtimeKind: contextForCurrent.runtimeKind,
594
684
  runtimeSessionId: contextForCurrent.runtimeSessionId ?? undefined,
685
+ runtimePid: contextForCurrent.runtimePid ?? undefined,
595
686
  tmuxPaneId: contextForCurrent.tmuxPaneId ?? undefined,
596
687
  tty: contextForCurrent.tty ?? undefined,
597
688
  termProgram: contextForCurrent.termProgram ?? undefined,
@@ -648,6 +739,30 @@ function parseOptionalNumber(value) {
648
739
  }
649
740
  return parsed;
650
741
  }
742
+ function parseDurationMs(value) {
743
+ if (/^\d+$/.test(value)) {
744
+ return Number(value);
745
+ }
746
+ const match = /^(\d+)(ms|s|m|h|d)$/.exec(value);
747
+ if (!match) {
748
+ throw new Error(`invalid duration: ${value}`);
749
+ }
750
+ const amount = Number(match[1]);
751
+ switch (match[2]) {
752
+ case "ms":
753
+ return amount;
754
+ case "s":
755
+ return amount * 1_000;
756
+ case "m":
757
+ return amount * 60_000;
758
+ case "h":
759
+ return amount * 60 * 60_000;
760
+ case "d":
761
+ return amount * 24 * 60 * 60_000;
762
+ default:
763
+ throw new Error(`invalid duration: ${value}`);
764
+ }
765
+ }
651
766
  function normalizeHelpArgs(args) {
652
767
  if (args[0] !== "help") {
653
768
  return args;
@@ -762,6 +877,7 @@ Commands:
762
877
  daemon
763
878
  source
764
879
  agent
880
+ timer
765
881
  subscription
766
882
  inbox
767
883
  gc
@@ -805,9 +921,20 @@ Usage:
805
921
  agentinbox agent current
806
922
  agentinbox agent show <agentId>
807
923
  agentinbox agent remove <agentId>
924
+ agentinbox agent resume <agentId>
808
925
  agentinbox agent target add webhook <agentId> --url URL [--activation-mode MODE] [--notify-lease-ms N]
809
926
  agentinbox agent target list <agentId>
810
927
  agentinbox agent target remove <agentId> <targetId>
928
+ agentinbox agent target resume <agentId> <targetId>
929
+ `,
930
+ timer: `agentinbox timer
931
+
932
+ Usage:
933
+ agentinbox timer add --agent-id ID (--at ISO8601 | --every DURATION | --cron EXPR) --message TEXT [--timezone TZ] [--sender SENDER]
934
+ agentinbox timer list [--agent-id ID]
935
+ agentinbox timer pause <scheduleId>
936
+ agentinbox timer resume <scheduleId>
937
+ agentinbox timer remove <scheduleId>
811
938
  `,
812
939
  subscription: `agentinbox subscription
813
940
 
@@ -826,6 +953,7 @@ Usage:
826
953
  agentinbox inbox list
827
954
  agentinbox inbox show <agentId>
828
955
  agentinbox inbox read [--agent-id ID] [--after-item ID] [--include-acked]
956
+ agentinbox inbox send --agent-id ID --message TEXT [--sender SENDER]
829
957
  agentinbox inbox watch [--agent-id ID] [--after-item ID] [--include-acked] [--heartbeat-ms N]
830
958
  agentinbox inbox ack [--agent-id ID] (--through <itemId> | --item <itemId> | --all)
831
959
  agentinbox inbox compact <agentId>
@@ -16,6 +16,7 @@ function summarizeActivationTarget(target) {
16
16
  itermSessionId: target.itermSessionId ?? null,
17
17
  runtimeKind: target.runtimeKind ?? null,
18
18
  runtimeSessionId: target.runtimeSessionId ?? null,
19
+ runtimePid: target.runtimePid ?? null,
19
20
  };
20
21
  }
21
22
  return {
@@ -15,6 +15,7 @@ const node_child_process_1 = require("node:child_process");
15
15
  const client_1 = require("./client");
16
16
  const paths_1 = require("./paths");
17
17
  const START_TIMEOUT_MS = 15_000;
18
+ const PACKAGE_VERSION = readOwnPackageVersion();
18
19
  async function ensureDaemonForClient(options = {}) {
19
20
  const transport = resolveDaemonClientTransport(options);
20
21
  if (options.noAutoStart || transport.kind !== "socket") {
@@ -95,14 +96,23 @@ async function daemonStatus(options = {}) {
95
96
  return {
96
97
  running: false,
97
98
  pid: null,
99
+ version: null,
100
+ startedAt: null,
101
+ command: null,
102
+ nodeVersion: null,
98
103
  pidPath,
99
104
  logPath,
100
105
  transport,
101
106
  };
102
107
  }
108
+ const processInfo = pid == null ? null : readProcessMetadata(pid);
103
109
  return {
104
110
  running: await canReachHealthz(transport),
105
111
  pid,
112
+ version: pid == null ? null : PACKAGE_VERSION,
113
+ startedAt: processInfo?.startedAt ?? null,
114
+ command: processInfo?.command ?? null,
115
+ nodeVersion: processInfo?.nodeVersion ?? null,
106
116
  pidPath,
107
117
  logPath,
108
118
  transport,
@@ -179,6 +189,24 @@ async function canReachHealthz(transport) {
179
189
  return false;
180
190
  }
181
191
  }
192
+ function readOwnPackageVersion() {
193
+ const candidatePaths = [
194
+ node_path_1.default.join(__dirname, "..", "package.json"),
195
+ node_path_1.default.join(__dirname, "..", "..", "package.json"),
196
+ ];
197
+ for (const candidate of candidatePaths) {
198
+ try {
199
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(candidate, "utf8"));
200
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
201
+ return parsed.version;
202
+ }
203
+ }
204
+ catch {
205
+ continue;
206
+ }
207
+ }
208
+ return null;
209
+ }
182
210
  async function waitForHealthz(transport, timeoutMs) {
183
211
  const startedAt = Date.now();
184
212
  while (Date.now() - startedAt < timeoutMs) {
@@ -222,6 +250,47 @@ function readPidFile(pidPath) {
222
250
  return null;
223
251
  }
224
252
  }
253
+ function readProcessMetadata(pid) {
254
+ try {
255
+ const output = (0, node_child_process_1.execFileSync)("ps", ["-o", "lstart=", "-o", "command=", "-p", String(pid)], {
256
+ encoding: "utf8",
257
+ stdio: ["ignore", "pipe", "ignore"],
258
+ }).trimEnd();
259
+ if (!output) {
260
+ return null;
261
+ }
262
+ const firstNonSpace = output.search(/\S/);
263
+ if (firstNonSpace < 0) {
264
+ return null;
265
+ }
266
+ const trimmed = output.slice(firstNonSpace);
267
+ const match = trimmed.match(/^([A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d{1,2}\s+\d\d:\d\d:\d\d\s+\d{4})\s+(.*)$/);
268
+ if (!match) {
269
+ return {
270
+ startedAt: null,
271
+ command: trimmed,
272
+ nodeVersion: inferNodeVersionFromCommand(trimmed),
273
+ };
274
+ }
275
+ const [, startedAtRaw, command] = match;
276
+ const startedAtMs = Date.parse(startedAtRaw);
277
+ return {
278
+ startedAt: Number.isNaN(startedAtMs) ? null : new Date(startedAtMs).toISOString(),
279
+ command: command || null,
280
+ nodeVersion: inferNodeVersionFromCommand(command),
281
+ };
282
+ }
283
+ catch {
284
+ return null;
285
+ }
286
+ }
287
+ function inferNodeVersionFromCommand(command) {
288
+ if (!command) {
289
+ return null;
290
+ }
291
+ const match = command.match(/node\/(v\d+\.\d+\.\d+)\//);
292
+ return match ? match[1] : null;
293
+ }
225
294
  function isProcessAlive(pid) {
226
295
  try {
227
296
  process.kill(pid, 0);
package/dist/src/http.js CHANGED
@@ -419,6 +419,7 @@ function buildFastifyServer(service) {
419
419
  backend: { type: "string", minLength: 1 },
420
420
  runtimeKind: { type: "string" },
421
421
  runtimeSessionId: { type: "string" },
422
+ runtimePid: { type: "integer", minimum: 1 },
422
423
  tmuxPaneId: { type: "string" },
423
424
  tty: { type: "string" },
424
425
  termProgram: { type: "string" },
@@ -439,6 +440,7 @@ function buildFastifyServer(service) {
439
440
  backend: String(body.backend),
440
441
  runtimeKind: optionalString(body.runtimeKind),
441
442
  runtimeSessionId: optionalString(body.runtimeSessionId),
443
+ runtimePid: typeof body.runtimePid === "number" ? body.runtimePid : null,
442
444
  mode: "agent_prompt",
443
445
  tmuxPaneId: optionalString(body.tmuxPaneId),
444
446
  tty: optionalString(body.tty),
@@ -485,6 +487,24 @@ function buildFastifyServer(service) {
485
487
  const params = request.params;
486
488
  return service.removeAgent(decodeURIComponent(params.agentId));
487
489
  });
490
+ app.post("/agents/:agentId/resume", {
491
+ schema: {
492
+ tags: ["agents"],
493
+ params: {
494
+ type: "object",
495
+ required: ["agentId"],
496
+ properties: {
497
+ agentId: { type: "string", minLength: 1 },
498
+ },
499
+ },
500
+ response: {
501
+ 200: jsonObjectSchema,
502
+ },
503
+ },
504
+ }, async (request) => {
505
+ const params = request.params;
506
+ return service.resumeAgent(decodeURIComponent(params.agentId));
507
+ });
488
508
  app.get("/agents/:agentId/targets", {
489
509
  schema: {
490
510
  tags: ["agents"],
@@ -563,6 +583,142 @@ function buildFastifyServer(service) {
563
583
  const params = request.params;
564
584
  return service.removeActivationTarget(decodeURIComponent(params.agentId), decodeURIComponent(params.targetId));
565
585
  });
586
+ app.post("/agents/:agentId/targets/:targetId/resume", {
587
+ schema: {
588
+ tags: ["agents"],
589
+ params: {
590
+ type: "object",
591
+ required: ["agentId", "targetId"],
592
+ properties: {
593
+ agentId: { type: "string", minLength: 1 },
594
+ targetId: { type: "string", minLength: 1 },
595
+ },
596
+ },
597
+ response: {
598
+ 200: jsonObjectSchema,
599
+ },
600
+ },
601
+ }, async (request) => {
602
+ const params = request.params;
603
+ return service.resumeActivationTarget(decodeURIComponent(params.agentId), decodeURIComponent(params.targetId));
604
+ });
605
+ app.get("/timers", {
606
+ schema: {
607
+ tags: ["timers"],
608
+ querystring: {
609
+ type: "object",
610
+ additionalProperties: false,
611
+ properties: {
612
+ agent_id: { type: "string" },
613
+ },
614
+ },
615
+ response: {
616
+ 200: {
617
+ type: "object",
618
+ required: ["timers"],
619
+ properties: {
620
+ timers: { type: "array", items: jsonObjectSchema },
621
+ },
622
+ },
623
+ },
624
+ },
625
+ }, async (request) => {
626
+ const query = request.query;
627
+ return { timers: service.listTimers(query.agent_id) };
628
+ });
629
+ app.post("/timers", {
630
+ schema: {
631
+ tags: ["timers"],
632
+ body: {
633
+ type: "object",
634
+ additionalProperties: false,
635
+ required: ["agentId", "message"],
636
+ properties: {
637
+ agentId: { type: "string", minLength: 1 },
638
+ at: { type: "string", minLength: 1 },
639
+ every: { type: "integer", minimum: 60000 },
640
+ cron: { type: "string", minLength: 1 },
641
+ timezone: { type: "string", minLength: 1 },
642
+ message: { type: "string", minLength: 1 },
643
+ sender: { type: "string", minLength: 1 },
644
+ },
645
+ },
646
+ response: {
647
+ 200: jsonObjectSchema,
648
+ 400: errorResponseSchema,
649
+ },
650
+ },
651
+ }, async (request) => {
652
+ const body = request.body;
653
+ return service.registerTimer({
654
+ agentId: String(body.agentId),
655
+ at: optionalString(body.at) ?? null,
656
+ every: typeof body.every === "number" ? body.every : null,
657
+ cron: optionalString(body.cron) ?? null,
658
+ timezone: optionalString(body.timezone) ?? null,
659
+ message: String(body.message),
660
+ sender: optionalString(body.sender) ?? null,
661
+ });
662
+ });
663
+ app.post("/timers/:scheduleId/pause", {
664
+ schema: {
665
+ tags: ["timers"],
666
+ params: {
667
+ type: "object",
668
+ required: ["scheduleId"],
669
+ properties: {
670
+ scheduleId: { type: "string", minLength: 1 },
671
+ },
672
+ },
673
+ response: {
674
+ 200: jsonObjectSchema,
675
+ 400: errorResponseSchema,
676
+ 404: errorResponseSchema,
677
+ },
678
+ },
679
+ }, async (request) => {
680
+ const params = request.params;
681
+ return service.pauseTimer(decodeURIComponent(params.scheduleId));
682
+ });
683
+ app.post("/timers/:scheduleId/resume", {
684
+ schema: {
685
+ tags: ["timers"],
686
+ params: {
687
+ type: "object",
688
+ required: ["scheduleId"],
689
+ properties: {
690
+ scheduleId: { type: "string", minLength: 1 },
691
+ },
692
+ },
693
+ response: {
694
+ 200: jsonObjectSchema,
695
+ 400: errorResponseSchema,
696
+ 404: errorResponseSchema,
697
+ },
698
+ },
699
+ }, async (request) => {
700
+ const params = request.params;
701
+ return service.resumeTimer(decodeURIComponent(params.scheduleId));
702
+ });
703
+ app.delete("/timers/:scheduleId", {
704
+ schema: {
705
+ tags: ["timers"],
706
+ params: {
707
+ type: "object",
708
+ required: ["scheduleId"],
709
+ properties: {
710
+ scheduleId: { type: "string", minLength: 1 },
711
+ },
712
+ },
713
+ response: {
714
+ 200: jsonObjectSchema,
715
+ 404: errorResponseSchema,
716
+ },
717
+ },
718
+ }, async (request) => {
719
+ const params = request.params;
720
+ return service.removeTimer(decodeURIComponent(params.scheduleId));
721
+ });
566
722
  app.get("/subscriptions", {
567
723
  schema: {
568
724
  tags: ["subscriptions"],
@@ -812,6 +968,39 @@ function buildFastifyServer(service) {
812
968
  }),
813
969
  };
814
970
  });
971
+ app.post("/agents/:agentId/inbox/items", {
972
+ schema: {
973
+ tags: ["inbox"],
974
+ params: {
975
+ type: "object",
976
+ required: ["agentId"],
977
+ properties: {
978
+ agentId: { type: "string", minLength: 1 },
979
+ },
980
+ },
981
+ body: {
982
+ type: "object",
983
+ additionalProperties: false,
984
+ required: ["message"],
985
+ properties: {
986
+ message: { type: "string", minLength: 1 },
987
+ sender: { type: "string", minLength: 1 },
988
+ },
989
+ },
990
+ response: {
991
+ 200: jsonObjectSchema,
992
+ 400: errorResponseSchema,
993
+ 404: errorResponseSchema,
994
+ },
995
+ },
996
+ }, async (request) => {
997
+ const params = request.params;
998
+ const body = request.body;
999
+ return service.addDirectInboxTextMessage(decodeURIComponent(params.agentId), {
1000
+ message: body.message,
1001
+ sender: body.sender ?? null,
1002
+ });
1003
+ });
815
1004
  app.get("/agents/:agentId/inbox/watch", {
816
1005
  schema: {
817
1006
  tags: ["inbox"],
@@ -1060,7 +1249,11 @@ function normalizeValidationMessage(message) {
1060
1249
  return message;
1061
1250
  }
1062
1251
  function isBadRequestError(message) {
1063
- return (message.startsWith("manual append is not supported") ||
1252
+ return (message.startsWith("direct inbox ") ||
1253
+ message.startsWith("timers require ") ||
1254
+ message.startsWith("timer ") ||
1255
+ message.startsWith("invalid timezone: ") ||
1256
+ message.startsWith("manual append is not supported") ||
1064
1257
  message.startsWith("sources/events requires") ||
1065
1258
  message.startsWith("source remove requires") ||
1066
1259
  message.startsWith("deliveryHandle requires") ||