@xopcai/xopc 0.0.205 → 0.0.206

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 (151) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-DJBadfSX.js → activity-store-URW-e_me.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Br6C32BX.js → agent-avatar-dicebear-BLK6kRCJ.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-D5HVmdNx.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-B5O7fKXa.js → api-C_MS4arC.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-Le1vJ1OO.js → app-management-settings-panel-CnUN6BJk.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-CZlAU60t.js → appearance-settings-C8Nx8hZE.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-DKCA_LFY.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-CRzXRXPE.js → archive-plugin-C7FqeJZD.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-CYAAQ6Qi.js → attachment-load-DCYQgsYE.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-BEOE4ZKM.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-CVR5cUmS.js → binary-plugins-Bf1WROsh.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-CFe74rWi.js → block-editor-BPK08Pjv.js} +46 -46
  15. package/dist/gateway/static/root/assets/browser-settings-page-CLRmMdU9.js +8 -0
  16. package/dist/gateway/static/root/assets/{browser-workflow-inputs-Bd68yluo.js → browser-workflow-inputs-cqVoDHyp.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-workflows-page-B0uiDLga.js → browser-workflows-page-Des1UTsS.js} +2 -2
  18. package/dist/gateway/static/root/assets/capabilities-settings-panel-BNcVi4ar.js +7 -0
  19. package/dist/gateway/static/root/assets/{capability-presets-api-BerPMp6f.js → capability-presets-api-D4MjqV2a.js} +1 -1
  20. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-DJjVOCJY.js → capability-presets-settings-panel-uFwW2ah5.js} +2 -2
  21. package/dist/gateway/static/root/assets/{channel-recipient-api-D2d21_XA.js → channel-recipient-api-CcimZduy.js} +1 -1
  22. package/dist/gateway/static/root/assets/channels-settings-DSLP8OjS.js +1 -0
  23. package/dist/gateway/static/root/assets/channels-status-swr-D0OR6Ylw.js +1 -0
  24. package/dist/gateway/static/root/assets/chat-terminal-dock-B1njb2Cr.css +1 -0
  25. package/dist/gateway/static/root/assets/chat-terminal-dock-wpi7abPg.js +38 -0
  26. package/dist/gateway/static/root/assets/{connectors-api-Bq_vUzxP.js → connectors-api-CUvFu8jh.js} +1 -1
  27. package/dist/gateway/static/root/assets/connectors-page-BJoHjWnu.js +2 -0
  28. package/dist/gateway/static/root/assets/{cron-expression-DZBxmKGR.js → cron-expression-hFSZZpvM.js} +1 -1
  29. package/dist/gateway/static/root/assets/{date-picker-BNHd7HT7.js → date-picker-CKZ0gc-Y.js} +1 -1
  30. package/dist/gateway/static/root/assets/{dependency-picker-D1kJBknt.js → dependency-picker-zv1oHtg4.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-Di75oHkC.js → desktop-pet-DY9ykMtD.js} +1 -1
  32. package/dist/gateway/static/root/assets/{desktop-pet-settings-D6tD1uym.js → desktop-pet-settings-XiSmmt26.js} +1 -1
  33. package/dist/gateway/static/root/assets/directory-picker-path-field-CZkgk15r.js +1 -0
  34. package/dist/gateway/static/root/assets/{extension-debug-page-D91_S7NQ.js → extension-debug-page-CTppsTEi.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-page-DHzmmr_N.js → extension-page-CHRTUvAa.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-settings-page-BpJNtePi.js → extension-settings-page-z2UBx25A.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-api-BAzLYTKZ.js → gateway-config-api-DzFPJTnm.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-config-swr-VVcfd3sG.js → gateway-config-swr-CGpoH1gU.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-settings-C5-aKkTp.js → gateway-settings-DBa2I6VM.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-startup-retry-qAX_T2t2.js → gateway-startup-retry-Te-TREBt.js} +1 -1
  41. package/dist/gateway/static/root/assets/heartbeat-settings-DyQGGdBu.js +1 -0
  42. package/dist/gateway/static/root/assets/home-page-EkT4eGeP.js +3 -0
  43. package/dist/gateway/static/root/assets/index-BpLqHdfz.css +2 -0
  44. package/dist/gateway/static/root/assets/index-CrwXzRpA.js +85 -0
  45. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-fpNjXnJK.js → keyboard-shortcuts-settings-fb0zS8pk.js} +1 -1
  46. package/dist/gateway/static/root/assets/local-app-workbench-page-CCC3Cvb8.js +2 -0
  47. package/dist/gateway/static/root/assets/{local-apps-page-DBpQIfNd.js → local-apps-page-DFdheruU.js} +1 -1
  48. package/dist/gateway/static/root/assets/{locale-store-CbPVxonP.js → locale-store-CieddUjx.js} +1 -1
  49. package/dist/gateway/static/root/assets/{logs-page-BmXcJCKz.js → logs-page-B6jfkNDF.js} +2 -2
  50. package/dist/gateway/static/root/assets/{management-settings-CiK7Xkvi.js → management-settings-D3PsQ612.js} +1 -1
  51. package/dist/gateway/static/root/assets/{markdown-split-DVgpcOze.js → markdown-split-x5K52Thz.js} +1 -1
  52. package/dist/gateway/static/root/assets/{markdown-view-Dl-0_9JR.js → markdown-view-Cjjey5Y6.js} +1 -1
  53. package/dist/gateway/static/root/assets/{media-plugins-DMj1lW-n.js → media-plugins-DNtEkRAh.js} +1 -1
  54. package/dist/gateway/static/root/assets/messages-CySMn8WL.js +3 -0
  55. package/dist/gateway/static/root/assets/{notes-page-DvX1sTlf.js → notes-page-nWEqlwDw.js} +1 -1
  56. package/dist/gateway/static/root/assets/notes-workbench-QstkgWsW.js +4 -0
  57. package/dist/gateway/static/root/assets/{preview-open-alternatives-DvsLigha.js → preview-open-alternatives-CXcIKVi9.js} +1 -1
  58. package/dist/gateway/static/root/assets/{product-open-page-DbCraAI3.js → product-open-page-CZLJudPk.js} +1 -1
  59. package/dist/gateway/static/root/assets/project-detail-page-Cf1v94LO.js +1 -0
  60. package/dist/gateway/static/root/assets/projects-page-BRMZkNht.js +1 -0
  61. package/dist/gateway/static/root/assets/remote-access-hub-D55FTXZw.js +1 -0
  62. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DzuUglQo.js → runtime-tools-settings-panel-RYjduQV6.js} +1 -1
  63. package/dist/gateway/static/root/assets/{schema-form-plDnazVj.js → schema-form--1ZXg4qC.js} +1 -1
  64. package/dist/gateway/static/root/assets/sessions-page-C5Yy6eIn.js +1 -0
  65. package/dist/gateway/static/root/assets/settings-advanced-gate-BqO_8bUh.js +1 -0
  66. package/dist/gateway/static/root/assets/{settings-form-section-BwAxX_7e.js → settings-form-section-71-8j3zt.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-loading-skeleton-B3OiTnF9.js → settings-loading-skeleton-C5Jn1l0q.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-page-DvIWXBvp.js → settings-page-TYYGe6kZ.js} +1 -1
  69. package/dist/gateway/static/root/assets/{setup-status-panel-DThKKjLV.js → setup-status-panel-jA5I-17g.js} +1 -1
  70. package/dist/gateway/static/root/assets/{share-preview-page-CFnoGHJb.js → share-preview-page-BMUQIGfk.js} +1 -1
  71. package/dist/gateway/static/root/assets/shares-settings-Dru1a2rZ.js +1 -0
  72. package/dist/gateway/static/root/assets/skill-reload-api-C8iqS6kF.js +1 -0
  73. package/dist/gateway/static/root/assets/{skills-page-B38fY22S.js → skills-page-BzZT1SKg.js} +1 -1
  74. package/dist/gateway/static/root/assets/{skills-page.utils-vLIlk1c3.js → skills-page.utils-DnaBw5Ji.js} +1 -1
  75. package/dist/gateway/static/root/assets/{system-settings-panel-DRV2wljT.js → system-settings-panel-DqiPQgVI.js} +1 -1
  76. package/dist/gateway/static/root/assets/task-detail-page-DQP5vDr-.js +1 -0
  77. package/dist/gateway/static/root/assets/{theme-store-Bc_eRUev.js → theme-store-BP3_RpaY.js} +1 -1
  78. package/dist/gateway/static/root/assets/{use-autosave-DTu23D9J.js → use-autosave-BJkQ-vpu.js} +1 -1
  79. package/dist/gateway/static/root/assets/{user-context-page-DCwqzN03.js → user-context-page-BP8-sOPi.js} +1 -1
  80. package/dist/gateway/static/root/assets/{voice-api-key-field-CVDMesWc.js → voice-api-key-field-C6c8e7Mx.js} +1 -1
  81. package/dist/gateway/static/root/assets/{work-discovery-overlay-cyt93k0V.js → work-discovery-overlay-CCNjWKql.js} +1 -1
  82. package/dist/gateway/static/root/assets/work-discovery-page-DL_Ie0s2.js +1 -0
  83. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DrdcfX_N.js → workflow-run-setup-panel-DLO-dMSN.js} +2 -2
  84. package/dist/gateway/static/root/assets/workflows-page-B4-k2FHB.js +5 -0
  85. package/dist/gateway/static/root/assets/{working-directory-picker-modal-CYWdZUJ5.js → working-directory-picker-modal-vWi33CZJ.js} +1 -1
  86. package/dist/gateway/static/root/index.html +10 -10
  87. package/dist/package.js +1 -1
  88. package/dist/src/agent/agent-manager.d.ts +11 -0
  89. package/dist/src/agent/agent-manager.js +36 -6
  90. package/dist/src/agent/service.d.ts +4 -1
  91. package/dist/src/agent/service.js +12 -0
  92. package/dist/src/agent/tools/xopc-use-tool.js +6 -2
  93. package/dist/src/automations/api/routes.js +8 -0
  94. package/dist/src/automations/domain/index.js +2 -2
  95. package/dist/src/automations/domain/types.d.ts +17 -12
  96. package/dist/src/automations/domain/validation.d.ts +30 -32
  97. package/dist/src/automations/domain/validation.js +8 -10
  98. package/dist/src/automations/draft/automation-draft-prompt.js +9 -5
  99. package/dist/src/automations/draft/automation-draft-validator.d.ts +3 -8
  100. package/dist/src/automations/draft/automation-draft-validator.js +3 -3
  101. package/dist/src/automations/index.js +2 -2
  102. package/dist/src/automations/service/action-executor.js +13 -4
  103. package/dist/src/automations/service/automation-service.d.ts +5 -1
  104. package/dist/src/automations/service/automation-service.js +19 -12
  105. package/dist/src/automations/storage/automation-repository.js +8 -4
  106. package/dist/src/automations/storage/automation-run-repository.d.ts +5 -0
  107. package/dist/src/automations/storage/automation-run-repository.js +24 -8
  108. package/dist/src/automations/storage/index.js +2 -2
  109. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  110. package/dist/src/gateway/automation-agent-session.d.ts +4 -0
  111. package/dist/src/gateway/automation-agent-session.js +40 -0
  112. package/dist/src/gateway/dreaming-automation-reconciler.js +3 -2
  113. package/dist/src/gateway/hono/routes/commands-skills.js +36 -2
  114. package/dist/src/gateway/hono/routes/lazy-bundles.js +1 -0
  115. package/dist/src/gateway/service/marketplace-service.d.ts +4 -1
  116. package/dist/src/gateway/service/marketplace-service.js +9 -0
  117. package/dist/src/gateway/service.js +19 -2
  118. package/dist/src/mobile/notification-service.js +7 -4
  119. package/dist/src/storage/sqlite/migrations/121_automation_inbox.sql +24 -0
  120. package/dist/src/storage/sqlite/migrations/122_automation_session_projects.sql +18 -0
  121. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  122. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  123. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  124. package/dist/src/storage/sqlite/schema.js +1 -1
  125. package/package.json +3 -2
  126. package/dist/gateway/static/root/assets/agents-DnFIy8Ye.js +0 -68
  127. package/dist/gateway/static/root/assets/apps-page-CONlVc-f.js +0 -1
  128. package/dist/gateway/static/root/assets/automations-page-DuG69tIj.js +0 -6
  129. package/dist/gateway/static/root/assets/browser-settings-page-Cvt-8fqE.js +0 -8
  130. package/dist/gateway/static/root/assets/capabilities-settings-panel-yk3QkGBP.js +0 -7
  131. package/dist/gateway/static/root/assets/channels-settings-CaNnYUQo.js +0 -1
  132. package/dist/gateway/static/root/assets/channels-status-swr-CZ5P9jbB.js +0 -1
  133. package/dist/gateway/static/root/assets/connectors-page-DTpCnOsQ.js +0 -2
  134. package/dist/gateway/static/root/assets/directory-picker-path-field-Ly0TUflq.js +0 -1
  135. package/dist/gateway/static/root/assets/heartbeat-settings-CMZr1_Op.js +0 -1
  136. package/dist/gateway/static/root/assets/home-page-CBG9Bk-1.js +0 -3
  137. package/dist/gateway/static/root/assets/index-BBw9QL0p.js +0 -85
  138. package/dist/gateway/static/root/assets/index-Dm-cvIpl.css +0 -2
  139. package/dist/gateway/static/root/assets/local-app-workbench-page-Bi_0Sknt.js +0 -2
  140. package/dist/gateway/static/root/assets/messages-9zCa8KkW.js +0 -3
  141. package/dist/gateway/static/root/assets/notes-workbench-CMyosgjY.js +0 -4
  142. package/dist/gateway/static/root/assets/project-detail-page-DkS-kbpj.js +0 -1
  143. package/dist/gateway/static/root/assets/projects-page-B3NxP6S4.js +0 -1
  144. package/dist/gateway/static/root/assets/remote-access-hub-DwhrMhjl.js +0 -1
  145. package/dist/gateway/static/root/assets/sessions-page-BOfXXmBL.js +0 -1
  146. package/dist/gateway/static/root/assets/settings-advanced-gate-sPrZfZVR.js +0 -1
  147. package/dist/gateway/static/root/assets/shares-settings-Dvl13Udt.js +0 -1
  148. package/dist/gateway/static/root/assets/skill-reload-api-D9elkZOz.js +0 -1
  149. package/dist/gateway/static/root/assets/task-detail-page-Xd8PlB24.js +0 -1
  150. package/dist/gateway/static/root/assets/work-discovery-page--DTi90d5.js +0 -1
  151. package/dist/gateway/static/root/assets/workflows-page-DXyButkm.js +0 -5
@@ -157,19 +157,28 @@ var AutomationActionExecutor = class {
157
157
  status: "failed",
158
158
  error: "Agent service is not available"
159
159
  };
160
+ const agentId = normalizeAgentId(action.agentId || this.deps.getDefaultAgentId?.() || "main");
161
+ const peerId = automation.conversationMode === "continuous" ? automation.id : `${automation.id}-${crypto.randomUUID().slice(0, 8)}`;
160
162
  const sessionKey = buildSessionKey({
161
- agentId: normalizeAgentId(action.agentId || this.deps.getDefaultAgentId?.() || "main"),
163
+ agentId,
162
164
  source: "automation",
163
165
  accountId: "default",
164
166
  peerKind: "dm",
165
- peerId: `${automation.id}-${crypto.randomUUID().slice(0, 8)}`
167
+ peerId
166
168
  });
167
- const effectiveWorkingDirectory = action.workingDirectory ?? (automation.projectId ? this.deps.getProjectWorkspaceRoot?.(automation.projectId) : void 0);
168
169
  await hooks.onRunPatch?.({
169
170
  sessionKey,
170
171
  currentPhase: "action"
171
172
  });
172
- await agentService.sessionConfig?.applyAutomationWorkingDirectory?.(sessionKey, effectiveWorkingDirectory);
173
+ await this.deps.prepareAgentSession?.({
174
+ sessionKey,
175
+ projectId: automation.projectId,
176
+ agentId,
177
+ peerId,
178
+ automationId: automation.id,
179
+ runId: run.id
180
+ });
181
+ await agentService.sessionConfig?.applyAutomationWorkingDirectory?.(sessionKey, automation.projectId ? void 0 : action.workingDirectory);
173
182
  if (agentService.sessionConfig?.applyAutomationModelOverride) {
174
183
  if (!await agentService.sessionConfig.applyAutomationModelOverride(sessionKey, action.model) && action.model) log.warn({
175
184
  automationId: automation.id,
@@ -49,6 +49,10 @@ export declare class AutomationService {
49
49
  }): Promise<AutomationProductEventRun[]>;
50
50
  getRun(runId: string): Promise<AutomationRun | null>;
51
51
  listRunEvents(runId: string): Promise<AutomationRunEvent[]>;
52
+ markRunRead(runId: string): Promise<boolean>;
53
+ markAllRunsRead(options?: {
54
+ projectId?: string;
55
+ }): Promise<number>;
52
56
  cancelRun(runId: string): Promise<boolean>;
53
57
  getMetrics(): Promise<AutomationMetrics>;
54
58
  private recomputeNextRuns;
@@ -60,5 +64,5 @@ export declare class AutomationService {
60
64
  private waitForRetry;
61
65
  private appendRunEvent;
62
66
  private finishAutomationRun;
63
- private postAfterRunWebhook;
67
+ private postCompletionWebhook;
64
68
  }
@@ -5,7 +5,7 @@ import { computeNextAutomationRunAtMs, timerDelayUntil } from "../domain/schedul
5
5
  import { AutomationSchema, CreateAutomationSchema, UpdateAutomationSchema } from "../domain/validation.js";
6
6
  import { AutomationActionExecutor } from "./action-executor.js";
7
7
  import { deleteAutomation, getAutomation, listAutomations, saveAutomation, saveAutomations } from "../storage/automation-repository.js";
8
- import { appendAutomationRunEvent, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, saveAutomationRun, touchAutomationRunLease } from "../storage/automation-run-repository.js";
8
+ import { appendAutomationRunEvent, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, markAllAutomationRunsRead, markAutomationRunRead, saveAutomationRun, touchAutomationRunLease } from "../storage/automation-run-repository.js";
9
9
  import "../storage/index.js";
10
10
  import { randomUUID } from "node:crypto";
11
11
  //#region src/automations/service/automation-service.ts
@@ -71,7 +71,8 @@ var AutomationService = class {
71
71
  id: parsed.id?.trim() || randomUUID().slice(0, 12),
72
72
  enabled: parsed.enabled ?? true,
73
73
  safety: parsed.safety ?? { mode: "auto_apply" },
74
- afterRun: parsed.afterRun ?? { kind: "none" },
74
+ conversationMode: parsed.conversationMode ?? "new_session",
75
+ notificationPolicy: parsed.notificationPolicy ?? "attention",
75
76
  state: parsed.state ?? {},
76
77
  createdAtMs: now,
77
78
  updatedAtMs: now
@@ -165,6 +166,12 @@ var AutomationService = class {
165
166
  async listRunEvents(runId) {
166
167
  return listAutomationRunEvents(runId);
167
168
  }
169
+ async markRunRead(runId) {
170
+ return markAutomationRunRead(runId);
171
+ }
172
+ async markAllRunsRead(options) {
173
+ return markAllAutomationRunsRead(options);
174
+ }
168
175
  async cancelRun(runId) {
169
176
  const controller = this.activeRuns.get(runId);
170
177
  if (!controller) return false;
@@ -389,30 +396,30 @@ var AutomationService = class {
389
396
  };
390
397
  this.appendRunEvent(run, task.termination.cancellationConfirmed ? "run.cancel_confirmed" : "run.cancellation_unconfirmed", task.termination.cancellationConfirmed ? "Automation cancellation confirmed" : "Automation cancellation was not confirmed within the cleanup grace period", { termination: task.termination });
391
398
  }
392
- if (status === "timeout" || status === "cancelled") {} else if ((automation.safety?.mode ?? "auto_apply") !== "auto_apply" && automation.afterRun?.kind === "webhook") this.appendRunEvent(run, "after_run.completed", "After-run webhook skipped by safety mode", { safety: automation.safety ?? { mode: "auto_apply" } });
393
- else if (automation.afterRun?.kind === "webhook") {
394
- activePhase = "after_run";
399
+ if (status === "timeout" || status === "cancelled") {} else if ((automation.safety?.mode ?? "auto_apply") !== "auto_apply" && automation.completionWebhookUrl) this.appendRunEvent(run, "completion_hook.completed", "Completion webhook skipped by safety mode", { safety: automation.safety ?? { mode: "auto_apply" } });
400
+ else if (automation.completionWebhookUrl) {
401
+ activePhase = "completion_hook";
395
402
  run = {
396
403
  ...run,
397
- currentPhase: "after_run"
404
+ currentPhase: "completion_hook"
398
405
  };
399
406
  saveAutomationRun(run);
400
- this.appendRunEvent(run, "after_run.started", "Calling after-run webhook", { url: automation.afterRun.url });
401
- await this.postAfterRunWebhook(automation.afterRun.url, run, controller.signal);
402
- this.appendRunEvent(run, "after_run.completed", "After-run webhook completed");
407
+ this.appendRunEvent(run, "completion_hook.started", "Calling completion webhook", { url: automation.completionWebhookUrl });
408
+ await this.postCompletionWebhook(automation.completionWebhookUrl, run, controller.signal);
409
+ this.appendRunEvent(run, "completion_hook.completed", "Completion webhook completed");
403
410
  }
404
411
  } catch (err) {
405
412
  error = err instanceof Error ? err.message : String(err);
406
413
  const deadlineExceeded = run.deadlineAtMs !== void 0 && Date.now() >= run.deadlineAtMs;
407
414
  status = controller.signal.aborted ? "cancelled" : deadlineExceeded ? "timeout" : "failed";
408
- this.appendRunEvent(run, activePhase === "after_run" ? "after_run.failed" : "action.failed", `Automation run ${status}`, { error });
415
+ this.appendRunEvent(run, activePhase === "completion_hook" ? "completion_hook.failed" : "action.failed", `Automation run ${status}`, { error });
409
416
  run = {
410
417
  ...run,
411
418
  status,
412
419
  error,
413
420
  termination: {
414
421
  reason: status === "cancelled" ? "user_cancelled" : status === "timeout" ? "deadline_exceeded" : "failed",
415
- component: activePhase === "after_run" ? "automation" : run.termination?.component,
422
+ component: activePhase === "completion_hook" ? "automation" : run.termination?.component,
416
423
  cancellationConfirmed: status !== "cancelled" || controller.signal.aborted
417
424
  }
418
425
  };
@@ -580,7 +587,7 @@ var AutomationService = class {
580
587
  saveAutomation(next);
581
588
  this.armTimer();
582
589
  }
583
- async postAfterRunWebhook(url, run, signal) {
590
+ async postCompletionWebhook(url, run, signal) {
584
591
  const remainingMs = run.deadlineAtMs === void 0 ? 3e4 : Math.max(1, run.deadlineAtMs - Date.now());
585
592
  const requestSignal = AbortSignal.any([signal, AbortSignal.timeout(remainingMs)]);
586
593
  const response = await fetch(url, {
@@ -18,7 +18,9 @@ function rowToAutomation(row) {
18
18
  trigger: parseJson(row.trigger_json),
19
19
  action: parseJson(row.action_json),
20
20
  safety: parseJson(row.safety_json) ?? { mode: "auto_apply" },
21
- afterRun: parseJson(row.after_run_json),
21
+ conversationMode: row.conversation_mode,
22
+ notificationPolicy: row.notification_policy,
23
+ completionWebhookUrl: row.completion_webhook_url ?? void 0,
22
24
  reliability: parseJson(row.reliability_json),
23
25
  state: parseJson(row.state_json) ?? {},
24
26
  createdAtMs: row.created_at_ms,
@@ -28,12 +30,14 @@ function rowToAutomation(row) {
28
30
  function upsertAutomation(db, automation) {
29
31
  db.prepare(`INSERT OR REPLACE INTO automations (
30
32
  automation_id, name, description, project_id, enabled, trigger_json, action_json,
31
- safety_json, after_run_json, reliability_json, state_json, created_at_ms, updated_at_ms
32
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(automation.id, automation.name, automation.description ?? null, automation.projectId ?? null, automation.enabled ? 1 : 0, JSON.stringify(automation.trigger), JSON.stringify(automation.action), bindJson(automation.safety ?? { mode: "auto_apply" }), bindJson(automation.afterRun), bindJson(automation.reliability), JSON.stringify(automation.state ?? {}), automation.createdAtMs, automation.updatedAtMs);
33
+ safety_json, conversation_mode, notification_policy, completion_webhook_url,
34
+ reliability_json, state_json, created_at_ms, updated_at_ms
35
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(automation.id, automation.name, automation.description ?? null, automation.projectId ?? null, automation.enabled ? 1 : 0, JSON.stringify(automation.trigger), JSON.stringify(automation.action), bindJson(automation.safety ?? { mode: "auto_apply" }), automation.conversationMode, automation.notificationPolicy, automation.completionWebhookUrl ?? null, bindJson(automation.reliability), JSON.stringify(automation.state ?? {}), automation.createdAtMs, automation.updatedAtMs);
33
36
  }
34
37
  const AUTOMATION_SELECT = `
35
38
  SELECT automation_id, name, description, project_id, enabled, trigger_json, action_json,
36
- safety_json, after_run_json, reliability_json, state_json, created_at_ms, updated_at_ms
39
+ safety_json, conversation_mode, notification_policy, completion_webhook_url,
40
+ reliability_json, state_json, created_at_ms, updated_at_ms
37
41
  FROM automations
38
42
  `;
39
43
  function listAutomations(options) {
@@ -17,3 +17,8 @@ export declare function listAutomationRunsForProductEvent(options: {
17
17
  limit?: number;
18
18
  }): AutomationProductEventRun[];
19
19
  export declare function deleteAutomationRunsForAutomation(automationId: string): void;
20
+ export declare function markAutomationRunRead(runId: string, readAtMs?: number): boolean;
21
+ export declare function markAllAutomationRunsRead(options?: {
22
+ projectId?: string;
23
+ readAtMs?: number;
24
+ }): number;
@@ -29,7 +29,8 @@ function rowToRun(row) {
29
29
  leaseOwner: row.lease_owner ?? void 0,
30
30
  leaseExpiresAtMs: row.lease_expires_at_ms ?? void 0,
31
31
  attemptNumber: row.attempt_number,
32
- rootRunId: row.root_run_id ?? void 0
32
+ rootRunId: row.root_run_id ?? void 0,
33
+ readAtMs: row.read_at_ms ?? void 0
33
34
  };
34
35
  }
35
36
  function rowToEvent(row) {
@@ -68,8 +69,8 @@ function saveAutomationRun(run) {
68
69
  duration_ms, summary, error, session_key, workflow_run_id, model,
69
70
  deadline_at_ms, current_phase, cancel_requested_at_ms, cancel_confirmed_at_ms,
70
71
  termination_json, heartbeat_at_ms, lease_owner, lease_expires_at_ms,
71
- attempt_number, root_run_id
72
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.id, run.automationId, run.automationName, run.status, JSON.stringify(run.triggerSnapshot), JSON.stringify(run.actionSnapshot), run.manual ? 1 : 0, run.createdAtMs, run.startedAtMs ?? null, run.endedAtMs ?? null, run.durationMs ?? null, run.summary ?? null, run.error ?? null, run.sessionKey ?? null, run.workflowRunId ?? null, run.model ?? null, run.deadlineAtMs ?? null, run.currentPhase ?? null, run.cancelRequestedAtMs ?? null, run.cancelConfirmedAtMs ?? null, run.termination ? JSON.stringify(run.termination) : null, run.heartbeatAtMs ?? null, run.leaseOwner ?? null, run.leaseExpiresAtMs ?? null, run.attemptNumber ?? 1, run.rootRunId ?? run.id);
72
+ attempt_number, root_run_id, read_at_ms
73
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.id, run.automationId, run.automationName, run.status, JSON.stringify(run.triggerSnapshot), JSON.stringify(run.actionSnapshot), run.manual ? 1 : 0, run.createdAtMs, run.startedAtMs ?? null, run.endedAtMs ?? null, run.durationMs ?? null, run.summary ?? null, run.error ?? null, run.sessionKey ?? null, run.workflowRunId ?? null, run.model ?? null, run.deadlineAtMs ?? null, run.currentPhase ?? null, run.cancelRequestedAtMs ?? null, run.cancelConfirmedAtMs ?? null, run.termination ? JSON.stringify(run.termination) : null, run.heartbeatAtMs ?? null, run.leaseOwner ?? null, run.leaseExpiresAtMs ?? null, run.attemptNumber ?? 1, run.rootRunId ?? run.id, run.readAtMs ?? null);
73
74
  trimRuns(db, run.automationId);
74
75
  });
75
76
  }
@@ -90,7 +91,7 @@ function getAutomationRun(runId) {
90
91
  duration_ms, summary, error, session_key, workflow_run_id, model,
91
92
  deadline_at_ms, current_phase, cancel_requested_at_ms, cancel_confirmed_at_ms,
92
93
  termination_json, heartbeat_at_ms, lease_owner, lease_expires_at_ms,
93
- attempt_number, root_run_id
94
+ attempt_number, root_run_id, read_at_ms
94
95
  FROM automation_runs
95
96
  WHERE run_id = ?`).get(runId);
96
97
  return row ? rowToRun(row) : null;
@@ -103,7 +104,7 @@ function listAutomationRuns(options) {
103
104
  duration_ms, summary, error, session_key, workflow_run_id, model,
104
105
  deadline_at_ms, current_phase, cancel_requested_at_ms, cancel_confirmed_at_ms,
105
106
  termination_json, heartbeat_at_ms, lease_owner, lease_expires_at_ms,
106
- attempt_number, root_run_id
107
+ attempt_number, root_run_id, read_at_ms
107
108
  FROM automation_runs`;
108
109
  let rows;
109
110
  if (options?.automationId) rows = db.prepare(`${base} WHERE automation_id = ? ORDER BY created_at_ms DESC LIMIT ?`).all(options.automationId, limit);
@@ -112,7 +113,7 @@ function listAutomationRuns(options) {
112
113
  r.duration_ms, r.summary, r.error, r.session_key, r.workflow_run_id, r.model,
113
114
  r.deadline_at_ms, r.current_phase, r.cancel_requested_at_ms, r.cancel_confirmed_at_ms,
114
115
  r.termination_json, r.heartbeat_at_ms, r.lease_owner, r.lease_expires_at_ms,
115
- r.attempt_number, r.root_run_id
116
+ r.attempt_number, r.root_run_id, r.read_at_ms
116
117
  FROM automation_runs r
117
118
  JOIN automations a ON a.automation_id = r.automation_id
118
119
  WHERE a.project_id = ?
@@ -138,7 +139,7 @@ function listAutomationRunsForProductEvent(options) {
138
139
  r.duration_ms, r.summary, r.error, r.session_key, r.workflow_run_id, r.model,
139
140
  r.deadline_at_ms, r.current_phase, r.cancel_requested_at_ms, r.cancel_confirmed_at_ms,
140
141
  r.termination_json, r.heartbeat_at_ms, r.lease_owner, r.lease_expires_at_ms,
141
- r.attempt_number, r.root_run_id,
142
+ r.attempt_number, r.root_run_id, r.read_at_ms,
142
143
  e.event_id, e.run_id AS event_run_id, e.automation_id AS event_automation_id,
143
144
  e.type AS event_type, e.message AS event_message, e.data_json AS event_data_json,
144
145
  e.created_at_ms AS event_created_at_ms
@@ -165,5 +166,20 @@ function deleteAutomationRunsForAutomation(automationId) {
165
166
  db.prepare(`DELETE FROM automation_runs WHERE automation_id = ?`).run(automationId);
166
167
  });
167
168
  }
169
+ function markAutomationRunRead(runId, readAtMs = Date.now()) {
170
+ return getSqliteDatabase().prepare(`UPDATE automation_runs SET read_at_ms = ? WHERE run_id = ? AND ended_at_ms IS NOT NULL`).run(readAtMs, runId).changes > 0;
171
+ }
172
+ function markAllAutomationRunsRead(options) {
173
+ const projectId = options?.projectId?.trim();
174
+ const result = getSqliteDatabase().prepare(projectId ? `UPDATE automation_runs
175
+ SET read_at_ms = ?
176
+ WHERE read_at_ms IS NULL
177
+ AND ended_at_ms IS NOT NULL
178
+ AND EXISTS (
179
+ SELECT 1 FROM automations a
180
+ WHERE a.automation_id = automation_runs.automation_id AND a.project_id = ?
181
+ )` : `UPDATE automation_runs SET read_at_ms = ? WHERE read_at_ms IS NULL AND ended_at_ms IS NOT NULL`).run(options?.readAtMs ?? Date.now(), ...projectId ? [projectId] : []);
182
+ return Number(result.changes);
183
+ }
168
184
  //#endregion
169
- export { appendAutomationRunEvent, deleteAutomationRunsForAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, saveAutomationRun, touchAutomationRunLease };
185
+ export { appendAutomationRunEvent, deleteAutomationRunsForAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, markAllAutomationRunsRead, markAutomationRunRead, saveAutomationRun, touchAutomationRunLease };
@@ -1,3 +1,3 @@
1
1
  import { deleteAutomation, getAutomation, listAutomations, saveAutomation, saveAutomations } from "./automation-repository.js";
2
- import { appendAutomationRunEvent, deleteAutomationRunsForAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, saveAutomationRun, touchAutomationRunLease } from "./automation-run-repository.js";
3
- export { appendAutomationRunEvent, deleteAutomation, deleteAutomationRunsForAutomation, getAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, listAutomations, saveAutomation, saveAutomationRun, saveAutomations, touchAutomationRunLease };
2
+ import { appendAutomationRunEvent, deleteAutomationRunsForAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, markAllAutomationRunsRead, markAutomationRunRead, saveAutomationRun, touchAutomationRunLease } from "./automation-run-repository.js";
3
+ export { appendAutomationRunEvent, deleteAutomation, deleteAutomationRunsForAutomation, getAutomation, getAutomationRun, listAutomationRunEvents, listAutomationRuns, listAutomationRunsForProductEvent, listAutomations, markAllAutomationRunsRead, markAutomationRunRead, saveAutomation, saveAutomationRun, saveAutomations, touchAutomationRunLease };
@@ -52,7 +52,7 @@ async function checkDatabaseSchema(ctx) {
52
52
  id: "database-schema",
53
53
  label: "Database schema",
54
54
  status: "pass",
55
- message: `SQLite schema v${status.dbVersion} (current release v120).`,
55
+ message: `SQLite schema v${status.dbVersion} (current release v122).`,
56
56
  hints: []
57
57
  };
58
58
  }
@@ -0,0 +1,4 @@
1
+ import type { PrepareAutomationAgentSessionInput } from '../automations/index.js';
2
+ import type { ProjectService } from '../projects/index.js';
3
+ import type { SessionStore } from '../session/index.js';
4
+ export declare function prepareAutomationAgentSession(store: SessionStore, projects: ProjectService, input: PrepareAutomationAgentSessionInput): Promise<void>;
@@ -0,0 +1,40 @@
1
+ //#region src/gateway/automation-agent-session.ts
2
+ async function prepareAutomationAgentSession(store, projects, input) {
3
+ await store.resolveTranscriptPath(input.sessionKey, { metadata: {
4
+ projectId: input.projectId,
5
+ sourceChannel: "automation",
6
+ sourceChatId: `default:dm:${input.peerId}`,
7
+ sessionType: "chat",
8
+ routing: {
9
+ agentId: input.agentId,
10
+ source: "automation",
11
+ accountId: "default",
12
+ peerKind: "dm",
13
+ peerId: input.peerId
14
+ }
15
+ } });
16
+ const current = await store.getMetadata(input.sessionKey);
17
+ await store.updateMetadata(input.sessionKey, {
18
+ sourceChannel: "automation",
19
+ sourceChatId: `default:dm:${input.peerId}`,
20
+ sessionType: "chat",
21
+ routing: {
22
+ agentId: input.agentId,
23
+ source: "automation",
24
+ accountId: "default",
25
+ peerKind: "dm",
26
+ peerId: input.peerId
27
+ },
28
+ tags: [...new Set([...current?.tags ?? [], "automation"])],
29
+ customData: {
30
+ ...current?.customData ?? {},
31
+ origin: "automation",
32
+ automationId: input.automationId,
33
+ latestAutomationRunId: input.runId
34
+ }
35
+ });
36
+ if (input.projectId) projects.attachSession(input.sessionKey, input.projectId);
37
+ else if (current?.projectId) projects.detachSession(input.sessionKey);
38
+ }
39
+ //#endregion
40
+ export { prepareAutomationAgentSession };
@@ -41,7 +41,8 @@ async function reconcileDreamingAutomations(params) {
41
41
  trigger,
42
42
  action,
43
43
  safety: { mode: "auto_apply" },
44
- afterRun: { kind: "none" },
44
+ conversationMode: "continuous",
45
+ notificationPolicy: "none",
45
46
  reliability: { disableAfterConsecutiveFailures: 3 }
46
47
  };
47
48
  if (!current) {
@@ -52,7 +53,7 @@ async function reconcileDreamingAutomations(params) {
52
53
  result.created = true;
53
54
  return result;
54
55
  }
55
- if (current.name !== next.name || current.description !== next.description || !current.enabled || JSON.stringify(current.trigger) !== JSON.stringify(trigger) || JSON.stringify(current.action) !== JSON.stringify(action) || current.safety?.mode !== "auto_apply" || current.afterRun?.kind !== "none" || current.reliability?.disableAfterConsecutiveFailures !== 3) {
56
+ if (current.name !== next.name || current.description !== next.description || !current.enabled || JSON.stringify(current.trigger) !== JSON.stringify(trigger) || JSON.stringify(current.action) !== JSON.stringify(action) || current.safety?.mode !== "auto_apply" || current.conversationMode !== "continuous" || current.notificationPolicy !== "none" || current.reliability?.disableAfterConsecutiveFailures !== 3) {
56
57
  await params.automationService.update(USER_CONTEXT_CONSOLIDATION_AUTOMATION_ID, next);
57
58
  result.updated = true;
58
59
  }
@@ -74,9 +74,43 @@ function registerCommandsSkillsRoutes(authenticated, deps) {
74
74
  }, 400);
75
75
  }
76
76
  });
77
- authenticated.get("/api/chat/skills", (c) => {
77
+ authenticated.get("/api/chat/skills", async (c) => {
78
+ const sessionKey = c.req.query("sessionKey")?.trim();
78
79
  const agentId = c.req.query("agentId")?.trim() || service.getConfig().agents?.default || "main";
79
- const payload = service.marketplace.getAgentSkillsApi(agentId);
80
+ const payload = sessionKey ? await service.marketplace.getSessionSkillsApi(sessionKey) : service.marketplace.getAgentSkillsApi(agentId);
81
+ return c.json({
82
+ ok: true,
83
+ payload
84
+ });
85
+ });
86
+ authenticated.get("/api/chat/workspace-trust", async (c) => {
87
+ const sessionKey = c.req.query("sessionKey")?.trim();
88
+ if (!sessionKey) return c.json({
89
+ ok: false,
90
+ error: "Missing sessionKey"
91
+ }, 400);
92
+ const payload = await service.marketplace.getSessionWorkspaceTrustApi(sessionKey);
93
+ return c.json({
94
+ ok: true,
95
+ payload
96
+ });
97
+ });
98
+ authenticated.patch("/api/chat/workspace-trust", async (c) => {
99
+ let body;
100
+ try {
101
+ body = await c.req.json();
102
+ } catch {
103
+ return c.json({
104
+ ok: false,
105
+ error: "Invalid JSON"
106
+ }, 400);
107
+ }
108
+ const sessionKey = typeof body.sessionKey === "string" ? body.sessionKey.trim() : "";
109
+ if (!sessionKey || typeof body.trusted !== "boolean") return c.json({
110
+ ok: false,
111
+ error: "Expected { sessionKey: string, trusted: boolean }"
112
+ }, 400);
113
+ const payload = await service.marketplace.setSessionWorkspaceTrustApi(sessionKey, body.trusted);
80
114
  return c.json({
81
115
  ok: true,
82
116
  payload
@@ -161,6 +161,7 @@ const AUTHENTICATED_LAZY_ROUTE_BUNDLES = [
161
161
  "/api/review",
162
162
  "/api/skills",
163
163
  "/api/chat/skills",
164
+ "/api/chat/workspace-trust",
164
165
  "/api/chat/capabilities"
165
166
  ]),
166
167
  load: async () => {
@@ -2,7 +2,7 @@ import type { Config } from '../../config/schema.js';
2
2
  import type { AgentService } from '../../agent/service.js';
3
3
  import type { ChannelManager } from '../../channels/manager.js';
4
4
  import type { ExtensionLoader } from '../../extensions/loader.js';
5
- import type { AgentSkillAvailabilityEntry, AgentSkillAvailabilityPayload, SkillCatalogEntry, SkillCatalogRuntimeMeta } from '../../agent/agent-manager.js';
5
+ import type { AgentSkillAvailabilityEntry, AgentSkillAvailabilityPayload, SkillCatalogEntry, SkillCatalogRuntimeMeta, WorkspaceTrustState } from '../../agent/agent-manager.js';
6
6
  import type { ManagedSkillListItem } from '../../agent/skills/managed-store.js';
7
7
  import type { SkillMarkdownPreviewPayload } from '../../agent/skills/types.js';
8
8
  import type { MarketplaceCategoryOption, SkillsStoreListParams, UnifiedMarketplaceListResponse, UnifiedMarketplacePackageDetail } from '../../agent/skills/skills-marketplace.js';
@@ -73,6 +73,9 @@ export declare class GatewayMarketplaceService {
73
73
  } & SkillCatalogRuntimeMeta;
74
74
  getSkillsStatusApi(): SkillCatalogRuntimeMeta;
75
75
  getAgentSkillsApi(agentId: string): AgentSkillAvailabilityPayload;
76
+ getSessionSkillsApi(sessionKey: string): Promise<AgentSkillAvailabilityPayload>;
77
+ getSessionWorkspaceTrustApi(sessionKey: string): Promise<WorkspaceTrustState>;
78
+ setSessionWorkspaceTrustApi(sessionKey: string, trusted: boolean): Promise<WorkspaceTrustState>;
76
79
  getSkillMarkdownSource(skillName: string): SkillMarkdownPreviewPayload | null;
77
80
  deleteSkill(skillId: string, target?: SkillInstallTarget): void;
78
81
  installSkillZip(buffer: Buffer, opts: {
@@ -68,6 +68,15 @@ var GatewayMarketplaceService = class {
68
68
  getAgentSkillsApi(agentId) {
69
69
  return this.opts.getAgentService().getAgentSkillAvailability(agentId);
70
70
  }
71
+ getSessionSkillsApi(sessionKey) {
72
+ return this.opts.getAgentService().getSessionSkillAvailability(sessionKey);
73
+ }
74
+ getSessionWorkspaceTrustApi(sessionKey) {
75
+ return this.opts.getAgentService().getSessionWorkspaceTrust(sessionKey);
76
+ }
77
+ setSessionWorkspaceTrustApi(sessionKey, trusted) {
78
+ return this.opts.getAgentService().setSessionWorkspaceTrust(sessionKey, trusted);
79
+ }
71
80
  getSkillMarkdownSource(skillName) {
72
81
  return this.opts.getAgentService().getSkillMarkdownSource(skillName);
73
82
  }
@@ -69,6 +69,7 @@ import "../discussions/index.js";
69
69
  import { EndpointToolRuntime } from "../endpoint-tools/runtime.js";
70
70
  import "../endpoint-tools/index.js";
71
71
  import { assertGatewayAuthConfigured, extractToken, resolveGatewayAuth, validateToken } from "./auth.js";
72
+ import { prepareAutomationAgentSession } from "./automation-agent-session.js";
72
73
  import { reconcileDreamingAutomations } from "./dreaming-automation-reconciler.js";
73
74
  import { NotesStore } from "../notes/store.js";
74
75
  import { buildNoteAgentContext } from "../notes/agent-context.js";
@@ -495,7 +496,7 @@ var GatewayService = class {
495
496
  this.automationService.setDeps({
496
497
  agentService: this._agentService,
497
498
  getDefaultAgentId: () => getDefaultAgentId(this.config),
498
- getProjectWorkspaceRoot: (projectId) => this.projects.get(projectId)?.workspaceRoot,
499
+ prepareAgentSession: (input) => prepareAutomationAgentSession(this.sessionIndex.getStore(), this.projects, input),
499
500
  workflowRunService: this.createWorkflowRunService(),
500
501
  browserRecipeService: this.browserRecipes,
501
502
  executeTaskCommand: ({ taskId, idempotencyKey, command }) => {
@@ -865,6 +866,7 @@ var GatewayService = class {
865
866
  this.automationService.setDeps({
866
867
  agentService: this.agentService,
867
868
  getDefaultAgentId: () => getDefaultAgentId(this.config),
869
+ prepareAgentSession: (input) => prepareAutomationAgentSession(this.sessionIndex.getStore(), this.projects, input),
868
870
  workflowRunService: this.createWorkflowRunService(),
869
871
  browserRecipeService: this.browserRecipes,
870
872
  executeTaskCommand: ({ taskId, idempotencyKey, command }) => {
@@ -1502,7 +1504,22 @@ var GatewayService = class {
1502
1504
  this.createMobileNotificationService().handleGatewayEvent(type, payload);
1503
1505
  }
1504
1506
  handleAutomationRunCompleted(run) {
1505
- this.emit("automation.run.completed", { run });
1507
+ this.automationService.get(run.automationId).then((automation) => {
1508
+ if (!automation) return;
1509
+ const requiresAttention = run.status !== "succeeded" || (automation.safety?.mode ?? "auto_apply") !== "auto_apply";
1510
+ this.emit("automation.run.completed", {
1511
+ run,
1512
+ notificationPolicy: automation.notificationPolicy,
1513
+ requiresAttention,
1514
+ projectId: automation.projectId
1515
+ });
1516
+ }).catch((err) => {
1517
+ log.warn({
1518
+ err,
1519
+ automationId: run.automationId,
1520
+ runId: run.id
1521
+ }, "Automation completion event failed");
1522
+ });
1506
1523
  }
1507
1524
  startAutomationProductEventBridge() {
1508
1525
  this.stopAutomationProductEventBridge?.();
@@ -40,10 +40,13 @@ function taskEvent(payload) {
40
40
  }
41
41
  function automationEvent(payload) {
42
42
  if (!payload || typeof payload !== "object") return null;
43
- if (payload.silent === true) return null;
44
- const run = payload.run;
43
+ const event = payload;
44
+ if (event.notificationPolicy === "none") return null;
45
+ if (event.notificationPolicy === "attention" && event.requiresAttention !== true) return null;
46
+ const run = event.run;
45
47
  if (!run || run.status !== "failed" && run.status !== "timeout" && run.status !== "succeeded") return null;
46
48
  const type = run.status === "succeeded" ? "automation.completed" : "automation.failed";
49
+ const route = `/automations?automation=${encodeURIComponent(run.automationId)}&run=${encodeURIComponent(run.id)}`;
47
50
  return {
48
51
  type,
49
52
  entity: {
@@ -53,9 +56,9 @@ function automationEvent(payload) {
53
56
  priority: type === "automation.failed" ? "high" : "normal",
54
57
  title: type === "automation.failed" ? "Automation failed" : "Automation completed",
55
58
  body: run.automationName.slice(0, 120),
56
- deepLink: "/automation",
59
+ deepLink: route,
57
60
  payload: {
58
- route: "/automation",
61
+ route,
59
62
  automationId: run.automationId,
60
63
  runId: run.id,
61
64
  eventType: type
@@ -0,0 +1,24 @@
1
+ ALTER TABLE automations ADD COLUMN conversation_mode TEXT NOT NULL DEFAULT 'new_session'
2
+ CHECK (conversation_mode IN ('new_session', 'continuous'));
3
+ ALTER TABLE automations ADD COLUMN notification_policy TEXT NOT NULL DEFAULT 'attention'
4
+ CHECK (notification_policy IN ('attention', 'all', 'none'));
5
+ ALTER TABLE automations ADD COLUMN completion_webhook_url TEXT;
6
+
7
+ UPDATE automations
8
+ SET completion_webhook_url = json_extract(after_run_json, '$.url')
9
+ WHERE json_extract(after_run_json, '$.kind') = 'webhook';
10
+
11
+ ALTER TABLE automations DROP COLUMN after_run_json;
12
+
13
+ UPDATE automation_runs
14
+ SET current_phase = 'completion_hook'
15
+ WHERE current_phase = 'after_run';
16
+
17
+ UPDATE automation_run_events
18
+ SET type = replace(type, 'after_run.', 'completion_hook.'),
19
+ message = replace(message, 'After-run webhook', 'Completion webhook')
20
+ WHERE type LIKE 'after_run.%';
21
+
22
+ ALTER TABLE automation_runs ADD COLUMN read_at_ms INTEGER;
23
+ CREATE INDEX IF NOT EXISTS idx_automation_runs_unread_created
24
+ ON automation_runs(read_at_ms, created_at_ms DESC);
@@ -0,0 +1,18 @@
1
+ UPDATE sessions
2
+ SET project_id = (
3
+ SELECT a.project_id
4
+ FROM automation_runs r
5
+ JOIN automations a ON a.automation_id = r.automation_id
6
+ WHERE r.session_key = sessions.session_key
7
+ AND a.project_id IS NOT NULL
8
+ ORDER BY r.created_at_ms DESC
9
+ LIMIT 1
10
+ )
11
+ WHERE project_id IS NULL
12
+ AND EXISTS (
13
+ SELECT 1
14
+ FROM automation_runs r
15
+ JOIN automations a ON a.automation_id = r.automation_id
16
+ WHERE r.session_key = sessions.session_key
17
+ AND a.project_id IS NOT NULL
18
+ );
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 120;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 122;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -12,7 +12,7 @@ const log = createLogger("Sqlite:Migrations");
12
12
  /** Baseline schema version applied from schema.sql on first open. */
13
13
  const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
14
14
  /** Latest schema version this release supports (increment when adding migrations). */
15
- const XOPC_DB_SCHEMA_VERSION = 120;
15
+ const XOPC_DB_SCHEMA_VERSION = 122;
16
16
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
17
  function backupBeforeTaskCutover(db, databasePath) {
18
18
  db.exec("PRAGMA wal_checkpoint(FULL)");
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration) {
57
57
  * Each step runs in its own transaction and updates schema_meta on success.
58
58
  */
59
59
  function applyPendingMigrations(db, options = {}) {
60
- const targetVersion = options.targetVersion ?? 120;
60
+ const targetVersion = options.targetVersion ?? 122;
61
61
  let currentVersion = readSchemaVersion(db);
62
62
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
63
63
  if (currentVersion >= targetVersion) return currentVersion;
@@ -94,7 +94,7 @@ function applyPendingMigrations(db, options = {}) {
94
94
  }
95
95
  /** Inspect schema version without mutating the database. */
96
96
  function inspectSchemaMigrationStatus(db, options = {}) {
97
- const appVersion = options.targetVersion ?? 120;
97
+ const appVersion = options.targetVersion ?? 122;
98
98
  const dbVersion = readSchemaVersion(db);
99
99
  if (dbVersion > appVersion) return {
100
100
  dbVersion,
@@ -19,7 +19,7 @@ const log = createLogger('Sqlite:Migrations');
19
19
  export const XOPC_DB_BASELINE_SCHEMA_VERSION = 11;
20
20
 
21
21
  /** Latest schema version this release supports (increment when adding migrations). */
22
- export const XOPC_DB_SCHEMA_VERSION = 120;
22
+ export const XOPC_DB_SCHEMA_VERSION = 122;
23
23
 
24
24
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
25
 
@@ -32,7 +32,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
32
32
  currentVersion = 11;
33
33
  }
34
34
  applyPendingMigrations(db, {
35
- targetVersion: 120,
35
+ targetVersion: 122,
36
36
  databasePath: options.databasePath
37
37
  });
38
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.205",
3
+ "version": "0.0.206",
4
4
  "description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -150,6 +150,7 @@
150
150
  "eslint": "^10.4.0",
151
151
  "jsdom": "^29.1.1",
152
152
  "onnxruntime-common": "1.21.0",
153
+ "node-pty": "1.1.0",
153
154
  "playwright-core": "^1.60.0",
154
155
  "sherpa-onnx-node": "1.13.4",
155
156
  "svgo": "4.0.2",
@@ -162,9 +163,9 @@
162
163
  "vitepress": "^1.6.4",
163
164
  "vitest": "4.1.5",
164
165
  "vue": "^3.5.35",
165
- "@xopcai/gateway-contract": "0.0.0",
166
166
  "@xopcai/endpoint-tools-protocol": "0.0.0",
167
167
  "@xopcai/realtime-protocol": "0.0.0",
168
+ "@xopcai/gateway-contract": "0.0.0",
168
169
  "@xopcai/realtime-client": "0.0.0"
169
170
  },
170
171
  "scripts": {