agentix-cli 0.23.0 → 0.25.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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -103
  3. package/agentx.example.json +9 -3
  4. package/dist/App-6Y7B3YQH.js +8 -0
  5. package/dist/App-6Y7B3YQH.js.map +1 -0
  6. package/dist/ChatApp-KIEUJ2DS.js +7 -0
  7. package/dist/ChatApp-KIEUJ2DS.js.map +1 -0
  8. package/dist/{agent-FSE4KRLX.js → agent-QGZIAZMY.js} +2 -2
  9. package/dist/architect-SEPWMWS4.js +2 -0
  10. package/dist/{board-dashboard-PUACKWAN.js → board-dashboard-CQ5F4GPD.js} +3756 -1010
  11. package/dist/board-dashboard-CQ5F4GPD.js.map +1 -0
  12. package/dist/builtin-FRKAQMFS.js +2 -0
  13. package/dist/chunk-2WVX3LEC.js +2 -0
  14. package/dist/chunk-2WVX3LEC.js.map +1 -0
  15. package/dist/chunk-3WQFCIB6.js +24 -0
  16. package/dist/chunk-3WQFCIB6.js.map +1 -0
  17. package/dist/chunk-53GCFZPR.js +116 -0
  18. package/dist/chunk-53GCFZPR.js.map +1 -0
  19. package/dist/{chunk-IKBHHTIT.js → chunk-5U35WAZF.js} +2 -2
  20. package/dist/chunk-67Q4DCPS.js +54 -0
  21. package/dist/chunk-67Q4DCPS.js.map +1 -0
  22. package/dist/chunk-76Z4VHDX.js +8 -0
  23. package/dist/chunk-76Z4VHDX.js.map +1 -0
  24. package/dist/{chunk-7P5QRX5W.js → chunk-BFXONUUN.js} +2 -2
  25. package/dist/{chunk-DXTIG5YV.js → chunk-FDMWOPEL.js} +2 -2
  26. package/dist/chunk-FDWOECJY.js +54 -0
  27. package/dist/chunk-FDWOECJY.js.map +1 -0
  28. package/dist/chunk-G57RZCXM.js +5 -0
  29. package/dist/chunk-G57RZCXM.js.map +1 -0
  30. package/dist/{chunk-NOF4HSGK.js → chunk-H5D7ATXS.js} +2 -2
  31. package/dist/chunk-HA3XI7Z2.js +155 -0
  32. package/dist/chunk-HA3XI7Z2.js.map +1 -0
  33. package/dist/chunk-HR4JSRZS.js +3 -0
  34. package/dist/{chunk-A6BKSZQA.js.map → chunk-HR4JSRZS.js.map} +1 -1
  35. package/dist/chunk-I3SIXTJO.js +44 -0
  36. package/dist/chunk-I3SIXTJO.js.map +1 -0
  37. package/dist/chunk-ISEBLHWX.js +118 -0
  38. package/dist/chunk-ISEBLHWX.js.map +1 -0
  39. package/dist/{chunk-4ITQP3XL.js → chunk-JP5IEXR7.js} +2 -2
  40. package/dist/chunk-JXWAGMI2.js +13 -0
  41. package/dist/chunk-JXWAGMI2.js.map +1 -0
  42. package/dist/chunk-KUCCW2VG.js +431 -0
  43. package/dist/chunk-KUCCW2VG.js.map +1 -0
  44. package/dist/chunk-LDHPMJ7M.js +257 -0
  45. package/dist/chunk-LDHPMJ7M.js.map +1 -0
  46. package/dist/{chunk-KWUWZQGV.js → chunk-LNXCJWOU.js} +29 -30
  47. package/dist/chunk-LNXCJWOU.js.map +1 -0
  48. package/dist/chunk-M4AMWP3H.js +178 -0
  49. package/dist/chunk-M4AMWP3H.js.map +1 -0
  50. package/dist/chunk-NGSIRE4X.js +126 -0
  51. package/dist/chunk-NGSIRE4X.js.map +1 -0
  52. package/dist/{chunk-CNNLREHD.js → chunk-P4VWIVKN.js} +268 -410
  53. package/dist/chunk-P4VWIVKN.js.map +1 -0
  54. package/dist/chunk-Q6YO7LYE.js +47 -0
  55. package/dist/chunk-Q6YO7LYE.js.map +1 -0
  56. package/dist/chunk-TMKLHTCG.js +971 -0
  57. package/dist/chunk-TMKLHTCG.js.map +1 -0
  58. package/dist/chunk-TRJTTBE2.js +7 -0
  59. package/dist/chunk-TRJTTBE2.js.map +1 -0
  60. package/dist/chunk-U2CKL755.js +4 -0
  61. package/dist/chunk-U2CKL755.js.map +1 -0
  62. package/dist/chunk-UCQ2N3KO.js +9 -0
  63. package/dist/chunk-UCQ2N3KO.js.map +1 -0
  64. package/dist/chunk-W4YR2RO2.js +2 -0
  65. package/dist/chunk-W4YR2RO2.js.map +1 -0
  66. package/dist/cli.js +1 -1
  67. package/dist/cli.js.map +1 -1
  68. package/dist/client-GGVVLOOF.js +2 -0
  69. package/dist/{compaction-BTUJJ3LJ.js → compaction-NWEMOSHA.js} +3 -3
  70. package/dist/config-7FSZFRTQ.js +2 -0
  71. package/dist/config-mutator-POCOYYJ4.js +2 -0
  72. package/dist/{context-planner-I7ID7XF3.js → context-planner-45GEWKGL.js} +2 -2
  73. package/dist/doctor-DHLBEKA4.js +2 -0
  74. package/dist/heal-HZ3EQD52.js +2 -0
  75. package/dist/index.d.ts +1193 -225
  76. package/dist/index.js +12 -12
  77. package/dist/index.js.map +1 -1
  78. package/dist/memory-extract-AENFVVO5.js +2 -0
  79. package/dist/processes-UIC4XXA6.js +2 -0
  80. package/dist/program-WVAVJFND.js +2 -0
  81. package/dist/projects-api-KRME2YKZ.js +2 -0
  82. package/dist/projects-api-KRME2YKZ.js.map +1 -0
  83. package/dist/projects-mutate-3NHVG437.js +7 -0
  84. package/dist/projects-mutate-3NHVG437.js.map +1 -0
  85. package/dist/providers-L27BJ5A6.js +2 -0
  86. package/dist/registry-T36ZIAWV.js +2 -0
  87. package/dist/rotation-memo-B3EXXSEH.js +13 -0
  88. package/dist/rotation-memo-B3EXXSEH.js.map +1 -0
  89. package/dist/scheduler-GH7TIKYS.js +2 -0
  90. package/dist/surface-usage-PRL6Z4U3.js +2 -0
  91. package/dist/wiki-M6YNRFC4.js +2 -0
  92. package/dist/workflows/templates/branching.yaml +75 -0
  93. package/dist/workflows/templates/extract.yaml +59 -0
  94. package/dist/workflows/templates/linear.yaml +37 -0
  95. package/dist/workflows/templates/retry.yaml +61 -0
  96. package/dist/workspace-setup-TBQAHYHJ.js +2 -0
  97. package/package.json +10 -3
  98. package/dist/board-dashboard-PUACKWAN.js.map +0 -1
  99. package/dist/builtin-TQKMR42Z.js +0 -2
  100. package/dist/chunk-3SHMHYPD.js +0 -450
  101. package/dist/chunk-3SHMHYPD.js.map +0 -1
  102. package/dist/chunk-4INOOHND.js +0 -6
  103. package/dist/chunk-4INOOHND.js.map +0 -1
  104. package/dist/chunk-5F3J55LG.js +0 -2
  105. package/dist/chunk-5F3J55LG.js.map +0 -1
  106. package/dist/chunk-72PAN7UI.js +0 -103
  107. package/dist/chunk-72PAN7UI.js.map +0 -1
  108. package/dist/chunk-A6BKSZQA.js +0 -3
  109. package/dist/chunk-BHSPAP4U.js +0 -44
  110. package/dist/chunk-BHSPAP4U.js.map +0 -1
  111. package/dist/chunk-BOUKM2PS.js +0 -163
  112. package/dist/chunk-BOUKM2PS.js.map +0 -1
  113. package/dist/chunk-CDNWCYF7.js +0 -2
  114. package/dist/chunk-CDNWCYF7.js.map +0 -1
  115. package/dist/chunk-CNNLREHD.js.map +0 -1
  116. package/dist/chunk-EK37JHJI.js +0 -13
  117. package/dist/chunk-EK37JHJI.js.map +0 -1
  118. package/dist/chunk-EQPUB7CZ.js +0 -685
  119. package/dist/chunk-EQPUB7CZ.js.map +0 -1
  120. package/dist/chunk-EW27SCCC.js +0 -2
  121. package/dist/chunk-EW27SCCC.js.map +0 -1
  122. package/dist/chunk-GOLZ6NEL.js +0 -111
  123. package/dist/chunk-GOLZ6NEL.js.map +0 -1
  124. package/dist/chunk-JGXE2YNV.js +0 -46
  125. package/dist/chunk-JGXE2YNV.js.map +0 -1
  126. package/dist/chunk-KWUWZQGV.js.map +0 -1
  127. package/dist/chunk-MTRWXRFF.js +0 -380
  128. package/dist/chunk-MTRWXRFF.js.map +0 -1
  129. package/dist/chunk-N67JZAXU.js +0 -2
  130. package/dist/chunk-N67JZAXU.js.map +0 -1
  131. package/dist/chunk-PD5Q7D6Z.js +0 -7
  132. package/dist/chunk-PD5Q7D6Z.js.map +0 -1
  133. package/dist/chunk-Q6CAFGXT.js +0 -118
  134. package/dist/chunk-Q6CAFGXT.js.map +0 -1
  135. package/dist/chunk-WJXU5F4Z.js +0 -53
  136. package/dist/chunk-WJXU5F4Z.js.map +0 -1
  137. package/dist/chunk-ZZFXI3CS.js +0 -8
  138. package/dist/chunk-ZZFXI3CS.js.map +0 -1
  139. package/dist/config-ADS54KLC.js +0 -2
  140. package/dist/config-mutator-WTRMBZTO.js +0 -2
  141. package/dist/doctor-T6RB4Q5X.js +0 -2
  142. package/dist/editor-chat-RVLCKKB7.js +0 -137
  143. package/dist/editor-chat-RVLCKKB7.js.map +0 -1
  144. package/dist/heal-YEQYWX2L.js +0 -2
  145. package/dist/inbox-BIN2S5XN.js +0 -2
  146. package/dist/memory-extract-KRCHD3OP.js +0 -2
  147. package/dist/processes-RJ6RDP32.js +0 -2
  148. package/dist/program-V7NGFB2R.js +0 -2
  149. package/dist/providers-WVCMIW67.js +0 -2
  150. package/dist/registry-42HEWCLA.js +0 -2
  151. package/dist/rotation-memo-GMTZPMNE.js +0 -13
  152. package/dist/rotation-memo-GMTZPMNE.js.map +0 -1
  153. package/dist/scheduler-C56ABAVP.js +0 -2
  154. package/dist/slack-6P7ZJ32Y.js +0 -3
  155. package/dist/slack-6P7ZJ32Y.js.map +0 -1
  156. package/dist/store-BAPDZHXE.js +0 -2
  157. package/dist/task-store-RHISEU6V.js +0 -2
  158. package/dist/telegram-PIYDRM46.js +0 -3
  159. package/dist/telegram-PIYDRM46.js.map +0 -1
  160. package/dist/types-ZRLHHCGB.js +0 -2
  161. package/dist/web/workflow-editor.global.js +0 -1452
  162. package/dist/web/workflow-editor.global.js.map +0 -1
  163. package/dist/whatsapp-PFRDMUMT.js +0 -3
  164. package/dist/whatsapp-PFRDMUMT.js.map +0 -1
  165. package/dist/wiki-53W5NLZV.js +0 -2
  166. package/dist/workspace-setup-DQCZX2NE.js +0 -2
  167. package/dist/workspace-setup-DQCZX2NE.js.map +0 -1
  168. /package/dist/{agent-FSE4KRLX.js.map → agent-QGZIAZMY.js.map} +0 -0
  169. /package/dist/{builtin-TQKMR42Z.js.map → architect-SEPWMWS4.js.map} +0 -0
  170. /package/dist/{config-ADS54KLC.js.map → builtin-FRKAQMFS.js.map} +0 -0
  171. /package/dist/{chunk-IKBHHTIT.js.map → chunk-5U35WAZF.js.map} +0 -0
  172. /package/dist/{chunk-7P5QRX5W.js.map → chunk-BFXONUUN.js.map} +0 -0
  173. /package/dist/{chunk-DXTIG5YV.js.map → chunk-FDMWOPEL.js.map} +0 -0
  174. /package/dist/{chunk-NOF4HSGK.js.map → chunk-H5D7ATXS.js.map} +0 -0
  175. /package/dist/{chunk-4ITQP3XL.js.map → chunk-JP5IEXR7.js.map} +0 -0
  176. /package/dist/{config-mutator-WTRMBZTO.js.map → client-GGVVLOOF.js.map} +0 -0
  177. /package/dist/{compaction-BTUJJ3LJ.js.map → compaction-NWEMOSHA.js.map} +0 -0
  178. /package/dist/{doctor-T6RB4Q5X.js.map → config-7FSZFRTQ.js.map} +0 -0
  179. /package/dist/{heal-YEQYWX2L.js.map → config-mutator-POCOYYJ4.js.map} +0 -0
  180. /package/dist/{context-planner-I7ID7XF3.js.map → context-planner-45GEWKGL.js.map} +0 -0
  181. /package/dist/{inbox-BIN2S5XN.js.map → doctor-DHLBEKA4.js.map} +0 -0
  182. /package/dist/{memory-extract-KRCHD3OP.js.map → heal-HZ3EQD52.js.map} +0 -0
  183. /package/dist/{processes-RJ6RDP32.js.map → memory-extract-AENFVVO5.js.map} +0 -0
  184. /package/dist/{program-V7NGFB2R.js.map → processes-UIC4XXA6.js.map} +0 -0
  185. /package/dist/{providers-WVCMIW67.js.map → program-WVAVJFND.js.map} +0 -0
  186. /package/dist/{registry-42HEWCLA.js.map → providers-L27BJ5A6.js.map} +0 -0
  187. /package/dist/{scheduler-C56ABAVP.js.map → registry-T36ZIAWV.js.map} +0 -0
  188. /package/dist/{store-BAPDZHXE.js.map → scheduler-GH7TIKYS.js.map} +0 -0
  189. /package/dist/{task-store-RHISEU6V.js.map → surface-usage-PRL6Z4U3.js.map} +0 -0
  190. /package/dist/{types-ZRLHHCGB.js.map → wiki-M6YNRFC4.js.map} +0 -0
  191. /package/dist/{wiki-53W5NLZV.js.map → workspace-setup-TBQAHYHJ.js.map} +0 -0
@@ -1,380 +0,0 @@
1
- import{a as o,b as t}from"./chunk-CNNLREHD.js";function s(e={}){let a=`<div class="ax-inbox__root" data-actor="${t(e.actor??"")}">
2
- <aside class="ax-inbox__list">
3
- <header>
4
- <h2>Inbox</h2>
5
- <button id="inbox-refresh" class="ax-inbox__icon" title="Refresh (r)">\u21BB</button>
6
- </header>
7
- <div class="ax-inbox__actor">
8
- <label>Actor <input id="inbox-actor" type="text" placeholder="actor:<id>" value="${t(e.actor??"")}" autocomplete="off" /></label>
9
- </div>
10
- <ul id="inbox-tasks" class="ax-inbox__cards" aria-live="polite"></ul>
11
- <div id="inbox-empty" class="ax-inbox__empty" hidden>
12
- <p>No open tasks.</p>
13
- <p class="hint">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>
14
- </div>
15
- </aside>
16
-
17
- <section class="ax-inbox__detail">
18
- <header id="inbox-detail-head">
19
- <span class="hint">Select a task to open its form.</span>
20
- <button id="inbox-history-toggle" type="button" style="margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer">Show completed history</button>
21
- </header>
22
- <div id="inbox-detail-body" class="ax-inbox__detail-body"></div>
23
- <div id="inbox-history" class="ax-inbox__detail-body" style="display:none;padding:14px 16px">
24
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
25
- <h3 style="margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600">Completed user-tasks (most recent first)</h3>
26
- <span style="font-size:11px;color:var(--ax-muted)">SLA breaches highlighted in red</span>
27
- </div>
28
- <table style="width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace">
29
- <thead><tr style="text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px"><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">submitted</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">workflow</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">title</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">by</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">duration</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">sla</th></tr></thead>
30
- <tbody id="inbox-history-rows"></tbody>
31
- </table>
32
- <p id="inbox-history-empty" style="font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none">no completed user-tasks yet</p>
33
- </div>
34
- </section>
35
- </div>
36
-
37
- <div id="inbox-toast" class="ax-inbox__toast" hidden></div>`;return o({title:"AgentX \xB7 Inbox",activeTab:"workflows",subtitle:"Inbox",peers:e.peers,currentPeerId:e.currentPeerId,body:a,css:r,scripts:`<script>${i}</script>`})}var r=`
38
- .ax-inbox__root {
39
- display: grid;
40
- grid-template-columns: 340px 1fr;
41
- height: calc(100vh - var(--ax-topbar-h, 48px));
42
- background: var(--ax-bg);
43
- }
44
- .ax-inbox__list {
45
- border-right: 1px solid var(--ax-border);
46
- display: flex;
47
- flex-direction: column;
48
- min-height: 0;
49
- }
50
- .ax-inbox__list > header {
51
- display: flex;
52
- justify-content: space-between;
53
- align-items: center;
54
- padding: 12px 14px;
55
- border-bottom: 1px solid var(--ax-border);
56
- }
57
- .ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }
58
- .ax-inbox__actor {
59
- padding: 10px 14px;
60
- border-bottom: 1px solid var(--ax-border);
61
- }
62
- .ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }
63
- .ax-inbox__actor input {
64
- font: inherit;
65
- padding: 6px 8px;
66
- border: 1px solid var(--ax-border);
67
- border-radius: 6px;
68
- background: var(--ax-bg);
69
- color: var(--ax-fg);
70
- }
71
- .ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }
72
- .ax-inbox__card {
73
- padding: 10px 12px;
74
- border-radius: 6px;
75
- margin-bottom: 4px;
76
- cursor: pointer;
77
- border: 1px solid transparent;
78
- }
79
- .ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }
80
- .ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }
81
- .ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }
82
- .ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }
83
- .ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }
84
- .ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }
85
- .ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }
86
- .ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }
87
- .ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }
88
- .ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }
89
- .ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }
90
- .ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }
91
- .ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {
92
- font: inherit;
93
- padding: 8px 10px;
94
- border: 1px solid var(--ax-border);
95
- border-radius: 6px;
96
- background: var(--ax-bg);
97
- color: var(--ax-fg);
98
- }
99
- .ax-inbox__field textarea { min-height: 100px; resize: vertical; }
100
- .ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }
101
- .ax-inbox__field-error { font-size: 11px; color: #c0392b; }
102
- .ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }
103
- .ax-inbox__actions button {
104
- font: inherit;
105
- padding: 8px 14px;
106
- border: 1px solid var(--ax-border);
107
- border-radius: 6px;
108
- cursor: pointer;
109
- background: var(--ax-surface-2);
110
- color: var(--ax-fg);
111
- }
112
- .ax-inbox__actions button.is-primary {
113
- background: #2ecc71; color: white; border-color: #27ae60;
114
- }
115
- .ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }
116
- .ax-inbox__icon {
117
- background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;
118
- font-size: 14px; padding: 4px 8px; border-radius: 4px;
119
- }
120
- .ax-inbox__icon:hover { background: var(--ax-surface-2); }
121
- .ax-inbox__toast {
122
- position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;
123
- background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;
124
- font-size: 13px; z-index: 100;
125
- }
126
- `,i=`
127
- (function(){
128
- const $ = (sel) => document.querySelector(sel);
129
- const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };
130
- const toast = (msg) => {
131
- const el = $('#inbox-toast');
132
- el.textContent = msg;
133
- el.hidden = false;
134
- setTimeout(() => { el.hidden = true; }, 2500);
135
- };
136
-
137
- async function fetchTasks() {
138
- const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';
139
- try {
140
- const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });
141
- if (!r.ok) { toast('fetch failed: ' + r.status); return; }
142
- const data = await r.json();
143
- state.tasks = Array.isArray(data.tasks) ? data.tasks : [];
144
- renderList();
145
- if (state.selected && !state.tasks.find(t => t.id === state.selected)) {
146
- state.selected = null;
147
- renderDetail();
148
- }
149
- } catch (e) { toast('fetch error: ' + e.message); }
150
- }
151
-
152
- function renderList() {
153
- const ul = $('#inbox-tasks');
154
- const empty = $('#inbox-empty');
155
- ul.innerHTML = '';
156
- if (!state.tasks.length) { empty.hidden = false; return; }
157
- empty.hidden = true;
158
- for (const t of state.tasks) {
159
- const li = document.createElement('li');
160
- li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');
161
- li.innerHTML =
162
- '<div class="ax-inbox__card-title">' + escHtml(t.title) + '</div>' +
163
- '<div class="ax-inbox__card-meta">' + escHtml(t.assignee) + ' \xB7 ' + escHtml(t.workflowId) + '</div>' +
164
- (t.dueAt ? ('<div class="ax-inbox__card-due">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');
165
- li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });
166
- ul.appendChild(li);
167
- }
168
- }
169
-
170
- function renderDetail() {
171
- const head = $('#inbox-detail-head');
172
- const body = $('#inbox-detail-body');
173
- const t = state.tasks.find(x => x.id === state.selected);
174
- if (!t) {
175
- head.innerHTML = '<span class="hint">Select a task to open its form.</span>';
176
- body.innerHTML = '';
177
- return;
178
- }
179
- head.innerHTML = '<h2 style="margin:0;font-size:15px">' + escHtml(t.title) + '</h2>' +
180
- (t.description ? '<p class="hint" style="margin:6px 0 0">' + escHtml(t.description) + '</p>' : '');
181
- const form = document.createElement('form');
182
- form.className = 'ax-inbox__form';
183
- form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });
184
- for (const field of (t.form.fields || [])) {
185
- form.appendChild(renderField(field));
186
- }
187
- const actions = document.createElement('div');
188
- actions.className = 'ax-inbox__actions';
189
- const primary = document.createElement('button');
190
- primary.type = 'submit';
191
- primary.className = 'is-primary';
192
- primary.textContent = t.form.submitLabel || 'Submit';
193
- actions.appendChild(primary);
194
- if (t.form.secondaryAction) {
195
- const secondary = document.createElement('button');
196
- secondary.type = 'button';
197
- secondary.textContent = t.form.secondaryAction.label;
198
- secondary.addEventListener('click', () => submit(t, 'secondary'));
199
- actions.appendChild(secondary);
200
- }
201
- form.appendChild(actions);
202
- body.innerHTML = '';
203
- body.appendChild(form);
204
- }
205
-
206
- function renderField(field) {
207
- const wrap = document.createElement('div');
208
- wrap.className = 'ax-inbox__field';
209
- const label = document.createElement('label');
210
- label.textContent = field.label + (field.required ? ' *' : '');
211
- wrap.appendChild(label);
212
- let input;
213
- switch (field.type) {
214
- case 'long-text':
215
- input = document.createElement('textarea');
216
- break;
217
- case 'number':
218
- input = document.createElement('input'); input.type = 'number';
219
- break;
220
- case 'boolean':
221
- input = document.createElement('select');
222
- for (const v of ['', 'yes', 'no']) {
223
- const o = document.createElement('option'); o.value = v; o.textContent = v || '\u2014';
224
- input.appendChild(o);
225
- }
226
- break;
227
- case 'date':
228
- input = document.createElement('input'); input.type = 'date';
229
- break;
230
- case 'select':
231
- input = document.createElement('select');
232
- if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '\u2014'; input.appendChild(o); }
233
- for (const opt of (field.options || [])) {
234
- const o = document.createElement('option'); o.value = opt; o.textContent = opt;
235
- input.appendChild(o);
236
- }
237
- break;
238
- case 'multi-select':
239
- input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);
240
- for (const opt of (field.options || [])) {
241
- const o = document.createElement('option'); o.value = opt; o.textContent = opt;
242
- input.appendChild(o);
243
- }
244
- break;
245
- case 'file':
246
- input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';
247
- break;
248
- default:
249
- input = document.createElement('input'); input.type = 'text';
250
- }
251
- input.name = field.key;
252
- if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);
253
- wrap.appendChild(input);
254
- if (field.hint) {
255
- const hint = document.createElement('div');
256
- hint.className = 'ax-inbox__field-hint';
257
- hint.textContent = field.hint;
258
- wrap.appendChild(hint);
259
- }
260
- const err = document.createElement('div');
261
- err.className = 'ax-inbox__field-error';
262
- err.dataset.for = field.key;
263
- wrap.appendChild(err);
264
- return wrap;
265
- }
266
-
267
- async function submit(task, action) {
268
- const form = $('.ax-inbox__form');
269
- if (!form) return;
270
- const values = {};
271
- for (const field of (task.form.fields || [])) {
272
- const el = form.querySelector('[name="' + field.key + '"]');
273
- if (!el) continue;
274
- if (field.type === 'multi-select') {
275
- values[field.key] = Array.from(el.selectedOptions).map(o => o.value);
276
- } else if (field.type === 'boolean') {
277
- values[field.key] = el.value;
278
- } else {
279
- values[field.key] = el.value;
280
- }
281
- }
282
- form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });
283
- try {
284
- const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {
285
- method: 'POST',
286
- credentials: 'same-origin',
287
- headers: { 'Content-Type': 'application/json' },
288
- body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),
289
- });
290
- const data = await r.json().catch(() => ({}));
291
- if (!r.ok) {
292
- if (data.fieldErrors && Array.isArray(data.fieldErrors)) {
293
- for (const fe of data.fieldErrors) {
294
- const node = form.querySelector('[data-for="' + fe.field + '"]');
295
- if (node) node.textContent = fe.message;
296
- }
297
- toast('form has errors');
298
- } else {
299
- toast(data.error || 'submit failed');
300
- }
301
- return;
302
- }
303
- toast('submitted');
304
- state.selected = null;
305
- fetchTasks();
306
- } catch (e) { toast('submit error: ' + e.message); }
307
- }
308
-
309
- function escHtml(s) {
310
- return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]);
311
- }
312
-
313
- $('#inbox-actor').addEventListener('change', (e) => {
314
- state.actor = e.target.value.trim();
315
- state.selected = null;
316
- const url = new URL(window.location.href);
317
- if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');
318
- history.replaceState({}, '', url.toString());
319
- fetchTasks();
320
- });
321
- $('#inbox-refresh').addEventListener('click', fetchTasks);
322
-
323
- // History toggle \u2014 surfaces the SLA-aware completion log.
324
- let historyOpen = false;
325
- function fmtDuration(ms) {
326
- if (ms == null) return '\u2014';
327
- const s = Math.floor(ms / 1000);
328
- if (s < 60) return s + 's';
329
- const m = Math.floor(s / 60);
330
- if (m < 60) return m + 'm ' + (s % 60) + 's';
331
- const h = Math.floor(m / 60);
332
- return h + 'h ' + (m % 60) + 'm';
333
- }
334
- function fmtTs(s) {
335
- if (!s) return '\u2014';
336
- return s.replace('T', ' ').slice(0, 16);
337
- }
338
- async function loadHistory() {
339
- try {
340
- const r = await fetch('/api/workflows/tasks/history?limit=100');
341
- const data = await r.json();
342
- const rows = data.rows || [];
343
- const tbody = $('#inbox-history-rows');
344
- const empty = $('#inbox-history-empty');
345
- if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }
346
- empty.style.display = 'none';
347
- tbody.innerHTML = rows.map(function(r){
348
- const slaCell = r.dueAt
349
- ? (r.breachedSla
350
- ? '<span style="color:var(--ax-err,#e74c3c)">breached</span>'
351
- : '<span style="color:#2ecc71">on time</span>')
352
- : '<span style="color:var(--ax-muted)">\u2014</span>';
353
- const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';
354
- return '<tr style="' + rowStyle + '">' +
355
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +
356
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.workflowId || '') + '</td>' +
357
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +
358
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.submittedBy || '') + '</td>' +
359
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +
360
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + slaCell + '</td>' +
361
- '</tr>';
362
- }).join('');
363
- } catch (e) {
364
- $('#inbox-history-empty').textContent = 'failed to load: ' + e.message;
365
- $('#inbox-history-empty').style.display = '';
366
- }
367
- }
368
- $('#inbox-history-toggle').addEventListener('click', () => {
369
- historyOpen = !historyOpen;
370
- $('#inbox-history').style.display = historyOpen ? 'block' : 'none';
371
- $('#inbox-detail-body').style.display = historyOpen ? 'none' : '';
372
- $('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';
373
- if (historyOpen) loadHistory();
374
- });
375
-
376
- fetchTasks();
377
- setInterval(fetchTasks, 10000);
378
- })();
379
- `;export{s as a};
380
- //# sourceMappingURL=chunk-MTRWXRFF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/daemon/ui/pages/inbox.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Inbox page ---\n//\n// Per-actor (or global) list of open user-tasks with an inline form\n// renderer. Reads /api/workflows/tasks; submits via\n// POST /api/workflows/tasks/:id/submit.\n//\n// Scope is intentionally tight for Phase 1:\n// - text / long-text / number / boolean / select / multi-select / date\n// - primary + optional secondary action buttons\n// - required-field validation is server-side; the UI just relays errors\n//\n// No channel-adapter coupling here — the form UI is the same whether the\n// renderer ran on Telegram or not.\n\nexport interface InboxPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n /** Optional actor id to pre-filter. When unset, the page shows a prompt\n * asking the user to pick their actor id. */\n actor?: string\n}\n\nexport function renderInboxPage(opts: InboxPageOpts = {}): string {\n const body = `<div class=\"ax-inbox__root\" data-actor=\"${esc(opts.actor ?? \"\")}\">\n <aside class=\"ax-inbox__list\">\n <header>\n <h2>Inbox</h2>\n <button id=\"inbox-refresh\" class=\"ax-inbox__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-inbox__actor\">\n <label>Actor <input id=\"inbox-actor\" type=\"text\" placeholder=\"actor:<id>\" value=\"${esc(opts.actor ?? \"\")}\" autocomplete=\"off\" /></label>\n </div>\n <ul id=\"inbox-tasks\" class=\"ax-inbox__cards\" aria-live=\"polite\"></ul>\n <div id=\"inbox-empty\" class=\"ax-inbox__empty\" hidden>\n <p>No open tasks.</p>\n <p class=\"hint\">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>\n </div>\n </aside>\n\n <section class=\"ax-inbox__detail\">\n <header id=\"inbox-detail-head\">\n <span class=\"hint\">Select a task to open its form.</span>\n <button id=\"inbox-history-toggle\" type=\"button\" style=\"margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer\">Show completed history</button>\n </header>\n <div id=\"inbox-detail-body\" class=\"ax-inbox__detail-body\"></div>\n <div id=\"inbox-history\" class=\"ax-inbox__detail-body\" style=\"display:none;padding:14px 16px\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:10px\">\n <h3 style=\"margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600\">Completed user-tasks (most recent first)</h3>\n <span style=\"font-size:11px;color:var(--ax-muted)\">SLA breaches highlighted in red</span>\n </div>\n <table style=\"width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace\">\n <thead><tr style=\"text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px\"><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">submitted</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">workflow</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">title</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">by</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">duration</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">sla</th></tr></thead>\n <tbody id=\"inbox-history-rows\"></tbody>\n </table>\n <p id=\"inbox-history-empty\" style=\"font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none\">no completed user-tasks yet</p>\n </div>\n </section>\n</div>\n\n<div id=\"inbox-toast\" class=\"ax-inbox__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Inbox\",\n activeTab: \"workflows\",\n subtitle: \"Inbox\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: INBOX_PAGE_CSS,\n scripts: `<script>${INBOX_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst INBOX_PAGE_CSS = `\n.ax-inbox__root {\n display: grid;\n grid-template-columns: 340px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-inbox__list {\n border-right: 1px solid var(--ax-border);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.ax-inbox__list > header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-inbox__actor {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-inbox__actor input {\n font: inherit;\n padding: 6px 8px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-inbox__card {\n padding: 10px 12px;\n border-radius: 6px;\n margin-bottom: 4px;\n cursor: pointer;\n border: 1px solid transparent;\n}\n.ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }\n.ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }\n.ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }\n.ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }\n.ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }\n.ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {\n font: inherit;\n padding: 8px 10px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__field textarea { min-height: 100px; resize: vertical; }\n.ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__field-error { font-size: 11px; color: #c0392b; }\n.ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }\n.ax-inbox__actions button {\n font: inherit;\n padding: 8px 14px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n cursor: pointer;\n background: var(--ax-surface-2);\n color: var(--ax-fg);\n}\n.ax-inbox__actions button.is-primary {\n background: #2ecc71; color: white; border-color: #27ae60;\n}\n.ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }\n.ax-inbox__icon {\n background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;\n font-size: 14px; padding: 4px 8px; border-radius: 4px;\n}\n.ax-inbox__icon:hover { background: var(--ax-surface-2); }\n.ax-inbox__toast {\n position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;\n background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;\n font-size: 13px; z-index: 100;\n}\n`\n\n// Deliberately vanilla JS — no framework dep. Fetches task list, renders\n// per-field inputs, POSTs submission.\nconst INBOX_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };\n const toast = (msg) => {\n const el = $('#inbox-toast');\n el.textContent = msg;\n el.hidden = false;\n setTimeout(() => { el.hidden = true; }, 2500);\n };\n\n async function fetchTasks() {\n const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';\n try {\n const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });\n if (!r.ok) { toast('fetch failed: ' + r.status); return; }\n const data = await r.json();\n state.tasks = Array.isArray(data.tasks) ? data.tasks : [];\n renderList();\n if (state.selected && !state.tasks.find(t => t.id === state.selected)) {\n state.selected = null;\n renderDetail();\n }\n } catch (e) { toast('fetch error: ' + e.message); }\n }\n\n function renderList() {\n const ul = $('#inbox-tasks');\n const empty = $('#inbox-empty');\n ul.innerHTML = '';\n if (!state.tasks.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const t of state.tasks) {\n const li = document.createElement('li');\n li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-inbox__card-title\">' + escHtml(t.title) + '</div>' +\n '<div class=\"ax-inbox__card-meta\">' + escHtml(t.assignee) + ' · ' + escHtml(t.workflowId) + '</div>' +\n (t.dueAt ? ('<div class=\"ax-inbox__card-due\">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');\n li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#inbox-detail-head');\n const body = $('#inbox-detail-body');\n const t = state.tasks.find(x => x.id === state.selected);\n if (!t) {\n head.innerHTML = '<span class=\"hint\">Select a task to open its form.</span>';\n body.innerHTML = '';\n return;\n }\n head.innerHTML = '<h2 style=\"margin:0;font-size:15px\">' + escHtml(t.title) + '</h2>' +\n (t.description ? '<p class=\"hint\" style=\"margin:6px 0 0\">' + escHtml(t.description) + '</p>' : '');\n const form = document.createElement('form');\n form.className = 'ax-inbox__form';\n form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });\n for (const field of (t.form.fields || [])) {\n form.appendChild(renderField(field));\n }\n const actions = document.createElement('div');\n actions.className = 'ax-inbox__actions';\n const primary = document.createElement('button');\n primary.type = 'submit';\n primary.className = 'is-primary';\n primary.textContent = t.form.submitLabel || 'Submit';\n actions.appendChild(primary);\n if (t.form.secondaryAction) {\n const secondary = document.createElement('button');\n secondary.type = 'button';\n secondary.textContent = t.form.secondaryAction.label;\n secondary.addEventListener('click', () => submit(t, 'secondary'));\n actions.appendChild(secondary);\n }\n form.appendChild(actions);\n body.innerHTML = '';\n body.appendChild(form);\n }\n\n function renderField(field) {\n const wrap = document.createElement('div');\n wrap.className = 'ax-inbox__field';\n const label = document.createElement('label');\n label.textContent = field.label + (field.required ? ' *' : '');\n wrap.appendChild(label);\n let input;\n switch (field.type) {\n case 'long-text':\n input = document.createElement('textarea');\n break;\n case 'number':\n input = document.createElement('input'); input.type = 'number';\n break;\n case 'boolean':\n input = document.createElement('select');\n for (const v of ['', 'yes', 'no']) {\n const o = document.createElement('option'); o.value = v; o.textContent = v || '—';\n input.appendChild(o);\n }\n break;\n case 'date':\n input = document.createElement('input'); input.type = 'date';\n break;\n case 'select':\n input = document.createElement('select');\n if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '—'; input.appendChild(o); }\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'multi-select':\n input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'file':\n input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';\n break;\n default:\n input = document.createElement('input'); input.type = 'text';\n }\n input.name = field.key;\n if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);\n wrap.appendChild(input);\n if (field.hint) {\n const hint = document.createElement('div');\n hint.className = 'ax-inbox__field-hint';\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n const err = document.createElement('div');\n err.className = 'ax-inbox__field-error';\n err.dataset.for = field.key;\n wrap.appendChild(err);\n return wrap;\n }\n\n async function submit(task, action) {\n const form = $('.ax-inbox__form');\n if (!form) return;\n const values = {};\n for (const field of (task.form.fields || [])) {\n const el = form.querySelector('[name=\"' + field.key + '\"]');\n if (!el) continue;\n if (field.type === 'multi-select') {\n values[field.key] = Array.from(el.selectedOptions).map(o => o.value);\n } else if (field.type === 'boolean') {\n values[field.key] = el.value;\n } else {\n values[field.key] = el.value;\n }\n }\n form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });\n try {\n const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),\n });\n const data = await r.json().catch(() => ({}));\n if (!r.ok) {\n if (data.fieldErrors && Array.isArray(data.fieldErrors)) {\n for (const fe of data.fieldErrors) {\n const node = form.querySelector('[data-for=\"' + fe.field + '\"]');\n if (node) node.textContent = fe.message;\n }\n toast('form has errors');\n } else {\n toast(data.error || 'submit failed');\n }\n return;\n }\n toast('submitted');\n state.selected = null;\n fetchTasks();\n } catch (e) { toast('submit error: ' + e.message); }\n }\n\n function escHtml(s) {\n return String(s).replace(/[&<>\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[c]);\n }\n\n $('#inbox-actor').addEventListener('change', (e) => {\n state.actor = e.target.value.trim();\n state.selected = null;\n const url = new URL(window.location.href);\n if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');\n history.replaceState({}, '', url.toString());\n fetchTasks();\n });\n $('#inbox-refresh').addEventListener('click', fetchTasks);\n\n // History toggle — surfaces the SLA-aware completion log.\n let historyOpen = false;\n function fmtDuration(ms) {\n if (ms == null) return '—';\n const s = Math.floor(ms / 1000);\n if (s < 60) return s + 's';\n const m = Math.floor(s / 60);\n if (m < 60) return m + 'm ' + (s % 60) + 's';\n const h = Math.floor(m / 60);\n return h + 'h ' + (m % 60) + 'm';\n }\n function fmtTs(s) {\n if (!s) return '—';\n return s.replace('T', ' ').slice(0, 16);\n }\n async function loadHistory() {\n try {\n const r = await fetch('/api/workflows/tasks/history?limit=100');\n const data = await r.json();\n const rows = data.rows || [];\n const tbody = $('#inbox-history-rows');\n const empty = $('#inbox-history-empty');\n if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }\n empty.style.display = 'none';\n tbody.innerHTML = rows.map(function(r){\n const slaCell = r.dueAt\n ? (r.breachedSla\n ? '<span style=\"color:var(--ax-err,#e74c3c)\">breached</span>'\n : '<span style=\"color:#2ecc71\">on time</span>')\n : '<span style=\"color:var(--ax-muted)\">—</span>';\n const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';\n return '<tr style=\"' + rowStyle + '\">' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.workflowId || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.submittedBy || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + slaCell + '</td>' +\n '</tr>';\n }).join('');\n } catch (e) {\n $('#inbox-history-empty').textContent = 'failed to load: ' + e.message;\n $('#inbox-history-empty').style.display = '';\n }\n }\n $('#inbox-history-toggle').addEventListener('click', () => {\n historyOpen = !historyOpen;\n $('#inbox-history').style.display = historyOpen ? 'block' : 'none';\n $('#inbox-detail-body').style.display = historyOpen ? 'none' : '';\n $('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';\n if (historyOpen) loadHistory();\n });\n\n fetchTasks();\n setInterval(fetchTasks, 10000);\n})();\n`\n"],"mappings":"+CAwBO,SAASA,EAAgBC,EAAsB,CAAC,EAAW,CAChE,IAAMC,EAAO,2CAA2CC,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yFAOWE,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6DA+B3G,OAAOG,EAAY,CACjB,MAAO,oBACP,UAAW,YACX,SAAU,QACV,MAAOH,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKG,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6FjBC,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderInboxPage","opts","body","esc","renderShell","INBOX_PAGE_CSS","INBOX_PAGE_SCRIPT"]}
@@ -1,2 +0,0 @@
1
- import{e as b}from"./chunk-CDNWCYF7.js";import{existsSync as p,mkdirSync as k,readdirSync as A,readFileSync as m,unlinkSync as v,writeFileSync as T}from"fs";import{resolve as d}from"path";import{randomUUID as w}from"crypto";import{z as s}from"zod";var D=s.enum(["open","claimed","completed","canceled"]),f=s.object({id:s.string(),runId:s.string(),workflowId:s.string(),nodeId:s.string(),title:s.string(),description:s.string().optional(),assignee:s.string(),assignedTo:s.array(s.string()),form:b,status:D,delivered:s.array(s.object({channel:s.string(),handle:s.string(),messageId:s.string().optional(),at:s.string()})).default([]),dueAt:s.string().optional(),submittedBy:s.string().optional(),submittedAt:s.string().optional(),submittedValues:s.record(s.unknown()).optional(),submittedAction:s.enum(["primary","secondary"]).optional(),createdAt:s.string(),updatedAt:s.string()}),y=class{baseDir;archiveDir;constructor(t={}){let e=t.baseDir??d(process.cwd(),".agentx/workflows");this.baseDir=d(e,"_tasks"),this.archiveDir=d(this.baseDir,"_completed"),k(this.baseDir,{recursive:!0}),k(this.archiveDir,{recursive:!0})}pathFor(t){return d(this.baseDir,`${t}.json`)}archivePath(t){return d(this.archiveDir,`${t}.json`)}create(t){let e=new Date().toISOString(),r={id:w(),runId:t.runId,workflowId:t.workflowId,nodeId:t.nodeId,title:t.title,description:t.description,assignee:t.assignee,assignedTo:t.assignedTo,form:t.form,status:"open",delivered:[],dueAt:t.dueAt,createdAt:e,updatedAt:e};return T(this.pathFor(r.id),JSON.stringify(r,null,2)),r}get(t){let e=this.pathFor(t);if(p(e))try{let i=JSON.parse(m(e,"utf-8")),a=f.safeParse(i);return a.success?a.data:null}catch{return null}let r=this.archivePath(t);if(p(r))try{let i=JSON.parse(m(r,"utf-8")),a=f.safeParse(i);return a.success?a.data:null}catch{return null}return null}save(t){let e={...t,updatedAt:new Date().toISOString()},r=e.status==="completed"||e.status==="canceled"?this.archivePath(e.id):this.pathFor(e.id);return T(r,JSON.stringify(e,null,2)),r===this.archivePath(e.id)&&p(this.pathFor(e.id))&&v(this.pathFor(e.id)),e}listOpen(){if(!p(this.baseDir))return[];let t=[];for(let e of A(this.baseDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(m(d(this.baseDir,e.name),"utf-8")),i=f.safeParse(r);i.success&&i.data.status==="open"&&t.push(i.data)}catch{}return t.sort((e,r)=>e.createdAt<r.createdAt?1:-1)}listForActor(t){return this.listOpen().filter(e=>e.assignedTo.includes(t))}findByRunNode(t,e){return this.listOpen().find(r=>r.runId===t&&r.nodeId===e)??null}listArchived(){if(!p(this.archiveDir))return[];let t=[];for(let e of A(this.archiveDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(m(d(this.archiveDir,e.name),"utf-8")),i=f.safeParse(r);i.success&&t.push(i.data)}catch{}return t.sort((e,r)=>e.createdAt<r.createdAt?1:-1)}};function x(u){let t=u.listOpen(),e=u.listArchived(),r=new Map,i=o=>{let n=r.get(o);return n||(n={open:0,completed:0,durations:[],dueCount:0,breached:0},r.set(o,n)),n};for(let o of t)for(let n of o.assignedTo)i(n).open++;for(let o of e){if(o.status!=="completed"||!o.submittedAt)continue;let n=Date.parse(o.createdAt),l=Date.parse(o.submittedAt);if(Number.isNaN(n)||Number.isNaN(l))continue;let g=Math.max(0,l-n),S=o.submittedBy??o.assignedTo[0]??"unknown",h=i(S);h.completed++,h.durations.push(g),o.dueAt&&(h.dueCount++,l>Date.parse(o.dueAt)&&h.breached++)}let a=Array.from(r.entries()).map(([o,n])=>({actorId:o,openTasks:n.open,completedTasks:n.completed,avgDurationMs:n.durations.length?Math.round(n.durations.reduce((l,g)=>l+g,0)/n.durations.length):null,slaBreachRate:n.dueCount?n.breached/n.dueCount:null,breachedCount:n.breached})).sort((o,n)=>n.completedTasks-o.completedTasks||o.actorId.localeCompare(n.actorId)),c=I(r);return{byActor:a,totals:c,generatedAt:new Date().toISOString()}}function I(u){let t=0,e=0,r=[],i=0,a=0;for(let c of u.values())t+=c.open,e+=c.completed,r.push(...c.durations),i+=c.dueCount,a+=c.breached;return{actorId:"__total__",openTasks:t,completedTasks:e,avgDurationMs:r.length?Math.round(r.reduce((c,o)=>c+o,0)/r.length):null,slaBreachRate:i?a/i:null,breachedCount:a}}export{D as a,f as b,y as c,x as d};
2
- //# sourceMappingURL=chunk-N67JZAXU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workflows/task-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\nimport { formSchemaSchema, type FormSchema } from \"../forms/types\"\n\n// --- UserTask store ---\n//\n// Persists open `userTask` records (the live inbox). One JSON file per task\n// under .agentx/workflows/_tasks/<taskId>.json. When a task is submitted the\n// record is moved to _tasks/_completed/ for historical lookup (keeps the\n// live inbox fast) and the paused run is resumed via the dispatcher.\n//\n// Task state machine:\n// open — freshly created, awaiting any assignee action\n// claimed — a specific assignee has started (optional in v1; reserved)\n// completed — submitted successfully, record archived\n// canceled — workflow canceled or timer-escalated past this task\n\nexport const taskStatusSchema = z.enum([\"open\", \"claimed\", \"completed\", \"canceled\"])\nexport type TaskStatus = z.infer<typeof taskStatusSchema>\n\nexport const userTaskRecordSchema = z.object({\n id: z.string(),\n runId: z.string(),\n workflowId: z.string(),\n nodeId: z.string(),\n title: z.string(),\n description: z.string().optional(),\n /** Assignee ref (\"actor:<id>\" or \"role:<id>\"). */\n assignee: z.string(),\n /** Concrete actors who currently see this task (resolved via role strategy). */\n assignedTo: z.array(z.string()),\n form: formSchemaSchema,\n status: taskStatusSchema,\n /** Channel-delivery tracking. Populated by the userTask handler after it\n * renders the form to the assignee's preferred channel(s). */\n delivered: z.array(z.object({\n channel: z.string(),\n handle: z.string(),\n messageId: z.string().optional(),\n at: z.string(),\n })).default([]),\n /** Optional due date (ISO-8601). Set by dueIn config. */\n dueAt: z.string().optional(),\n /** Set on completion. */\n submittedBy: z.string().optional(),\n submittedAt: z.string().optional(),\n submittedValues: z.record(z.unknown()).optional(),\n submittedAction: z.enum([\"primary\", \"secondary\"]).optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\nexport type UserTaskRecord = z.infer<typeof userTaskRecordSchema>\n\nexport interface TaskStoreOptions {\n baseDir?: string\n}\n\nexport class TaskStore {\n readonly baseDir: string\n readonly archiveDir: string\n\n constructor(opts: TaskStoreOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx/workflows\")\n this.baseDir = resolve(root, \"_tasks\")\n this.archiveDir = resolve(this.baseDir, \"_completed\")\n mkdirSync(this.baseDir, { recursive: true })\n mkdirSync(this.archiveDir, { recursive: true })\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.json`)\n }\n\n private archivePath(id: string): string {\n return resolve(this.archiveDir, `${id}.json`)\n }\n\n create(args: {\n runId: string\n workflowId: string\n nodeId: string\n title: string\n description?: string\n assignee: string\n assignedTo: string[]\n form: FormSchema\n dueAt?: string\n }): UserTaskRecord {\n const now = new Date().toISOString()\n const record: UserTaskRecord = {\n id: randomUUID(),\n runId: args.runId,\n workflowId: args.workflowId,\n nodeId: args.nodeId,\n title: args.title,\n description: args.description,\n assignee: args.assignee,\n assignedTo: args.assignedTo,\n form: args.form,\n status: \"open\",\n delivered: [],\n dueAt: args.dueAt,\n createdAt: now,\n updatedAt: now,\n }\n writeFileSync(this.pathFor(record.id), JSON.stringify(record, null, 2))\n return record\n }\n\n get(id: string): UserTaskRecord | null {\n const live = this.pathFor(id)\n if (existsSync(live)) {\n try {\n const raw = JSON.parse(readFileSync(live, \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch { return null }\n }\n const archived = this.archivePath(id)\n if (existsSync(archived)) {\n try {\n const raw = JSON.parse(readFileSync(archived, \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch { return null }\n }\n return null\n }\n\n save(record: UserTaskRecord): UserTaskRecord {\n const updated = { ...record, updatedAt: new Date().toISOString() }\n const dest = updated.status === \"completed\" || updated.status === \"canceled\"\n ? this.archivePath(updated.id)\n : this.pathFor(updated.id)\n writeFileSync(dest, JSON.stringify(updated, null, 2))\n // If we just moved to archive, clean up the live file.\n if (dest === this.archivePath(updated.id) && existsSync(this.pathFor(updated.id))) {\n unlinkSync(this.pathFor(updated.id))\n }\n return updated\n }\n\n /** Open tasks only — drives the inbox page. */\n listOpen(): UserTaskRecord[] {\n if (!existsSync(this.baseDir)) return []\n const out: UserTaskRecord[] = []\n for (const entry of readdirSync(this.baseDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.baseDir, entry.name), \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n if (parsed.success && parsed.data.status === \"open\") out.push(parsed.data)\n } catch { /* skip */ }\n }\n return out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))\n }\n\n /** Open tasks assigned to a specific actor (directly or via role). */\n listForActor(actorId: string): UserTaskRecord[] {\n return this.listOpen().filter((t) => t.assignedTo.includes(actorId))\n }\n\n /** Look up the open task owned by a specific run/node pair. */\n findByRunNode(runId: string, nodeId: string): UserTaskRecord | null {\n return this.listOpen().find((t) => t.runId === runId && t.nodeId === nodeId) ?? null\n }\n\n /** Return every completed/canceled task record in the archive. Used by\n * KPI aggregation. Walks the archive directory once. */\n listArchived(): UserTaskRecord[] {\n if (!existsSync(this.archiveDir)) return []\n const out: UserTaskRecord[] = []\n for (const entry of readdirSync(this.archiveDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.archiveDir, entry.name), \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch { /* skip */ }\n }\n return out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))\n }\n}\n\n// --- KPI aggregation ---\n//\n// Cheap on-demand stats. For v1 we re-read the fs on every call; cache\n// lands later if/when the dashboard polls aggressively. KPIs compute\n// over open + archived tasks combined (open contribute to pending count;\n// archived contribute to durations + breach rate).\n\nexport interface ActorKpi {\n actorId: string\n openTasks: number\n completedTasks: number\n /** Average milliseconds from createdAt → submittedAt (completed only). */\n avgDurationMs: number | null\n /** Fraction 0..1 of completed tasks submitted after dueAt. Null when\n * no completed tasks had a dueAt to measure against. */\n slaBreachRate: number | null\n /** Count of completed tasks where submittedAt > dueAt. */\n breachedCount: number\n}\n\nexport interface WorkflowKpis {\n byActor: ActorKpi[]\n totals: ActorKpi\n generatedAt: string\n}\n\nexport function computeKpis(store: TaskStore): WorkflowKpis {\n const open = store.listOpen()\n const archived = store.listArchived()\n const byActor = new Map<string, {\n open: number\n completed: number\n durations: number[]\n dueCount: number\n breached: number\n }>()\n\n const bucket = (id: string) => {\n let b = byActor.get(id)\n if (!b) { b = { open: 0, completed: 0, durations: [], dueCount: 0, breached: 0 }; byActor.set(id, b) }\n return b\n }\n\n for (const t of open) {\n for (const actor of t.assignedTo) bucket(actor).open++\n }\n for (const t of archived) {\n if (t.status !== \"completed\" || !t.submittedAt) continue\n const createdMs = Date.parse(t.createdAt)\n const submittedMs = Date.parse(t.submittedAt)\n if (Number.isNaN(createdMs) || Number.isNaN(submittedMs)) continue\n const dur = Math.max(0, submittedMs - createdMs)\n const submitter = t.submittedBy ?? t.assignedTo[0] ?? \"unknown\"\n const b = bucket(submitter)\n b.completed++\n b.durations.push(dur)\n if (t.dueAt) {\n b.dueCount++\n if (submittedMs > Date.parse(t.dueAt)) b.breached++\n }\n }\n\n const perActor: ActorKpi[] = Array.from(byActor.entries()).map(([actorId, b]) => ({\n actorId,\n openTasks: b.open,\n completedTasks: b.completed,\n avgDurationMs: b.durations.length ? Math.round(b.durations.reduce((a, n) => a + n, 0) / b.durations.length) : null,\n slaBreachRate: b.dueCount ? b.breached / b.dueCount : null,\n breachedCount: b.breached,\n })).sort((a, b) => b.completedTasks - a.completedTasks || a.actorId.localeCompare(b.actorId))\n\n const totals = aggregateTotals(byActor)\n return {\n byActor: perActor,\n totals,\n generatedAt: new Date().toISOString(),\n }\n}\n\nfunction aggregateTotals(byActor: Map<string, { open: number; completed: number; durations: number[]; dueCount: number; breached: number }>): ActorKpi {\n let open = 0, completed = 0, durations: number[] = [], dueCount = 0, breached = 0\n for (const b of byActor.values()) {\n open += b.open; completed += b.completed\n durations.push(...b.durations)\n dueCount += b.dueCount; breached += b.breached\n }\n return {\n actorId: \"__total__\",\n openTasks: open,\n completedTasks: completed,\n avgDurationMs: durations.length ? Math.round(durations.reduce((a, n) => a + n, 0) / durations.length) : null,\n slaBreachRate: dueCount ? breached / dueCount : null,\n breachedCount: breached,\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","randomUUID","z","taskStatusSchema","z","userTaskRecordSchema","formSchemaSchema","TaskStore","opts","root","resolve","mkdirSync","id","args","now","record","randomUUID","writeFileSync","live","existsSync","raw","readFileSync","parsed","archived","updated","dest","unlinkSync","out","entry","readdirSync","a","b","actorId","t","runId","nodeId","computeKpis","store","open","byActor","bucket","actor","createdMs","submittedMs","dur","submitter","perActor","n","totals","aggregateTotals","completed","durations","dueCount","breached"]}
@@ -1,7 +0,0 @@
1
- import{a as C,b as T}from"./chunk-DXTIG5YV.js";import{a as S,c as x}from"./chunk-GOLZ6NEL.js";import{a as L}from"./chunk-VJUTQAPJ.js";import{writeFileSync as P,mkdirSync as R,existsSync as D,readFileSync as q}from"fs";import{resolve as v}from"path";var H=["off","shadow","authoritative"],z={telegram:"INTENT_LEDGER_MODE_TELEGRAM",slack:"INTENT_LEDGER_MODE_SLACK",whatsapp:"INTENT_LEDGER_MODE_WHATSAPP",discord:"INTENT_LEDGER_MODE_DISCORD",gitlab:"INTENT_LEDGER_MODE_GITLAB",github:"INTENT_LEDGER_MODE_GITHUB",workflow:"INTENT_LEDGER_MODE_WORKFLOW",cron:"INTENT_LEDGER_MODE_CRON",mesh:"INTENT_LEDGER_MODE_MESH"},K="INTENT_LEDGER_MODE";function O(s,e=process.env){let n=_(e[z[s]]);if(n)return n;let t=_(e[K]);return t||"off"}function _(s){if(typeof s!="string")return null;let e=s.trim().toLowerCase();return H.includes(e)?e:null}var A;function X(s){A=s}function N(){return A}function k(s,e,n,t=Date.now,r){return s.db.transaction(i=>{let c=s.recordEvent(e),a=r??N(),u=a?.pmFor?.(c.project),d=u?`pm:${u}`:n.decidedBy,p=s.getDecisionsForEvent(c.id).find(h=>h.decidedBy===d);if(p)return p;let y=s.getActiveDecisionForSubject(c.project,c.subject);if(y){let h={eventId:c.id,decidedAt:i,decidedBy:d,agentId:null,outcome:"deduped",reason:`active dispatch in flight: ${y.decidedBy} \u2192 ${y.agentId??"?"}`};return s.recordDecision(h),h}let g=n.decide(c),l=g?g.agentId:null,f=g?g.outcome:"halted",m=g?g.reason:"no policy match";if(a?.canHandle&&f==="dispatched"&&l&&!a.canHandle(l,c.project,c.intent)){let $=l;l=null,f="halted",m=`org-chart: agent "${$}" cannot handle (${c.project??"no-project"}/${c.intent??"no-intent"})`}if(a?.withinDelegationBudget&&f==="dispatched"&&l&&!a.withinDelegationBudget(l,c.project,c.subject)){let h=l;l=null,f="halted",m=`delegation-budget: agent "${h}" depth-limit exceeded on ${c.project??"?"}/${c.subject??"?"}`}u&&f==="dispatched"&&l&&(m=m?`${m} (pm-gate: rubber-stamped by ${u})`:`pm-gate: rubber-stamped by ${u}`);let E={eventId:c.id,decidedAt:i,decidedBy:d,agentId:l,outcome:f,reason:m};return s.recordDecision(E),E})(t())}function M(s,e,n,t,r=Date.now){if(U(n,t))return!1;let o=r(),i={id:S(o),ts:o,source:e,eventId:n.eventId,decidedBy:n.decidedBy,ledgerAgentId:n.agentId,ledgerOutcome:n.outcome,ledgerReason:n.reason,legacyAgentId:t.agentId,legacyOutcome:t.outcome,legacyReason:t.reason??null};return s.recordDivergence(i),L.warn(`[ledger-divergence] source=${e} event=${n.eventId} decided_by=${n.decidedBy} ledger=${n.outcome}/${n.agentId??"null"} legacy=${t.outcome}/${t.agentId??"null"}`),!0}function U(s,e){return s.outcome===e.outcome&&s.agentId===e.agentId}function V(s,e,n=Date.now){return{ts:n(),source:"cron",sourceEventId:`${s.jobId}:${s.firedAt.getTime()}`,project:null,subject:`cron:${s.jobId}`,intent:"cron.fired",rawJson:e}}function W(s){return{decidedBy:"cron-scheduler",decide:()=>({agentId:s.agentId,outcome:s.outcome==="deduped"?"halted":s.outcome,reason:s.outcome==="deduped"?`legacy-dedup: ${s.reason??""}`.trim():s.reason??null})}}function J(s,e,n,t,r=Date.now){let o=V(e,n,r),i=W(t),c=k(s,o,i,r);return M(s,"cron",c,t,r),c}function F(s,e){if(!e)return s;let n=e*4;return`${s}
2
-
3
- [Response budget]
4
- Keep your response under ~${e} tokens (~${n} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function b(s,e,n){let t=[];for(let r of s.split(","))if(r==="*")for(let o=e;o<=n;o++)t.push(o);else if(r.includes("/")){let[o,i]=r.split("/"),c=parseInt(i,10),a=o==="*"?e:parseInt(o,10);for(let u=a;u<=n;u+=c)t.push(u)}else if(r.includes("-")){let[o,i]=r.split("-").map(Number);for(let c=o;c<=i;c++)t.push(c)}else t.push(parseInt(r,10));return[...new Set(t)].sort((r,o)=>r-o)}function B(s,e,n){let t=s.trim().split(/\s+/);if(t.length!==5)throw new Error(`Invalid cron: ${s}`);let r=b(t[0],0,59),o=b(t[1],0,23),i=b(t[2],1,31),c=b(t[3],1,12),a=b(t[4],0,6),u=new Intl.DateTimeFormat("en-US",{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}),d=new Date(e.getTime()+6e4);d.setSeconds(0,0);let p=new Date(d.getTime()+366*24*60*60*1e3);for(;d<p;){let y=u.formatToParts(d),g=$=>parseInt(y.find(j=>j.type===$)?.value||"0",10),l=g("minute"),f=g("hour"),m=g("day"),E=g("month"),h=d.getDay();if(r.includes(l)&&o.includes(f)&&i.includes(m)&&c.includes(E)&&a.includes(h))return d;d.setTime(d.getTime()+6e4)}throw new Error(`No next run found for cron "${s}" within 1 year`)}var I=5,w=[3e4,6e4,3e5,9e5,36e5],G=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,n,t,r=console.error.bind(console,"[cron]")){this.registry=n,this.hooks=t,this.log=r,this.runsDir=v(process.cwd(),".agentx/cron/runs"),this.lastRunFile=v(process.cwd(),".agentx/cron/last-runs.json");for(let[o,i]of Object.entries(e.crons))this.jobs.set(o,{id:o,enabled:i.enabled,schedule:i.schedule,timezone:i.timezone,agent:i.agent,prompt:i.prompt,timeout:i.timeout,model:i.model,maxOutputTokens:i.maxOutputTokens,onError:i.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,D(this.runsDir)||R(this.runsDir,{recursive:!0});let e=this.detectMissedRuns();if(e.length>0){this.log(`Detected ${e.length} missed cron run(s) while daemon was down:`);for(let{jobId:n,missedAt:t}of e)this.log(` "${n}" should have run at ${t.toISOString()}`);this.executeMissedRuns(e).catch(n=>{this.log(`Error executing missed runs: ${n.message}`)})}for(let[n,t]of this.jobs){if(!t.enabled){this.log(`Job "${n}" is disabled, skipping`);continue}this.scheduleNext(n)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(n=>n.enabled).length} enabled`)}async stop(){this.running=!1;for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.saveLastRuns()}scheduleNext(e){let n=this.jobs.get(e);if(!n||!n.enabled||!this.running)return;let t=this.timers.get(e);t&&clearTimeout(t);try{let r=B(n.schedule,new Date,n.timezone);n.nextRun=r,n.retryPending=!1;let o=r.getTime()-Date.now();this.log(`Job "${e}" next run: ${r.toISOString()} (in ${Math.round(o/1e3)}s)`);let i=setTimeout(()=>this.executeJob(e),o);this.timers.set(e,i)}catch(r){this.log(`Failed to schedule "${e}": ${r.message}`)}}scheduleRetry(e,n){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;if(n>=I){this.log(`Job "${e}" exhausted all ${I} retries, scheduling next regular run`),this.scheduleNext(e);return}let r=w[n]||w[w.length-1];t.retryPending=!0,this.log(`Job "${e}" retry ${n+1}/${I} in ${Math.round(r/1e3)}s`);let o=setTimeout(()=>this.executeJob(e,n+1),r);this.timers.set(e,o)}recordCronDecisionInLedger(e,n,t,r){if(O("cron")!=="off")try{let o=J(x(),{jobId:e,agentId:n,firedAt:t},JSON.stringify({jobId:e,agentId:n,firedAt:t.toISOString()}),r);return o.outcome==="dispatched"?{eventId:o.eventId,decidedBy:o.decidedBy}:void 0}catch(o){this.log(`[ledger] cron "${e}" record failed: ${o?.message??o}`);return}}async executeJob(e,n=0){let t=this.jobs.get(e);if(!t||!this.running)return;let r=n>0,o=new Date;if(this.hooks?.has("pre:cron-run")){let a=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:t.agent,prompt:t.prompt});if(a.blocked){this.log(`Job "${e}" blocked by hook: ${a.message}`),this.recordCronDecisionInLedger(e,t.agent,o,{agentId:null,outcome:"halted",reason:`pre-hook blocked: ${a.message??""}`.trim()}),this.scheduleNext(e);return}}this.log(`${r?`[retry ${n}] `:""}Executing job "${e}" -> agent "${t.agent}"`);let i=o;t.lastRun=i,t.totalRuns++;let c=this.recordCronDecisionInLedger(e,t.agent,o,{agentId:t.agent,outcome:"dispatched",reason:r?`retry ${n}`:null});try{let a=await this.registry.execute({message:F(t.prompt,t.maxOutputTokens),agentId:t.agent,model:t.model,intentRef:c,context:{channel:"cron",chatId:`cron:${e}`}}),u={jobId:e,startedAt:i,completedAt:new Date,success:!a.error,response:a.content,error:a.error,duration:a.duration||Date.now()-i.getTime(),isRetry:r,retryAttempt:n};if(a.error){if(t.consecutiveErrors++,t.totalFailures++,t.lastError=a.error,this.log(`Job "${e}" failed (${t.consecutiveErrors} consecutive): ${a.error}`),await this.notifyFailure(t,a.error),t.onError.includes("disable")&&t.consecutiveErrors>=3){t.enabled=!1,this.log(`Job "${e}" disabled after ${t.consecutiveErrors} consecutive errors`);try{let d=await C(p=>T(p,`crons.${e}.enabled`,!1),{reload:!1});d.success||this.log(`Failed to persist disable for "${e}": ${d.error}`)}catch(d){this.log(`Failed to persist disable for "${e}": ${d.message}`)}await this.notifyDisabled(t)}this.logRun(u),this.scheduleRetry(e,n);return}else t.consecutiveErrors>0&&this.log(`Job "${e}" recovered after ${t.consecutiveErrors} failure(s)`),t.consecutiveErrors=0,t.lastSuccess=new Date,t.lastError=void 0,t.retryPending=!1,this.log(`Job "${e}" completed in ${u.duration}ms`);this.logRun(u),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:u.success,duration:u.duration,error:u.error?new Error(u.error):void 0})}catch(a){t.consecutiveErrors++,t.totalFailures++,t.lastError=a.message,this.log(`Job "${e}" threw: ${a.message}`),await this.notifyFailure(t,a.message),this.scheduleRetry(e,n);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],n=this.loadLastRuns();if(!n)return e;for(let[t,r]of this.jobs){if(!r.enabled)continue;let o=n[t];if(!o)continue;let i=new Date(o);try{let c=B(r.schedule,i,r.timezone);c.getTime()<Date.now()&&e.push({jobId:t,missedAt:c})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:n,missedAt:t}of e){let r=this.jobs.get(n);if(!(!r||!r.enabled)){this.log(`Running missed job "${n}" (was due at ${t.toISOString()})`);try{let o=await this.registry.execute({message:F(`[MISSED RUN \u2014 was scheduled for ${t.toISOString()}]
5
-
6
- ${r.prompt}`,r.maxOutputTokens),agentId:r.agent,context:{channel:"cron",chatId:`cron:${n}`}}),i={jobId:n,startedAt:new Date,completedAt:new Date,success:!o.error,response:o.content,error:o.error,duration:o.duration||0,isRetry:!1};this.logRun(i),o.error?this.log(`Missed job "${n}" failed: ${o.error}`):(this.log(`Missed job "${n}" completed`),r.lastSuccess=new Date),r.lastRun=new Date,r.totalRuns++}catch(o){this.log(`Missed job "${n}" threw: ${o.message}`)}}}this.saveLastRuns()}async notifyFailure(e,n){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${n.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,n,e.consecutiveErrors)}catch{}}}async notifyDisabled(e){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" has been DISABLED after ${e.consecutiveErrors} consecutive failures`);return}try{await this.notifyCallback(e.id,e.agent,`AUTO-DISABLED after ${e.consecutiveErrors} consecutive failures. Last error: ${e.lastError||"unknown"}`,e.consecutiveErrors)}catch{}}saveLastRuns(){try{let e={};for(let[t,r]of this.jobs)r.lastRun&&(e[t]=r.lastRun.toISOString());let n=v(process.cwd(),".agentx/cron");D(n)||R(n,{recursive:!0}),P(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{return D(this.lastRunFile)?JSON.parse(q(this.lastRunFile,"utf-8")):null}catch{return null}}logRun(e){try{let n=v(this.runsDir,e.jobId);D(n)||R(n,{recursive:!0});let t=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;P(v(n,t),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),n=this.detectMissedRuns();return{healthy:e.filter(t=>t.enabled&&t.consecutiveErrors===0).length,failing:e.filter(t=>t.enabled&&t.consecutiveErrors>0).length,disabled:e.filter(t=>!t.enabled).length,missed:n.length,jobs:e.map(t=>({id:t.id,status:t.enabled?t.retryPending?"retrying":t.consecutiveErrors>0?"failing":"healthy":"disabled",consecutiveErrors:t.consecutiveErrors,lastError:t.lastError}))}}};export{O as a,X as b,k as c,M as d,B as e,G as f};
7
- //# sourceMappingURL=chunk-PD5Q7D6Z.js.map