@askexenow/exe-os 0.9.269 → 0.9.270

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 (51) hide show
  1. package/deploy/compose/.env.customer.example +2 -0
  2. package/deploy/compose/.env.default +1 -0
  3. package/deploy/compose/.env.example +2 -0
  4. package/deploy/compose/docker-compose.yml +1 -0
  5. package/deploy/compose/generate-env.ts +5 -0
  6. package/deploy/compose/init-db.sql +236 -56
  7. package/dist/bin/cli.js +1 -1
  8. package/dist/bin/exe-forget.js +1 -1
  9. package/dist/bin/exe-new-employee.js +1 -1
  10. package/dist/bin/exe-search.js +1 -1
  11. package/dist/bin/exe-start.sh +28 -1
  12. package/dist/bin/install.js +1 -1
  13. package/dist/bin/setup.js +1 -1
  14. package/dist/bin/stack-update.js +2 -2
  15. package/dist/bin/vps-health-gate.js +1 -1
  16. package/dist/catchup-brief-7G3HIQT3.js +151 -0
  17. package/dist/catchup-brief-IA2K5RYM.js +151 -0
  18. package/dist/catchup-brief-KABFKY7U.js +151 -0
  19. package/dist/chunk-4GJHWD4H.js +1148 -0
  20. package/dist/chunk-6H7PZOYD.js +58 -0
  21. package/dist/chunk-AQU2CVD4.js +1148 -0
  22. package/dist/chunk-AX6EKVRZ.js +13696 -0
  23. package/dist/chunk-DFI2IZXM.js +1395 -0
  24. package/dist/chunk-EM4EYF3P.js +149 -0
  25. package/dist/chunk-F7RM3Z4R.js +230 -0
  26. package/dist/chunk-LMYSCMSQ.js +1148 -0
  27. package/dist/chunk-RJVFHGFD.js +13696 -0
  28. package/dist/chunk-U2O2UXVQ.js +13696 -0
  29. package/dist/chunk-X56OLWQS.js +58 -0
  30. package/dist/chunk-YYO5RQRT.js +1021 -0
  31. package/dist/chunk-ZFHXFDWX.js +58 -0
  32. package/dist/hooks/error-recall.js +1 -1
  33. package/dist/hooks/manifest.json +4 -4
  34. package/dist/hooks/prompt-submit.js +1 -1
  35. package/dist/hooks/session-start.js +1 -1
  36. package/dist/lib/exe-daemon.js +49 -3
  37. package/dist/lib/hybrid-search.js +1 -1
  38. package/dist/lib/session-wrappers.js +1 -1
  39. package/dist/mcp/register-tools.js +3 -3
  40. package/dist/mcp/server.js +3 -3
  41. package/dist/reranker-7GCUQ6LC.js +19 -0
  42. package/dist/reranker-NLH3VBTN.js +19 -0
  43. package/dist/reranker-RUOD4YHZ.js +19 -0
  44. package/dist/setup-wizard-M7A7MXH4.js +12 -0
  45. package/dist/stack-update-MYPMZQEI.js +52 -0
  46. package/dist/task-enforcement-CJKWU43B.js +364 -0
  47. package/dist/task-enforcement-FYDLTS3R.js +391 -0
  48. package/dist/task-enforcement-YXKZYS5L.js +369 -0
  49. package/package.json +1 -1
  50. package/release-notes.json +33 -14
  51. package/stack.release.json +48 -48
@@ -0,0 +1,369 @@
1
+ import {
2
+ sendIntercom,
3
+ updateTaskStatus
4
+ } from "./chunk-BXY5Y3RI.js";
5
+ import "./chunk-SD6UFZRH.js";
6
+ import "./chunk-5CHYEKMH.js";
7
+ import "./chunk-4JERP7NT.js";
8
+ import "./chunk-GQ2XYRG3.js";
9
+ import "./chunk-MVW62NIZ.js";
10
+ import "./chunk-OYNKIAVW.js";
11
+ import "./chunk-CX6GL3ZJ.js";
12
+ import {
13
+ recordOrchestrationEventBestEffort
14
+ } from "./chunk-JP45GCVR.js";
15
+ import "./chunk-XJUUWHVN.js";
16
+ import "./chunk-NGP6LSV2.js";
17
+ import "./chunk-7SLLNRR3.js";
18
+ import "./chunk-CVYC6DUW.js";
19
+ import "./chunk-OPU3NYOO.js";
20
+ import "./chunk-GJV3WDWM.js";
21
+ import "./chunk-MP2AFCGL.js";
22
+ import "./chunk-TN45CTOI.js";
23
+ import "./chunk-HYZV25LY.js";
24
+ import "./chunk-UNCGZXPU.js";
25
+ import "./chunk-2I23RPSI.js";
26
+ import "./chunk-VGYL7CN2.js";
27
+ import "./chunk-FXU7JOXK.js";
28
+ import "./chunk-MOZ2YQ54.js";
29
+ import "./chunk-VXIMSRTO.js";
30
+ import "./chunk-LYH5HE24.js";
31
+ import "./chunk-MLKGABMK.js";
32
+
33
+ // src/lib/task-enforcement.ts
34
+ import { appendFile } from "fs/promises";
35
+ import path from "path";
36
+ var TASK_ENFORCEMENT_INTERVAL_MS = 6e4;
37
+ var TASK_ENFORCEMENT_DEBOUNCE_MS = 18e4;
38
+ var MANAGER_GRACE_PERIOD_MS = 6e5;
39
+ var IDLE_PATTERNS = [
40
+ "Standing by",
41
+ "All tasks complete",
42
+ "No tasks found",
43
+ "Anything else?",
44
+ "What do you need?"
45
+ ];
46
+ var NUDGE_ECHO_PATTERNS = [
47
+ "You have pending notifications",
48
+ "You have open tasks. Run list_tasks",
49
+ "You have more open tasks to dispatch",
50
+ "Run list_tasks to check for assigned work",
51
+ "Run list_tasks to find them"
52
+ ];
53
+ var MCP_ZOMBIE_PATTERNS = [
54
+ "session expired",
55
+ "not connected",
56
+ "MCP server disconnected",
57
+ "MCP connection lost",
58
+ "MCP error",
59
+ "MCP_UNHEALTHY"
60
+ ];
61
+ var MANAGER_ROLES = ["COO", "CTO"];
62
+ var AUDIT_LOG_PATH = path.join(
63
+ process.env.HOME ?? process.env.USERPROFILE ?? "/tmp",
64
+ ".exe-os",
65
+ "enforcement-audit.jsonl"
66
+ );
67
+ function writeAuditEntry(entry) {
68
+ const line = JSON.stringify(entry) + "\n";
69
+ appendFile(AUDIT_LOG_PATH, line).catch(() => {
70
+ });
71
+ }
72
+ function decideManagerAction(counts, isIdle, withinGracePeriod, nudgeEcho) {
73
+ if (counts.openTasks === 0) return "skip";
74
+ if (withinGracePeriod) return "grace_period";
75
+ if (!isIdle) return "active";
76
+ if (nudgeEcho) return "active";
77
+ if ((counts.inProgressTasks ?? 0) > 0) return "active";
78
+ if (counts.activeSubtasks === 0) return "nudge";
79
+ return "soft_nudge";
80
+ }
81
+ function decideWorkerAction(taskCount, isIdle, nudgeEcho) {
82
+ if (taskCount === 0) return "skip";
83
+ if (nudgeEcho) return "idle";
84
+ return isIdle ? "nudge" : "idle";
85
+ }
86
+ function isIdlePane(paneText) {
87
+ return IDLE_PATTERNS.some((p) => paneText.includes(p));
88
+ }
89
+ function isNudgeEcho(paneText) {
90
+ return NUDGE_ECHO_PATTERNS.some((p) => paneText.includes(p));
91
+ }
92
+ function isMcpZombiePane(paneText, paneIdle) {
93
+ if (!paneIdle) return false;
94
+ const nonEmptyLines = paneText.split("\n").map((line) => line.trim()).filter(Boolean);
95
+ const lastLine = nonEmptyLines.at(-1) ?? "";
96
+ if (/(?:^|[>❯$#])\s*\/mcp\s*$/.test(lastLine)) return true;
97
+ const currentLines = nonEmptyLines.slice(-3);
98
+ return currentLines.some((line) => {
99
+ const lower = line.toLowerCase();
100
+ return MCP_ZOMBIE_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
101
+ });
102
+ }
103
+ function sendNudge(transport, session, runtime, action) {
104
+ const message = action === "nudge" ? "You have open tasks. Run list_tasks to find them." : "You have more open tasks to dispatch. Run list_tasks.";
105
+ if (transport.sendKeysLiteral) {
106
+ transport.sendKeysLiteral(session, message);
107
+ } else {
108
+ transport.sendKeys(session, message);
109
+ }
110
+ process.stderr.write(
111
+ `[enforcement-audit] SENT nudge to ${session} (${runtime}, ${action})
112
+ `
113
+ );
114
+ }
115
+ function _resetNudgeState() {
116
+ }
117
+ async function runTaskEnforcementTick(deps) {
118
+ const { transport, employees, client, scopeFilter } = deps;
119
+ const now = deps.now ?? Date.now();
120
+ const tickStartedAt = Date.now();
121
+ const sessions = transport.listSessions();
122
+ let processedAnyEmployee = false;
123
+ let processedSessions = 0;
124
+ let nudgesSent = 0;
125
+ recordOrchestrationEventBestEffort({
126
+ eventType: "daemon.tick.started",
127
+ source: "task-enforcement.runTaskEnforcementTick",
128
+ payload: { sessionCount: sessions.length }
129
+ });
130
+ for (const session of sessions) {
131
+ if (!session.includes("-")) continue;
132
+ const agentName = session.split("-")[0]?.replace(/\d+$/, "");
133
+ if (!agentName) continue;
134
+ if ("isAlive" in transport && typeof transport.isAlive === "function") {
135
+ if (!transport.isAlive(session)) continue;
136
+ }
137
+ const employee = employees.find((e) => e.name === agentName);
138
+ if (!employee) continue;
139
+ processedAnyEmployee = true;
140
+ processedSessions++;
141
+ let effectiveScope = scopeFilter;
142
+ const dashIndex = session.indexOf("-");
143
+ if (dashIndex > 0) {
144
+ const coordinatorSession = session.slice(dashIndex + 1);
145
+ if (coordinatorSession) {
146
+ effectiveScope = {
147
+ sql: " AND (session_scope IS NULL OR session_scope = ?)",
148
+ args: [coordinatorSession]
149
+ };
150
+ }
151
+ }
152
+ const isManager = MANAGER_ROLES.includes(employee.role);
153
+ try {
154
+ let action = "skip";
155
+ let reason = "";
156
+ let openTasks = 0;
157
+ let activeSubtasks = 0;
158
+ let paneIdle;
159
+ if (isManager) {
160
+ const openResult = await client.execute({
161
+ sql: `SELECT COUNT(*) as cnt FROM tasks WHERE assigned_to = ? AND status = 'open'${effectiveScope.sql}`,
162
+ args: [agentName, ...effectiveScope.args]
163
+ });
164
+ openTasks = Number(openResult.rows[0]?.cnt ?? 0);
165
+ if (openTasks > 0) {
166
+ const subtaskResult = await client.execute({
167
+ sql: `SELECT COUNT(*) as cnt FROM tasks WHERE assigned_by = ? AND status = 'in_progress'${scopeFilter.sql}`,
168
+ args: [agentName, ...scopeFilter.args]
169
+ });
170
+ activeSubtasks = Number(subtaskResult.rows[0]?.cnt ?? 0);
171
+ }
172
+ let withinGracePeriod = false;
173
+ let graceRemaining = 0;
174
+ if (openTasks > 0) {
175
+ const newestResult = await client.execute({
176
+ sql: `SELECT created_at FROM tasks WHERE assigned_to = ? AND status = 'open'${scopeFilter.sql} ORDER BY created_at DESC LIMIT 1`,
177
+ args: [agentName, ...scopeFilter.args]
178
+ });
179
+ const newestCreatedAt = newestResult.rows[0]?.created_at;
180
+ if (newestCreatedAt) {
181
+ const taskAge = now - new Date(newestCreatedAt).getTime();
182
+ if (taskAge < MANAGER_GRACE_PERIOD_MS) {
183
+ withinGracePeriod = true;
184
+ graceRemaining = Math.round((MANAGER_GRACE_PERIOD_MS - taskAge) / 1e3);
185
+ }
186
+ }
187
+ }
188
+ let inProgressTasks = 0;
189
+ if (openTasks > 0) {
190
+ const ipResult = await client.execute({
191
+ sql: `SELECT COUNT(*) as cnt FROM tasks WHERE assigned_to = ? AND status = 'in_progress'${effectiveScope.sql}`,
192
+ args: [agentName, ...effectiveScope.args]
193
+ });
194
+ inProgressTasks = Number(ipResult.rows[0]?.cnt ?? 0);
195
+ }
196
+ const pane = deps.paneCache?.get(session) ?? (deps.capturePaneAsync ? await deps.capturePaneAsync(session, 20) : transport.capturePane(session, 20));
197
+ paneIdle = isIdlePane(pane);
198
+ const nudgeEcho = isNudgeEcho(pane);
199
+ action = decideManagerAction({ openTasks, activeSubtasks, inProgressTasks }, paneIdle, withinGracePeriod, nudgeEcho);
200
+ if (action === "skip") reason = "no open tasks";
201
+ else if (action === "grace_period") reason = `task assigned <10min ago (${graceRemaining}s remaining)`;
202
+ else if (action === "active") reason = nudgeEcho ? "pane shows previous nudge (anti-loop)" : inProgressTasks > 0 ? `${inProgressTasks} tasks in_progress` : "pane shows active work";
203
+ else if (action === "nudge") reason = `${openTasks} open tasks, 0 delegated, pane idle`;
204
+ else if (action === "soft_nudge") reason = `${openTasks} open tasks, ${activeSubtasks} delegated, pane idle`;
205
+ writeAuditEntry({
206
+ timestamp: new Date(now).toISOString(),
207
+ session,
208
+ agent: agentName,
209
+ role: "manager",
210
+ action,
211
+ reason,
212
+ openTasks,
213
+ activeSubtasks,
214
+ paneIdle,
215
+ graceRemaining: withinGracePeriod ? graceRemaining : void 0
216
+ });
217
+ process.stderr.write(
218
+ `[exed] Task enforcement: ${agentName} (manager) ${openTasks} open, ${activeSubtasks} delegated, pane=${paneIdle ? "idle" : "active"}. Action: ${action} (${reason})
219
+ `
220
+ );
221
+ } else {
222
+ const taskResult = await client.execute({
223
+ sql: `SELECT COUNT(*) as cnt FROM tasks WHERE assigned_to = ? AND status IN ('open', 'in_progress')${effectiveScope.sql}`,
224
+ args: [agentName, ...effectiveScope.args]
225
+ });
226
+ const taskCount = Number(taskResult.rows[0]?.cnt ?? 0);
227
+ openTasks = taskCount;
228
+ if (taskCount > 0) {
229
+ const pane = deps.paneCache?.get(session) ?? (deps.capturePaneAsync ? await deps.capturePaneAsync(session, 20) : transport.capturePane(session, 20));
230
+ paneIdle = isIdlePane(pane);
231
+ const workerNudgeEcho = isNudgeEcho(pane);
232
+ if (isMcpZombiePane(pane, paneIdle) && deps.zombieWorkerRecover) {
233
+ const reason2 = "pane shows dead MCP client";
234
+ await deps.zombieWorkerRecover(session, agentName, effectiveScope.args[0] ? String(effectiveScope.args[0]) : "", reason2);
235
+ writeAuditEntry({
236
+ timestamp: new Date(now).toISOString(),
237
+ session,
238
+ agent: agentName,
239
+ role: "worker",
240
+ action: "active",
241
+ reason: reason2,
242
+ openTasks: taskCount,
243
+ paneIdle
244
+ });
245
+ continue;
246
+ }
247
+ action = decideWorkerAction(taskCount, paneIdle, workerNudgeEcho);
248
+ }
249
+ if (action === "skip") reason = "no tasks";
250
+ else if (action === "idle") reason = `${taskCount} tasks but pane active`;
251
+ else if (action === "nudge") reason = `${taskCount} tasks, pane idle`;
252
+ writeAuditEntry({
253
+ timestamp: new Date(now).toISOString(),
254
+ session,
255
+ agent: agentName,
256
+ role: "worker",
257
+ action,
258
+ reason,
259
+ openTasks: taskCount,
260
+ paneIdle
261
+ });
262
+ process.stderr.write(
263
+ `[exed] Task enforcement: ${agentName} (worker) ${taskCount} tasks, pane=${paneIdle ?? "n/a"}. Action: ${action} (${reason})
264
+ `
265
+ );
266
+ }
267
+ if (action === "skip" || action === "idle" || action === "grace_period" || action === "active") continue;
268
+ nudgesSent++;
269
+ sendIntercom(session);
270
+ } catch {
271
+ }
272
+ }
273
+ if (processedAnyEmployee) {
274
+ try {
275
+ const staleBlocked = await client.execute({
276
+ sql: `SELECT t.id, t.title, b.status as blocker_status
277
+ FROM tasks t
278
+ JOIN tasks b ON t.blocked_by = b.id
279
+ WHERE t.status = 'blocked'
280
+ AND b.status IN ('done', 'cancelled', 'closed')${scopeFilter.sql}`,
281
+ args: [...scopeFilter.args]
282
+ });
283
+ const nowIso = new Date(now).toISOString();
284
+ for (const row of staleBlocked.rows) {
285
+ try {
286
+ await updateTaskStatus({ taskId: String(row.id), status: "open" });
287
+ } catch {
288
+ await client.execute({
289
+ sql: `UPDATE tasks SET status = 'open', updated_at = ? WHERE id = ?`,
290
+ args: [nowIso, String(row.id)]
291
+ });
292
+ }
293
+ await client.execute({
294
+ sql: `UPDATE tasks SET blocked_by = NULL WHERE id = ?`,
295
+ args: [String(row.id)]
296
+ });
297
+ process.stderr.write(
298
+ `[exed] Auto-unblocked "${String(row.title)}" \u2014 blocker already ${String(row.blocker_status)}
299
+ `
300
+ );
301
+ }
302
+ } catch {
303
+ }
304
+ }
305
+ if (processedAnyEmployee && deps.reviewerSpawn) {
306
+ try {
307
+ const reviewerRows = await client.execute({
308
+ sql: `SELECT DISTINCT reviewer, session_scope FROM tasks
309
+ WHERE status = 'needs_review'
310
+ AND reviewer IS NOT NULL
311
+ AND reviewer != ''${scopeFilter.sql}`,
312
+ args: [...scopeFilter.args]
313
+ });
314
+ const liveSessionSet = new Set(sessions);
315
+ for (const row of reviewerRows.rows) {
316
+ const reviewerName = String(row.reviewer ?? "");
317
+ const sessionScope = row.session_scope ? String(row.session_scope) : null;
318
+ if (!reviewerName || !sessionScope) continue;
319
+ const reviewerSession = `${reviewerName}-${sessionScope}`;
320
+ if (liveSessionSet.has(reviewerSession)) continue;
321
+ try {
322
+ const result = deps.reviewerSpawn(reviewerName, sessionScope);
323
+ if (!result) continue;
324
+ if (result.status === "spawned") {
325
+ process.stderr.write(
326
+ `[exed] Reviewer auto-spawn: spawned ${reviewerName} in ${sessionScope} (has needs_review tasks)
327
+ `
328
+ );
329
+ } else if (result.status === "failed") {
330
+ process.stderr.write(
331
+ `[exed] Reviewer auto-spawn: failed to spawn ${reviewerName} in ${sessionScope}: ${result.error ?? "unknown"}
332
+ `
333
+ );
334
+ }
335
+ } catch (spawnErr) {
336
+ process.stderr.write(
337
+ `[exed] Reviewer auto-spawn error for ${reviewerName}: ${spawnErr instanceof Error ? spawnErr.message : String(spawnErr)}
338
+ `
339
+ );
340
+ }
341
+ }
342
+ } catch {
343
+ }
344
+ }
345
+ recordOrchestrationEventBestEffort({
346
+ eventType: "daemon.tick.completed",
347
+ source: "task-enforcement.runTaskEnforcementTick",
348
+ durationMs: Date.now() - tickStartedAt,
349
+ result: processedAnyEmployee ? "processed" : "no_employee_sessions",
350
+ payload: { sessionCount: sessions.length, processedSessions, nudgesSent }
351
+ });
352
+ }
353
+ export {
354
+ IDLE_PATTERNS,
355
+ MANAGER_GRACE_PERIOD_MS,
356
+ MANAGER_ROLES,
357
+ MCP_ZOMBIE_PATTERNS,
358
+ NUDGE_ECHO_PATTERNS,
359
+ TASK_ENFORCEMENT_DEBOUNCE_MS,
360
+ TASK_ENFORCEMENT_INTERVAL_MS,
361
+ _resetNudgeState,
362
+ decideManagerAction,
363
+ decideWorkerAction,
364
+ isIdlePane,
365
+ isMcpZombiePane,
366
+ isNudgeEcho,
367
+ runTaskEnforcementTick,
368
+ sendNudge
369
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.269",
3
+ "version": "0.9.270",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -1,6 +1,38 @@
1
1
  {
2
- "current": "0.9.269",
2
+ "current": "0.9.270",
3
3
  "notes": {
4
+ "0.9.270": {
5
+ "version": "0.9.270",
6
+ "date": "2026-06-11",
7
+ "features": [],
8
+ "fixes": [
9
+ "harden MCP zombie gate + stale init-db.sql staging + CRM_GRAPHQL_URL default",
10
+ "exe-start PATH resolution + conservative MCP zombie/session recovery",
11
+ "stack-update rollback migrations, image pull verification, cloudflared healthcheck",
12
+ "Fix remaining exe-os support bugs",
13
+ "widen gateway-client waitFor to 15s for loaded 4-core runner",
14
+ "bind gateway-client mock WS server to 127.0.0.1 explicitly",
15
+ "ephemeral WS port + registry-host-agnostic parity assert",
16
+ "make tests robust on persistent self-hosted runner",
17
+ "Fix shard founder scope and MCP session TTL",
18
+ "Fix session-scoped notifications and RSS smoke gate",
19
+ "Fix degraded daemon boot bug paths",
20
+ "resource-triggered daemon restarts exit non-zero so launchd relaunches"
21
+ ],
22
+ "security": [
23
+ "Enforce cross-stack UX and security contracts"
24
+ ],
25
+ "other": [
26
+ "Update memory score history for 0.9.267 publish",
27
+ "quarantine gateway-client suite in CI with loud warning — tracked bug",
28
+ "retry budget (2) for gateway-client on the shared runner",
29
+ "separate vitest invocations for serial files — perf-stress fake timers poison a shared fork",
30
+ "daemon-perf-stress joins the serial pass — timing comparisons need a quiet box",
31
+ "run gateway-client serially after the parallel suite",
32
+ "retrigger"
33
+ ],
34
+ "migration_notes": []
35
+ },
4
36
  "0.9.269": {
5
37
  "version": "0.9.269",
6
38
  "date": "2026-06-10",
@@ -90,19 +122,6 @@
90
122
  ],
91
123
  "other": [],
92
124
  "migration_notes": []
93
- },
94
- "0.9.265": {
95
- "version": "0.9.265",
96
- "date": "2026-06-10",
97
- "features": [
98
- "honor sendUrl from gateway.json in send_whatsapp MCP tool"
99
- ],
100
- "fixes": [
101
- "scope MCP queues and preserve live daemon chunks"
102
- ],
103
- "security": [],
104
- "other": [],
105
- "migration_notes": []
106
125
  }
107
126
  }
108
127
  }
@@ -1,51 +1,51 @@
1
1
  {
2
- "$schema": "https://updates.askexe.com/schemas/stack.release.schema.json",
3
- "schemaVersion": 1,
4
- "repo": "AskExe/exe-os",
5
- "service": "exe-os",
6
- "packageName": "@askexenow/exe-os",
7
- "version": "0.9.258",
8
- "image": "ghcr.io/askexe/exe-os:v0.9.258",
9
- "imageEnv": "EXE_OS_IMAGE_TAG",
10
- "stackParticipation": {
11
- "required": true,
12
- "compatibleStack": ">=0.9.0 <1.0.0",
13
- "health": [
14
- {
15
- "type": "http",
16
- "url": "http://127.0.0.1:8765/health"
17
- }
18
- ],
19
- "smokeTests": [
20
- {
21
- "name": "exe healthcheck",
22
- "command": "exe-os healthcheck"
23
- },
24
- {
25
- "name": "mcp startup",
26
- "command": "node dist/mcp/server.js --help",
27
- "optional": true
28
- }
29
- ],
30
- "migrations": {
31
- "command": "npm run build",
32
- "rollback": "Restore previous EXE_OS_IMAGE_TAG and restart exe-os; local SQLCipher memory data is not overwritten by updates."
2
+ "$schema": "https://updates.askexe.com/schemas/stack.release.schema.json",
3
+ "schemaVersion": 1,
4
+ "repo": "AskExe/exe-os",
5
+ "service": "exe-os",
6
+ "packageName": "@askexenow/exe-os",
7
+ "version": "0.9.270",
8
+ "image": "ghcr.io/askexe/exe-os:v0.9.270",
9
+ "imageEnv": "EXE_OS_IMAGE_TAG",
10
+ "stackParticipation": {
11
+ "required": true,
12
+ "compatibleStack": ">=0.9.0 <1.0.0",
13
+ "health": [
14
+ {
15
+ "type": "http",
16
+ "url": "http://127.0.0.1:8765/health"
17
+ }
18
+ ],
19
+ "smokeTests": [
20
+ {
21
+ "name": "exe healthcheck",
22
+ "command": "exe-os healthcheck"
23
+ },
24
+ {
25
+ "name": "mcp startup",
26
+ "command": "node dist/mcp/server.js --help",
27
+ "optional": true
28
+ }
29
+ ],
30
+ "migrations": {
31
+ "command": "npm run build",
32
+ "rollback": "Restore previous EXE_OS_IMAGE_TAG and restart exe-os; local SQLCipher memory data is not overwritten by updates."
33
+ },
34
+ "config": {
35
+ "envExample": "deploy/compose/.env.example",
36
+ "requiredEnv": [
37
+ "EXE_OS_IMAGE_TAG",
38
+ "EXED_MCP_TOKEN",
39
+ "EXED_DEVICE_ID"
40
+ ]
41
+ },
42
+ "breakingChanges": [],
43
+ "dataSovereignty": "Customer-local memory/tasks/behaviors stay in SQLCipher/local storage. Updates must not overwrite roster, identity, behavior, or local memory files.",
44
+ "releaseLine": "v0.9 private/customer pilot; v1.0 is public-beta stable.",
45
+ "highGhostStack": "0.9.9",
46
+ "deploymentScope": "customer"
33
47
  },
34
- "config": {
35
- "envExample": "deploy/compose/.env.example",
36
- "requiredEnv": [
37
- "EXE_OS_IMAGE_TAG",
38
- "EXED_MCP_TOKEN",
39
- "EXED_DEVICE_ID"
40
- ]
41
- },
42
- "breakingChanges": [],
43
- "dataSovereignty": "Customer-local memory/tasks/behaviors stay in SQLCipher/local storage. Updates must not overwrite roster, identity, behavior, or local memory files.",
44
- "releaseLine": "v0.9 private/customer pilot; v1.0 is public-beta stable.",
48
+ "deploymentScope": "customer",
45
49
  "highGhostStack": "0.9.9",
46
- "deploymentScope": "customer"
47
- },
48
- "deploymentScope": "customer",
49
- "highGhostStack": "0.9.9",
50
- "sourceVersion": "0.9.258"
51
- }
50
+ "sourceVersion": "0.9.270"
51
+ }