@holon-run/agentinbox 0.7.0 → 1.0.0-beta.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.
@@ -16,6 +16,28 @@ const node_fs_1 = __importDefault(require("node:fs"));
16
16
  const node_os_1 = __importDefault(require("node:os"));
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
19
+ const AGENT_ADJECTIVES = [
20
+ "amber", "ancient", "autumn", "brisk", "bright", "bronze", "calm", "cedar",
21
+ "cinder", "clear", "cobalt", "cool", "copper", "crisp", "dawn", "deep",
22
+ "drift", "ember", "falling", "fern", "frost", "gentle", "glint", "golden",
23
+ "granite", "harbor", "hazel", "hidden", "hollow", "ice", "indigo", "iron",
24
+ "ivory", "jade", "juniper", "kind", "lake", "lively", "lunar", "maple",
25
+ "meadow", "misty", "moss", "night", "north", "oak", "olive", "opal",
26
+ "orchid", "pale", "pine", "plum", "quiet", "rapid", "river", "rose",
27
+ "royal", "rust", "sage", "silver", "soft", "solar", "spring", "stone",
28
+ "summer", "swift", "timber", "velvet", "warm", "west", "whisper", "winter",
29
+ ];
30
+ const AGENT_ANIMALS = [
31
+ "antelope", "badger", "beaver", "bison", "bittern", "boar", "bobcat", "buffalo",
32
+ "canary", "cougar", "crane", "crow", "deer", "dingo", "dolphin", "dragonfly",
33
+ "eagle", "falcon", "finch", "firefly", "fox", "frog", "gazelle", "gecko",
34
+ "gull", "hare", "hawk", "heron", "ibis", "jaguar", "jay", "kingfisher",
35
+ "koala", "koi", "lemur", "leopard", "lizard", "lynx", "magpie", "martin",
36
+ "mink", "mole", "narwhal", "newt", "ocelot", "orca", "otter", "owl",
37
+ "panther", "parrot", "pebble", "pelican", "pika", "puma", "quail", "raven",
38
+ "robin", "seal", "sparrow", "stoat", "stork", "swift", "tern", "tiger",
39
+ "toad", "toucan", "trout", "viper", "weasel", "wren", "yak", "zebra",
40
+ ];
19
41
  function detectTerminalContext(env = process.env) {
20
42
  const tmuxPaneId = normalizeOptionalString(env.TMUX_PANE);
21
43
  const termProgram = normalizeOptionalString(env.TERM_PROGRAM);
@@ -48,7 +70,7 @@ function detectTerminalContext(env = process.env) {
48
70
  throw new Error("unable to detect a supported terminal context");
49
71
  }
50
72
  function renderAgentPrompt(input) {
51
- const totalUnackedCount = input.totalUnackedCount ?? input.newItemCount ?? 0;
73
+ const totalUnackedCount = input.totalUnackedCount;
52
74
  const itemWord = totalUnackedCount === 1 ? "item" : "items";
53
75
  const base = `AgentInbox: ${totalUnackedCount} unacked ${itemWord} in inbox ${input.inboxId}.`;
54
76
  if (totalUnackedCount === 1 && input.preview) {
@@ -89,7 +111,7 @@ function deriveInlineItemPreview(item, summary) {
89
111
  function normalizeInlinePreviewText(candidate) {
90
112
  return normalizeInlinePreview(candidate);
91
113
  }
92
- function assignedAgentIdFromContext(input) {
114
+ function assignedAgentIdFromContext(input, attempt = 0) {
93
115
  const primary = normalizeOptionalString(input.runtimeSessionId)
94
116
  ?? normalizeOptionalString(input.tmuxPaneId)
95
117
  ?? normalizeOptionalString(input.itermSessionId)
@@ -97,12 +119,16 @@ function assignedAgentIdFromContext(input) {
97
119
  if (!primary) {
98
120
  throw new Error("unable to derive agentId from terminal context");
99
121
  }
100
- const normalized = primary.replace(/[^a-zA-Z0-9]+/g, "").toLowerCase();
101
- if (normalized.length > 0) {
102
- return `agent_${input.runtimeKind ?? "unknown"}_${normalized.slice(0, 40)}`;
122
+ const digest = node_crypto_1.default.createHash("sha256")
123
+ .update(`${input.runtimeKind ?? "unknown"}:${input.backend}:${primary}:${attempt}`)
124
+ .digest();
125
+ const adjective = AGENT_ADJECTIVES[digest[0] % AGENT_ADJECTIVES.length];
126
+ const animal = AGENT_ANIMALS[digest[1] % AGENT_ANIMALS.length];
127
+ if (attempt < 16) {
128
+ return `agt_${adjective}-${animal}`;
103
129
  }
104
- const digest = node_crypto_1.default.createHash("sha256").update(primary).digest("hex").slice(0, 16);
105
- return `agent_${input.runtimeKind ?? "unknown"}_${digest}`;
130
+ const suffix = digest.toString("hex").slice(0, 3);
131
+ return `agt_${adjective}-${animal}-${suffix}`;
106
132
  }
107
133
  class TerminalDispatcher {
108
134
  execAsync;
package/dist/src/util.js CHANGED
@@ -4,16 +4,41 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.nowIso = nowIso;
7
+ exports.generateCanonicalId = generateCanonicalId;
7
8
  exports.generateId = generateId;
9
+ exports.generateShortToken = generateShortToken;
8
10
  exports.parseJsonArg = parseJsonArg;
9
11
  exports.asObject = asObject;
10
12
  exports.jsonResponse = jsonResponse;
13
+ exports.formatEntryRef = formatEntryRef;
14
+ exports.parseEntryRef = parseEntryRef;
15
+ exports.formatThreadRef = formatThreadRef;
16
+ exports.parseThreadRef = parseThreadRef;
11
17
  const node_crypto_1 = __importDefault(require("node:crypto"));
18
+ const CANONICAL_ID_ALPHABET = "23456789abcdefghjkmnpqrstvwxyz";
19
+ const CANONICAL_ID_TOKEN_LENGTH = 12;
20
+ const ENTRY_THREAD_ID_PATTERN = new RegExp(`^(ent|thr)_[${CANONICAL_ID_ALPHABET}]{${CANONICAL_ID_TOKEN_LENGTH}}$`);
12
21
  function nowIso() {
13
22
  return new Date().toISOString();
14
23
  }
24
+ function generateCanonicalId(prefix, length = CANONICAL_ID_TOKEN_LENGTH) {
25
+ return `${prefix}_${generateShortToken(length)}`;
26
+ }
15
27
  function generateId(prefix) {
16
- return `${prefix}_${node_crypto_1.default.randomUUID()}`;
28
+ return generateCanonicalId(prefix);
29
+ }
30
+ function generateShortToken(length = CANONICAL_ID_TOKEN_LENGTH) {
31
+ let token = "";
32
+ while (token.length < length) {
33
+ const bytes = node_crypto_1.default.randomBytes(length - token.length);
34
+ for (const byte of bytes) {
35
+ token += CANONICAL_ID_ALPHABET[byte % CANONICAL_ID_ALPHABET.length];
36
+ if (token.length >= length) {
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ return token;
17
42
  }
18
43
  function parseJsonArg(raw, source = "JSON argument", options) {
19
44
  if (!raw) {
@@ -50,3 +75,23 @@ function asObject(value) {
50
75
  function jsonResponse(data) {
51
76
  return JSON.stringify(data, null, 2);
52
77
  }
78
+ function formatEntryRef(entryId) {
79
+ return parseEntryRef(entryId);
80
+ }
81
+ function parseEntryRef(ref) {
82
+ const value = ref.trim();
83
+ if (!ENTRY_THREAD_ID_PATTERN.test(value) || !value.startsWith("ent_")) {
84
+ throw new Error(`invalid inbox entry id: ${ref}`);
85
+ }
86
+ return value;
87
+ }
88
+ function formatThreadRef(threadId) {
89
+ return parseThreadRef(threadId);
90
+ }
91
+ function parseThreadRef(ref) {
92
+ const value = ref.trim();
93
+ if (!ENTRY_THREAD_ID_PATTERN.test(value) || !value.startsWith("thr_")) {
94
+ throw new Error(`invalid digest thread id: ${ref}`);
95
+ }
96
+ return value;
97
+ }
@@ -1,5 +1,8 @@
1
1
  create table if not exists sources (
2
2
  source_id text primary key,
3
+ host_id text not null,
4
+ stream_kind text not null,
5
+ stream_key text not null,
3
6
  source_type text not null,
4
7
  source_key text not null,
5
8
  config_ref text,
@@ -7,8 +10,7 @@ create table if not exists sources (
7
10
  status text not null,
8
11
  checkpoint text,
9
12
  created_at text not null,
10
- updated_at text not null,
11
- unique(source_type, source_key)
13
+ updated_at text not null
12
14
  );
13
15
 
14
16
  create table if not exists agents (
@@ -25,7 +27,11 @@ create table if not exists agents (
25
27
  create table if not exists inboxes (
26
28
  inbox_id text primary key,
27
29
  owner_agent_id text not null unique,
28
- created_at text not null
30
+ created_at text not null,
31
+ aggregation_enabled integer not null default 0,
32
+ aggregation_window_ms integer,
33
+ aggregation_max_items integer,
34
+ aggregation_max_thread_age_ms integer
29
35
  );
30
36
 
31
37
  create table if not exists subscriptions (
@@ -33,8 +39,8 @@ create table if not exists subscriptions (
33
39
  agent_id text not null,
34
40
  source_id text not null,
35
41
  filter_json text not null,
36
- lifecycle_mode text not null,
37
- expires_at text,
42
+ tracked_resource_ref text,
43
+ cleanup_policy_json text not null,
38
44
  start_policy text not null,
39
45
  start_offset integer,
40
46
  start_time text,
@@ -62,7 +68,9 @@ create table if not exists activation_targets (
62
68
  iterm_session_id text,
63
69
  created_at text not null,
64
70
  updated_at text not null,
65
- last_seen_at text not null
71
+ last_seen_at text not null,
72
+ runtime_pid integer,
73
+ min_unacked_items integer
66
74
  );
67
75
 
68
76
  create table if not exists activation_dispatch_states (
@@ -75,6 +83,12 @@ create table if not exists activation_dispatch_states (
75
83
  pending_subscription_ids_json text not null,
76
84
  pending_source_ids_json text not null,
77
85
  updated_at text not null,
86
+ last_notified_fingerprint text,
87
+ defer_reason text,
88
+ defer_attempts integer not null default 0,
89
+ first_deferred_at text,
90
+ last_deferred_at text,
91
+ pending_fingerprint text,
78
92
  primary key (agent_id, target_id)
79
93
  );
80
94
 
@@ -89,6 +103,7 @@ create table if not exists inbox_items (
89
103
  raw_payload_json text not null,
90
104
  delivery_handle_json text,
91
105
  acked_at text,
106
+ inbox_sequence integer,
92
107
  unique(source_id, source_native_id, event_variant, inbox_id)
93
108
  );
94
109
 
@@ -165,6 +180,107 @@ create table if not exists consumer_commits (
165
180
  committed_at text not null
166
181
  );
167
182
 
183
+ create table if not exists subscription_lifecycle_retirements (
184
+ subscription_id text primary key,
185
+ source_id text not null,
186
+ tracked_resource_ref text not null,
187
+ retire_at text not null,
188
+ terminal_state text,
189
+ terminal_result text,
190
+ terminal_occurred_at text,
191
+ created_at text not null,
192
+ updated_at text not null
193
+ );
194
+
195
+ create table if not exists source_idle_states (
196
+ source_id text primary key not null,
197
+ idle_since text not null,
198
+ auto_pause_at text not null,
199
+ auto_paused_at text,
200
+ updated_at text not null
201
+ );
202
+
203
+ create table if not exists timers (
204
+ schedule_id text primary key not null,
205
+ agent_id text not null,
206
+ status text not null,
207
+ mode text not null,
208
+ at text,
209
+ interval_ms integer,
210
+ cron_expr text,
211
+ timezone text not null,
212
+ message text not null,
213
+ sender text,
214
+ next_fire_at text,
215
+ last_fired_at text,
216
+ created_at text not null,
217
+ updated_at text not null
218
+ );
219
+
220
+ create table if not exists source_hosts (
221
+ host_id text primary key,
222
+ host_type text not null,
223
+ host_key text not null,
224
+ config_ref text,
225
+ config_json text not null,
226
+ status text not null,
227
+ created_at text not null,
228
+ updated_at text not null
229
+ );
230
+
231
+ create table if not exists digest_threads (
232
+ thread_id text primary key,
233
+ inbox_id text not null,
234
+ source_id text not null,
235
+ group_key text not null,
236
+ resource_ref text,
237
+ event_family text,
238
+ latest_revision integer not null,
239
+ latest_entry_id text,
240
+ status text not null,
241
+ summary text not null,
242
+ first_item_at text not null,
243
+ last_item_at text not null,
244
+ flush_after_at text,
245
+ created_at text not null,
246
+ updated_at text not null
247
+ );
248
+
249
+ create table if not exists inbox_entries (
250
+ entry_id text primary key,
251
+ inbox_id text not null,
252
+ kind text not null,
253
+ sequence integer not null unique,
254
+ thread_id text,
255
+ revision integer,
256
+ group_key text,
257
+ resource_ref text,
258
+ event_family text,
259
+ item_json text,
260
+ count integer not null,
261
+ summary text not null,
262
+ first_item_at text not null,
263
+ last_item_at text not null,
264
+ source_ids_json text not null,
265
+ subscription_ids_json text not null,
266
+ delivery_handle_json text,
267
+ acked_at text,
268
+ superseded_at text,
269
+ created_at text not null
270
+ );
271
+
272
+ create table if not exists inbox_entry_items (
273
+ entry_id text not null,
274
+ item_id text not null,
275
+ primary key (entry_id, item_id)
276
+ );
277
+
278
+ create table if not exists digest_thread_items (
279
+ thread_id text not null,
280
+ item_id text not null,
281
+ primary key (thread_id, item_id)
282
+ );
283
+
168
284
  create unique index if not exists idx_activation_targets_terminal_tmux
169
285
  on activation_targets(tmux_pane_id)
170
286
  where kind = 'terminal' and tmux_pane_id is not null;
@@ -204,3 +320,63 @@ create index if not exists idx_consumers_stream
204
320
  create index if not exists idx_consumer_commits_consumer
205
321
  on consumer_commits(consumer_id, committed_offset);
206
322
 
323
+ create index if not exists idx_subscription_lifecycle_retirements_retire_at
324
+ on subscription_lifecycle_retirements(retire_at);
325
+
326
+ create index if not exists idx_source_idle_states_auto_pause_at
327
+ on source_idle_states(auto_pause_at);
328
+
329
+ create index if not exists idx_timers_agent_next_fire_at
330
+ on timers(agent_id, next_fire_at);
331
+
332
+ create index if not exists idx_timers_next_fire_at
333
+ on timers(next_fire_at);
334
+
335
+ create unique index if not exists idx_source_hosts_type_key
336
+ on source_hosts(host_type, host_key);
337
+
338
+ create unique index if not exists idx_sources_host_stream_key
339
+ on sources(host_id, stream_kind, stream_key);
340
+
341
+ create index if not exists idx_sources_type_key
342
+ on sources(source_type, source_key);
343
+
344
+ create unique index if not exists idx_inbox_items_inbox_sequence
345
+ on inbox_items(inbox_sequence);
346
+
347
+ create index if not exists idx_inbox_items_inbox_sequence_order
348
+ on inbox_items(inbox_id, inbox_sequence);
349
+
350
+ create index if not exists idx_inbox_items_source_occurred_at
351
+ on inbox_items(source_id, occurred_at);
352
+
353
+ create index if not exists idx_digest_threads_inbox_status
354
+ on digest_threads(inbox_id, status);
355
+
356
+ create index if not exists idx_digest_threads_group_status
357
+ on digest_threads(inbox_id, group_key, status);
358
+
359
+ create index if not exists idx_digest_threads_flush_after
360
+ on digest_threads(status, flush_after_at);
361
+
362
+ create index if not exists idx_inbox_entries_inbox_visible
363
+ on inbox_entries(inbox_id, acked_at, superseded_at, sequence);
364
+
365
+ create index if not exists idx_inbox_entries_thread_revision
366
+ on inbox_entries(thread_id, revision);
367
+
368
+ create index if not exists idx_inbox_entry_items_item
369
+ on inbox_entry_items(item_id);
370
+
371
+ create index if not exists idx_digest_thread_items_item
372
+ on digest_thread_items(item_id);
373
+
374
+ create trigger if not exists trg_inbox_items_set_sequence_after_insert
375
+ after insert on inbox_items
376
+ for each row
377
+ when new.inbox_sequence is null
378
+ begin
379
+ update inbox_items
380
+ set inbox_sequence = new.rowid
381
+ where rowid = new.rowid;
382
+ end;
package/drizzle/schema.ts CHANGED
@@ -1,7 +1,23 @@
1
1
  import { index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
2
2
 
3
+ export const sourceHosts = sqliteTable("source_hosts", {
4
+ hostId: text("host_id").primaryKey(),
5
+ hostType: text("host_type").notNull(),
6
+ hostKey: text("host_key").notNull(),
7
+ configRef: text("config_ref"),
8
+ configJson: text("config_json").notNull(),
9
+ status: text("status").notNull(),
10
+ createdAt: text("created_at").notNull(),
11
+ updatedAt: text("updated_at").notNull(),
12
+ }, (table) => ({
13
+ hostTypeKey: uniqueIndex("idx_source_hosts_type_key").on(table.hostType, table.hostKey),
14
+ }));
15
+
3
16
  export const sources = sqliteTable("sources", {
4
17
  sourceId: text("source_id").primaryKey(),
18
+ hostId: text("host_id").notNull(),
19
+ streamKind: text("stream_kind").notNull(),
20
+ streamKey: text("stream_key").notNull(),
5
21
  sourceType: text("source_type").notNull(),
6
22
  sourceKey: text("source_key").notNull(),
7
23
  configRef: text("config_ref"),
@@ -11,7 +27,8 @@ export const sources = sqliteTable("sources", {
11
27
  createdAt: text("created_at").notNull(),
12
28
  updatedAt: text("updated_at").notNull(),
13
29
  }, (table) => ({
14
- sourceTypeKey: uniqueIndex("idx_sources_type_key").on(table.sourceType, table.sourceKey),
30
+ sourceTypeKey: index("idx_sources_type_key").on(table.sourceType, table.sourceKey),
31
+ hostStreamKey: uniqueIndex("idx_sources_host_stream_key").on(table.hostId, table.streamKind, table.streamKey),
15
32
  }));
16
33
 
17
34
  export const sourceIdleStates = sqliteTable("source_idle_states", {
@@ -60,6 +77,10 @@ export const agents = sqliteTable("agents", {
60
77
  export const inboxes = sqliteTable("inboxes", {
61
78
  inboxId: text("inbox_id").primaryKey(),
62
79
  ownerAgentId: text("owner_agent_id").notNull(),
80
+ aggregationEnabled: integer("aggregation_enabled").notNull().default(0),
81
+ aggregationWindowMs: integer("aggregation_window_ms"),
82
+ aggregationMaxItems: integer("aggregation_max_items"),
83
+ aggregationMaxThreadAgeMs: integer("aggregation_max_thread_age_ms"),
63
84
  createdAt: text("created_at").notNull(),
64
85
  }, (table) => ({
65
86
  ownerAgentIdUnique: uniqueIndex("idx_inboxes_owner_agent_id").on(table.ownerAgentId),
@@ -101,6 +122,7 @@ export const activationTargets = sqliteTable("activation_targets", {
101
122
  lastError: text("last_error"),
102
123
  mode: text("mode").notNull(),
103
124
  notifyLeaseMs: integer("notify_lease_ms").notNull(),
125
+ minUnackedItems: integer("min_unacked_items"),
104
126
  url: text("url"),
105
127
  runtimeKind: text("runtime_kind"),
106
128
  runtimeSessionId: text("runtime_session_id"),
@@ -123,6 +145,11 @@ export const activationDispatchStates = sqliteTable("activation_dispatch_states"
123
145
  status: text("status").notNull(),
124
146
  leaseExpiresAt: text("lease_expires_at"),
125
147
  lastNotifiedFingerprint: text("last_notified_fingerprint"),
148
+ deferReason: text("defer_reason"),
149
+ deferAttempts: integer("defer_attempts").notNull().default(0),
150
+ firstDeferredAt: text("first_deferred_at"),
151
+ lastDeferredAt: text("last_deferred_at"),
152
+ pendingFingerprint: text("pending_fingerprint"),
126
153
  pendingNewItemCount: integer("pending_new_item_count").notNull(),
127
154
  pendingSummary: text("pending_summary"),
128
155
  pendingSubscriptionIdsJson: text("pending_subscription_ids_json").notNull(),
@@ -155,6 +182,71 @@ export const inboxItems = sqliteTable("inbox_items", {
155
182
  sourceOccurredAtIdx: index("idx_inbox_items_source_occurred_at").on(table.sourceId, table.occurredAt),
156
183
  }));
157
184
 
185
+ export const digestThreads = sqliteTable("digest_threads", {
186
+ threadId: text("thread_id").primaryKey(),
187
+ inboxId: text("inbox_id").notNull(),
188
+ sourceId: text("source_id").notNull(),
189
+ groupKey: text("group_key").notNull(),
190
+ resourceRef: text("resource_ref"),
191
+ eventFamily: text("event_family"),
192
+ latestRevision: integer("latest_revision").notNull(),
193
+ latestEntryId: text("latest_entry_id"),
194
+ status: text("status").notNull(),
195
+ summary: text("summary").notNull(),
196
+ firstItemAt: text("first_item_at").notNull(),
197
+ lastItemAt: text("last_item_at").notNull(),
198
+ flushAfterAt: text("flush_after_at"),
199
+ createdAt: text("created_at").notNull(),
200
+ updatedAt: text("updated_at").notNull(),
201
+ }, (table) => ({
202
+ inboxStatusIdx: index("idx_digest_threads_inbox_status").on(table.inboxId, table.status),
203
+ groupStatusIdx: index("idx_digest_threads_group_status").on(table.inboxId, table.groupKey, table.status),
204
+ flushIdx: index("idx_digest_threads_flush_after").on(table.status, table.flushAfterAt),
205
+ }));
206
+
207
+ export const inboxEntries = sqliteTable("inbox_entries", {
208
+ entryId: text("entry_id").primaryKey(),
209
+ inboxId: text("inbox_id").notNull(),
210
+ kind: text("kind").notNull(),
211
+ sequence: integer("sequence").notNull(),
212
+ threadId: text("thread_id"),
213
+ revision: integer("revision"),
214
+ groupKey: text("group_key"),
215
+ resourceRef: text("resource_ref"),
216
+ eventFamily: text("event_family"),
217
+ itemJson: text("item_json"),
218
+ count: integer("count").notNull(),
219
+ summary: text("summary").notNull(),
220
+ firstItemAt: text("first_item_at").notNull(),
221
+ lastItemAt: text("last_item_at").notNull(),
222
+ sourceIdsJson: text("source_ids_json").notNull(),
223
+ subscriptionIdsJson: text("subscription_ids_json").notNull(),
224
+ deliveryHandleJson: text("delivery_handle_json"),
225
+ ackedAt: text("acked_at"),
226
+ supersededAt: text("superseded_at"),
227
+ createdAt: text("created_at").notNull(),
228
+ }, (table) => ({
229
+ sequenceUnique: uniqueIndex("idx_inbox_entries_sequence").on(table.sequence),
230
+ inboxVisibleIdx: index("idx_inbox_entries_inbox_visible").on(table.inboxId, table.ackedAt, table.supersededAt, table.sequence),
231
+ threadRevisionIdx: index("idx_inbox_entries_thread_revision").on(table.threadId, table.revision),
232
+ }));
233
+
234
+ export const inboxEntryItems = sqliteTable("inbox_entry_items", {
235
+ entryId: text("entry_id").notNull(),
236
+ itemId: text("item_id").notNull(),
237
+ }, (table) => ({
238
+ pk: primaryKey({ columns: [table.entryId, table.itemId] }),
239
+ itemIdx: index("idx_inbox_entry_items_item").on(table.itemId),
240
+ }));
241
+
242
+ export const digestThreadItems = sqliteTable("digest_thread_items", {
243
+ threadId: text("thread_id").notNull(),
244
+ itemId: text("item_id").notNull(),
245
+ }, (table) => ({
246
+ pk: primaryKey({ columns: [table.threadId, table.itemId] }),
247
+ itemIdx: index("idx_digest_thread_items_item").on(table.itemId),
248
+ }));
249
+
158
250
  export const activations = sqliteTable("activations", {
159
251
  activationId: text("activation_id").primaryKey(),
160
252
  kind: text("kind").notNull(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holon-run/agentinbox",
3
- "version": "0.7.0",
3
+ "version": "1.0.0-beta.0",
4
4
  "description": "Local event subscription and delivery service for agents.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,6 +38,7 @@
38
38
  "docs:deploy": "npm run docs:index && npm run docs:build && npx wrangler@4 deploy --config wrangler.jsonc",
39
39
  "db:migrations:generate": "drizzle-kit generate --config drizzle.config.ts",
40
40
  "prepack": "npm run build",
41
+ "smoke:release": "bash scripts/release_smoke.sh",
41
42
  "test": "node --require ts-node/register --test --test-force-exit test/*.test.ts"
42
43
  },
43
44
  "dependencies": {
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "$#" -ne 1 ]; then
5
+ echo "usage: scripts/release_smoke.sh <packed-tarball>" >&2
6
+ exit 1
7
+ fi
8
+
9
+ PACK_FILE="$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
10
+ if [ ! -f "$PACK_FILE" ]; then
11
+ echo "packed tarball not found: $PACK_FILE" >&2
12
+ exit 1
13
+ fi
14
+
15
+ TMP_BASE="${TMPDIR:-/tmp}"
16
+ TMP_ROOT="$(mktemp -d "$TMP_BASE/aixsmk.XXXXXX")"
17
+ cleanup() {
18
+ rm -rf "$TMP_ROOT" >/dev/null 2>&1 || true
19
+ }
20
+ trap cleanup EXIT
21
+
22
+ PKG_ROOT="$TMP_ROOT/pkg"
23
+ HOME_DIR="$TMP_ROOT/home"
24
+ mkdir -p "$PKG_ROOT" "$HOME_DIR"
25
+
26
+ cd "$PKG_ROOT"
27
+ npm init -y >/dev/null 2>&1
28
+ npm install "$PACK_FILE" >/dev/null 2>&1
29
+
30
+ export HOME="$HOME_DIR"
31
+ unset AGENTINBOX_SOCKET
32
+ unset AGENTINBOX_URL
33
+ FRESH_HOME="$TMP_ROOT/f1"
34
+ LEGACY_HOME="$TMP_ROOT/f2"
35
+ CLI_HOME="$TMP_ROOT/f3"
36
+
37
+ echo "[smoke] fresh install"
38
+ export AGENTINBOX_HOME="$FRESH_HOME"
39
+ DB_PATH="$AGENTINBOX_HOME/agentinbox.sqlite"
40
+ npx agentinbox --version >/dev/null
41
+ npx agentinbox daemon start >/dev/null
42
+ npx agentinbox host list >/dev/null
43
+ [ -f "$DB_PATH" ]
44
+
45
+ echo "[smoke] archive pre-v1 database and start fresh"
46
+ export AGENTINBOX_HOME="$LEGACY_HOME"
47
+ DB_PATH="$AGENTINBOX_HOME/agentinbox.sqlite"
48
+ rm -rf "$AGENTINBOX_HOME"
49
+ mkdir -p "$AGENTINBOX_HOME"
50
+ python3 - "$DB_PATH" <<'PY'
51
+ import sqlite3
52
+ import sys
53
+
54
+ db_path = sys.argv[1]
55
+ conn = sqlite3.connect(db_path)
56
+ conn.execute("create table sources (source_id text primary key, source_type text, source_key text)")
57
+ conn.commit()
58
+ conn.close()
59
+ PY
60
+ npx agentinbox daemon start >/dev/null
61
+ npx agentinbox host list >/dev/null
62
+ [ -f "$DB_PATH" ]
63
+ find "$AGENTINBOX_HOME" -maxdepth 1 -name 'agentinbox.sqlite.pre-v1.*.bak*' | grep -q .
64
+
65
+ echo "[smoke] canonical v1 CLI and HTTP surfaces"
66
+ export AGENTINBOX_HOME="$CLI_HOME"
67
+ DB_PATH="$AGENTINBOX_HOME/agentinbox.sqlite"
68
+ npx agentinbox daemon start >/dev/null
69
+ HOST_JSON="$(npx agentinbox host add local_event smoke-local)"
70
+ HOST_ID="$(printf '%s' "$HOST_JSON" | node -e 'let input=""; process.stdin.on("data", (chunk) => input += chunk); process.stdin.on("end", () => process.stdout.write(JSON.parse(input).hostId));')"
71
+ SOURCE_JSON="$(npx agentinbox source add "$HOST_ID" events smoke-local)"
72
+ SOURCE_ID="$(printf '%s' "$SOURCE_JSON" | node -e 'let input=""; process.stdin.on("data", (chunk) => input += chunk); process.stdin.on("end", () => process.stdout.write(JSON.parse(input).sourceId));')"
73
+ npx agentinbox source schema "$SOURCE_ID" >/dev/null
74
+
75
+ node - "$AGENTINBOX_HOME/agentinbox.sock" <<'PY'
76
+ const http = require("http");
77
+
78
+ const socketPath = process.argv[2];
79
+
80
+ function request(pathname) {
81
+ return new Promise((resolve, reject) => {
82
+ const req = http.request({
83
+ agent: false,
84
+ socketPath,
85
+ path: pathname,
86
+ method: "GET",
87
+ }, (res) => {
88
+ let body = "";
89
+ res.setEncoding("utf8");
90
+ res.on("data", (chunk) => {
91
+ body += chunk;
92
+ });
93
+ res.on("end", () => resolve({ statusCode: res.statusCode ?? 0, body }));
94
+ });
95
+ req.on("error", reject);
96
+ req.end();
97
+ });
98
+ }
99
+
100
+ (async () => {
101
+ const health = await request("/healthz");
102
+ if (health.statusCode !== 200) {
103
+ throw new Error(`healthz failed: ${health.statusCode}`);
104
+ }
105
+ const openapi = await request("/openapi.json");
106
+ if (openapi.statusCode !== 200) {
107
+ throw new Error(`openapi failed: ${openapi.statusCode}`);
108
+ }
109
+ const schema = JSON.parse(openapi.body);
110
+ const raw = JSON.stringify(schema);
111
+ if (raw.includes("/agents/{agentId}/inbox/raw-items")) {
112
+ throw new Error("legacy raw-items endpoint still exposed");
113
+ }
114
+ if (raw.includes("throughItemId") || raw.includes("afterItemId")) {
115
+ throw new Error("legacy inbox paging fields still exposed");
116
+ }
117
+ })().catch((error) => {
118
+ console.error(error);
119
+ process.exit(1);
120
+ });
121
+ PY
122
+
123
+ echo "[smoke] ok"
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.profileConfigForSource = exports.builtInProfileIdForSourceType = exports.RemoteSourceProfileRegistry = exports.moduleConfigForSource = exports.builtInModuleIdForSourceType = exports.RemoteSourceModuleRegistry = void 0;
4
- var remote_modules_1 = require("./remote_modules");
5
- Object.defineProperty(exports, "RemoteSourceModuleRegistry", { enumerable: true, get: function () { return remote_modules_1.RemoteSourceModuleRegistry; } });
6
- Object.defineProperty(exports, "builtInModuleIdForSourceType", { enumerable: true, get: function () { return remote_modules_1.builtInModuleIdForSourceType; } });
7
- Object.defineProperty(exports, "moduleConfigForSource", { enumerable: true, get: function () { return remote_modules_1.moduleConfigForSource; } });
8
- const remote_modules_2 = require("./remote_modules");
9
- class RemoteSourceProfileRegistry extends remote_modules_2.RemoteSourceModuleRegistry {
10
- }
11
- exports.RemoteSourceProfileRegistry = RemoteSourceProfileRegistry;
12
- exports.builtInProfileIdForSourceType = remote_modules_2.builtInModuleIdForSourceType;
13
- exports.profileConfigForSource = remote_modules_2.moduleConfigForSource;
@@ -1,3 +0,0 @@
1
- create index if not exists idx_inbox_items_source_occurred_at
2
- on inbox_items(source_id, occurred_at);
3
-