@rubytech/create-sitedesk-code 0.1.528 → 0.1.529

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 (214) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/account-ingest-token/dist/index.d.ts +21 -0
  3. package/payload/platform/lib/account-ingest-token/dist/index.d.ts.map +1 -0
  4. package/payload/platform/lib/account-ingest-token/dist/index.js +128 -0
  5. package/payload/platform/lib/account-ingest-token/dist/index.js.map +1 -0
  6. package/payload/platform/lib/account-ingest-token/src/index.ts +137 -0
  7. package/payload/platform/lib/account-ingest-token/tsconfig.json +8 -0
  8. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts +5 -0
  9. package/payload/platform/lib/agent-dispatch-rule/dist/index.d.ts.map +1 -1
  10. package/payload/platform/lib/agent-dispatch-rule/dist/index.js.map +1 -1
  11. package/payload/platform/lib/agent-dispatch-rule/src/index.ts +5 -0
  12. package/payload/platform/package.json +2 -2
  13. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  14. package/payload/platform/plugins/admin/PLUGIN.md +5 -1
  15. package/payload/platform/plugins/admin/mcp/dist/index.js +37 -0
  16. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/admin/mcp/dist/tools/linkedin-ingest-token.d.ts +10 -0
  18. package/payload/platform/plugins/admin/mcp/dist/tools/linkedin-ingest-token.d.ts.map +1 -0
  19. package/payload/platform/plugins/admin/mcp/dist/tools/linkedin-ingest-token.js +22 -0
  20. package/payload/platform/plugins/admin/mcp/dist/tools/linkedin-ingest-token.js.map +1 -0
  21. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +25 -4
  22. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  23. package/payload/platform/plugins/docs/references/admin-ui.md +22 -1
  24. package/payload/platform/plugins/docs/references/linkedin-extension.md +2 -2
  25. package/payload/platform/plugins/linkedin-extension/extension/README.md +3 -3
  26. package/payload/platform/plugins/linkedin-extension/extension/background/sw.js +12 -7
  27. package/payload/platform/plugins/linkedin-extension/extension/content/profile.js +4 -1
  28. package/payload/platform/plugins/linkedin-extension/extension/content/thread.js +4 -1
  29. package/payload/platform/plugins/linkedin-extension/extension/options/options.html +3 -3
  30. package/payload/platform/plugins/linkedin-extension/extension/options/options.js +24 -16
  31. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js +45 -2
  32. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/suspension-census.test.js.map +1 -1
  33. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  34. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +15 -2
  35. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  36. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  37. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  38. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  39. package/payload/server/public/activity.html +5 -5
  40. package/payload/server/public/agents.html +4 -4
  41. package/payload/server/public/assets/{AdminLoginScreens-Bx9vPuUg.js → AdminLoginScreens-zfexKfbT.js} +1 -1
  42. package/payload/server/public/assets/AdminLoginScreens-zfexKfbT.js.br +0 -0
  43. package/payload/server/public/assets/AdminLoginScreens-zfexKfbT.js.gz +0 -0
  44. package/payload/server/public/assets/{AdminShell-BxJPbC3p.js → AdminShell-Cbk0TL1B.js} +1 -1
  45. package/payload/server/public/assets/AdminShell-Cbk0TL1B.js.br +0 -0
  46. package/payload/server/public/assets/AdminShell-Cbk0TL1B.js.gz +0 -0
  47. package/payload/server/public/assets/{CdpScreencastSurface-5HLhTQTV.js → CdpScreencastSurface-CLSQHh4e.js} +1 -1
  48. package/payload/server/public/assets/CdpScreencastSurface-CLSQHh4e.js.br +0 -0
  49. package/payload/server/public/assets/CdpScreencastSurface-CLSQHh4e.js.gz +0 -0
  50. package/payload/server/public/assets/{activity-BaSYn62l.js → activity-u275Oc-u.js} +1 -1
  51. package/payload/server/public/assets/activity-u275Oc-u.js.br +0 -0
  52. package/payload/server/public/assets/activity-u275Oc-u.js.gz +0 -0
  53. package/payload/server/public/assets/{admin-CYCKrV_x.js → admin-Du1nR_hP.js} +1 -1
  54. package/payload/server/public/assets/admin-Du1nR_hP.js.br +0 -0
  55. package/payload/server/public/assets/admin-Du1nR_hP.js.gz +0 -0
  56. package/payload/server/public/assets/{agents-D8EJG-ta.js → agents-DJTxRLLU.js} +1 -1
  57. package/payload/server/public/assets/agents-DJTxRLLU.js.br +0 -0
  58. package/payload/server/public/assets/agents-DJTxRLLU.js.gz +0 -0
  59. package/payload/server/public/assets/{browser-D6Y_Zu8C.js → browser-Bft6zVkX.js} +1 -1
  60. package/payload/server/public/assets/browser-Bft6zVkX.js.br +0 -0
  61. package/payload/server/public/assets/browser-Bft6zVkX.js.gz +0 -0
  62. package/payload/server/public/assets/{calendar-DD4oVa-r.js → calendar-Ct-DCCqY.js} +1 -1
  63. package/payload/server/public/assets/calendar-Ct-DCCqY.js.br +0 -0
  64. package/payload/server/public/assets/calendar-Ct-DCCqY.js.gz +0 -0
  65. package/payload/server/public/assets/chat-pqasbAg8.js +1 -0
  66. package/payload/server/public/assets/chat-pqasbAg8.js.br +0 -0
  67. package/payload/server/public/assets/chat-pqasbAg8.js.gz +0 -0
  68. package/payload/server/public/assets/chevron-left-C8LTc7lb.js +1 -0
  69. package/payload/server/public/assets/chevron-right-Lz9t7Hed.js +1 -0
  70. package/payload/server/public/assets/chevron-right-Lz9t7Hed.js.br +0 -0
  71. package/payload/server/public/assets/clock-D6QQFHwJ.js +1 -0
  72. package/payload/server/public/assets/clock-D6QQFHwJ.js.br +0 -0
  73. package/payload/server/public/assets/clock-D6QQFHwJ.js.gz +0 -0
  74. package/payload/server/public/assets/data-B1JJ3Y1T.js +1 -0
  75. package/payload/server/public/assets/data-B1JJ3Y1T.js.br +0 -0
  76. package/payload/server/public/assets/data-B1JJ3Y1T.js.gz +0 -0
  77. package/payload/server/public/assets/{file-download-sXU3_umF.js → file-download-C_fSe4dm.js} +1 -1
  78. package/payload/server/public/assets/file-download-C_fSe4dm.js.br +0 -0
  79. package/payload/server/public/assets/file-download-C_fSe4dm.js.gz +0 -0
  80. package/payload/server/public/assets/{file-text-CeXBviDH.js → file-text-D7GbEcqj.js} +1 -1
  81. package/payload/server/public/assets/file-text-D7GbEcqj.js.br +0 -0
  82. package/payload/server/public/assets/file-text-D7GbEcqj.js.gz +0 -0
  83. package/payload/server/public/assets/{graph-labels-D2pQkgYW.js → graph-labels-Dlcb6cW7.js} +1 -1
  84. package/payload/server/public/assets/graph-labels-Dlcb6cW7.js.br +0 -0
  85. package/payload/server/public/assets/graph-labels-Dlcb6cW7.js.gz +0 -0
  86. package/payload/server/public/assets/{graph-BSuDP6k7.js → graph-yl5qU1kO.js} +1 -1
  87. package/payload/server/public/assets/graph-yl5qU1kO.js.br +0 -0
  88. package/payload/server/public/assets/graph-yl5qU1kO.js.gz +0 -0
  89. package/payload/server/public/assets/{maximize-2-DlzSNygU.js → maximize-2-BS689r4W.js} +1 -1
  90. package/payload/server/public/assets/maximize-2-BS689r4W.js.br +0 -0
  91. package/payload/server/public/assets/maximize-2-BS689r4W.js.gz +0 -0
  92. package/payload/server/public/assets/{operator-BP61-xjz.js → operator-CvapnpIW.js} +1 -1
  93. package/payload/server/public/assets/operator-CvapnpIW.js.br +0 -0
  94. package/payload/server/public/assets/operator-CvapnpIW.js.gz +0 -0
  95. package/payload/server/public/assets/{page-eQ4OVros.js → page-987y4JbQ.js} +1 -1
  96. package/payload/server/public/assets/page-987y4JbQ.js.br +0 -0
  97. package/payload/server/public/assets/page-987y4JbQ.js.gz +0 -0
  98. package/payload/server/public/assets/{page-DX7ArY_s.js → page-DWkUH4Z_.js} +1 -1
  99. package/payload/server/public/assets/page-DWkUH4Z_.js.br +0 -0
  100. package/payload/server/public/assets/page-DWkUH4Z_.js.gz +0 -0
  101. package/payload/server/public/assets/{public-DPDKFKM2.js → public-D5pi-VFn.js} +1 -1
  102. package/payload/server/public/assets/public-D5pi-VFn.js.br +0 -0
  103. package/payload/server/public/assets/public-D5pi-VFn.js.gz +0 -0
  104. package/payload/server/public/assets/{rotate-ccw-BR0JbPFV.js → rotate-ccw-zNxnK36R.js} +1 -1
  105. package/payload/server/public/assets/rotate-ccw-zNxnK36R.js.br +0 -0
  106. package/payload/server/public/assets/rotate-ccw-zNxnK36R.js.gz +0 -0
  107. package/payload/server/public/assets/routines-mZzrGA5y.js +2 -0
  108. package/payload/server/public/assets/routines-mZzrGA5y.js.br +0 -0
  109. package/payload/server/public/assets/routines-mZzrGA5y.js.gz +0 -0
  110. package/payload/server/public/assets/{skills-fH0uA7cj.js → skills-5A8PockT.js} +1 -1
  111. package/payload/server/public/assets/skills-5A8PockT.js.br +0 -0
  112. package/payload/server/public/assets/skills-5A8PockT.js.gz +0 -0
  113. package/payload/server/public/assets/{tasks-CrVzFrTR.js → tasks-B1_2wc6L.js} +1 -1
  114. package/payload/server/public/assets/tasks-B1_2wc6L.js.br +0 -0
  115. package/payload/server/public/assets/tasks-B1_2wc6L.js.gz +0 -0
  116. package/payload/server/public/assets/{time-entry-format-C_0jiMpQ.js → time-entry-format-CJ2FSSQt.js} +1 -1
  117. package/payload/server/public/assets/time-entry-format-CJ2FSSQt.js.br +0 -0
  118. package/payload/server/public/assets/time-entry-format-CJ2FSSQt.js.gz +0 -0
  119. package/payload/server/public/assets/{triangle-alert-eLJHC_QJ.js → triangle-alert-CedsrgME.js} +1 -1
  120. package/payload/server/public/assets/triangle-alert-CedsrgME.js.br +0 -0
  121. package/payload/server/public/assets/triangle-alert-CedsrgME.js.gz +0 -0
  122. package/payload/server/public/assets/{useCopyFeedback-vPCmdOmY.js → useCopyFeedback-zjZZUC2-.js} +1 -1
  123. package/payload/server/public/assets/useCopyFeedback-zjZZUC2-.js.br +0 -0
  124. package/payload/server/public/assets/useCopyFeedback-zjZZUC2-.js.gz +0 -0
  125. package/payload/server/public/assets/{useSubAccountSwitcher-DxuUS_C7.css → useSubAccountSwitcher-72THldvm.css} +1 -1
  126. package/payload/server/public/assets/useSubAccountSwitcher-72THldvm.css.br +0 -0
  127. package/payload/server/public/assets/{useSubAccountSwitcher-DxuUS_C7.css.gz → useSubAccountSwitcher-72THldvm.css.gz} +0 -0
  128. package/payload/server/public/assets/{useVoiceRecorder-D5bFDRqL.js → useVoiceRecorder-CJecnefX.js} +1 -1
  129. package/payload/server/public/assets/useVoiceRecorder-CJecnefX.js.br +0 -0
  130. package/payload/server/public/assets/useVoiceRecorder-CJecnefX.js.gz +0 -0
  131. package/payload/server/public/assets/{wrench-sXisQXkf.js → wrench-D8BH20W8.js} +1 -1
  132. package/payload/server/public/assets/wrench-D8BH20W8.js.br +0 -0
  133. package/payload/server/public/assets/wrench-D8BH20W8.js.gz +0 -0
  134. package/payload/server/public/browser.html +5 -5
  135. package/payload/server/public/calendar.html +7 -7
  136. package/payload/server/public/chat.html +13 -13
  137. package/payload/server/public/data.html +12 -12
  138. package/payload/server/public/graph.html +9 -9
  139. package/payload/server/public/index.html +16 -16
  140. package/payload/server/public/operator.html +15 -15
  141. package/payload/server/public/public.html +13 -13
  142. package/payload/server/public/routines.html +7 -7
  143. package/payload/server/public/skills.html +5 -5
  144. package/payload/server/public/tasks.html +6 -6
  145. package/payload/server/server.js +343 -205
  146. package/payload/server/public/assets/AdminLoginScreens-Bx9vPuUg.js.br +0 -0
  147. package/payload/server/public/assets/AdminLoginScreens-Bx9vPuUg.js.gz +0 -0
  148. package/payload/server/public/assets/AdminShell-BxJPbC3p.js.br +0 -0
  149. package/payload/server/public/assets/AdminShell-BxJPbC3p.js.gz +0 -0
  150. package/payload/server/public/assets/CdpScreencastSurface-5HLhTQTV.js.br +0 -0
  151. package/payload/server/public/assets/CdpScreencastSurface-5HLhTQTV.js.gz +0 -0
  152. package/payload/server/public/assets/activity-BaSYn62l.js.br +0 -0
  153. package/payload/server/public/assets/activity-BaSYn62l.js.gz +0 -0
  154. package/payload/server/public/assets/admin-CYCKrV_x.js.br +0 -0
  155. package/payload/server/public/assets/admin-CYCKrV_x.js.gz +0 -0
  156. package/payload/server/public/assets/agents-D8EJG-ta.js.br +0 -0
  157. package/payload/server/public/assets/agents-D8EJG-ta.js.gz +0 -0
  158. package/payload/server/public/assets/browser-D6Y_Zu8C.js.br +0 -0
  159. package/payload/server/public/assets/browser-D6Y_Zu8C.js.gz +0 -0
  160. package/payload/server/public/assets/calendar-DD4oVa-r.js.br +0 -0
  161. package/payload/server/public/assets/calendar-DD4oVa-r.js.gz +0 -0
  162. package/payload/server/public/assets/chat-CwWs69PO.js +0 -1
  163. package/payload/server/public/assets/chat-CwWs69PO.js.br +0 -0
  164. package/payload/server/public/assets/chat-CwWs69PO.js.gz +0 -0
  165. package/payload/server/public/assets/chevron-left-ClfIy5Fx.js +0 -1
  166. package/payload/server/public/assets/chevron-right-B0QvXpxl.js +0 -1
  167. package/payload/server/public/assets/chevron-right-B0QvXpxl.js.br +0 -0
  168. package/payload/server/public/assets/clock-BIaRnIP_.js +0 -1
  169. package/payload/server/public/assets/clock-BIaRnIP_.js.br +0 -0
  170. package/payload/server/public/assets/clock-BIaRnIP_.js.gz +0 -0
  171. package/payload/server/public/assets/data-O_9Kiicl.js +0 -1
  172. package/payload/server/public/assets/data-O_9Kiicl.js.br +0 -2
  173. package/payload/server/public/assets/data-O_9Kiicl.js.gz +0 -0
  174. package/payload/server/public/assets/file-download-sXU3_umF.js.br +0 -0
  175. package/payload/server/public/assets/file-download-sXU3_umF.js.gz +0 -0
  176. package/payload/server/public/assets/file-text-CeXBviDH.js.br +0 -0
  177. package/payload/server/public/assets/file-text-CeXBviDH.js.gz +0 -0
  178. package/payload/server/public/assets/graph-BSuDP6k7.js.br +0 -0
  179. package/payload/server/public/assets/graph-BSuDP6k7.js.gz +0 -0
  180. package/payload/server/public/assets/graph-labels-D2pQkgYW.js.br +0 -0
  181. package/payload/server/public/assets/graph-labels-D2pQkgYW.js.gz +0 -0
  182. package/payload/server/public/assets/maximize-2-DlzSNygU.js.br +0 -0
  183. package/payload/server/public/assets/maximize-2-DlzSNygU.js.gz +0 -0
  184. package/payload/server/public/assets/operator-BP61-xjz.js.br +0 -0
  185. package/payload/server/public/assets/operator-BP61-xjz.js.gz +0 -0
  186. package/payload/server/public/assets/page-DX7ArY_s.js.br +0 -0
  187. package/payload/server/public/assets/page-DX7ArY_s.js.gz +0 -0
  188. package/payload/server/public/assets/page-eQ4OVros.js.br +0 -0
  189. package/payload/server/public/assets/page-eQ4OVros.js.gz +0 -0
  190. package/payload/server/public/assets/public-DPDKFKM2.js.br +0 -0
  191. package/payload/server/public/assets/public-DPDKFKM2.js.gz +0 -0
  192. package/payload/server/public/assets/rotate-ccw-BR0JbPFV.js.br +0 -0
  193. package/payload/server/public/assets/rotate-ccw-BR0JbPFV.js.gz +0 -0
  194. package/payload/server/public/assets/routines-y413YZa-.js +0 -1
  195. package/payload/server/public/assets/routines-y413YZa-.js.br +0 -0
  196. package/payload/server/public/assets/routines-y413YZa-.js.gz +0 -0
  197. package/payload/server/public/assets/skills-fH0uA7cj.js.br +0 -0
  198. package/payload/server/public/assets/skills-fH0uA7cj.js.gz +0 -0
  199. package/payload/server/public/assets/tasks-CrVzFrTR.js.br +0 -0
  200. package/payload/server/public/assets/tasks-CrVzFrTR.js.gz +0 -0
  201. package/payload/server/public/assets/time-entry-format-C_0jiMpQ.js.br +0 -1
  202. package/payload/server/public/assets/time-entry-format-C_0jiMpQ.js.gz +0 -0
  203. package/payload/server/public/assets/triangle-alert-eLJHC_QJ.js.br +0 -1
  204. package/payload/server/public/assets/triangle-alert-eLJHC_QJ.js.gz +0 -0
  205. package/payload/server/public/assets/useCopyFeedback-vPCmdOmY.js.br +0 -0
  206. package/payload/server/public/assets/useCopyFeedback-vPCmdOmY.js.gz +0 -0
  207. package/payload/server/public/assets/useSubAccountSwitcher-DxuUS_C7.css.br +0 -0
  208. package/payload/server/public/assets/useVoiceRecorder-D5bFDRqL.js.br +0 -0
  209. package/payload/server/public/assets/useVoiceRecorder-D5bFDRqL.js.gz +0 -0
  210. package/payload/server/public/assets/wrench-sXisQXkf.js.br +0 -0
  211. package/payload/server/public/assets/wrench-sXisQXkf.js.gz +0 -0
  212. /package/payload/server/public/assets/{useSubAccountSwitcher-DMr3DR5N.js → useSubAccountSwitcher-HjQZjJBr.js} +0 -0
  213. /package/payload/server/public/assets/{useSubAccountSwitcher-DMr3DR5N.js.br → useSubAccountSwitcher-HjQZjJBr.js.br} +0 -0
  214. /package/payload/server/public/assets/{useSubAccountSwitcher-DMr3DR5N.js.gz → useSubAccountSwitcher-HjQZjJBr.js.gz} +0 -0
@@ -0,0 +1,10 @@
1
+ export type IngestTokenAction = "show" | "rotate";
2
+ export interface IngestTokenResult {
3
+ token: string;
4
+ /** True only when this call created the token. */
5
+ minted: boolean;
6
+ /** True only when this call replaced an existing token. */
7
+ rotated: boolean;
8
+ }
9
+ export declare function resolveIngestToken(accountsDir: string, accountId: string, action: IngestTokenAction): IngestTokenResult;
10
+ //# sourceMappingURL=linkedin-ingest-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkedin-ingest-token.d.ts","sourceRoot":"","sources":["../../src/tools/linkedin-ingest-token.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,iBAAiB,GACxB,iBAAiB,CAMnB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * linkedin-ingest-token — hand the operator the LinkedIn extension's durable
3
+ * credential (Task 2208).
4
+ *
5
+ * The agent walks the operator through side-loading the extension and then has
6
+ * to supply a credential. Before this tool the docs pointed at a `session_key`
7
+ * cookie that is never minted, so the operator pasted conversation session keys
8
+ * and every capture came back 401. This is the deterministic source of the one
9
+ * value the extension needs.
10
+ *
11
+ * IDENTITY.md forbids the agent editing account.json directly, so the write
12
+ * goes through here rather than through Edit.
13
+ */
14
+ import { ensureIngestToken, rotateIngestToken, } from "../../../../../lib/account-ingest-token/dist/index.js";
15
+ export function resolveIngestToken(accountsDir, accountId, action) {
16
+ if (action === "rotate") {
17
+ return { token: rotateIngestToken(accountsDir, accountId), minted: false, rotated: true };
18
+ }
19
+ const { token, minted } = ensureIngestToken(accountsDir, accountId);
20
+ return { token, minted, rotated: false };
21
+ }
22
+ //# sourceMappingURL=linkedin-ingest-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linkedin-ingest-token.js","sourceRoot":"","sources":["../../src/tools/linkedin-ingest-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,uDAAuD,CAAC;AAY/D,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,SAAiB,EACjB,MAAyB;IAEzB,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,EAAE,KAAK,EAAE,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC5F,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACpE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC3C,CAAC"}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what SiteDesk ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:7106e5166b451f13c373e48b7eb6057846d8e385002c774fa6ba4db1b05aef6e
4
+ content-hash: sha256:a4d904a0b258e267d582d5bd8e968e1de3734c6e60863c401ea5e1b185bccba5
5
5
  brand: sitedesk-code
6
6
  product-name: SiteDesk
7
7
  ---
@@ -2854,7 +2854,7 @@ Capture a LinkedIn profile or DM thread to your SiteDesk graph with one click. T
2854
2854
  5. Click the puzzle icon → pin **SiteDesk LinkedIn Ingest** → open its options.
2855
2855
  6. Paste two values:
2856
2856
  - **Admin host** — your tunnel URL, e.g. `https://your-tunnel.example.com`.
2857
- - **Session key** — open your admin browser, copy the value of `session_key` from the cookie. (Same key the admin uses to authenticate every other admin request.)
2857
+ - **Ingest token** — ask your SiteDesk agent for the ingest token. It mints one on request and hands it to you in the chat. The same value keeps working: it survives restarts and new chat sessions, and it does not expire. This is not a conversation session key, and not the admin browser's `session_key`; the route accepts neither.
2858
2858
  7. Save. The pill is now armed.
2859
2859
 
2860
2860
  ## Use
@@ -2879,7 +2879,7 @@ The plugin will never create `:Communication`, `:ConversationArchive` rows (i.e.
2879
2879
 
2880
2880
  ## When the pill turns amber
2881
2881
 
2882
- The pill shows **Sign in to SiteDesk** when your session key has expired. Click it to open the options page; paste a fresh `session_key` from your admin browser; save. The next click on the LinkedIn pill will succeed.
2882
+ The pill shows **Paste SiteDesk token** when the ingest token is missing or was refused. Click it to open the options page, ask your SiteDesk agent for the ingest token, paste it, and save. The next click on the LinkedIn pill will succeed. There is no sign-in step: the token is the whole credential, and the only way it stops working is if someone rotates it.
2883
2883
 
2884
2884
  ## When the pill turns red
2885
2885
 
@@ -3363,7 +3363,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
3363
3363
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
3364
3364
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
3365
3365
  | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
3366
- | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`). Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses. |
3366
+ | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
3367
3367
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
3368
3368
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
3369
3369
  | `POST /api/admin/routines/suspend-all` | Suspends every routine currently `'scheduled'` and marks each one. Returns the count. |
@@ -3397,6 +3397,18 @@ rows — a routine suspended one at a time carries no mark and stays suspended,
3397
3397
  which is what makes resume-all safe to press. The seeded roster is unmarked by
3398
3398
  construction, so resume-all never enables it.
3399
3399
 
3400
+ **The modal states why a routine is in the state it is in.** Every trigger,
3401
+ dispatch, suspend and resume appends one `[<iso>] <text>` line to the routine's
3402
+ own `notes`, and the detail modal lists that trail newest first under
3403
+ `History (<n> entries)`. So a suspended routine names its reason, the path that
3404
+ suspended it (`Suspended from chat`, `Suspended from the routines dashboard`,
3405
+ `Suspended by a suspend-all from the routines dashboard`) and the time, without
3406
+ reading the graph. Entries render as stored and none is dropped; the box scrolls.
3407
+ A routine with no trail says `No history recorded.`, which is the true state of a
3408
+ seeded roster routine that has never run. This is not the run history: a
3409
+ `:RoutineRun` node records a fire, and a status change is not a fire, so a
3410
+ suspension appears only here.
3411
+
3400
3412
  Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
3401
3413
  `op=get id=<8> found=<bool>`, `op=patch id=<8> fields=<…> outcome=ok|rejected
3402
3414
  reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`, and
@@ -3406,6 +3418,15 @@ past-one-time|invalid-cron>`. A `count=0` while routines exist for the account i
3406
3418
  the account-scope-miss signature; the logged `cron=` on `outcome=ok` confirms the
3407
3419
  persisted expression without opening the graph.
3408
3420
 
3421
+ The panel can only show a reason that was written, and a suspend path that wrote
3422
+ no note emits no event of its own. The scheduling sweep's standing census closes
3423
+ that: `[schedule-audit] op=suspension-census` carries `unattributed=<n>`, the
3424
+ count of suspended routines whose notes never record the suspension, on every
3425
+ tick including zero. It sits outside the `other=` arithmetic, which reconciles
3426
+ status buckets against the row count rather than attribution. A routine seeded
3427
+ suspended and never touched carries no notes and is excluded, having nothing to
3428
+ attribute.
3429
+
3409
3430
  Two scheduler-side lines close the loop, because a suspend that the sweep ignores
3410
3431
  looks identical to a healthy routine until it fires. `[schedule-sweep]
3411
3432
  op=skip-suspended account=<8> count=<n>` is emitted every sweep for every local
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-07-30 (0.1.529)
13
+
14
+ - The LinkedIn browser extension now signs in with its own long-lived key, handed to you by an admin tool, instead of borrowing a chat session's key. It keeps working when the session it was set up from ends.
15
+ - The routine editing window has been fixed properly. It is sized to its contents, its dropdowns are styled like the rest of the product, it lists the real places a routine can send its result, and it builds every kind of schedule it offers instead of dropping you into a raw timing expression.
16
+ - A suspended routine now shows why it was suspended, who did it and when, with the most recent entry first. Routines suspended without any reason recorded are counted so they can be found.
17
+
12
18
  ## 2026-07-30 (0.1.528)
13
19
 
14
20
  - Where two or more accounts share one device, the assistant can no longer edit or write files belonging to a different account. Every account is now checked for that protection being in place.
@@ -136,7 +136,7 @@ The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.t
136
136
  | `GET /api/admin/routines/:eventId/runs` | One routine's `:RoutineRun` history, newest first. `limit` clamps to 1..500 and defaults to 50, so a caller that wants the full page must ask for it. |
137
137
  | `GET /api/admin/routines/:eventId/runs.csv` | The same projection as a `text/csv` attachment with `Content-Disposition`, and **no** limit, so it is not the same row set as a clamped `/runs` call. Re-parses its own output and returns 500 rather than serving a document with fewer rows than the query returned. Zero rows is 404, not a header-only file: the query cannot tell a routine with no runs from an `eventId` on another account. |
138
138
  | `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt`, `recurrence`, and the `agentChannel`/`agentDestination` pair. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. The binding is revalidated against the same house-authority rule, shared as `platform/lib/agent-dispatch-rule`; it is refused `partial-binding`, `invalid-channel`, `no-prompt`, `not-registered`, `cross-account`, `action-routine` (a routine that runs a tool keeps it — an agent binding outranks the action at fire time) or `clear-would-orphan` (clearing the only field holding a one-time routine inside the automation filter would hide it from every routines surface), and no refusal writes. Both fields as `null` clears the binding. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
139
- | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`). Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses. |
139
+ | `GET /api/admin/dispatch-destinations` | Every destination the session's account may bind a routine to, across both channels, each labelled by the authoritative list it came from (`House admin`, `Account manager`, `Telegram admin`), plus an optional `name`: a display-only person name resolved from `:Person` in the row's own account scope — the house for a `House admin` row, the managed sub-account for an `Account manager` row, and never for Telegram, whose destinations are chat ids. Computed from the same lists the validator reads and filtered by the same rule, so the picker cannot offer what the save refuses; `name` is never part of what Save posts, and a graph outage returns an unnamed roster rather than an error. |
140
140
  | `POST /api/admin/routines/:eventId/suspend` | Writes `eventStatus = 'suspended'` on a routine currently `'scheduled'`. 404s otherwise, so a second suspend is not a silent no-op. |
141
141
  | `POST /api/admin/routines/:eventId/resume` | Returns a suspended routine to `'scheduled'`. 404s if it is not suspended, 409s if it cannot be resumed. |
142
142
  | `POST /api/admin/routines/suspend-all` | Suspends every routine currently `'scheduled'` and marks each one. Returns the count. |
@@ -170,6 +170,18 @@ rows — a routine suspended one at a time carries no mark and stays suspended,
170
170
  which is what makes resume-all safe to press. The seeded roster is unmarked by
171
171
  construction, so resume-all never enables it.
172
172
 
173
+ **The modal states why a routine is in the state it is in.** Every trigger,
174
+ dispatch, suspend and resume appends one `[<iso>] <text>` line to the routine's
175
+ own `notes`, and the detail modal lists that trail newest first under
176
+ `History (<n> entries)`. So a suspended routine names its reason, the path that
177
+ suspended it (`Suspended from chat`, `Suspended from the routines dashboard`,
178
+ `Suspended by a suspend-all from the routines dashboard`) and the time, without
179
+ reading the graph. Entries render as stored and none is dropped; the box scrolls.
180
+ A routine with no trail says `No history recorded.`, which is the true state of a
181
+ seeded roster routine that has never run. This is not the run history: a
182
+ `:RoutineRun` node records a fire, and a status change is not a fire, so a
183
+ suspension appears only here.
184
+
173
185
  Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
174
186
  `op=get id=<8> found=<bool>`, `op=patch id=<8> fields=<…> outcome=ok|rejected
175
187
  reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`, and
@@ -179,6 +191,15 @@ past-one-time|invalid-cron>`. A `count=0` while routines exist for the account i
179
191
  the account-scope-miss signature; the logged `cron=` on `outcome=ok` confirms the
180
192
  persisted expression without opening the graph.
181
193
 
194
+ The panel can only show a reason that was written, and a suspend path that wrote
195
+ no note emits no event of its own. The scheduling sweep's standing census closes
196
+ that: `[schedule-audit] op=suspension-census` carries `unattributed=<n>`, the
197
+ count of suspended routines whose notes never record the suspension, on every
198
+ tick including zero. It sits outside the `other=` arithmetic, which reconciles
199
+ status buckets against the row count rather than attribution. A routine seeded
200
+ suspended and never touched carries no notes and is excluded, having nothing to
201
+ attribute.
202
+
182
203
  Two scheduler-side lines close the loop, because a suspend that the sweep ignores
183
204
  looks identical to a healthy routine until it fires. `[schedule-sweep]
184
205
  op=skip-suspended account=<8> count=<n>` is emitted every sweep for every local
@@ -11,7 +11,7 @@ Capture a LinkedIn profile or DM thread to your SiteDesk graph with one click. T
11
11
  5. Click the puzzle icon → pin **SiteDesk LinkedIn Ingest** → open its options.
12
12
  6. Paste two values:
13
13
  - **Admin host** — your tunnel URL, e.g. `https://your-tunnel.example.com`.
14
- - **Session key** — open your admin browser, copy the value of `session_key` from the cookie. (Same key the admin uses to authenticate every other admin request.)
14
+ - **Ingest token** — ask your SiteDesk agent for the ingest token. It mints one on request and hands it to you in the chat. The same value keeps working: it survives restarts and new chat sessions, and it does not expire. This is not a conversation session key, and not the admin browser's `session_key`; the route accepts neither.
15
15
  7. Save. The pill is now armed.
16
16
 
17
17
  ## Use
@@ -36,7 +36,7 @@ The plugin will never create `:Communication`, `:ConversationArchive` rows (i.e.
36
36
 
37
37
  ## When the pill turns amber
38
38
 
39
- The pill shows **Sign in to SiteDesk** when your session key has expired. Click it to open the options page; paste a fresh `session_key` from your admin browser; save. The next click on the LinkedIn pill will succeed.
39
+ The pill shows **Paste SiteDesk token** when the ingest token is missing or was refused. Click it to open the options page, ask your SiteDesk agent for the ingest token, paste it, and save. The next click on the LinkedIn pill will succeed. There is no sign-in step: the token is the whole credential, and the only way it stops working is if someone rotates it.
40
40
 
41
41
  ## When the pill turns red
42
42
 
@@ -8,7 +8,7 @@ Unpacked-extension dev build. No Chrome Web Store publication.
8
8
  2. Enable **Developer mode** (top right).
9
9
  3. **Load unpacked** → select this `extension/` directory.
10
10
  4. Click the new puzzle icon → open **SiteDesk LinkedIn Ingest** options.
11
- 5. Paste your admin host (e.g. `https://your-tunnel.example.com`) and your admin **session key** (the value of `session_key` on the cookie or query in your admin browser). Save.
11
+ 5. Paste your admin host (e.g. `https://your-tunnel.example.com`) and your **ingest token**. Ask your SiteDesk agent for the ingest token; it mints one on request and gives it to you in the chat. The token is stable, so this is a one-time paste unless someone rotates it. Save.
12
12
  6. Chrome asks you to allow the extension to talk to that host. **Allow it.** The pill cannot post without it, so declining saves nothing.
13
13
 
14
14
  ## Use
@@ -21,7 +21,7 @@ The pill tells you which thing is wrong:
21
21
 
22
22
  | Pill | Meaning | Fix |
23
23
  |---|---|---|
24
- | **Sign in to SiteDesk** (amber) | the session key expired | open options, paste a fresh key |
24
+ | **Paste SiteDesk token** (amber) | the ingest token is missing or was refused | ask the agent for the ingest token, paste it in options |
25
25
  | **Grant access** (amber) | the admin host was never allowed, so the post never left the browser | open options, Save, allow the prompt |
26
26
  | **SiteDesk unreachable** (red) | the host was allowed but did not answer | check the tunnel and the admin server |
27
27
  | **Capture failed** (red) | the admin rejected the payload, or the extension's own service worker did not answer | read the service-worker console: `reason=extract` is a rejected payload, `reason=worker-unreachable` is the worker |
@@ -35,7 +35,7 @@ The pill tells you which thing is wrong:
35
35
  | `content/profile.js` | Profile pill + click handler. |
36
36
  | `content/thread.js` | Thread pill + click handler. |
37
37
  | `background/sw.js` | Service worker. Checks the admin-host grant, then POSTs envelopes to the admin route. |
38
- | `options/options.html` + `options/options.js` | Admin host + session-key entry. |
38
+ | `options/options.html` + `options/options.js` | Admin host + ingest-token entry. |
39
39
  | `assets/pill.css` | Pill styling. |
40
40
  | `__tests__/` | jsdom unit tests for the extractors, run via vitest from `platform/ui`. |
41
41
 
@@ -1,5 +1,5 @@
1
1
  // Service worker. Receives envelopes from content scripts, POSTs them
2
- // to the admin host with the session key stored in extension options.
2
+ // to the admin host with the ingest token stored in extension options.
3
3
  //
4
4
  // Task 2185 — the manifest's static host_permissions cover linkedin.com only,
5
5
  // so the admin host has to be granted at runtime from the options page before
@@ -14,11 +14,16 @@
14
14
 
15
15
  const TAG = '[linkedin-ext-post]'
16
16
 
17
+ // Task 2208 — the credential is the account's durable ingest token, minted by
18
+ // the linkedin-ingest-token admin tool and pasted in on the options page. The
19
+ // admin browser's session_key it replaced could neither be obtained as
20
+ // documented nor stay valid: in-memory, 24h TTL, wiped by restart, rotated on
21
+ // every new chat session.
17
22
  async function getConfig() {
18
- const stored = await chrome.storage.local.get(['adminHost', 'sessionKey'])
23
+ const stored = await chrome.storage.local.get(['adminHost', 'ingestToken'])
19
24
  return {
20
25
  adminHost: typeof stored.adminHost === 'string' ? stored.adminHost.replace(/\/+$/, '') : '',
21
- sessionKey: typeof stored.sessionKey === 'string' ? stored.sessionKey : '',
26
+ ingestToken: typeof stored.ingestToken === 'string' ? stored.ingestToken : '',
22
27
  }
23
28
  }
24
29
 
@@ -28,9 +33,9 @@ function originPattern(adminHost) {
28
33
  }
29
34
 
30
35
  async function postEnvelope(envelope) {
31
- const { adminHost, sessionKey } = await getConfig()
36
+ const { adminHost, ingestToken } = await getConfig()
32
37
  if (!adminHost) return { ok: false, reason: 'auth', detail: 'admin-host-missing' }
33
- if (!sessionKey) return { ok: false, reason: 'auth', detail: 'session-key-missing' }
38
+ if (!ingestToken) return { ok: false, reason: 'auth', detail: 'ingest-token-missing' }
34
39
 
35
40
  let origin
36
41
  try {
@@ -53,7 +58,7 @@ async function postEnvelope(envelope) {
53
58
  res = await fetch(url, {
54
59
  method: 'POST',
55
60
  headers: { 'content-type': 'application/json' },
56
- body: JSON.stringify({ ...envelope, session_key: sessionKey }),
61
+ body: JSON.stringify({ ...envelope, ingest_token: ingestToken }),
57
62
  })
58
63
  } catch (err) {
59
64
  console.error(
@@ -66,7 +71,7 @@ async function postEnvelope(envelope) {
66
71
  const ms = Date.now() - started
67
72
  console.log(TAG + ' op=fetch dispatchId=' + envelope.dispatchId + ' status=' + res.status + ' ms=' + ms)
68
73
 
69
- if (res.status === 401) return { ok: false, reason: 'auth', detail: 'session-rejected' }
74
+ if (res.status === 401) return { ok: false, reason: 'auth', detail: 'token-rejected' }
70
75
  if (res.status >= 500) return { ok: false, reason: 'server', detail: 'http-' + res.status }
71
76
  if (!res.ok) {
72
77
  let body = ''
@@ -51,8 +51,11 @@
51
51
  const reason = (reply && reply.reason) || 'worker-unreachable'
52
52
  const detail = (reply && reply.detail) || ''
53
53
  console.error(TAG_ERROR + ' reason=' + reason + ' detail=' + detail)
54
+ // Task 2208 — the ingest token is missing or was refused. There is no
55
+ // sign-in to perform: the operator asks the SiteDesk agent for the token and
56
+ // pastes it on the options page.
54
57
  if (reason === 'auth') {
55
- setState(pill, 'auth', 'Sign in to SiteDesk')
58
+ setState(pill, 'auth', 'Paste SiteDesk token')
56
59
  pill.onclick = function () { chrome.runtime.openOptionsPage() }
57
60
  return
58
61
  }
@@ -53,8 +53,11 @@
53
53
  const reason = (reply && reply.reason) || 'worker-unreachable'
54
54
  const detail = (reply && reply.detail) || ''
55
55
  console.error(TAG_ERROR + ' reason=' + reason + ' detail=' + detail)
56
+ // Task 2208 — the ingest token is missing or was refused. There is no
57
+ // sign-in to perform: the operator asks the SiteDesk agent for the token and
58
+ // pastes it on the options page.
56
59
  if (reason === 'auth') {
57
- setState(pill, 'auth', 'Sign in to SiteDesk')
60
+ setState(pill, 'auth', 'Paste SiteDesk token')
58
61
  pill.onclick = function () { chrome.runtime.openOptionsPage() }
59
62
  return
60
63
  }
@@ -17,14 +17,14 @@
17
17
  </head>
18
18
  <body>
19
19
  <h1>SiteDesk LinkedIn Ingest</h1>
20
- <p>Point the extension at your SiteDesk admin host and paste your admin session key. The pill on LinkedIn will then post one-click captures to your admin.</p>
20
+ <p>Point the extension at your SiteDesk admin host and paste your ingest token. Ask your SiteDesk agent for the token and it will give you one. The pill on LinkedIn will then post one-click captures to your admin.</p>
21
21
  <p>On Save, Chrome asks you to allow this extension to talk to that host. The capture pill cannot post without it, so declining leaves the extension unconfigured.</p>
22
22
 
23
23
  <label for="admin-host">Admin host</label>
24
24
  <input id="admin-host" type="url" placeholder="https://your-tunnel.example.com" />
25
25
 
26
- <label for="session-key">Session key</label>
27
- <input id="session-key" type="password" placeholder="paste the value of session_key from your admin browser" />
26
+ <label for="ingest-token">Ingest token</label>
27
+ <input id="ingest-token" type="password" placeholder="paste the token your SiteDesk agent gives you" />
28
28
 
29
29
  <button id="save">Save</button>
30
30
  <div id="status" class="status"></div>
@@ -6,9 +6,16 @@
6
6
  // request that Chrome preflights and the admin route never answers. The
7
7
  // runtime grant requested here is what makes that fetch permission-exempt.
8
8
  // Without it every capture dies in the browser and the server sees nothing.
9
+ //
10
+ // Task 2208 — the credential is the account's durable ingest token, obtained by
11
+ // asking the SiteDesk agent, which calls the linkedin-ingest-token admin tool. It is
12
+ // NOT the admin browser's session_key: that lived in an in-memory map with a 24h
13
+ // TTL, every restart wiped it and every new chat session rotated it, and no
14
+ // cookie ever carried it, so the previous instruction to copy it from a cookie
15
+ // named something that does not exist.
9
16
 
10
17
  const $host = document.getElementById('admin-host')
11
- const $key = document.getElementById('session-key')
18
+ const $token = document.getElementById('ingest-token')
12
19
  const $btn = document.getElementById('save')
13
20
  const $status = document.getElementById('status')
14
21
 
@@ -28,26 +35,27 @@ function originPattern(adminHost) {
28
35
  // console; this page says what actually went wrong in plain words.
29
36
  const PROBE_COPY = {
30
37
  unreachable: 'that host did not answer',
31
- auth: 'that host rejected the session key',
38
+ auth: 'that host rejected the ingest token',
32
39
  server: 'that host answered with an unexpected status',
33
40
  }
34
41
 
35
- // Config-time probe. The body carries none of the five envelope fields, so
36
- // the route answers 400 {error:'schema', reason:'envelope', missing:[all
37
- // five]} — a signature no real capture can produce, because both content
38
- // scripts always set all five. Nothing is dispatched and nothing is written.
39
- async function probe(adminHost, sessionKey) {
42
+ // Config-time probe. The body carries the token but none of the five envelope
43
+ // fields, so the route answers 400 {error:'schema', reason:'envelope', missing:
44
+ // [all five]} — a signature no real capture can produce, because both content
45
+ // scripts always set all five. Nothing is dispatched and nothing is written. A
46
+ // 401 here means the token itself was refused.
47
+ async function probe(adminHost, ingestToken) {
40
48
  let res
41
49
  try {
42
50
  res = await fetch(adminHost + '/api/admin/linkedin-ingest', {
43
51
  method: 'POST',
44
52
  headers: { 'content-type': 'application/json' },
45
- body: JSON.stringify({ session_key: sessionKey }),
53
+ body: JSON.stringify({ ingest_token: ingestToken }),
46
54
  })
47
55
  } catch (err) {
48
56
  return { ok: false, reason: 'unreachable', detail: err && err.message ? err.message : String(err) }
49
57
  }
50
- if (res.status === 401) return { ok: false, reason: 'auth', detail: 'session-rejected' }
58
+ if (res.status === 401) return { ok: false, reason: 'auth', detail: 'token-rejected' }
51
59
  let body = null
52
60
  try { body = await res.json() } catch { /* non-JSON body */ }
53
61
  if (res.status === 400 && body && body.error === 'schema') return { ok: true }
@@ -55,17 +63,17 @@ async function probe(adminHost, sessionKey) {
55
63
  }
56
64
 
57
65
  async function load() {
58
- const stored = await chrome.storage.local.get(['adminHost', 'sessionKey'])
66
+ const stored = await chrome.storage.local.get(['adminHost', 'ingestToken'])
59
67
  if (typeof stored.adminHost === 'string') $host.value = stored.adminHost
60
- if (typeof stored.sessionKey === 'string') $key.value = stored.sessionKey
68
+ if (typeof stored.ingestToken === 'string') $token.value = stored.ingestToken
61
69
  }
62
70
 
63
71
  async function save() {
64
72
  const adminHost = $host.value.trim().replace(/\/+$/, '')
65
- const sessionKey = $key.value.trim()
73
+ const ingestToken = $token.value.trim()
66
74
  if (!adminHost) return setStatus('Admin host is required.', true)
67
75
  if (!/^https?:\/\//.test(adminHost)) return setStatus('Admin host must start with http(s)://.', true)
68
- if (!sessionKey) return setStatus('Session key is required.', true)
76
+ if (!ingestToken) return setStatus('Ingest token is required. Ask your SiteDesk agent for it.', true)
69
77
 
70
78
  let origin
71
79
  try {
@@ -93,10 +101,10 @@ async function save() {
93
101
  )
94
102
  }
95
103
 
96
- const reachable = await probe(adminHost, sessionKey)
97
- await chrome.storage.local.set({ adminHost: adminHost, sessionKey: sessionKey })
104
+ const reachable = await probe(adminHost, ingestToken)
105
+ await chrome.storage.local.set({ adminHost: adminHost, ingestToken: ingestToken })
98
106
  if (reachable.ok) {
99
- return setStatus('Saved. The admin host is reachable and accepted the session key.', false)
107
+ return setStatus('Saved. The admin host is reachable and accepted the ingest token.', false)
100
108
  }
101
109
  setStatus('Saved, but ' + PROBE_COPY[reachable.reason] + ' (' + reachable.detail + ')', true)
102
110
  }
@@ -32,14 +32,14 @@ describe("auditSuspensionCensus", () => {
32
32
  it("emits a line for an account with no routines at all, so silence is never healthy", async () => {
33
33
  await auditSuspensionCensus(driverReturning([]), [ACCT]);
34
34
  expect(censusLines()).toEqual([
35
- "[schedule-audit] op=suspension-census acct=aaaaaaaa routines=0 dispatchable=0 suspended=0 due=0 cancelled=0 completed=0 other=0",
35
+ "[schedule-audit] op=suspension-census acct=aaaaaaaa routines=0 dispatchable=0 suspended=0 due=0 cancelled=0 completed=0 other=0 unattributed=0",
36
36
  ]);
37
37
  });
38
38
  it("reports each status and leaves other at zero when they account for every row", async () => {
39
39
  await auditSuspensionCensus(driverReturning([
40
40
  { accountId: ACCT, routines: 10, dispatchable: 4, suspended: 3, due: 2, cancelled: 1 },
41
41
  ]), [ACCT]);
42
- expect(censusLines()[0]).toBe("[schedule-audit] op=suspension-census acct=aaaaaaaa routines=10 dispatchable=4 suspended=3 due=2 cancelled=1 completed=0 other=0");
42
+ expect(censusLines()[0]).toBe("[schedule-audit] op=suspension-census acct=aaaaaaaa routines=10 dispatchable=4 suspended=3 due=2 cancelled=1 completed=0 other=0 unattributed=0");
43
43
  });
44
44
  // The standing check. dispatchable + suspended + due + cancelled + other =
45
45
  // routines holds by construction, so a non-zero `other` is the only signal
@@ -79,6 +79,49 @@ describe("auditSuspensionCensus", () => {
79
79
  ]), [ACCT]);
80
80
  expect(censusLines()[0]).toContain("completed=1 other=0");
81
81
  });
82
+ // Task 2209 — the detail-modal history panel can only show a reason that was
83
+ // written. A suspend path that appended no note leaves the panel blank with
84
+ // nothing to say why, and no log line exists for a note that was never
85
+ // written, so this count is the only signal. These cases stub the driver, so
86
+ // they prove the field plumbing and the emitted line, not the Cypher
87
+ // predicate; that is the publish-gated device check in Task 2209.
88
+ it("counts a suspended row whose notes never record the suspension", async () => {
89
+ await auditSuspensionCensus(driverReturning([
90
+ { accountId: ACCT, routines: 2, dispatchable: 1, suspended: 1, due: 0, cancelled: 0, completed: 0, unattributed: 1 },
91
+ ]), [ACCT]);
92
+ expect(censusLines()[0]).toContain("other=0 unattributed=1");
93
+ });
94
+ it("reports unattributed at zero rather than omitting the field, so a healthy account and a dead audit do not read alike", async () => {
95
+ await auditSuspensionCensus(driverReturning([
96
+ { accountId: ACCT, routines: 2, dispatchable: 1, suspended: 1, due: 0, cancelled: 0, completed: 0, unattributed: 0 },
97
+ ]), [ACCT]);
98
+ expect(censusLines()[0]).toContain("unattributed=0");
99
+ });
100
+ it("carries unattributed=0 for an account the query returned no row for", async () => {
101
+ await auditSuspensionCensus(driverReturning([]), [ACCT]);
102
+ expect(censusLines()[0]).toContain("unattributed=0");
103
+ });
104
+ // `other` reconciles status buckets against the row count; `unattributed`
105
+ // reconciles a suspended row against whether anything recorded why. Folding
106
+ // one into the other would make a missing note look like an unknown status.
107
+ it("keeps unattributed out of the other arithmetic", async () => {
108
+ await auditSuspensionCensus(driverReturning([
109
+ { accountId: ACCT, routines: 3, dispatchable: 1, suspended: 2, due: 0, cancelled: 0, completed: 0, unattributed: 2 },
110
+ ]), [ACCT]);
111
+ expect(censusLines()[0]).toContain("other=0 unattributed=2");
112
+ });
113
+ it("unwraps a neo4j integer for unattributed too", async () => {
114
+ await auditSuspensionCensus(driverReturning([
115
+ {
116
+ accountId: ACCT,
117
+ routines: { toNumber: () => 1 }, dispatchable: { toNumber: () => 0 },
118
+ suspended: { toNumber: () => 1 }, due: { toNumber: () => 0 },
119
+ cancelled: { toNumber: () => 0 }, completed: { toNumber: () => 0 },
120
+ unattributed: { toNumber: () => 1 },
121
+ },
122
+ ]), [ACCT]);
123
+ expect(censusLines()[0]).toContain("unattributed=1");
124
+ });
82
125
  it("never throws, so a census failure cannot take the sweep down", async () => {
83
126
  const driver = {
84
127
  session: () => ({
@@ -1 +1 @@
1
- {"version":3,"file":"suspension-census.test.js","sourceRoot":"","sources":["../../../src/scripts/__tests__/suspension-census.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"suspension-census.test.js","sourceRoot":"","sources":["../../../src/scripts/__tests__/suspension-census.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-turn-dispatch.d.ts","sourceRoot":"","sources":["../../src/scripts/agent-turn-dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-turn-dispatch.d.ts","sourceRoot":"","sources":["../../src/scripts/agent-turn-dispatch.ts"],"names":[],"mappings":"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"}
@@ -670,7 +670,11 @@ export async function auditSuspensionCensus(driver, localAccountIds) {
670
670
  sum(CASE WHEN e.eventStatus = 'suspended' THEN 1 ELSE 0 END) AS suspended,
671
671
  sum(CASE WHEN e.eventStatus = 'due' THEN 1 ELSE 0 END) AS due,
672
672
  sum(CASE WHEN e.eventStatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
673
- sum(CASE WHEN e.eventStatus = 'completed' THEN 1 ELSE 0 END) AS completed`, { localAccountIds });
673
+ sum(CASE WHEN e.eventStatus = 'completed' THEN 1 ELSE 0 END) AS completed,
674
+ sum(CASE WHEN e.eventStatus = 'suspended'
675
+ AND ( (e.notes IS NULL AND e.routineTemplate IS NULL)
676
+ OR (e.notes IS NOT NULL AND NOT e.notes CONTAINS 'Suspended') )
677
+ THEN 1 ELSE 0 END) AS unattributed`, { localAccountIds });
674
678
  const num = (raw) => typeof raw === "number"
675
679
  ? raw
676
680
  : raw && typeof raw.toNumber === "function"
@@ -685,6 +689,7 @@ export async function auditSuspensionCensus(driver, localAccountIds) {
685
689
  due: num(rec.get("due")),
686
690
  cancelled: num(rec.get("cancelled")),
687
691
  completed: num(rec.get("completed")),
692
+ unattributed: num(rec.get("unattributed")),
688
693
  });
689
694
  }
690
695
  // Driven by localAccountIds, not by the result rows: an account with no
@@ -693,11 +698,19 @@ export async function auditSuspensionCensus(driver, localAccountIds) {
693
698
  for (const accountId of localAccountIds) {
694
699
  const c = byAccount.get(accountId) ?? {
695
700
  routines: 0, dispatchable: 0, suspended: 0, due: 0, cancelled: 0, completed: 0,
701
+ unattributed: 0,
696
702
  };
697
703
  const other = c.routines - c.dispatchable - c.suspended - c.due - c.cancelled - c.completed;
704
+ // Task 2209 — `unattributed` is appended after `other` and deliberately
705
+ // outside its arithmetic: `other` reconciles status buckets against the
706
+ // row count, while this reconciles a suspended row against whether
707
+ // anything recorded why it stopped. The detail modal can only show a
708
+ // reason that was written, and a suspend path that appended no note emits
709
+ // no event of its own, so this count is the only control that reveals it.
698
710
  console.error(`[schedule-audit] op=suspension-census acct=${accountId.slice(0, 8)} ` +
699
711
  `routines=${c.routines} dispatchable=${c.dispatchable} suspended=${c.suspended} ` +
700
- `due=${c.due} cancelled=${c.cancelled} completed=${c.completed} other=${other}`);
712
+ `due=${c.due} cancelled=${c.cancelled} completed=${c.completed} other=${other} ` +
713
+ `unattributed=${c.unattributed}`);
701
714
  }
702
715
  }
703
716
  catch (err) {