@holon-run/agentinbox 0.7.0 → 1.0.0-beta.1

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.
Files changed (33) hide show
  1. package/README.md +36 -7
  2. package/dist/src/adapters.js +55 -0
  3. package/dist/src/backend.js +4 -16
  4. package/dist/src/cli.js +329 -52
  5. package/dist/src/client.js +2 -2
  6. package/dist/src/http.js +479 -55
  7. package/dist/src/runtime_gate.js +49 -41
  8. package/dist/src/service.js +922 -200
  9. package/dist/src/source_hosts.js +75 -0
  10. package/dist/src/source_resolution.js +2 -2
  11. package/dist/src/source_schema.js +2 -2
  12. package/dist/src/sources/feishu.js +66 -28
  13. package/dist/src/sources/github.js +222 -36
  14. package/dist/src/sources/remote.js +50 -3
  15. package/dist/src/sources/remote_modules.js +100 -15
  16. package/dist/src/store.js +875 -110
  17. package/dist/src/terminal.js +33 -7
  18. package/dist/src/util.js +46 -1
  19. package/drizzle/migrations/{0000_initial.sql → 0000_v1_initial.sql} +182 -6
  20. package/drizzle/migrations/0002_host_scoped_lifecycle_retirements.sql +42 -0
  21. package/drizzle/migrations/0003_subscription_tracked_resource_indexes.sql +5 -0
  22. package/drizzle/schema.ts +98 -3
  23. package/package.json +2 -1
  24. package/scripts/release_smoke.sh +123 -0
  25. package/dist/src/sources/remote_profiles.js +0 -13
  26. package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +0 -3
  27. package/drizzle/migrations/0002_subscription_cleanup_policy.sql +0 -45
  28. package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +0 -14
  29. package/drizzle/migrations/0004_source_idle_states.sql +0 -10
  30. package/drizzle/migrations/0005_timers.sql +0 -22
  31. package/drizzle/migrations/0006_terminal_runtime_pid.sql +0 -1
  32. package/drizzle/migrations/0007_activation_dispatch_last_notified_fingerprint.sql +0 -2
  33. package/drizzle/migrations/0008_inbox_item_sequence.sql +0 -21
@@ -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;
@@ -0,0 +1,42 @@
1
+ pragma foreign_keys=off;
2
+
3
+ create table if not exists subscription_lifecycle_retirements__new (
4
+ subscription_id text primary key,
5
+ host_id text not null,
6
+ tracked_resource_ref text not null,
7
+ retire_at text not null,
8
+ terminal_state text,
9
+ terminal_result text,
10
+ terminal_occurred_at text not null,
11
+ created_at text not null,
12
+ updated_at text not null
13
+ );
14
+
15
+ insert into subscription_lifecycle_retirements__new (
16
+ subscription_id,
17
+ host_id,
18
+ tracked_resource_ref,
19
+ retire_at,
20
+ terminal_state,
21
+ terminal_result,
22
+ terminal_occurred_at,
23
+ created_at,
24
+ updated_at
25
+ )
26
+ select
27
+ retirement.subscription_id,
28
+ coalesce(source.host_id, retirement.source_id),
29
+ retirement.tracked_resource_ref,
30
+ retirement.retire_at,
31
+ retirement.terminal_state,
32
+ retirement.terminal_result,
33
+ retirement.terminal_occurred_at,
34
+ retirement.created_at,
35
+ retirement.updated_at
36
+ from subscription_lifecycle_retirements as retirement
37
+ left join sources as source on source.source_id = retirement.source_id;
38
+
39
+ drop table subscription_lifecycle_retirements;
40
+ alter table subscription_lifecycle_retirements__new rename to subscription_lifecycle_retirements;
41
+
42
+ pragma foreign_keys=on;
@@ -0,0 +1,5 @@
1
+ create index if not exists idx_subscriptions_tracked_resource_ref
2
+ on subscriptions(tracked_resource_ref);
3
+
4
+ create index if not exists idx_subscriptions_tracked_resource_source
5
+ on subscriptions(tracked_resource_ref, source_id);
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),
@@ -76,11 +97,14 @@ export const subscriptions = sqliteTable("subscriptions", {
76
97
  startOffset: integer("start_offset"),
77
98
  startTime: text("start_time"),
78
99
  createdAt: text("created_at").notNull(),
79
- });
100
+ }, (table) => ({
101
+ trackedResourceRefIdx: index("idx_subscriptions_tracked_resource_ref").on(table.trackedResourceRef),
102
+ trackedResourceRefSourceIdx: index("idx_subscriptions_tracked_resource_source").on(table.trackedResourceRef, table.sourceId),
103
+ }));
80
104
 
81
105
  export const subscriptionLifecycleRetirements = sqliteTable("subscription_lifecycle_retirements", {
82
106
  subscriptionId: text("subscription_id").primaryKey(),
83
- sourceId: text("source_id").notNull(),
107
+ hostId: text("host_id").notNull(),
84
108
  trackedResourceRef: text("tracked_resource_ref").notNull(),
85
109
  retireAt: text("retire_at").notNull(),
86
110
  terminalState: text("terminal_state"),
@@ -101,6 +125,7 @@ export const activationTargets = sqliteTable("activation_targets", {
101
125
  lastError: text("last_error"),
102
126
  mode: text("mode").notNull(),
103
127
  notifyLeaseMs: integer("notify_lease_ms").notNull(),
128
+ minUnackedItems: integer("min_unacked_items"),
104
129
  url: text("url"),
105
130
  runtimeKind: text("runtime_kind"),
106
131
  runtimeSessionId: text("runtime_session_id"),
@@ -123,6 +148,11 @@ export const activationDispatchStates = sqliteTable("activation_dispatch_states"
123
148
  status: text("status").notNull(),
124
149
  leaseExpiresAt: text("lease_expires_at"),
125
150
  lastNotifiedFingerprint: text("last_notified_fingerprint"),
151
+ deferReason: text("defer_reason"),
152
+ deferAttempts: integer("defer_attempts").notNull().default(0),
153
+ firstDeferredAt: text("first_deferred_at"),
154
+ lastDeferredAt: text("last_deferred_at"),
155
+ pendingFingerprint: text("pending_fingerprint"),
126
156
  pendingNewItemCount: integer("pending_new_item_count").notNull(),
127
157
  pendingSummary: text("pending_summary"),
128
158
  pendingSubscriptionIdsJson: text("pending_subscription_ids_json").notNull(),
@@ -155,6 +185,71 @@ export const inboxItems = sqliteTable("inbox_items", {
155
185
  sourceOccurredAtIdx: index("idx_inbox_items_source_occurred_at").on(table.sourceId, table.occurredAt),
156
186
  }));
157
187
 
188
+ export const digestThreads = sqliteTable("digest_threads", {
189
+ threadId: text("thread_id").primaryKey(),
190
+ inboxId: text("inbox_id").notNull(),
191
+ sourceId: text("source_id").notNull(),
192
+ groupKey: text("group_key").notNull(),
193
+ resourceRef: text("resource_ref"),
194
+ eventFamily: text("event_family"),
195
+ latestRevision: integer("latest_revision").notNull(),
196
+ latestEntryId: text("latest_entry_id"),
197
+ status: text("status").notNull(),
198
+ summary: text("summary").notNull(),
199
+ firstItemAt: text("first_item_at").notNull(),
200
+ lastItemAt: text("last_item_at").notNull(),
201
+ flushAfterAt: text("flush_after_at"),
202
+ createdAt: text("created_at").notNull(),
203
+ updatedAt: text("updated_at").notNull(),
204
+ }, (table) => ({
205
+ inboxStatusIdx: index("idx_digest_threads_inbox_status").on(table.inboxId, table.status),
206
+ groupStatusIdx: index("idx_digest_threads_group_status").on(table.inboxId, table.groupKey, table.status),
207
+ flushIdx: index("idx_digest_threads_flush_after").on(table.status, table.flushAfterAt),
208
+ }));
209
+
210
+ export const inboxEntries = sqliteTable("inbox_entries", {
211
+ entryId: text("entry_id").primaryKey(),
212
+ inboxId: text("inbox_id").notNull(),
213
+ kind: text("kind").notNull(),
214
+ sequence: integer("sequence").notNull(),
215
+ threadId: text("thread_id"),
216
+ revision: integer("revision"),
217
+ groupKey: text("group_key"),
218
+ resourceRef: text("resource_ref"),
219
+ eventFamily: text("event_family"),
220
+ itemJson: text("item_json"),
221
+ count: integer("count").notNull(),
222
+ summary: text("summary").notNull(),
223
+ firstItemAt: text("first_item_at").notNull(),
224
+ lastItemAt: text("last_item_at").notNull(),
225
+ sourceIdsJson: text("source_ids_json").notNull(),
226
+ subscriptionIdsJson: text("subscription_ids_json").notNull(),
227
+ deliveryHandleJson: text("delivery_handle_json"),
228
+ ackedAt: text("acked_at"),
229
+ supersededAt: text("superseded_at"),
230
+ createdAt: text("created_at").notNull(),
231
+ }, (table) => ({
232
+ sequenceUnique: uniqueIndex("idx_inbox_entries_sequence").on(table.sequence),
233
+ inboxVisibleIdx: index("idx_inbox_entries_inbox_visible").on(table.inboxId, table.ackedAt, table.supersededAt, table.sequence),
234
+ threadRevisionIdx: index("idx_inbox_entries_thread_revision").on(table.threadId, table.revision),
235
+ }));
236
+
237
+ export const inboxEntryItems = sqliteTable("inbox_entry_items", {
238
+ entryId: text("entry_id").notNull(),
239
+ itemId: text("item_id").notNull(),
240
+ }, (table) => ({
241
+ pk: primaryKey({ columns: [table.entryId, table.itemId] }),
242
+ itemIdx: index("idx_inbox_entry_items_item").on(table.itemId),
243
+ }));
244
+
245
+ export const digestThreadItems = sqliteTable("digest_thread_items", {
246
+ threadId: text("thread_id").notNull(),
247
+ itemId: text("item_id").notNull(),
248
+ }, (table) => ({
249
+ pk: primaryKey({ columns: [table.threadId, table.itemId] }),
250
+ itemIdx: index("idx_digest_thread_items_item").on(table.itemId),
251
+ }));
252
+
158
253
  export const activations = sqliteTable("activations", {
159
254
  activationId: text("activation_id").primaryKey(),
160
255
  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.1",
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": {