@holon-run/agentinbox 0.6.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.
- package/README.md +32 -7
- package/dist/src/adapters.js +61 -6
- package/dist/src/backend.js +4 -16
- package/dist/src/cli.js +307 -53
- package/dist/src/client.js +2 -2
- package/dist/src/daemon.js +36 -2
- package/dist/src/http.js +467 -45
- package/dist/src/logging.js +112 -0
- package/dist/src/paths.js +1 -0
- package/dist/src/runtime_gate.js +394 -63
- package/dist/src/service.js +856 -154
- package/dist/src/source_hosts.js +75 -0
- package/dist/src/source_resolution.js +20 -20
- package/dist/src/source_schema.js +2 -2
- package/dist/src/sources/feishu.js +66 -28
- package/dist/src/sources/github.js +188 -24
- package/dist/src/sources/remote.js +44 -24
- package/dist/src/sources/{remote_profiles.js → remote_modules.js} +143 -58
- package/dist/src/store.js +861 -106
- package/dist/src/terminal.js +33 -7
- package/dist/src/util.js +46 -1
- package/drizzle/migrations/{0000_initial.sql → 0000_v1_initial.sql} +182 -6
- package/drizzle/schema.ts +93 -1
- package/package.json +2 -1
- package/scripts/iterm2_cursor_probe.py +8 -3
- package/scripts/release_smoke.sh +123 -0
- package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +0 -3
- package/drizzle/migrations/0002_subscription_cleanup_policy.sql +0 -45
- package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +0 -14
- package/drizzle/migrations/0004_source_idle_states.sql +0 -10
- package/drizzle/migrations/0005_timers.sql +0 -22
- package/drizzle/migrations/0006_terminal_runtime_pid.sql +0 -1
- package/drizzle/migrations/0007_activation_dispatch_last_notified_fingerprint.sql +0 -2
- package/drizzle/migrations/0008_inbox_item_sequence.sql +0 -21
package/dist/src/terminal.js
CHANGED
|
@@ -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
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
105
|
-
return `
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
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:
|
|
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.
|
|
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": {
|
|
@@ -8,6 +8,11 @@ import sys
|
|
|
8
8
|
import json
|
|
9
9
|
import asyncio
|
|
10
10
|
|
|
11
|
+
# Keep this value in sync with the TypeScript probe normalization constant
|
|
12
|
+
# `NORMALIZED_BUFFER_TAIL_LINES`, since both are intended to mirror the same
|
|
13
|
+
# CLI/probe tail window.
|
|
14
|
+
CAPTURE_TAIL_LINES = 20
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
def probe_session(connection, session_id):
|
|
13
18
|
"""Probe iTerm2 session state and return cursor position with buffer contents."""
|
|
@@ -25,9 +30,9 @@ def probe_session(connection, session_id):
|
|
|
25
30
|
screen = await session.async_get_screen_contents()
|
|
26
31
|
cursor = screen.cursor_coord
|
|
27
32
|
|
|
28
|
-
#
|
|
33
|
+
# Capture a tail comparable to the CLI probe window.
|
|
29
34
|
lines = []
|
|
30
|
-
start_line = max(0, screen.number_of_lines -
|
|
35
|
+
start_line = max(0, screen.number_of_lines - CAPTURE_TAIL_LINES)
|
|
31
36
|
for i in range(start_line, screen.number_of_lines):
|
|
32
37
|
try:
|
|
33
38
|
line = screen.line(i)
|
|
@@ -83,4 +88,4 @@ def main():
|
|
|
83
88
|
|
|
84
89
|
|
|
85
90
|
if __name__ == "__main__":
|
|
86
|
-
main()
|
|
91
|
+
main()
|
|
@@ -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"
|