@pcircle/memesh 4.7.3 → 4.8.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +3 -1
- package/README.de.md +42 -16
- package/README.md +46 -19
- package/README.zh-TW.md +42 -17
- package/dashboard/dist/index.html +1 -1
- package/dist/core/agent-message-storage.d.ts +76 -0
- package/dist/core/agent-message-storage.d.ts.map +1 -0
- package/dist/core/agent-message-storage.js +359 -0
- package/dist/core/agent-message-storage.js.map +1 -0
- package/dist/core/agent-messaging.d.ts +224 -0
- package/dist/core/agent-messaging.d.ts.map +1 -0
- package/dist/core/agent-messaging.js +881 -0
- package/dist/core/agent-messaging.js.map +1 -0
- package/dist/core/agent-router.d.ts +194 -0
- package/dist/core/agent-router.d.ts.map +1 -0
- package/dist/core/agent-router.js +1138 -0
- package/dist/core/agent-router.js.map +1 -0
- package/dist/core/briefing.d.ts.map +1 -1
- package/dist/core/briefing.js +8 -1
- package/dist/core/briefing.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/doctor.d.ts +19 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +176 -2
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +8 -5
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +88 -3
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/product-improvements.d.ts +61 -0
- package/dist/core/product-improvements.d.ts.map +1 -0
- package/dist/core/product-improvements.js +216 -0
- package/dist/core/product-improvements.js.map +1 -0
- package/dist/core/repo-state.d.ts +11 -0
- package/dist/core/repo-state.d.ts.map +1 -0
- package/dist/core/repo-state.js +70 -0
- package/dist/core/repo-state.js.map +1 -0
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +70 -0
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/task-state.d.ts.map +1 -1
- package/dist/core/task-state.js +3 -3
- package/dist/core/task-state.js.map +1 -1
- package/dist/core/updater.d.ts +2 -0
- package/dist/core/updater.d.ts.map +1 -1
- package/dist/core/updater.js +11 -5
- package/dist/core/updater.js.map +1 -1
- package/dist/core/work-topology.d.ts.map +1 -1
- package/dist/core/work-topology.js +1 -0
- package/dist/core/work-topology.js.map +1 -1
- package/dist/host-adapters/acp-client.d.ts +147 -0
- package/dist/host-adapters/acp-client.d.ts.map +1 -0
- package/dist/host-adapters/acp-client.js +793 -0
- package/dist/host-adapters/acp-client.js.map +1 -0
- package/dist/host-adapters/claude-channel.d.ts +75 -0
- package/dist/host-adapters/claude-channel.d.ts.map +1 -0
- package/dist/host-adapters/claude-channel.js +255 -0
- package/dist/host-adapters/claude-channel.js.map +1 -0
- package/dist/host-adapters/codex-app-server.d.ts +83 -0
- package/dist/host-adapters/codex-app-server.d.ts.map +1 -0
- package/dist/host-adapters/codex-app-server.js +360 -0
- package/dist/host-adapters/codex-app-server.js.map +1 -0
- package/dist/host-adapters/codex-cli-queue.d.ts +17 -0
- package/dist/host-adapters/codex-cli-queue.d.ts.map +1 -0
- package/dist/host-adapters/codex-cli-queue.js +85 -0
- package/dist/host-adapters/codex-cli-queue.js.map +1 -0
- package/dist/host-runtime/acp.d.ts +57 -0
- package/dist/host-runtime/acp.d.ts.map +1 -0
- package/dist/host-runtime/acp.js +330 -0
- package/dist/host-runtime/acp.js.map +1 -0
- package/dist/host-runtime/claude.d.ts +41 -0
- package/dist/host-runtime/claude.d.ts.map +1 -0
- package/dist/host-runtime/claude.js +213 -0
- package/dist/host-runtime/claude.js.map +1 -0
- package/dist/host-runtime/codex-session.d.ts +24 -0
- package/dist/host-runtime/codex-session.d.ts.map +1 -0
- package/dist/host-runtime/codex-session.js +98 -0
- package/dist/host-runtime/codex-session.js.map +1 -0
- package/dist/host-runtime/codex.d.ts +33 -0
- package/dist/host-runtime/codex.d.ts.map +1 -0
- package/dist/host-runtime/codex.js +215 -0
- package/dist/host-runtime/codex.js.map +1 -0
- package/dist/host-runtime/config.d.ts +9 -0
- package/dist/host-runtime/config.d.ts.map +1 -0
- package/dist/host-runtime/config.js +91 -0
- package/dist/host-runtime/config.js.map +1 -0
- package/dist/host-runtime/router-client.d.ts +38 -0
- package/dist/host-runtime/router-client.d.ts.map +1 -0
- package/dist/host-runtime/router-client.js +463 -0
- package/dist/host-runtime/router-client.js.map +1 -0
- package/dist/host-runtime/router.d.ts +3 -0
- package/dist/host-runtime/router.d.ts.map +1 -0
- package/dist/host-runtime/router.js +43 -0
- package/dist/host-runtime/router.js.map +1 -0
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +22 -17
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +8 -1
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/schema.d.ts +1 -1
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/schema.js +255 -0
- package/dist/storage/schema.js.map +1 -1
- package/dist/transports/agent-messaging.d.ts +11 -0
- package/dist/transports/agent-messaging.d.ts.map +1 -0
- package/dist/transports/agent-messaging.js +273 -0
- package/dist/transports/agent-messaging.js.map +1 -0
- package/dist/transports/cli/cli.d.ts +1 -1
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +376 -12
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +31 -2
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +152 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +108 -3
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +106 -1
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +86 -0
- package/dist/transports/schemas.js.map +1 -1
- package/docs/platforms/README.md +167 -0
- package/docs/platforms/agent-messaging.md +290 -0
- package/hooks/hooks.json +11 -0
- package/llms-install.md +101 -25
- package/package.json +14 -5
- package/scripts/hooks/_generated/repo-state.js +77 -0
- package/scripts/hooks/_generated/schema.js +255 -0
- package/scripts/hooks/_generated/task-state.js +3 -3
- package/scripts/hooks/_generated/work-topology.js +1 -0
- package/scripts/hooks/_shared.js +77 -97
- package/scripts/hooks/auto-update-runner.mjs +243 -0
- package/scripts/hooks/session-start.js +12 -2
- package/scripts/hooks/session-summary.js +7 -6
- package/skills/memesh/SKILL.md +40 -1
|
@@ -69,6 +69,235 @@ CREATE TABLE IF NOT EXISTS memesh_metadata (
|
|
|
69
69
|
value TEXT NOT NULL
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
+
-- Local agent messaging core. Messages are immutable, deliveries identify the
|
|
73
|
+
-- authorized recipient, events are the wakeup/catch-up surface, cursors keep
|
|
74
|
+
-- the internal sequence opaque, and receipts are append-only facts that remain
|
|
75
|
+
-- separate from delivery existence.
|
|
76
|
+
CREATE TABLE IF NOT EXISTS agent_messages (
|
|
77
|
+
message_id TEXT PRIMARY KEY,
|
|
78
|
+
project TEXT NOT NULL,
|
|
79
|
+
sender TEXT NOT NULL,
|
|
80
|
+
sender_host TEXT,
|
|
81
|
+
recipient TEXT NOT NULL,
|
|
82
|
+
content_type TEXT NOT NULL,
|
|
83
|
+
correlation_id TEXT,
|
|
84
|
+
reply_to_message_id TEXT,
|
|
85
|
+
privacy TEXT NOT NULL,
|
|
86
|
+
payload_json TEXT NOT NULL,
|
|
87
|
+
-- A tombstone replaces only a terminal payload. The original JSON bytes
|
|
88
|
+
-- are never reconstructed from these fields; they retain enough identity to
|
|
89
|
+
-- audit the erasure while the routing and lifecycle tables remain intact.
|
|
90
|
+
payload_sha256 TEXT,
|
|
91
|
+
payload_original_bytes INTEGER,
|
|
92
|
+
payload_tombstoned_at TIMESTAMP,
|
|
93
|
+
provenance_json TEXT NOT NULL,
|
|
94
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
CREATE TABLE IF NOT EXISTS agent_message_deliveries (
|
|
98
|
+
delivery_id TEXT PRIMARY KEY,
|
|
99
|
+
message_id TEXT NOT NULL,
|
|
100
|
+
project TEXT NOT NULL,
|
|
101
|
+
recipient TEXT NOT NULL,
|
|
102
|
+
target_kind TEXT NOT NULL DEFAULT 'principal' CHECK (target_kind IN ('principal', 'session')),
|
|
103
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
104
|
+
FOREIGN KEY (message_id) REFERENCES agent_messages(message_id) ON DELETE CASCADE,
|
|
105
|
+
UNIQUE(message_id, project, recipient)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS agent_message_events (
|
|
109
|
+
event_sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
111
|
+
message_id TEXT NOT NULL,
|
|
112
|
+
delivery_id TEXT NOT NULL,
|
|
113
|
+
project TEXT NOT NULL,
|
|
114
|
+
recipient TEXT NOT NULL,
|
|
115
|
+
event_kind TEXT NOT NULL,
|
|
116
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
117
|
+
FOREIGN KEY (message_id) REFERENCES agent_messages(message_id) ON DELETE CASCADE,
|
|
118
|
+
FOREIGN KEY (delivery_id) REFERENCES agent_message_deliveries(delivery_id) ON DELETE CASCADE
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
CREATE TABLE IF NOT EXISTS agent_message_idempotency (
|
|
122
|
+
project TEXT NOT NULL,
|
|
123
|
+
sender TEXT NOT NULL,
|
|
124
|
+
idempotency_key TEXT NOT NULL,
|
|
125
|
+
request_hash TEXT NOT NULL,
|
|
126
|
+
message_id TEXT NOT NULL UNIQUE,
|
|
127
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
128
|
+
FOREIGN KEY (message_id) REFERENCES agent_messages(message_id) ON DELETE CASCADE,
|
|
129
|
+
PRIMARY KEY(project, sender, idempotency_key)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
CREATE TABLE IF NOT EXISTS agent_message_cursors (
|
|
133
|
+
cursor_token TEXT PRIMARY KEY,
|
|
134
|
+
project TEXT NOT NULL,
|
|
135
|
+
recipient TEXT NOT NULL,
|
|
136
|
+
event_sequence INTEGER NOT NULL,
|
|
137
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
CREATE TABLE IF NOT EXISTS agent_message_receipts (
|
|
141
|
+
receipt_id TEXT PRIMARY KEY,
|
|
142
|
+
message_id TEXT NOT NULL,
|
|
143
|
+
project TEXT NOT NULL,
|
|
144
|
+
recipient TEXT NOT NULL,
|
|
145
|
+
receipt_kind TEXT NOT NULL,
|
|
146
|
+
actor TEXT NOT NULL,
|
|
147
|
+
idempotency_key TEXT NOT NULL,
|
|
148
|
+
request_hash TEXT NOT NULL,
|
|
149
|
+
detail_json TEXT NOT NULL,
|
|
150
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
151
|
+
FOREIGN KEY (message_id) REFERENCES agent_messages(message_id) ON DELETE CASCADE,
|
|
152
|
+
UNIQUE(project, recipient, message_id, receipt_kind, idempotency_key)
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
-- Host-native push identity and lifecycle. A principal is stable, a session
|
|
156
|
+
-- instance is ephemeral, and every connection to that session gets a strictly
|
|
157
|
+
-- increasing generation. The activation checkpoint is intentionally created
|
|
158
|
+
-- at first principal registration: principal-targeted history at or before it
|
|
159
|
+
-- is durable inbox history, but is never replayed as a first-time host push.
|
|
160
|
+
CREATE TABLE IF NOT EXISTS agent_principals (
|
|
161
|
+
project TEXT NOT NULL,
|
|
162
|
+
principal_id TEXT NOT NULL,
|
|
163
|
+
activation_event_sequence INTEGER NOT NULL,
|
|
164
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
165
|
+
PRIMARY KEY(project, principal_id)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
CREATE TABLE IF NOT EXISTS agent_session_instances (
|
|
169
|
+
project TEXT NOT NULL,
|
|
170
|
+
session_instance_id TEXT NOT NULL,
|
|
171
|
+
principal_id TEXT NOT NULL,
|
|
172
|
+
adapter_kind TEXT NOT NULL,
|
|
173
|
+
last_generation INTEGER NOT NULL DEFAULT 0,
|
|
174
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
175
|
+
PRIMARY KEY(project, session_instance_id),
|
|
176
|
+
FOREIGN KEY (project, principal_id) REFERENCES agent_principals(project, principal_id)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
CREATE TABLE IF NOT EXISTS agent_session_connections (
|
|
180
|
+
connection_id TEXT PRIMARY KEY,
|
|
181
|
+
project TEXT NOT NULL,
|
|
182
|
+
principal_id TEXT NOT NULL,
|
|
183
|
+
session_instance_id TEXT NOT NULL,
|
|
184
|
+
generation INTEGER NOT NULL,
|
|
185
|
+
adapter_kind TEXT NOT NULL,
|
|
186
|
+
router_instance_id TEXT NOT NULL,
|
|
187
|
+
lease_expires_at_ms INTEGER NOT NULL,
|
|
188
|
+
connected_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
189
|
+
disconnected_at TIMESTAMP,
|
|
190
|
+
disconnect_reason TEXT,
|
|
191
|
+
UNIQUE(project, session_instance_id, generation),
|
|
192
|
+
FOREIGN KEY (project, session_instance_id)
|
|
193
|
+
REFERENCES agent_session_instances(project, session_instance_id)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
CREATE TABLE IF NOT EXISTS agent_presence_facts (
|
|
197
|
+
presence_fact_id TEXT PRIMARY KEY,
|
|
198
|
+
project TEXT NOT NULL,
|
|
199
|
+
principal_id TEXT NOT NULL,
|
|
200
|
+
session_instance_id TEXT NOT NULL,
|
|
201
|
+
connection_id TEXT NOT NULL,
|
|
202
|
+
generation INTEGER NOT NULL,
|
|
203
|
+
presence_kind TEXT NOT NULL CHECK (presence_kind IN ('connected', 'heartbeat', 'disconnected', 'superseded')),
|
|
204
|
+
detail_json TEXT NOT NULL,
|
|
205
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
206
|
+
FOREIGN KEY (connection_id) REFERENCES agent_session_connections(connection_id)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
-- Dispatch is at-least-once across a crash boundary. delivery_id is the stable
|
|
210
|
+
-- adapter dispatch key; attempt_id identifies one invocation. A host_accept is
|
|
211
|
+
-- a separate fact and never implies an agent acknowledgement or workflow
|
|
212
|
+
-- outcome.
|
|
213
|
+
CREATE TABLE IF NOT EXISTS agent_dispatch_attempts (
|
|
214
|
+
attempt_id TEXT PRIMARY KEY,
|
|
215
|
+
delivery_id TEXT NOT NULL,
|
|
216
|
+
project TEXT NOT NULL,
|
|
217
|
+
principal_id TEXT NOT NULL,
|
|
218
|
+
session_instance_id TEXT NOT NULL,
|
|
219
|
+
connection_id TEXT NOT NULL,
|
|
220
|
+
generation INTEGER NOT NULL,
|
|
221
|
+
router_instance_id TEXT NOT NULL,
|
|
222
|
+
attempt_number INTEGER NOT NULL,
|
|
223
|
+
result TEXT NOT NULL DEFAULT 'started' CHECK (result IN ('started', 'adapter_returned', 'adapter_rejected', 'adapter_failed', 'stale_generation')),
|
|
224
|
+
failure_code TEXT,
|
|
225
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
226
|
+
completed_at TIMESTAMP,
|
|
227
|
+
UNIQUE(delivery_id, attempt_number),
|
|
228
|
+
FOREIGN KEY (delivery_id) REFERENCES agent_message_deliveries(delivery_id) ON DELETE CASCADE,
|
|
229
|
+
FOREIGN KEY (connection_id) REFERENCES agent_session_connections(connection_id)
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
CREATE TABLE IF NOT EXISTS agent_host_accepts (
|
|
233
|
+
host_accept_id TEXT PRIMARY KEY,
|
|
234
|
+
attempt_id TEXT NOT NULL UNIQUE,
|
|
235
|
+
delivery_id TEXT NOT NULL UNIQUE,
|
|
236
|
+
adapter_kind TEXT NOT NULL,
|
|
237
|
+
receipt_json TEXT NOT NULL,
|
|
238
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
239
|
+
FOREIGN KEY (attempt_id) REFERENCES agent_dispatch_attempts(attempt_id),
|
|
240
|
+
FOREIGN KEY (delivery_id) REFERENCES agent_message_deliveries(delivery_id) ON DELETE CASCADE
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
CREATE TABLE IF NOT EXISTS agent_ack_facts (
|
|
244
|
+
ack_fact_id TEXT PRIMARY KEY,
|
|
245
|
+
delivery_id TEXT NOT NULL,
|
|
246
|
+
host_accept_id TEXT NOT NULL,
|
|
247
|
+
actor TEXT NOT NULL,
|
|
248
|
+
idempotency_key TEXT NOT NULL,
|
|
249
|
+
request_hash TEXT NOT NULL,
|
|
250
|
+
detail_json TEXT NOT NULL,
|
|
251
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
252
|
+
UNIQUE(delivery_id, actor, idempotency_key),
|
|
253
|
+
FOREIGN KEY (delivery_id) REFERENCES agent_message_deliveries(delivery_id) ON DELETE CASCADE,
|
|
254
|
+
FOREIGN KEY (host_accept_id) REFERENCES agent_host_accepts(host_accept_id)
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
CREATE TABLE IF NOT EXISTS agent_workflow_facts (
|
|
258
|
+
workflow_fact_id TEXT PRIMARY KEY,
|
|
259
|
+
delivery_id TEXT NOT NULL,
|
|
260
|
+
actor TEXT NOT NULL,
|
|
261
|
+
workflow_state TEXT NOT NULL,
|
|
262
|
+
idempotency_key TEXT NOT NULL,
|
|
263
|
+
request_hash TEXT NOT NULL,
|
|
264
|
+
detail_json TEXT NOT NULL,
|
|
265
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
266
|
+
UNIQUE(delivery_id, actor, idempotency_key),
|
|
267
|
+
FOREIGN KEY (delivery_id) REFERENCES agent_message_deliveries(delivery_id) ON DELETE CASCADE
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
CREATE TABLE IF NOT EXISTS agent_retention_facts (
|
|
271
|
+
retention_fact_id TEXT PRIMARY KEY,
|
|
272
|
+
message_id TEXT NOT NULL,
|
|
273
|
+
actor TEXT NOT NULL,
|
|
274
|
+
retention_state TEXT NOT NULL,
|
|
275
|
+
idempotency_key TEXT NOT NULL,
|
|
276
|
+
request_hash TEXT NOT NULL,
|
|
277
|
+
detail_json TEXT NOT NULL,
|
|
278
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
279
|
+
UNIQUE(message_id, actor, idempotency_key),
|
|
280
|
+
FOREIGN KEY (message_id) REFERENCES agent_messages(message_id) ON DELETE CASCADE
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_agent_message_events_recipient_sequence
|
|
284
|
+
ON agent_message_events(project, recipient, event_sequence);
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_agent_message_deliveries_scope
|
|
286
|
+
ON agent_message_deliveries(project, recipient, message_id);
|
|
287
|
+
CREATE INDEX IF NOT EXISTS idx_agent_message_receipts_scope
|
|
288
|
+
ON agent_message_receipts(project, recipient, message_id, created_at);
|
|
289
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_message_cursors_unique_scope_sequence
|
|
290
|
+
ON agent_message_cursors(project, recipient, event_sequence);
|
|
291
|
+
CREATE INDEX IF NOT EXISTS idx_agent_principals_activation
|
|
292
|
+
ON agent_principals(project, activation_event_sequence);
|
|
293
|
+
CREATE INDEX IF NOT EXISTS idx_agent_session_connections_active
|
|
294
|
+
ON agent_session_connections(project, principal_id, session_instance_id, lease_expires_at_ms)
|
|
295
|
+
WHERE disconnected_at IS NULL;
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_agent_dispatch_attempts_delivery
|
|
297
|
+
ON agent_dispatch_attempts(delivery_id, attempt_number);
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_agent_workflow_facts_delivery_created
|
|
299
|
+
ON agent_workflow_facts(delivery_id, created_at, workflow_fact_id);
|
|
300
|
+
|
|
72
301
|
-- Proof that a capture hook actually RAN. Nothing else in this schema can
|
|
73
302
|
-- give it: every other signal is "a row was written", and "the hook ran and
|
|
74
303
|
-- found nothing worth saving" is the healthy case that produces no row at
|
|
@@ -148,6 +377,32 @@ export function migrateEntitiesSchema(db) {
|
|
|
148
377
|
addColumn('title', "ALTER TABLE entities ADD COLUMN title TEXT");
|
|
149
378
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
150
379
|
CREATE INDEX IF NOT EXISTS idx_entities_namespace ON entities(namespace);`);
|
|
380
|
+
const deliveryTableExists = db
|
|
381
|
+
.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'agent_message_deliveries'")
|
|
382
|
+
.get();
|
|
383
|
+
if (!deliveryTableExists)
|
|
384
|
+
return;
|
|
385
|
+
const deliveryColumns = new Set(db.prepare("PRAGMA table_info(agent_message_deliveries)").all().map((column) => column.name));
|
|
386
|
+
if (!deliveryColumns.has('target_kind')) {
|
|
387
|
+
safeAlter(db, "ALTER TABLE agent_message_deliveries ADD COLUMN target_kind TEXT NOT NULL DEFAULT 'principal' " +
|
|
388
|
+
"CHECK (target_kind IN ('principal', 'session'))");
|
|
389
|
+
}
|
|
390
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_agent_message_deliveries_target
|
|
391
|
+
ON agent_message_deliveries(project, target_kind, recipient, message_id);`);
|
|
392
|
+
const messageColumns = new Set(db.prepare("PRAGMA table_info(agent_messages)").all().map((column) => column.name));
|
|
393
|
+
const addMessageColumn = (column, sql) => {
|
|
394
|
+
if (messageColumns.has(column))
|
|
395
|
+
return;
|
|
396
|
+
safeAlter(db, sql);
|
|
397
|
+
messageColumns.add(column);
|
|
398
|
+
};
|
|
399
|
+
addMessageColumn('payload_sha256', 'ALTER TABLE agent_messages ADD COLUMN payload_sha256 TEXT');
|
|
400
|
+
addMessageColumn('payload_original_bytes', 'ALTER TABLE agent_messages ADD COLUMN payload_original_bytes INTEGER');
|
|
401
|
+
addMessageColumn('payload_tombstoned_at', 'ALTER TABLE agent_messages ADD COLUMN payload_tombstoned_at TIMESTAMP');
|
|
402
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_agent_messages_tombstone
|
|
403
|
+
ON agent_messages(payload_tombstoned_at, created_at);
|
|
404
|
+
CREATE INDEX IF NOT EXISTS idx_agent_workflow_facts_delivery_created
|
|
405
|
+
ON agent_workflow_facts(delivery_id, created_at, workflow_fact_id);`);
|
|
151
406
|
}
|
|
152
407
|
export function ensureTagsUniqueIndex(db) {
|
|
153
408
|
try {
|
|
@@ -72,7 +72,7 @@ const FIELD_LABELS = {
|
|
|
72
72
|
goal: 'Goal',
|
|
73
73
|
next: 'Next',
|
|
74
74
|
blocked: 'Blocked',
|
|
75
|
-
done: '
|
|
75
|
+
done: 'Had just finished',
|
|
76
76
|
};
|
|
77
77
|
function ageInDays(updatedAt, now) {
|
|
78
78
|
if (!updatedAt)
|
|
@@ -87,8 +87,8 @@ export function taskStateLines(state, project, now = new Date()) {
|
|
|
87
87
|
if (isEmptyTaskState(state))
|
|
88
88
|
return [];
|
|
89
89
|
const days = ageInDays(state.updated_at, now);
|
|
90
|
-
const age = days === null ? '' : days === 0 ? '
|
|
91
|
-
const lines = [`
|
|
90
|
+
const age = days === null ? 'at some point' : days === 0 ? 'today' : days === 1 ? 'yesterday' : `${days} days ago`;
|
|
91
|
+
const lines = [`Stated about "${project}" ${age}, and not revisited since:`];
|
|
92
92
|
for (const field of TASK_STATE_FIELDS) {
|
|
93
93
|
const value = state[field];
|
|
94
94
|
if (value)
|
package/scripts/hooks/_shared.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
-
import { createRequire } from 'module';
|
|
4
3
|
import { MemeshDatabase } from './_generated/sqlite.js';
|
|
5
4
|
import { dirname, join } from 'path';
|
|
6
5
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -36,6 +35,7 @@ import {
|
|
|
36
35
|
} from './_generated/core-paths.js';
|
|
37
36
|
import { autoCaptureDecision } from './_generated/capture-flag.js';
|
|
38
37
|
export { assembleTopologyBlock, buildReferenceContext, extractCitedMemoryIds, DEFAULT_TOPOLOGY_BUDGET, SNIPPET_FETCH_CHARS, TOPOLOGY_CANDIDATE_CAP } from './_generated/work-topology.js';
|
|
38
|
+
export { readRepoState, repoStateLines } from './_generated/repo-state.js';
|
|
39
39
|
export { matchingGuards, guardFromMetadata } from './_generated/guards.js';
|
|
40
40
|
export { writeCitationRule, citationRulePath, CITATION_RULE_BODY } from './_generated/citation-rule.js';
|
|
41
41
|
import { guardFromMetadata as guardFromMetadataLocal } from './_generated/guards.js';
|
|
@@ -128,8 +128,6 @@ import {
|
|
|
128
128
|
|
|
129
129
|
export { homeDir, memeshDir, getDbPath, getMemeshDirFromDbPath, getProjectName, redactSecrets, slugFromRemoteUrl };
|
|
130
130
|
|
|
131
|
-
const require = createRequire(import.meta.url);
|
|
132
|
-
|
|
133
131
|
/**
|
|
134
132
|
* Resolve the package root from a hook file's `import.meta.url`.
|
|
135
133
|
*
|
|
@@ -285,9 +283,13 @@ const VALID_AUTO_UPDATE_POLICIES = new Set(['off', 'patch', 'minor', 'major']);
|
|
|
285
283
|
*/
|
|
286
284
|
export function resolveAutoUpdatePolicy(env = process.env) {
|
|
287
285
|
const envVal = env.MEMESH_AUTO_UPDATE;
|
|
288
|
-
if (
|
|
286
|
+
if (envVal !== undefined) {
|
|
287
|
+
if (typeof envVal !== 'string') return 'off';
|
|
289
288
|
const lowered = envVal.toLowerCase();
|
|
290
289
|
if (VALID_AUTO_UPDATE_POLICIES.has(lowered)) return lowered;
|
|
290
|
+
// An explicit but malformed higher-priority value must not uncover a
|
|
291
|
+
// permissive config value and authorise an unattended global mutation.
|
|
292
|
+
return 'off';
|
|
291
293
|
}
|
|
292
294
|
const cfg = readHookConfig(env);
|
|
293
295
|
if (typeof cfg.autoUpdate === 'string') {
|
|
@@ -827,84 +829,28 @@ export function decideAutoUpdateHook(currentVersion, cache, policy) {
|
|
|
827
829
|
|
|
828
830
|
export function logAutoUpdate(line) {
|
|
829
831
|
try {
|
|
830
|
-
const dir =
|
|
832
|
+
const dir = memeshDir();
|
|
831
833
|
ensurePrivateDir(dir);
|
|
832
834
|
const path = join(dir, 'auto-update.log');
|
|
833
835
|
appendFileSync(path, `[${new Date().toISOString()}] ${line}\n`);
|
|
834
836
|
try { chmodSync(path, 0o600); } catch { /* non-POSIX */ }
|
|
837
|
+
return true;
|
|
835
838
|
} catch {
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
export const AUTO_UPDATE_LOCK_TTL_MS = 10 * 60 * 1000;
|
|
841
|
-
|
|
842
|
-
export function tryAcquireAutoUpdateLock(version) {
|
|
843
|
-
try {
|
|
844
|
-
const dir = memeshDir();
|
|
845
|
-
ensurePrivateDir(dir);
|
|
846
|
-
const lockPath = join(dir, 'auto-update.lock');
|
|
847
|
-
const fs = require('fs');
|
|
848
|
-
const myToken = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
849
|
-
const payload = `${myToken}\n${process.pid}\n${Date.now()}\n${version}\n`;
|
|
850
|
-
try {
|
|
851
|
-
// O_CREAT|O_EXCL is the POSIX atomic create primitive — only one process wins.
|
|
852
|
-
const O_FLAGS = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL;
|
|
853
|
-
const fd = fs.openSync(lockPath, O_FLAGS, 0o600);
|
|
854
|
-
try { fs.writeFileSync(fd, payload); } finally { fs.closeSync(fd); }
|
|
855
|
-
return { acquired: true, lockPath };
|
|
856
|
-
} catch (err) {
|
|
857
|
-
if (err?.code !== 'EEXIST') throw err;
|
|
858
|
-
}
|
|
859
|
-
let stat;
|
|
860
|
-
try { stat = fs.statSync(lockPath); } catch { return { acquired: false, lockPath }; }
|
|
861
|
-
if (Date.now() - stat.mtimeMs <= AUTO_UPDATE_LOCK_TTL_MS) {
|
|
862
|
-
return { acquired: false, lockPath };
|
|
863
|
-
}
|
|
864
|
-
const tempPath = `${lockPath}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
865
|
-
try {
|
|
866
|
-
fs.writeFileSync(tempPath, payload, { mode: 0o600 });
|
|
867
|
-
try { fs.unlinkSync(lockPath); } catch (e2) {
|
|
868
|
-
if (e2?.code !== 'ENOENT') {
|
|
869
|
-
try { fs.unlinkSync(tempPath); } catch { /* best-effort */ }
|
|
870
|
-
return { acquired: false, lockPath };
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
fs.renameSync(tempPath, lockPath);
|
|
874
|
-
} catch {
|
|
875
|
-
try { fs.unlinkSync(tempPath); } catch { /* best-effort */ }
|
|
876
|
-
return { acquired: false, lockPath };
|
|
877
|
-
}
|
|
878
|
-
// The "race" here is the lock primitive itself: we just renamed our
|
|
879
|
-
// temp file onto lockPath, then immediately read back to see whether
|
|
880
|
-
// our token won. If a concurrent caller's rename landed in the
|
|
881
|
-
// window between our rename and our read, their token is now there
|
|
882
|
-
// and we correctly conclude we did NOT acquire the lock. That race
|
|
883
|
-
// outcome IS the contract — last-writer-wins lock-file pattern, not
|
|
884
|
-
// a TOCTOU defect. (CodeQL js/file-system-race #82 is dismissed on
|
|
885
|
-
// the security dashboard with this rationale; CodeQL does not honor
|
|
886
|
-
// inline suppression comments, so dismissal is the correct route.)
|
|
887
|
-
let recorded;
|
|
888
|
-
try { recorded = fs.readFileSync(lockPath, 'utf8'); } catch { return { acquired: false, lockPath }; }
|
|
889
|
-
return recorded.split('\n')[0] === myToken
|
|
890
|
-
? { acquired: true, lockPath }
|
|
891
|
-
: { acquired: false, lockPath };
|
|
892
|
-
} catch {
|
|
893
|
-
return { acquired: false, lockPath: null };
|
|
839
|
+
return false;
|
|
894
840
|
}
|
|
895
841
|
}
|
|
896
842
|
|
|
897
843
|
/**
|
|
898
|
-
*
|
|
899
|
-
*
|
|
844
|
+
* Dispatch the detached updater runner after proving that this is an
|
|
845
|
+
* npm-global install and that its durable outcome log is writable. The runner
|
|
846
|
+
* owns the single-flight lock and emits the only terminal SUCCESS/FAILED line.
|
|
900
847
|
*
|
|
901
848
|
* @param {string} version - Target version to install
|
|
902
|
-
* @param {boolean} deprecationOverride - Whether this is a security-override install
|
|
903
849
|
* @param {object|null} installChannelMod - Preloaded dist/core/install-channel.js module
|
|
904
|
-
* @returns {{ state: '
|
|
850
|
+
* @returns {Promise<{ state: 'dispatched'|'channel'|'failed' }>}
|
|
905
851
|
*/
|
|
906
|
-
export function spawnAutoUpdate(version,
|
|
907
|
-
let
|
|
852
|
+
export async function spawnAutoUpdate(version, installChannelMod) {
|
|
853
|
+
let fd = -1;
|
|
908
854
|
try {
|
|
909
855
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
910
856
|
let channel = 'unknown';
|
|
@@ -914,42 +860,76 @@ export function spawnAutoUpdate(version, deprecationOverride, installChannelMod)
|
|
|
914
860
|
} catch { /* best-effort */ }
|
|
915
861
|
}
|
|
916
862
|
if (channel !== 'npm-global') {
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
863
|
+
const line = `auto-update SKIPPED: install channel '${channel}' does not support self-update via npm install -g`;
|
|
864
|
+
if (!logAutoUpdate(line)) {
|
|
865
|
+
try { process.stderr.write(`[memesh] ${line}\n`); } catch { /* stderr unavailable */ }
|
|
866
|
+
}
|
|
920
867
|
return { state: 'channel' };
|
|
921
868
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
logAutoUpdate(
|
|
925
|
-
`auto-update SKIPPED: another session already holds ${lock.lockPath ?? 'auto-update.lock'} for this upgrade`
|
|
926
|
-
);
|
|
927
|
-
return { state: 'in-progress' };
|
|
928
|
-
}
|
|
929
|
-
const dir = getMemeshDirFromDbPath();
|
|
869
|
+
|
|
870
|
+
const dir = memeshDir();
|
|
930
871
|
ensurePrivateDir(dir);
|
|
931
872
|
const logPath = join(dir, 'auto-update.log');
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
873
|
+
const lockPath = join(dir, 'auto-update.lock');
|
|
874
|
+
const runnerPath = join(pluginRoot, 'scripts', 'hooks', 'auto-update-runner.mjs');
|
|
875
|
+
if (!existsSync(runnerPath)) {
|
|
876
|
+
const line = `auto-update FAILED: runner missing at ${runnerPath}`;
|
|
877
|
+
if (!logAutoUpdate(line)) {
|
|
878
|
+
try { process.stderr.write(`[memesh] ${line}\n`); } catch { /* stderr unavailable */ }
|
|
879
|
+
}
|
|
880
|
+
return { state: 'failed' };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
try {
|
|
884
|
+
fd = openSync(logPath, 'a', 0o600);
|
|
885
|
+
try { chmodSync(logPath, 0o600); } catch { /* non-POSIX */ }
|
|
886
|
+
} catch (err) {
|
|
887
|
+
try {
|
|
888
|
+
process.stderr.write(
|
|
889
|
+
`[memesh] auto-update FAILED: durable log unavailable at ${logPath} (${err?.message ?? err})\n`,
|
|
890
|
+
);
|
|
891
|
+
} catch { /* stderr unavailable */ }
|
|
892
|
+
return { state: 'failed' };
|
|
893
|
+
}
|
|
894
|
+
|
|
935
895
|
const child = spawn(
|
|
936
|
-
|
|
937
|
-
[
|
|
938
|
-
|
|
939
|
-
{ detached: true, stdio, env: process.env, windowsHide: true },
|
|
896
|
+
process.execPath,
|
|
897
|
+
[runnerPath, version, lockPath],
|
|
898
|
+
{ detached: true, stdio: ['ignore', fd, fd], env: process.env, windowsHide: true },
|
|
940
899
|
);
|
|
941
|
-
|
|
900
|
+
|
|
901
|
+
return await new Promise((resolve) => {
|
|
902
|
+
let settled = false;
|
|
903
|
+
const finish = (state, line) => {
|
|
904
|
+
if (settled) return;
|
|
905
|
+
settled = true;
|
|
906
|
+
if (fd >= 0) {
|
|
907
|
+
try { closeSync(fd); } catch { /* already closed */ }
|
|
908
|
+
fd = -1;
|
|
909
|
+
}
|
|
910
|
+
if (!logAutoUpdate(line)) {
|
|
911
|
+
try { process.stderr.write(`[memesh] ${line}\n`); } catch { /* stderr unavailable */ }
|
|
912
|
+
}
|
|
913
|
+
resolve({ state });
|
|
914
|
+
};
|
|
915
|
+
child.once('spawn', () => {
|
|
916
|
+
child.unref();
|
|
917
|
+
finish(
|
|
918
|
+
'dispatched',
|
|
919
|
+
`auto-update DISPATCHED: target=${version} worker_pid=${child.pid ?? 'unknown'} lock=${lockPath}`,
|
|
920
|
+
);
|
|
921
|
+
});
|
|
922
|
+
child.once('error', (err) => {
|
|
923
|
+
finish('failed', `auto-update FAILED: target=${version} stage=dispatch error=${err?.message ?? err}`);
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
} catch (err) {
|
|
942
927
|
if (fd >= 0) {
|
|
943
|
-
try { closeSync(fd); } catch { /*
|
|
928
|
+
try { closeSync(fd); } catch { /* already closed */ }
|
|
944
929
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
return { state: 'spawned' };
|
|
949
|
-
} catch (err) {
|
|
950
|
-
logAutoUpdate(`auto-update spawn FAILED: ${err?.message ?? err}`);
|
|
951
|
-
if (lock?.acquired && lock.lockPath) {
|
|
952
|
-
try { require('fs').unlinkSync(lock.lockPath); } catch { /* best-effort */ }
|
|
930
|
+
const line = `auto-update FAILED: target=${version} stage=dispatch error=${err?.message ?? err}`;
|
|
931
|
+
if (!logAutoUpdate(line)) {
|
|
932
|
+
try { process.stderr.write(`[memesh] ${line}\n`); } catch { /* stderr unavailable */ }
|
|
953
933
|
}
|
|
954
934
|
return { state: 'failed' };
|
|
955
935
|
}
|