@holon-run/agentinbox 1.0.0-beta.2 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -871,7 +871,7 @@ async function printCurrentAgent(client) {
871
871
  const records = await listAgentsWithTargets(client);
872
872
  const current = (0, current_agent_1.resolveCurrentAgent)(records, context);
873
873
  if (!current) {
874
- throw new Error("no current agent is registered for this terminal/runtime context; run `agentinbox agent register`");
874
+ throw new Error(noCurrentAgentMessage());
875
875
  }
876
876
  console.log((0, util_1.jsonResponse)(current));
877
877
  }
@@ -907,7 +907,7 @@ async function selectAgentForCommand(client, options) {
907
907
  };
908
908
  }
909
909
  if (!options.autoRegister) {
910
- throw new Error("no current agent is registered for this terminal/runtime context; run `agentinbox agent register`");
910
+ throw new Error(noCurrentAgentMessage());
911
911
  }
912
912
  await requestRemote(client, "/agents", {
913
913
  backend: contextForCurrent.backend,
@@ -946,6 +946,9 @@ async function requestRemote(client, endpoint, body, method = "POST") {
946
946
  }
947
947
  return { data: response.data };
948
948
  }
949
+ function noCurrentAgentMessage() {
950
+ 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";
951
+ }
949
952
  function takeFlagValue(args, flag) {
950
953
  const index = args.indexOf(flag);
951
954
  if (index === -1) {
@@ -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
  }
@@ -505,7 +505,7 @@ class AgentInboxService {
505
505
  return { removed: true };
506
506
  }
507
507
  registerTimer(input) {
508
- this.getAgent(input.agentId);
508
+ this.assertAgentNotifyCapable(input.agentId, "timer add");
509
509
  const normalized = normalizeTimerInput(input);
510
510
  const timezone = normalized.timezone ?? detectHostTimezone();
511
511
  assertValidTimeZone(timezone);
@@ -794,7 +794,7 @@ class AgentInboxService {
794
794
  return sibling;
795
795
  }
796
796
  async registerSingleSubscription(input) {
797
- this.getAgent(input.agentId);
797
+ this.assertAgentNotifyCapable(input.agentId, "subscription add");
798
798
  const source = this.store.getSource(input.sourceId);
799
799
  if (!source) {
800
800
  throw new Error(`unknown source: ${input.sourceId}`);
@@ -1595,6 +1595,19 @@ class AgentInboxService {
1595
1595
  watcher.onItems(items);
1596
1596
  }
1597
1597
  }
1598
+ assertAgentNotifyCapable(agentId, operation) {
1599
+ this.getAgent(agentId);
1600
+ const targets = this.store.listActivationTargetsForAgent(agentId);
1601
+ const activeTargetCount = targets.filter((target) => target.status === "active").length;
1602
+ if (activeTargetCount > 0) {
1603
+ return;
1604
+ }
1605
+ const offlineTargetCount = targets.filter((target) => target.status === "offline").length;
1606
+ const targetSummary = targets.length === 0
1607
+ ? "it has no activation targets"
1608
+ : `all ${offlineTargetCount} activation target(s) are offline`;
1609
+ throw new Error(`agent ${agentId} is not notify-capable: ${targetSummary}; refusing ${operation} to avoid a silent notification black hole. Rebind with \`agentinbox agent register --force-rebind --agent-id ${agentId}\` or resume/add a target first.`);
1610
+ }
1598
1611
  resolveAutoAgentId(input) {
1599
1612
  for (let attempt = 0; attempt < 32; attempt += 1) {
1600
1613
  const candidate = (0, terminal_1.assignedAgentIdFromContext)({
@@ -89,6 +89,7 @@ const SOURCE_SCHEMAS = {
89
89
  { name: "event", type: "string|null", description: "GitHub trigger event for the workflow run." },
90
90
  { name: "headBranch", type: "string|null", description: "Head branch for the workflow run." },
91
91
  { name: "headSha", type: "string|null", description: "Head commit SHA for the workflow run." },
92
+ { name: "pullRequestNumbers", type: "number[]", description: "Pull request numbers associated with the workflow run." },
92
93
  { name: "actor", type: "string|null", description: "Actor login for the workflow run." },
93
94
  { name: "commitMessage", type: "string|null", description: "Head commit message when present." },
94
95
  { name: "htmlUrl", type: "string|null", description: "GitHub Actions run URL." },
@@ -101,6 +102,7 @@ const SOURCE_SCHEMAS = {
101
102
  conclusion: "failure",
102
103
  event: "pull_request",
103
104
  head_branch: "main",
105
+ pull_requests: [{ number: 93 }],
104
106
  actor: "jolestar",
105
107
  head_commit: { message: "fix ci" },
106
108
  },
@@ -382,6 +382,11 @@ function expandGithubSubscriptionShortcut(input) {
382
382
  ...(withCi
383
383
  ? [{
384
384
  streamKind: "ci_runs",
385
+ filter: {
386
+ metadata: {
387
+ pullRequestNumbers: [number],
388
+ },
389
+ },
385
390
  trackedResourceRef,
386
391
  cleanupPolicy: { mode: "on_terminal" },
387
392
  }]
@@ -240,6 +240,7 @@ function normalizeGithubWorkflowRunEvent(source, config, raw) {
240
240
  const variant = buildWorkflowRunVariant(workflowName, status, conclusion);
241
241
  const actor = asRecord(run.actor);
242
242
  const headCommit = asRecord(run.head_commit);
243
+ const pullRequestNumbers = extractPullRequestNumbers(run.pull_requests);
243
244
  return {
244
245
  sourceId: source.sourceId,
245
246
  sourceNativeId: `workflow_run:${runId}`,
@@ -259,6 +260,7 @@ function normalizeGithubWorkflowRunEvent(source, config, raw) {
259
260
  event: asString(run.event),
260
261
  headSha: asString(run.head_sha),
261
262
  headBranch: asString(run.head_branch),
263
+ pullRequestNumbers,
262
264
  runNumber: asNumber(run.run_number),
263
265
  runAttempt: asNumber(run.run_attempt),
264
266
  actor: asString(actor.login),
@@ -277,6 +279,7 @@ function normalizeGithubWorkflowRunEvent(source, config, raw) {
277
279
  event: asString(run.event),
278
280
  head_sha: asString(run.head_sha),
279
281
  head_branch: asString(run.head_branch),
282
+ pull_requests: pullRequestNumbers.map((number) => ({ number })),
280
283
  run_number: asNumber(run.run_number),
281
284
  run_attempt: asNumber(run.run_attempt),
282
285
  html_url: asString(run.html_url),
@@ -343,6 +346,19 @@ function asString(value) {
343
346
  function asNumber(value) {
344
347
  return typeof value === "number" && Number.isFinite(value) ? value : null;
345
348
  }
349
+ function extractPullRequestNumbers(value) {
350
+ if (!Array.isArray(value)) {
351
+ return [];
352
+ }
353
+ const numbers = new Set();
354
+ for (const entry of value) {
355
+ const number = asNumber(asRecord(entry).number);
356
+ if (number && Number.isInteger(number) && number > 0) {
357
+ numbers.add(number);
358
+ }
359
+ }
360
+ return Array.from(numbers).sort((left, right) => left - right);
361
+ }
346
362
  function inferWorkflowRunStatus(run, conclusion) {
347
363
  return asString(run.status)
348
364
  ?? (conclusion ? "completed" : null)
@@ -324,6 +324,7 @@ const GITHUB_REPO_CI_MODULE = {
324
324
  { name: "event", type: "string|null", description: "GitHub trigger event for the workflow run." },
325
325
  { name: "headBranch", type: "string|null", description: "Head branch for the workflow run." },
326
326
  { name: "headSha", type: "string|null", description: "Head commit SHA for the workflow run." },
327
+ { name: "pullRequestNumbers", type: "number[]", description: "Pull request numbers associated with the workflow run." },
327
328
  { name: "actor", type: "string|null", description: "Actor login for the workflow run." },
328
329
  { name: "commitMessage", type: "string|null", description: "Head commit message when present." },
329
330
  { name: "htmlUrl", type: "string|null", description: "GitHub Actions run URL." },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holon-run/agentinbox",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "Local event subscription and delivery service for agents.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {