@holon-run/agentinbox 1.0.0-beta.2 → 1.0.0-beta.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/README.md CHANGED
@@ -85,7 +85,7 @@ database.
85
85
  Requires:
86
86
 
87
87
  - Node.js 20 or newer
88
- - `uxc` 0.15.0 or newer if you want to use GitHub or Feishu adapters:
88
+ - `uxc` 0.15.3 or newer if you want to use GitHub or Feishu adapters:
89
89
  https://github.com/holon-run/uxc
90
90
 
91
91
  Install globally:
@@ -44,7 +44,9 @@ class AdapterRegistry {
44
44
  remoteModuleRegistry;
45
45
  constructor(store, appendSourceEvent, options) {
46
46
  this.homeDir = options?.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
47
- this.remoteModuleRegistry = options?.remoteModuleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
47
+ this.remoteModuleRegistry = options?.remoteModuleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry({
48
+ githubCallClient: options?.githubCallClient,
49
+ });
48
50
  this.remoteSource = new remote_1.RemoteSourceRuntime(store, appendSourceEvent, {
49
51
  homeDir: this.homeDir,
50
52
  client: options?.remoteSourceClient,
@@ -140,6 +142,31 @@ class AdapterRegistry {
140
142
  }
141
143
  return this.remoteSource.expandSubscriptionShortcut(source, input);
142
144
  }
145
+ async listFollowTemplates(source) {
146
+ if (source.sourceType === "local_event") {
147
+ return [];
148
+ }
149
+ return this.remoteSource.listFollowTemplates(source);
150
+ }
151
+ async expandFollowTemplate(source, input) {
152
+ if (source.sourceType === "local_event") {
153
+ return null;
154
+ }
155
+ return this.remoteSource.expandFollowTemplate(source, input);
156
+ }
157
+ followPreviewRefsForProviderOrKind(providerOrKind, config) {
158
+ const trimmed = providerOrKind.trim();
159
+ if (trimmed.length === 0) {
160
+ throw new Error("follow requires a providerOrKind");
161
+ }
162
+ if (isExplicitFollowPreviewRef(trimmed)) {
163
+ return [trimmed];
164
+ }
165
+ const candidates = hasRemoteSourceModulePath(config)
166
+ ? ["remote_source", ...(0, remote_modules_1.builtinRemoteSourceTypes)()]
167
+ : (0, remote_modules_1.builtinRemoteSourceTypes)();
168
+ return Array.from(new Set(candidates));
169
+ }
143
170
  async deriveInlinePreview(source, item) {
144
171
  if (source.sourceType === "local_event") {
145
172
  return null;
@@ -152,6 +179,15 @@ class AdapterRegistry {
152
179
  }
153
180
  return this.remoteSource.deriveNotificationGrouping(source, item);
154
181
  }
182
+ async getSourceRuntime(source) {
183
+ if (source.sourceType === "local_event") {
184
+ return null;
185
+ }
186
+ return this.remoteSource.getSourceRuntime(source);
187
+ }
188
+ async listSourceRuntimes(sources) {
189
+ return this.remoteSource.listSourceRuntimes(sources);
190
+ }
155
191
  async summarizeDigestThread(source, items, grouping) {
156
192
  if (source.sourceType === "local_event") {
157
193
  return null;
@@ -194,6 +230,18 @@ class AdapterRegistry {
194
230
  }
195
231
  }
196
232
  exports.AdapterRegistry = AdapterRegistry;
233
+ function isExplicitFollowPreviewRef(value) {
234
+ return (value === "local_event" ||
235
+ value === "remote_source" ||
236
+ value === "github_repo" ||
237
+ value === "github_repo_ci" ||
238
+ value === "feishu_bot" ||
239
+ value.startsWith("remote:"));
240
+ }
241
+ function hasRemoteSourceModulePath(config) {
242
+ const modulePath = config?.modulePath;
243
+ return typeof modulePath === "string" && modulePath.trim().length > 0;
244
+ }
197
245
  function syntheticBuiltinSource(sourceType) {
198
246
  return {
199
247
  sourceId: `builtin:${sourceType}`,
package/dist/src/cli.js CHANGED
@@ -182,6 +182,47 @@ async function main() {
182
182
  await printRemote(client, `/streams/${encodeURIComponent(streamRef)}/schema`, undefined, "GET");
183
183
  return;
184
184
  }
185
+ if (command === "follow") {
186
+ const args = normalized.slice(1);
187
+ const positionals = positionalArgs(args, [
188
+ "--agent-id",
189
+ "--args-json",
190
+ "--arg",
191
+ "--config-json",
192
+ "--config-ref",
193
+ "--start-policy",
194
+ "--start-offset",
195
+ "--start-time",
196
+ ]);
197
+ const [providerOrKind, template] = positionals;
198
+ if (!providerOrKind || !template || positionals[2]) {
199
+ throw new Error("usage: agentinbox follow <providerOrKind> <template> [--agent-id ID] [--args-json JSON | --arg KEY=VALUE ...] [--config-json JSON] [--config-ref REF] [--start-policy POLICY] [--start-offset N] [--start-time ISO8601]");
200
+ }
201
+ const argsJson = takeFlagValue(normalized, "--args-json");
202
+ const hasArgFlags = hasFlag(normalized, "--arg");
203
+ if (argsJson != null && hasArgFlags) {
204
+ throw new Error("follow accepts either --args-json or repeated --arg flags, but not both");
205
+ }
206
+ const selection = await selectAgentForCommand(client, {
207
+ explicitAgentId: takeFlagValue(normalized, "--agent-id"),
208
+ autoRegister: true,
209
+ });
210
+ const response = await requestRemote(client, "/follow", {
211
+ agentId: selection.agentId,
212
+ providerOrKind,
213
+ template,
214
+ templateArgs: argsJson != null
215
+ ? (0, util_1.parseJsonArg)(argsJson, "--args-json")
216
+ : readFollowTemplateArgs(normalized),
217
+ configRef: takeFlagValue(normalized, "--config-ref") ?? undefined,
218
+ config: (0, util_1.parseJsonArg)(takeFlagValue(normalized, "--config-json")),
219
+ startPolicy: takeFlagValue(normalized, "--start-policy") ?? undefined,
220
+ startOffset: parseOptionalNumber(takeFlagValue(normalized, "--start-offset")),
221
+ startTime: takeFlagValue(normalized, "--start-time") ?? undefined,
222
+ });
223
+ console.log((0, util_1.jsonResponse)(withCommandMetadata(response.data, selection)));
224
+ return;
225
+ }
185
226
  if (command === "stream" && normalized[1] === "poll") {
186
227
  const streamId = normalized[2];
187
228
  if (!streamId) {
@@ -871,7 +912,7 @@ async function printCurrentAgent(client) {
871
912
  const records = await listAgentsWithTargets(client);
872
913
  const current = (0, current_agent_1.resolveCurrentAgent)(records, context);
873
914
  if (!current) {
874
- throw new Error("no current agent is registered for this terminal/runtime context; run `agentinbox agent register`");
915
+ throw new Error(noCurrentAgentMessage());
875
916
  }
876
917
  console.log((0, util_1.jsonResponse)(current));
877
918
  }
@@ -907,7 +948,7 @@ async function selectAgentForCommand(client, options) {
907
948
  };
908
949
  }
909
950
  if (!options.autoRegister) {
910
- throw new Error("no current agent is registered for this terminal/runtime context; run `agentinbox agent register`");
951
+ throw new Error(noCurrentAgentMessage());
911
952
  }
912
953
  await requestRemote(client, "/agents", {
913
954
  backend: contextForCurrent.backend,
@@ -946,6 +987,9 @@ async function requestRemote(client, endpoint, body, method = "POST") {
946
987
  }
947
988
  return { data: response.data };
948
989
  }
990
+ function noCurrentAgentMessage() {
991
+ return "no current agent is registered for this terminal/runtime context; run `agentinbox agent list` to inspect offline agents, then `agentinbox agent register` or `agentinbox agent register --force-rebind --agent-id <agentId>` to rebind one";
992
+ }
949
993
  function takeFlagValue(args, flag) {
950
994
  const index = args.indexOf(flag);
951
995
  if (index === -1) {
@@ -1062,6 +1106,50 @@ function readSubscriptionFilter(args) {
1062
1106
  }
1063
1107
  return {};
1064
1108
  }
1109
+ function readFollowTemplateArgs(args) {
1110
+ const parsed = {};
1111
+ for (let index = 0; index < args.length; index += 1) {
1112
+ if (args[index] !== "--arg") {
1113
+ continue;
1114
+ }
1115
+ const raw = args[index + 1];
1116
+ if (!raw || raw.startsWith("--")) {
1117
+ throw new Error("flag --arg requires KEY=VALUE");
1118
+ }
1119
+ const separator = raw.indexOf("=");
1120
+ if (separator <= 0) {
1121
+ throw new Error("flag --arg requires KEY=VALUE");
1122
+ }
1123
+ const key = raw.slice(0, separator).trim();
1124
+ const rawValue = raw.slice(separator + 1);
1125
+ if (key.length === 0) {
1126
+ throw new Error("flag --arg requires a non-empty key");
1127
+ }
1128
+ parsed[key] = parseFollowTemplateArgValue(rawValue);
1129
+ index += 1;
1130
+ }
1131
+ return parsed;
1132
+ }
1133
+ function parseFollowTemplateArgValue(raw) {
1134
+ if (raw === "true") {
1135
+ return true;
1136
+ }
1137
+ if (raw === "false") {
1138
+ return false;
1139
+ }
1140
+ if (/^-?\d+(?:\.\d+)?$/.test(raw)) {
1141
+ return Number(raw);
1142
+ }
1143
+ if ((raw.startsWith("[") && raw.endsWith("]")) || (raw.startsWith("{") && raw.endsWith("}")) || raw === "null") {
1144
+ try {
1145
+ return JSON.parse(raw);
1146
+ }
1147
+ catch {
1148
+ return raw;
1149
+ }
1150
+ }
1151
+ return raw;
1152
+ }
1065
1153
  function withCommandMetadata(data, selection) {
1066
1154
  return {
1067
1155
  ...data,
@@ -1139,6 +1227,7 @@ Usage:
1139
1227
  Commands:
1140
1228
  serve
1141
1229
  daemon
1230
+ follow
1142
1231
  source
1143
1232
  agent
1144
1233
  timer
@@ -1184,6 +1273,11 @@ Usage:
1184
1273
  agentinbox stream schema preview <sourceKind|sourceType> [--config-json JSON] [--config-ref REF]
1185
1274
  agentinbox stream poll <streamId>
1186
1275
  agentinbox stream event <streamId> --native-id ID --event EVENT [--occurred-at ISO8601] [--metadata-json JSON] [--payload-json JSON]
1276
+ `,
1277
+ follow: `agentinbox follow
1278
+
1279
+ Usage:
1280
+ agentinbox follow <providerOrKind> <template> [--agent-id ID] [--args-json JSON | --arg KEY=VALUE ...] [--config-json JSON] [--config-ref REF] [--start-policy POLICY] [--start-offset N] [--start-time ISO8601]
1187
1281
  `,
1188
1282
  source: `agentinbox source
1189
1283
 
@@ -39,6 +39,9 @@ function resolveCurrentAgent(agents, context) {
39
39
  matchesCurrentTerminal: matchesCurrentTerminal(agent, context),
40
40
  matchesCurrentRuntime: matchesCurrentRuntime(agent, context),
41
41
  terminalIdentity: terminalIdentityForAgent(agent),
42
+ notifyCapable: notifyCapableForAgent(agent),
43
+ activeTargetCount: activeActivationTargetCount(agent),
44
+ offlineTargetCount: offlineActivationTargetCount(agent),
42
45
  };
43
46
  }
44
47
  function annotateAgents(agents, context) {
@@ -52,24 +55,27 @@ function annotateAgents(agents, context) {
52
55
  matchesCurrentRuntime: context ? matchesCurrentRuntime(entry, context) : false,
53
56
  terminalIdentity: terminalIdentityForAgent(entry),
54
57
  isCurrent: current?.agentId === entry.agent.agentId,
58
+ notifyCapable: notifyCapableForAgent(entry),
59
+ activeTargetCount: activeActivationTargetCount(entry),
60
+ offlineTargetCount: offlineActivationTargetCount(entry),
55
61
  })),
56
62
  };
57
63
  }
58
64
  function resolveAgentRecord(agents, context) {
59
65
  if (context.tmuxPaneId) {
60
- const match = agents.find((agent) => activeTerminalTargets(agent).some((target) => target.backend === "tmux" && target.tmuxPaneId === context.tmuxPaneId));
66
+ const match = agents.find((agent) => terminalTargetsForCurrentMatch(agent).some((target) => target.backend === "tmux" && target.tmuxPaneId === context.tmuxPaneId));
61
67
  if (match) {
62
68
  return match;
63
69
  }
64
70
  }
65
71
  if (context.itermSessionId) {
66
- const match = agents.find((agent) => activeTerminalTargets(agent).some((target) => target.backend === "iterm2" && target.itermSessionId === context.itermSessionId));
72
+ const match = agents.find((agent) => terminalTargetsForCurrentMatch(agent).some((target) => target.backend === "iterm2" && target.itermSessionId === context.itermSessionId));
67
73
  if (match) {
68
74
  return match;
69
75
  }
70
76
  }
71
77
  if (context.tty) {
72
- const match = agents.find((agent) => activeTerminalTargets(agent).some((target) => target.tty === context.tty));
78
+ const match = agents.find((agent) => terminalTargetsForCurrentMatch(agent).some((target) => target.tty === context.tty));
73
79
  if (match) {
74
80
  return match;
75
81
  }
@@ -94,6 +100,18 @@ function activeTerminalTargets(agent) {
94
100
  }
95
101
  return terminalTargets(agent).filter((target) => target.status === "active");
96
102
  }
103
+ function terminalTargetsForCurrentMatch(agent) {
104
+ return activeTerminalTargets(agent);
105
+ }
106
+ function activeActivationTargetCount(agent) {
107
+ return agent.activationTargets.filter((target) => target.status === "active").length;
108
+ }
109
+ function offlineActivationTargetCount(agent) {
110
+ return agent.activationTargets.filter((target) => target.status === "offline").length;
111
+ }
112
+ function notifyCapableForAgent(agent) {
113
+ return agent.agent.status === "active" && activeActivationTargetCount(agent) > 0;
114
+ }
97
115
  function terminalIdentityForAgent(agent) {
98
116
  const target = activeTerminalTargets(agent)[0];
99
117
  if (!target) {
@@ -111,7 +129,7 @@ function terminalIdentityForAgent(agent) {
111
129
  return null;
112
130
  }
113
131
  function matchesCurrentTerminal(agent, context) {
114
- return activeTerminalTargets(agent).some((target) => (context.tmuxPaneId != null && target.tmuxPaneId === context.tmuxPaneId)
132
+ return terminalTargetsForCurrentMatch(agent).some((target) => (context.tmuxPaneId != null && target.tmuxPaneId === context.tmuxPaneId)
115
133
  || (context.itermSessionId != null && target.itermSessionId === context.itermSessionId)
116
134
  || (context.tty != null && target.tty === context.tty));
117
135
  }
package/dist/src/http.js CHANGED
@@ -29,6 +29,10 @@ const errorResponseSchema = {
29
29
  error: { type: "string" },
30
30
  },
31
31
  };
32
+ const subscriptionStartPolicySchema = {
33
+ type: "string",
34
+ enum: ["latest", "earliest", "at_offset", "at_time"],
35
+ };
32
36
  const subscriptionCollectionSchema = {
33
37
  type: "object",
34
38
  required: ["subscriptions"],
@@ -286,7 +290,7 @@ function buildFastifyServer(service) {
286
290
  },
287
291
  },
288
292
  },
289
- }, async () => ({ sources: service.listSources() }));
293
+ }, async () => ({ sources: await service.listSourceViews() }));
290
294
  app.post("/sources", {
291
295
  schema: {
292
296
  tags: ["sources"],
@@ -464,6 +468,48 @@ function buildFastifyServer(service) {
464
468
  },
465
469
  },
466
470
  }, async (request) => service.previewSourceSchema(request.body));
471
+ app.post("/follow", {
472
+ schema: {
473
+ tags: ["subscriptions"],
474
+ body: {
475
+ type: "object",
476
+ additionalProperties: false,
477
+ required: ["agentId", "providerOrKind", "template"],
478
+ properties: {
479
+ agentId: { type: "string", minLength: 1 },
480
+ providerOrKind: { type: "string", minLength: 1 },
481
+ template: { type: "string", minLength: 1 },
482
+ templateArgs: jsonObjectSchema,
483
+ configRef: { anyOf: [{ type: "string" }, { type: "null" }] },
484
+ config: jsonObjectSchema,
485
+ startPolicy: subscriptionStartPolicySchema,
486
+ startOffset: { type: "integer" },
487
+ startTime: { type: "string" },
488
+ },
489
+ },
490
+ response: {
491
+ 200: jsonObjectSchema,
492
+ 400: errorResponseSchema,
493
+ },
494
+ },
495
+ }, async (request) => {
496
+ const body = request.body;
497
+ return service.follow({
498
+ agentId: String(body.agentId),
499
+ providerOrKind: String(body.providerOrKind),
500
+ template: String(body.template),
501
+ templateArgs: body.templateArgs && typeof body.templateArgs === "object"
502
+ ? body.templateArgs
503
+ : undefined,
504
+ configRef: optionalString(body.configRef) ?? null,
505
+ config: body.config && typeof body.config === "object"
506
+ ? body.config
507
+ : undefined,
508
+ startPolicy: optionalString(body.startPolicy),
509
+ startOffset: typeof body.startOffset === "number" ? body.startOffset : undefined,
510
+ startTime: optionalString(body.startTime) ?? undefined,
511
+ });
512
+ });
467
513
  app.post("/streams/:streamId/poll", {
468
514
  schema: {
469
515
  tags: ["sources"],
@@ -1083,7 +1129,7 @@ function buildFastifyServer(service) {
1083
1129
  filter: jsonObjectSchema,
1084
1130
  trackedResourceRef: { type: "string" },
1085
1131
  cleanupPolicy: jsonObjectSchema,
1086
- startPolicy: { type: "string" },
1132
+ startPolicy: subscriptionStartPolicySchema,
1087
1133
  startOffset: { type: "integer" },
1088
1134
  startTime: { type: "string" },
1089
1135
  },
@@ -1204,7 +1250,7 @@ function buildFastifyServer(service) {
1204
1250
  additionalProperties: false,
1205
1251
  required: ["startPolicy"],
1206
1252
  properties: {
1207
- startPolicy: { type: "string", minLength: 1 },
1253
+ startPolicy: subscriptionStartPolicySchema,
1208
1254
  startOffset: { type: "integer" },
1209
1255
  startTime: { type: "string" },
1210
1256
  },
@@ -1690,6 +1736,10 @@ function isBadRequestError(message) {
1690
1736
  message.startsWith("cleanupPolicy ") ||
1691
1737
  message.startsWith("trackedResourceRef ") ||
1692
1738
  message.startsWith("preview failed: ") ||
1739
+ message.startsWith("follow preview failed: ") ||
1740
+ message.startsWith("follow template ") ||
1741
+ message.startsWith("unknown follow template ") ||
1742
+ message.startsWith("follow requires a providerOrKind") ||
1693
1743
  message.startsWith("unknown source kind or type for preview") ||
1694
1744
  message.startsWith("preview source kind ") ||
1695
1745
  message.startsWith("subscription add shortcut") ||