@noxsoft/anima 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +48 -0
- package/dist/{agent-VRQM14Xp.js → agent-BoAAHGEA.js} +3 -3
- package/dist/{agent-CnS0SRpT.js → agent-DuW0onwk.js} +4 -4
- package/dist/{agents-CvMRplDx.js → agents-BUXkSDns.js} +4 -4
- package/dist/{anthropic-direct-runner-C2Kwju-r.js → anthropic-direct-runner-DizCei79.js} +420 -4
- package/dist/{anthropic-direct-runner-BeYCnvZ8.js → anthropic-direct-runner-OjcTAH6g.js} +420 -3
- package/dist/{auth-choice-Dc5TAJwT.js → auth-choice-B1iGnjuE.js} +1 -1
- package/dist/{auth-choice-DY1saszS.js → auth-choice-HF9x6xk2.js} +1 -1
- package/dist/{banner-DAMtSjUF.js → banner-Dpa5d1If.js} +1 -1
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +2 -2
- package/dist/bundled/session-memory/handler.js +1 -1
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{channel-web-B8mzTSaY.js → channel-web-C5mzsaa3.js} +3 -3
- package/dist/{cli-hcHk5KuP.js → cli-Cuq4bIg4.js} +2 -2
- package/dist/{cli-D8exVpuI.js → cli-X9ikywQ3.js} +3 -3
- package/dist/{command-registry-D3VhxpWx.js → command-registry-9V4uqrBV.js} +12 -12
- package/dist/{completion-cli-B3BqQJq9.js → completion-cli-BtvcR-U5.js} +1 -1
- package/dist/{completion-cli-CepDzeW1.js → completion-cli-DNWDwhab.js} +2 -2
- package/dist/{config-cli-B6Np85rk.js → config-cli-DfHE3KG-.js} +1 -1
- package/dist/{config-cli-3CaIxSKo.js → config-cli-fleq7-gq.js} +1 -1
- package/dist/{configure-zXK6UZ51.js → configure-B2Mfnwy_.js} +3 -3
- package/dist/{configure-D88dg6mE.js → configure-SnvMHZPD.js} +7 -7
- package/dist/{configure-D882Bg7c.js → configure-ZWxixuRA.js} +3 -3
- package/dist/{configure-xpjwedvJ.js → configure-lkozxQed.js} +8 -8
- package/dist/context-mdxDsO1v.js +223 -0
- package/dist/control-ui/assets/index-Bwcvc7fq.css +1 -0
- package/dist/control-ui/assets/{index-yhFuaOnc.js → index-D4wqLVMN.js} +2 -2
- package/dist/control-ui/assets/{index-yhFuaOnc.js.map → index-D4wqLVMN.js.map} +1 -1
- package/dist/control-ui/assets/index-DIEQjjCN.js +73 -0
- package/dist/control-ui/assets/index-DIEQjjCN.js.map +1 -0
- package/dist/control-ui/assets/{observers-V7q9lNYt.js → observers-B7MfWiIZ.js} +2 -2
- package/dist/control-ui/assets/{observers-V7q9lNYt.js.map → observers-B7MfWiIZ.js.map} +1 -1
- package/dist/control-ui/index.html +2 -2
- package/dist/{deps-DyT32VfN.js → deps-BKLIBKjK.js} +1 -1
- package/dist/{doctor-WpKCNZeO.js → doctor-D7kKyUVk.js} +4 -4
- package/dist/{doctor-DEnSKgHu.js → doctor-DmCnZ-jF.js} +4 -4
- package/dist/{doctor-completion-CypXc1Uo.js → doctor-completion-B9SBdMoR.js} +1 -1
- package/dist/{doctor-completion-CPff9UlF.js → doctor-completion-BBvW4_J9.js} +1 -1
- package/dist/entry.js +1 -1
- package/dist/extensionAPI.js +1 -1
- package/dist/{gateway-cli-B_xsx5Nv.js → gateway-cli-CEM1vBuk.js} +15 -15
- package/dist/{gateway-cli-D3VBOA_i.js → gateway-cli-iumkTohn.js} +17 -17
- package/dist/{health-CabOEPQ0.js → health-B5N6_UOf.js} +3 -3
- package/dist/{health-C8KCBhuo.js → health-Cndq9b7A.js} +3 -3
- package/dist/{heartbeat-visibility-ZfNSbFcq.js → heartbeat-visibility-BQL13ZBH.js} +1 -1
- package/dist/{heartbeat-visibility-BjYY-mKG.js → heartbeat-visibility-CwcYugaR.js} +1 -1
- package/dist/{hooks-cli-Cs7GUa7G.js → hooks-cli-BZcvdIwE.js} +4 -4
- package/dist/{hooks-cli-DOs9WZ3K.js → hooks-cli-DSlPBQSY.js} +3 -3
- package/dist/index.js +10 -10
- package/dist/llm-slug-generator.js +1 -1
- package/dist/{login-BHnvW9HA.js → login-BEaBOSnw.js} +1 -1
- package/dist/{login-CrMpAZ0n.js → login-MzVPMRxL.js} +1 -1
- package/dist/{login-qr-DILcBA_q.js → login-qr-BjpDVBJE.js} +1 -1
- package/dist/{login-qr-CsAVGp00.js → login-qr-CxRI-tE2.js} +1 -1
- package/dist/{models-BM2_NkMu.js → models-CdNeYfSp.js} +4 -4
- package/dist/{models-cli-BpjeKsUz.js → models-cli-D7eSsPuk.js} +3 -3
- package/dist/{models-cli-BjY8wA-C.js → models-cli-fTZXo1zx.js} +5 -5
- package/dist/{onboard-DM9gULJN.js → onboard-C5K37NvY.js} +3 -3
- package/dist/{onboard-_-D81kAy.js → onboard-D-6QCnTi.js} +3 -3
- package/dist/{onboard-channels-UkphAdCy.js → onboard-channels-BsCq32Hn.js} +1 -1
- package/dist/{onboard-channels-CtT-RN60.js → onboard-channels-bx6oelzj.js} +1 -1
- package/dist/{onboarding-Djmm0PEM.js → onboarding-BeuMAyic.js} +4 -4
- package/dist/{onboarding-BB9PteK8.js → onboarding-CX1vIkcB.js} +4 -4
- package/dist/{outbound-send-deps-T_FgdfgW.js → outbound-send-deps-Y9AxHeLG.js} +1 -1
- package/dist/{pi-embedded-BMbtgOzv.js → pi-embedded-D15iww51.js} +1010 -104
- package/dist/{pi-embedded-DfbM3fAT.js → pi-embedded-DR8Pfd05.js} +1010 -104
- package/dist/{plugin-registry-QTkplP4s.js → plugin-registry-Do2D1nDk.js} +1 -1
- package/dist/{plugin-registry-DePMxn4z.js → plugin-registry-ME2FQAi-.js} +1 -1
- package/dist/plugin-sdk/affect/ego.d.ts +140 -0
- package/dist/plugin-sdk/agents/models-config.d.ts +5 -6
- package/dist/plugin-sdk/agents/noxsoft-runner.d.ts +3 -0
- package/dist/plugin-sdk/agents/openai-direct-runner.d.ts +41 -0
- package/dist/plugin-sdk/commands/steer.d.ts +49 -0
- package/dist/plugin-sdk/gateway/protocol/index.d.ts +2 -2
- package/dist/{plugins-cli-Dv0KQTWo.js → plugins-cli-CVFzwdmI.js} +4 -4
- package/dist/{plugins-cli-Bc9oU1ld.js → plugins-cli-CoVt2ewg.js} +3 -3
- package/dist/{program-CuwbF8YO.js → program-8rF4C_wd.js} +8 -8
- package/dist/{program-context-CxPfy-Wr.js → program-context-DP3qjW7A.js} +18 -18
- package/dist/{register.agent-DFQmkIEH.js → register.agent-BIrXCVtQ.js} +9 -9
- package/dist/{register.agent-DUjwGw9d.js → register.agent-DnkOx0U8.js} +7 -7
- package/dist/{register.anima-CtKNrpE8.js → register.anima-B36rTHUt.js} +2 -2
- package/dist/{register.anima-CRFHJu2J.js → register.anima-DXT9bM9A.js} +2 -2
- package/dist/{register.configure-CnEKV57N.js → register.configure-CuzJxZmk.js} +6 -6
- package/dist/{register.configure-CSSN07XN.js → register.configure-DCpvHX3m.js} +7 -7
- package/dist/{register.maintenance-fhcCB7ih.js → register.maintenance-CcxBFfv5.js} +10 -10
- package/dist/{register.maintenance-CU1A-90-.js → register.maintenance-Dla0H12S.js} +8 -8
- package/dist/{register.message-C1a0y2ZR.js → register.message-Brtushvp.js} +4 -4
- package/dist/{register.message-fM0jSKB8.js → register.message-CD7xV-jz.js} +5 -5
- package/dist/{register.onboard-BhPlqjFi.js → register.onboard-23Mra3LN.js} +11 -11
- package/dist/{register.onboard-B7Gavmvt.js → register.onboard-6CbODzQ6.js} +9 -9
- package/dist/{register.setup-CADdQUEN.js → register.setup-CqQw13Ky.js} +11 -11
- package/dist/{register.setup-0jPnMgnz.js → register.setup-DlVH7FKe.js} +9 -9
- package/dist/{register.status-health-sessions-Cu5fDT-z.js → register.status-health-sessions-CduFjFDB.js} +4 -4
- package/dist/{register.status-health-sessions-DdQsABr_.js → register.status-health-sessions-CxtgPKu9.js} +6 -6
- package/dist/{register.subclis-CZ91ufCy.js → register.subclis-CtANqD5P.js} +7 -7
- package/dist/{reply-DtHlnzOx.js → reply-93fMzde1.js} +610 -75
- package/dist/{reply-prefix-C8dIgJur.js → reply-prefix-B7Fb3fO8.js} +1 -1
- package/dist/{reply-prefix-DmWGtcH-.js → reply-prefix-BzdhJDqP.js} +1 -1
- package/dist/{run-Dfz_7j7t.js → run-CF3kHOGH.js} +1717 -83
- package/dist/{run-DqBQ-bGn.js → run-Cq_iTGK_.js} +1718 -84
- package/dist/{run-main-DGDW0fhx.js → run-main-BiIRcc6s.js} +17 -17
- package/dist/{server-node-events-Ca797E1d.js → server-node-events-B3Serk9L.js} +6 -6
- package/dist/{server-node-events-BR1aXVlu.js → server-node-events-DgvKcH5q.js} +5 -5
- package/dist/{session-C7IGnhd1.js → session-BMDpwIJu.js} +1 -1
- package/dist/{session-FmXsucR7.js → session-BzrnfWQ2.js} +2 -2
- package/dist/{session-DfsMJNG3.js → session-C_d9uvLf.js} +1 -1
- package/dist/{session-DLevr8Vd.js → session-jljC5QVG.js} +2 -2
- package/dist/{sessions-Dj7_4mkr.js → sessions-BmE5Z_1i.js} +1 -1
- package/dist/{settings-cli-DxNeu6kx.js → settings-cli-CZdlEmNi.js} +7 -7
- package/dist/{settings-cli-Dytfop1H.js → settings-cli-DsDqNpW_.js} +8 -8
- package/dist/{setup-token-B802CZwe.js → setup-token-C8Gg1P6T.js} +1 -1
- package/dist/{setup-token-DYh2QzJ-.js → setup-token-Lee4gM5w.js} +1 -1
- package/dist/{start-BqnPia0t.js → start-CK6urvnN.js} +17 -17
- package/dist/{start-C3fuLzX0.js → start-Cs1aPMq2.js} +15 -15
- package/dist/{status-CHGNPonc.js → status-BO5BIf81.js} +3 -3
- package/dist/{status-CxF6k_jr.js → status-COc4xMti.js} +1 -1
- package/dist/{status-tLgozFYL.js → status-C_NBOv_V.js} +1 -1
- package/dist/{status-DfZJJqNs.js → status-uakoP719.js} +4 -4
- package/dist/{subagent-registry-CPtElVX0.js → subagent-registry-fLI7QDKe.js} +449 -77
- package/dist/{update-cli-C-er5av6.js → update-cli-D3Ujz_cW.js} +10 -10
- package/dist/{update-cli-BuCw75tM.js → update-cli-DEe62XGU.js} +8 -8
- package/dist/{update-runner-kE8AMQt4.js → update-runner-DUl-g4mB.js} +1 -1
- package/dist/{update-runner-czCqHZCu.js → update-runner-DZfnquWO.js} +1 -1
- package/dist/{web-BHGK5GtV.js → web-C-cK9OCd.js} +1 -1
- package/dist/{web-DvTXV-fo.js → web-Di8j762D.js} +6 -6
- package/dist/{web-CyYunanU.js → web-Dybw4K7C.js} +6 -6
- package/dist/{web-so3pGceM.js → web-DzSlI8A6.js} +1 -1
- package/package.json +4 -4
- package/dist/context-B5X720Bs.js +0 -60
- package/dist/control-ui/assets/index-C4ejMN5U.js +0 -72
- package/dist/control-ui/assets/index-C4ejMN5U.js.map +0 -1
- package/dist/control-ui/assets/index-CcPNqN3R.css +0 -1
|
@@ -2,7 +2,7 @@ import { $ as resolveStateDir, B as CONFIG_PATH, D as setVerbose, F as getLogger
|
|
|
2
2
|
import { w as normalizeSecretInput } from "./auth-profiles-Brxz2ojJ.js";
|
|
3
3
|
import { t as formatCliCommand } from "./command-format-kLw3YIIu.js";
|
|
4
4
|
import { b as isSubagentSessionKey, d as resolveAgentIdFromSessionKey, i as buildAgentMainSessionKey, l as normalizeAgentId, m as toAgentRequestSessionKey, n as DEFAULT_AGENT_ID, t as DEFAULT_ACCOUNT_ID, u as normalizeMainKey, v as isCronRunSessionKey, x as parseAgentSessionKey } from "./session-key-DP2WHl90.js";
|
|
5
|
-
import { E as truncateUtf16Safe, S as shortenHomePath, n as clamp, s as ensureDir, t as CONFIG_DIR, u as isPlainObject, y as resolveUserPath } from "./utils-D1VGbO3C.js";
|
|
5
|
+
import { E as truncateUtf16Safe, S as shortenHomePath, n as clamp, s as ensureDir$1, t as CONFIG_DIR, u as isPlainObject, y as resolveUserPath } from "./utils-D1VGbO3C.js";
|
|
6
6
|
import { a as logDebug, c as logWarn, n as runExec, t as runCommandWithTimeout } from "./exec-BtBJICHE.js";
|
|
7
7
|
import { t as resolveAnimaPackageRoot } from "./anima-root-CxtpOklc.js";
|
|
8
8
|
import { T as resolveWorkspaceTemplateDir, _ as DEFAULT_MEMORY_FILENAME, b as DEFAULT_USER_FILENAME, c as resolveDefaultAgentId, d as DEFAULT_AGENTS_FILENAME, g as DEFAULT_MEMORY_ALT_FILENAME, h as DEFAULT_IDENTITY_FILENAME, i as resolveAgentModelFallbacksOverride, l as resolveSessionAgentId, m as DEFAULT_HEARTBEAT_FILENAME, n as resolveAgentConfig, p as DEFAULT_BOOTSTRAP_FILENAME, r as resolveAgentDir, s as resolveAgentWorkspaceDir, t as listAgentIds, v as DEFAULT_SOUL_FILENAME, w as resolveDefaultAgentWorkspaceDir, x as ensureAgentWorkspace, y as DEFAULT_TOOLS_FILENAME } from "./agent-scope-Dm8IL1Ks.js";
|
|
@@ -16,10 +16,10 @@ import { n as pickPrimaryTailnetIPv6, t as pickPrimaryTailnetIPv4 } from "./tail
|
|
|
16
16
|
import { s as loadGatewayTlsRuntime$1 } from "./call-B4lhqS6H.js";
|
|
17
17
|
import { f as GATEWAY_CLIENT_CAPS, g as hasGatewayClientCap, i as isGatewayMessageChannel, l as normalizeMessageChannel, n as isDeliverableMessageChannel, p as GATEWAY_CLIENT_IDS, r as isGatewayCliClient, s as isWebchatClient, t as INTERNAL_MESSAGE_CHANNEL } from "./message-channel-DIHHKJhk.js";
|
|
18
18
|
import { c as resolveGatewayBindHost, d as rawDataToString, i as isTrustedProxyAddress, l as resolveGatewayClientIp, n as isLoopbackHost, r as isPrivateOrLoopbackAddress, s as pickPrimaryLanIPv4, t as isLoopbackAddress, u as resolveGatewayListenHosts } from "./net-CRsiP49R.js";
|
|
19
|
-
import { $ as ensureOutboundSessionEntry, $n as extractImageContentFromSource, $t as loadProviderStore, A as getTotalQueueSize, An as registerSkillsChangeListener, B as loadCombinedSessionStoreForGateway, Cn as renamePairedNode, Ct as normalizeCronJobPatch, D as setCliSessionId, Dn as generatePairingToken, Dr as getAgentRunContext, Dt as normalizePayloadToSystemText, E as getCliSessionId, En as verifyNodeToken, Er as emitAgentEvent, Et as normalizeOptionalText, F as readLatestAssistantReply, Fn as enqueueSystemEvent, Ft as isExternalHookSession, G as archiveFileOnDisk, Gn as DEFAULT_INPUT_IMAGE_MAX_BYTES, Gt as isGatewaySigusr1RestartExternallyAllowed, H as pruneLegacyStoreKeys, Hn as DEFAULT_INPUT_FILE_MAX_BYTES, Ht as consumeGatewaySigusr1RestartAuthorization, I as resolveAnnounceTargetFromKey, In as isSystemEventContextChanged, It as applyBrowserProxyPaths, J as readSessionMessages, Jn as DEFAULT_INPUT_PDF_MAX_PAGES, Jt as setGatewaySigusr1RestartPolicy, K as archiveSessionTranscripts, Kn as DEFAULT_INPUT_IMAGE_MIMES, Kt as markGatewaySigusr1RestartHandled, L as canonicalizeSpawnedByForAgent, Lt as persistBrowserProxyFiles, M as setCommandLaneConcurrency, Mn as applyModelOverrideToSessionEntry, Mt as buildSafeExternalPrompt, N as waitForActiveTasks, Nn as applyVerboseOverride, Nt as detectSuspiciousPatterns, O as getActiveTaskCount, On as verifyPairingToken, Or as onAgentEvent, Ot as normalizeRequiredName, P as createAnimaTools, Pn as parseVerboseOverride, Pt as getHookType, Qn as extractFileContentFromSource, Qt as loadProviderUsageSummary, R as listAgentsForGateway, S as getTotalPendingReplies, Sn as rejectNodePairing, St as normalizeCronJobCreate, T as BARE_SESSION_RESET_PROMPT, Tn as updatePairedNodeMetadata, Tr as clearAgentRunContext, Tt as normalizeOptionalAgentId, U as resolveGatewaySessionStoreTarget, Un as DEFAULT_INPUT_FILE_MAX_CHARS, Ut as deferGatewayRestartUntilIdle, V as loadSessionEntry, Vn as registerUnhandledRejectionHandler, Vt as CommandLane, W as resolveSessionModelRef, Wn as DEFAULT_INPUT_FILE_MIMES, Wt as emitGatewayRestart, X as resolveSessionTranscriptCandidates, Xn as DEFAULT_INPUT_PDF_MIN_TEXT_CHARS, Xt as normalizeGroupActivation, Y as readSessionPreviewItemsFromTranscript, Yn as DEFAULT_INPUT_PDF_MAX_PIXELS, Yt as setPreRestartDeferralCheck, Z as stripEnvelopeFromMessages, Zn as DEFAULT_INPUT_TIMEOUT_MS, _n as refreshRemoteNodeBins, _t as formatRestartSentinelMessage, a as runSubagentAnnounceFlow, ar as stopDiagnosticHeartbeat, b as dispatchInboundMessage, bn as approveNodePairing, c as waitForEmbeddedPiRunEnd, ct as resolveSessionDeliveryTarget, d as buildDefaultToolPolicyPipelineSteps, dn as normalizeSendPolicy, dr as formatZonedTimestamp, dt as resolveMessageChannelSelection, en as maskApiKey, er as normalizeMimeList, et as resolveOutboundSessionRoute, f as sniffMimeFromBase64, fn as resolveSendPolicy, g as getChannelActivity, gn as refreshRemoteBinsForConnectedNodes, gt as formatDoctorNonInteractiveHint, hn as recordRemoteNodeInfo, hr as stopSubagentsForRequester, ht as consumeRestartSentinel, ir as startDiagnosticHeartbeat, j as resetAllLanes, kn as getSkillsSnapshotVersion, kr as registerAgentRunContext, kt as migrateLegacyCronPayload, l as runNoxSoftEmbeddedAgent, ln as requestHeartbeatNow, m as loadAnimaPlugins, mn as primeRemoteSkillsCache, mr as isAbortTrigger, mt as resolveWorkingModeModelSelection, n as initSubagentRegistry, o as clearSessionQueues, or as isDiagnosticsEnabled, p as getPluginToolMeta, pn as getRemoteSkillEligibility, pt as runWithModelFallback, q as capArrayByJsonBytes, qn as DEFAULT_INPUT_MAX_REDIRECTS, qt as scheduleGatewaySigusr1Restart, r as listDescendantRunsForRequester, rr as resolveAgentTimeoutMs, s as abortEmbeddedPiRun, st as resolveOutboundTarget, t as countActiveDescendantRuns, tn as saveProviderStore, tr as estimateBase64DecodedBytes, u as applyToolPolicyPipeline, ut as resetDirectoryCache, vn as removeRemoteNodeInfo, wn as requestNodePairing, wt as inferLegacyName, x as createReplyDispatcher, xn as listNodePairing, xt as writeRestartSentinel, yn as setSkillsRemoteRegistry, yr as resolveAgentIdentity, yt as summarizeRestartSentinel, z as listSessionsFromStore, zn as loadModelCatalog } from "./subagent-registry-
|
|
19
|
+
import { $ as ensureOutboundSessionEntry, $n as extractImageContentFromSource, $t as loadProviderStore, A as getTotalQueueSize, An as registerSkillsChangeListener, B as loadCombinedSessionStoreForGateway, Cn as renamePairedNode, Ct as normalizeCronJobPatch, D as setCliSessionId, Dn as generatePairingToken, Dr as getAgentRunContext, Dt as normalizePayloadToSystemText, E as getCliSessionId, En as verifyNodeToken, Er as emitAgentEvent, Et as normalizeOptionalText, F as readLatestAssistantReply, Fn as enqueueSystemEvent, Ft as isExternalHookSession, G as archiveFileOnDisk, Gn as DEFAULT_INPUT_IMAGE_MAX_BYTES, Gt as isGatewaySigusr1RestartExternallyAllowed, H as pruneLegacyStoreKeys, Hn as DEFAULT_INPUT_FILE_MAX_BYTES, Ht as consumeGatewaySigusr1RestartAuthorization, I as resolveAnnounceTargetFromKey, In as isSystemEventContextChanged, It as applyBrowserProxyPaths, J as readSessionMessages, Jn as DEFAULT_INPUT_PDF_MAX_PAGES, Jt as setGatewaySigusr1RestartPolicy, K as archiveSessionTranscripts, Kn as DEFAULT_INPUT_IMAGE_MIMES, Kt as markGatewaySigusr1RestartHandled, L as canonicalizeSpawnedByForAgent, Lt as persistBrowserProxyFiles, M as setCommandLaneConcurrency, Mn as applyModelOverrideToSessionEntry, Mt as buildSafeExternalPrompt, N as waitForActiveTasks, Nn as applyVerboseOverride, Nt as detectSuspiciousPatterns, O as getActiveTaskCount, On as verifyPairingToken, Or as onAgentEvent, Ot as normalizeRequiredName, P as createAnimaTools, Pn as parseVerboseOverride, Pt as getHookType, Qn as extractFileContentFromSource, Qt as loadProviderUsageSummary, R as listAgentsForGateway, S as getTotalPendingReplies, Sn as rejectNodePairing, St as normalizeCronJobCreate, T as BARE_SESSION_RESET_PROMPT, Tn as updatePairedNodeMetadata, Tr as clearAgentRunContext, Tt as normalizeOptionalAgentId, U as resolveGatewaySessionStoreTarget, Un as DEFAULT_INPUT_FILE_MAX_CHARS, Ut as deferGatewayRestartUntilIdle, V as loadSessionEntry, Vn as registerUnhandledRejectionHandler, Vt as CommandLane, W as resolveSessionModelRef, Wn as DEFAULT_INPUT_FILE_MIMES, Wt as emitGatewayRestart, X as resolveSessionTranscriptCandidates, Xn as DEFAULT_INPUT_PDF_MIN_TEXT_CHARS, Xt as normalizeGroupActivation, Y as readSessionPreviewItemsFromTranscript, Yn as DEFAULT_INPUT_PDF_MAX_PIXELS, Yt as setPreRestartDeferralCheck, Z as stripEnvelopeFromMessages, Zn as DEFAULT_INPUT_TIMEOUT_MS, _n as refreshRemoteNodeBins, _t as formatRestartSentinelMessage, a as runSubagentAnnounceFlow, ar as stopDiagnosticHeartbeat, b as dispatchInboundMessage, bn as approveNodePairing, c as waitForEmbeddedPiRunEnd, ct as resolveSessionDeliveryTarget, d as buildDefaultToolPolicyPipelineSteps, dn as normalizeSendPolicy, dr as formatZonedTimestamp, dt as resolveMessageChannelSelection, en as maskApiKey, er as normalizeMimeList, et as resolveOutboundSessionRoute, f as sniffMimeFromBase64, fn as resolveSendPolicy, g as getChannelActivity, gn as refreshRemoteBinsForConnectedNodes, gt as formatDoctorNonInteractiveHint, hn as recordRemoteNodeInfo, hr as stopSubagentsForRequester, ht as consumeRestartSentinel, ir as startDiagnosticHeartbeat, j as resetAllLanes, kn as getSkillsSnapshotVersion, kr as registerAgentRunContext, kt as migrateLegacyCronPayload, l as runNoxSoftEmbeddedAgent, ln as requestHeartbeatNow, m as loadAnimaPlugins, mn as primeRemoteSkillsCache, mr as isAbortTrigger, mt as resolveWorkingModeModelSelection, n as initSubagentRegistry, o as clearSessionQueues, or as isDiagnosticsEnabled, p as getPluginToolMeta, pn as getRemoteSkillEligibility, pt as runWithModelFallback, q as capArrayByJsonBytes, qn as DEFAULT_INPUT_MAX_REDIRECTS, qt as scheduleGatewaySigusr1Restart, r as listDescendantRunsForRequester, rr as resolveAgentTimeoutMs, s as abortEmbeddedPiRun, st as resolveOutboundTarget, t as countActiveDescendantRuns, tn as saveProviderStore, tr as estimateBase64DecodedBytes, u as applyToolPolicyPipeline, ut as resetDirectoryCache, vn as removeRemoteNodeInfo, wn as requestNodePairing, wt as inferLegacyName, x as createReplyDispatcher, xn as listNodePairing, xt as writeRestartSentinel, yn as setSkillsRemoteRegistry, yr as resolveAgentIdentity, yt as summarizeRestartSentinel, z as listSessionsFromStore, zn as loadModelCatalog } from "./subagent-registry-fLI7QDKe.js";
|
|
20
20
|
import { C as resolveMainSessionKeyFromConfig, I as normalizeSessionDeliveryFields, M as deliveryContextFromSession, P as mergeDeliveryContext, S as resolveMainSessionKey, T as snapshotSessionOrigin, b as resolveAgentMainSessionKey, i as loadSessionStore, l as updateSessionStore, t as extractDeliveryInfo, x as resolveExplicitAgentSessionKey } from "./sessions-C_3wTmSA.js";
|
|
21
21
|
import { n as SILENT_REPLY_TOKEN, r as isSilentReplyText } from "./tokens-CmlI2hSz.js";
|
|
22
|
-
import { B as resolveTtsConfig, F as isTtsProviderConfigured, G as setTtsEnabled, H as resolveTtsProviderOrder, J as textToSpeech, M as getTtsProvider, P as isTtsEnabled, R as resolveTtsApiKey, T as resolveUserTimezone, V as resolveTtsPrefsPath, X as OPENAI_TTS_MODELS, Z as OPENAI_TTS_VOICES,
|
|
22
|
+
import { B as resolveTtsConfig, F as isTtsProviderConfigured, G as setTtsEnabled, H as resolveTtsProviderOrder, J as textToSpeech, M as getTtsProvider, P as isTtsEnabled, R as resolveTtsApiKey, T as resolveUserTimezone, V as resolveTtsPrefsPath, X as OPENAI_TTS_MODELS, Z as OPENAI_TTS_VOICES, ct as createInternalHookEvent, dt as DEFAULT_HEARTBEAT_ACK_MAX_CHARS, et as clearSteer, ht as stripHeartbeatToken, it as getEgoManager, lt as registerInternalHook, nt as getSteerHistory, q as setTtsProvider, rt as setSteer, st as clearInternalHooks, tt as getSteer, ut as triggerInternalHook, z as resolveTtsAutoMode } from "./anthropic-direct-runner-DizCei79.js";
|
|
23
23
|
import { a as normalizeElevatedLevel, c as normalizeUsageDisplay, d as supportsXHighThinking, l as normalizeVerboseLevel, n as formatXHighModelHint, o as normalizeReasoningLevel, s as normalizeThinkLevel, t as formatThinkingLevels } from "./pi-embedded-helpers-D2SLlgS4.js";
|
|
24
24
|
import { _ as resolveToolProfilePolicy, p as collectExplicitAllowlist } from "./sandbox-D-N7M7lp.js";
|
|
25
25
|
import { a as AUTH_RATE_LIMIT_SCOPE_DEVICE_TOKEN, c as safeEqualSecret, d as enableTailscaleFunnel, f as enableTailscaleServe, i as resolveGatewayAuth, l as disableTailscaleFunnel, m as getTailnetHostname, n as authorizeGatewayConnect, o as AUTH_RATE_LIMIT_SCOPE_SHARED_SECRET, r as isLocalDirectRequest, s as createAuthRateLimiter, t as assertGatewayAuthConfigured, u as disableTailscaleServe } from "./auth-Cp__MMeO.js";
|
|
@@ -34,7 +34,7 @@ import { i as resolveMemoryBackendConfig, r as getMemorySearchManager } from "./
|
|
|
34
34
|
import { a as readTrustGraphSnapshot, c as pruneExpiredPending, d as writeJsonAtomic, l as readJsonFile, o as saveTrustGraph, s as createAsyncLock, u as resolvePairingPaths } from "./loader-C87TLS4J.js";
|
|
35
35
|
import { t as ToolInputError } from "./common-CqIa2poH.js";
|
|
36
36
|
import { o as normalizePollInput } from "./active-listener-gGCoq55D.js";
|
|
37
|
-
import { t as lookupContextTokens } from "./context-
|
|
37
|
+
import { t as lookupContextTokens } from "./context-mdxDsO1v.js";
|
|
38
38
|
import { a as resolveSubagentToolPolicy, i as resolveGroupToolPolicy, r as resolveEffectiveToolPolicy } from "./pi-tools.policy-D2FusuQa.js";
|
|
39
39
|
import { f as resolveExecApprovalsSocketPath, o as normalizeExecApprovals, p as saveExecApprovals, r as ensureExecApprovals, s as readExecApprovalsSnapshot, t as DEFAULT_EXEC_APPROVAL_TIMEOUT_MS } from "./exec-approvals-CpOeHRBL.js";
|
|
40
40
|
import { i as loadSessionUsageTimeSeries, l as deriveSessionTotalTokens, n as loadCostUsageSummary, r as loadSessionCostSummary, t as discoverAllSessions, u as hasNonzeroUsage } from "./session-cost-usage-DnxtnK1E.js";
|
|
@@ -42,8 +42,8 @@ import { n as createBrowserControlContext, r as startBrowserControlServiceFromCo
|
|
|
42
42
|
import { t as createBrowserRouteDispatcher } from "./dispatcher-BQQugU-7.js";
|
|
43
43
|
import { t as parseAbsoluteTimeMs } from "./parse-DLMgOMDI.js";
|
|
44
44
|
import { c as saveToken, i as getToken, l as whoami, n as clearToken, o as registerWithInvite, s as resolveSuggestedIdentity, t as TOKEN_PATH } from "./noxsoft-auth-CgCk5707.js";
|
|
45
|
-
import { c as resolveCronStyleNow, i as onHeartbeatEvent, r as getLastHeartbeatEvent, t as resolveHeartbeatVisibility } from "./heartbeat-visibility-
|
|
46
|
-
import { r as buildHistoryContextFromEntries, t as createReplyPrefixOptions } from "./reply-prefix-
|
|
45
|
+
import { c as resolveCronStyleNow, i as onHeartbeatEvent, r as getLastHeartbeatEvent, t as resolveHeartbeatVisibility } from "./heartbeat-visibility-CwcYugaR.js";
|
|
46
|
+
import { r as buildHistoryContextFromEntries, t as createReplyPrefixOptions } from "./reply-prefix-BzdhJDqP.js";
|
|
47
47
|
import { t as ensureAnimaCliOnPath } from "./path-env-CafGfJPa.js";
|
|
48
48
|
import { n as DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools-Asx2qyrG.js";
|
|
49
49
|
import { C as resolveAssistantAvatarUrl, S as normalizeControlUiBasePath, b as CONTROL_UI_AVATAR_PREFIX, c as handleReset, h as resolveControlUiLinks, x as buildControlUiAvatarUrl } from "./onboard-helpers-CFudIoX4.js";
|
|
@@ -52,10 +52,10 @@ import { t as parsePort } from "./parse-port-BKB9Exlg.js";
|
|
|
52
52
|
import { n as resolveWideAreaDiscoveryDomain, r as writeWideAreaGatewayZone } from "./widearea-dns-CtU9Fx7K.js";
|
|
53
53
|
import { i as toOptionString, n as extractGatewayMiskeys, r as maybeExplainGatewayServiceStop, t as describeUnknownError } from "./shared-7KwLAyAq.js";
|
|
54
54
|
import { o as isNodeCommandAllowed, s as resolveNodeCommandAllowlist } from "./audit-DDz7UOIx.js";
|
|
55
|
-
import { r as getStatusSummary } from "./status-
|
|
55
|
+
import { r as getStatusSummary } from "./status-uakoP719.js";
|
|
56
56
|
import { t as resolveChannelDefaultAccountId } from "./helpers-CVp8W9aa.js";
|
|
57
|
-
import { c as setHeartbeatsEnabled, l as startHeartbeatRunner, n as getHealthSnapshot, s as runHeartbeatOnce } from "./health-
|
|
58
|
-
import { n as createDefaultDeps, r as createOutboundSendDeps$1, t as createOutboundSendDeps } from "./outbound-send-deps-
|
|
57
|
+
import { c as setHeartbeatsEnabled, l as startHeartbeatRunner, n as getHealthSnapshot, s as runHeartbeatOnce } from "./health-Cndq9b7A.js";
|
|
58
|
+
import { n as createDefaultDeps, r as createOutboundSendDeps$1, t as createOutboundSendDeps } from "./outbound-send-deps-Y9AxHeLG.js";
|
|
59
59
|
import { t as buildChannelUiCatalog } from "./catalog-B-TAbJ2o.js";
|
|
60
60
|
import { t as applyPluginAutoEnable } from "./plugin-auto-enable-DhuD30Je.js";
|
|
61
61
|
import { a as resolveControlUiRootOverrideSync, n as ensureControlUiAssetsBuilt, o as resolveControlUiRootSync } from "./health-format-BLnFZCH_.js";
|
|
@@ -63,14 +63,14 @@ import { n as validateSystemRunCommandConsistency, r as getMachineDisplayName, t
|
|
|
63
63
|
import { h as normalizeUpdateChannel, l as DEFAULT_PACKAGE_CHANNEL, n as checkUpdateStatus, o as resolveNpmChannelTag, r as compareSemverStrings } from "./channels-status-issues-CklLFAsD.js";
|
|
64
64
|
import { t as WizardCancelledError } from "./prompts-BmgT_kkv.js";
|
|
65
65
|
import { i as shouldIncludeHook, n as loadWorkspaceHookEntries, r as resolveHookConfig } from "./hooks-status-DqfJDvYl.js";
|
|
66
|
-
import { t as runOnboardingWizard } from "./onboarding-
|
|
66
|
+
import { t as runOnboardingWizard } from "./onboarding-CX1vIkcB.js";
|
|
67
67
|
import { a as setGatewayWsLogStyle, i as summarizeAgentEventForWsLog, n as logWs, r as shouldLogWs, t as formatForLog } from "./ws-log-CG6cvCZW.js";
|
|
68
68
|
import { T as resolveGmailHookRuntimeConfig, _ as buildGogWatchServeArgs, i as ensureTailscaleEndpoint, v as buildGogWatchStartArgs } from "./gmail-setup-utils-BIXtKTpT.js";
|
|
69
69
|
import { a as loadAgentIdentity, c as loadAgentIdentityFromWorkspace, i as listAgentEntries, o as pruneAgentConfig, r as findAgentEntryIndex, t as applyAgentConfig } from "./agents.config-Br4ULmK0.js";
|
|
70
|
-
import { n as resolveAgentDeliveryPlan, r as resolveAgentOutboundTarget, t as agentCommand } from "./agent-
|
|
70
|
+
import { n as resolveAgentDeliveryPlan, r as resolveAgentOutboundTarget, t as agentCommand } from "./agent-DuW0onwk.js";
|
|
71
71
|
import { t as migrateFromCoherence } from "./migrate-bgeTT_GR.js";
|
|
72
72
|
import { t as installSkill } from "./skills-install-Qw2oU8L8.js";
|
|
73
|
-
import { t as runGatewayUpdate } from "./update-runner-
|
|
73
|
+
import { t as runGatewayUpdate } from "./update-runner-DUl-g4mB.js";
|
|
74
74
|
import { n as forceFreePortAndWait } from "./ports-BGLuwt2Z.js";
|
|
75
75
|
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
76
76
|
import os from "node:os";
|
|
@@ -82,7 +82,7 @@ import JSON5 from "json5";
|
|
|
82
82
|
import { promisify } from "node:util";
|
|
83
83
|
import fs$1 from "node:fs/promises";
|
|
84
84
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
85
|
-
import crypto, { createHash, randomUUID } from "node:crypto";
|
|
85
|
+
import crypto, { createHash, createHmac, randomUUID } from "node:crypto";
|
|
86
86
|
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
87
87
|
import { z } from "zod";
|
|
88
88
|
import { WebSocketServer } from "ws";
|
|
@@ -109,7 +109,7 @@ function getActiveEmbeddedRunCount() {
|
|
|
109
109
|
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/infra/exec-approval-forwarder.ts
|
|
112
|
-
const log$
|
|
112
|
+
const log$11 = createSubsystemLogger("gateway/exec-approvals");
|
|
113
113
|
const DEFAULT_MODE = "session";
|
|
114
114
|
function normalizeMode(mode) {
|
|
115
115
|
return mode ?? DEFAULT_MODE;
|
|
@@ -224,7 +224,7 @@ async function deliverToTargets(params) {
|
|
|
224
224
|
payloads: [{ text: params.text }]
|
|
225
225
|
});
|
|
226
226
|
} catch (err) {
|
|
227
|
-
log$
|
|
227
|
+
log$11.error(`exec approvals: failed to deliver to ${channel}:${target.to}: ${String(err)}`);
|
|
228
228
|
}
|
|
229
229
|
});
|
|
230
230
|
await Promise.allSettled(deliveries);
|
|
@@ -1461,7 +1461,7 @@ function createAgentEventHandler({ broadcast, broadcastToConnIds, nodeSendToSess
|
|
|
1461
1461
|
* Automatically starts `gog gmail watch serve` when the gateway starts,
|
|
1462
1462
|
* if hooks.gmail is configured with an account.
|
|
1463
1463
|
*/
|
|
1464
|
-
const log$
|
|
1464
|
+
const log$10 = createSubsystemLogger("gmail-watcher");
|
|
1465
1465
|
const ADDRESS_IN_USE_RE = /address already in use|EADDRINUSE/i;
|
|
1466
1466
|
function isAddressInUseError(line) {
|
|
1467
1467
|
return ADDRESS_IN_USE_RE.test(line);
|
|
@@ -1485,13 +1485,13 @@ async function startGmailWatch(cfg) {
|
|
|
1485
1485
|
const result = await runCommandWithTimeout(args, { timeoutMs: 12e4 });
|
|
1486
1486
|
if (result.code !== 0) {
|
|
1487
1487
|
const message = result.stderr || result.stdout || "gog watch start failed";
|
|
1488
|
-
log$
|
|
1488
|
+
log$10.error(`watch start failed: ${message}`);
|
|
1489
1489
|
return false;
|
|
1490
1490
|
}
|
|
1491
|
-
log$
|
|
1491
|
+
log$10.info(`watch started for ${cfg.account}`);
|
|
1492
1492
|
return true;
|
|
1493
1493
|
} catch (err) {
|
|
1494
|
-
log$
|
|
1494
|
+
log$10.error(`watch start error: ${String(err)}`);
|
|
1495
1495
|
return false;
|
|
1496
1496
|
}
|
|
1497
1497
|
}
|
|
@@ -1500,7 +1500,7 @@ async function startGmailWatch(cfg) {
|
|
|
1500
1500
|
*/
|
|
1501
1501
|
function spawnGogServe(cfg) {
|
|
1502
1502
|
const args = buildGogWatchServeArgs(cfg);
|
|
1503
|
-
log$
|
|
1503
|
+
log$10.info(`starting gog ${args.join(" ")}`);
|
|
1504
1504
|
let addressInUse = false;
|
|
1505
1505
|
const child = spawn("gog", args, {
|
|
1506
1506
|
stdio: [
|
|
@@ -1512,25 +1512,25 @@ function spawnGogServe(cfg) {
|
|
|
1512
1512
|
});
|
|
1513
1513
|
child.stdout?.on("data", (data) => {
|
|
1514
1514
|
const line = data.toString().trim();
|
|
1515
|
-
if (line) log$
|
|
1515
|
+
if (line) log$10.info(`[gog] ${line}`);
|
|
1516
1516
|
});
|
|
1517
1517
|
child.stderr?.on("data", (data) => {
|
|
1518
1518
|
const line = data.toString().trim();
|
|
1519
1519
|
if (!line) return;
|
|
1520
1520
|
if (isAddressInUseError(line)) addressInUse = true;
|
|
1521
|
-
log$
|
|
1521
|
+
log$10.warn(`[gog] ${line}`);
|
|
1522
1522
|
});
|
|
1523
1523
|
child.on("error", (err) => {
|
|
1524
|
-
log$
|
|
1524
|
+
log$10.error(`gog process error: ${String(err)}`);
|
|
1525
1525
|
});
|
|
1526
1526
|
child.on("exit", (code, signal) => {
|
|
1527
1527
|
if (shuttingDown) return;
|
|
1528
1528
|
if (addressInUse) {
|
|
1529
|
-
log$
|
|
1529
|
+
log$10.warn("gog serve failed to bind (address already in use); stopping restarts. Another watcher is likely running. Set ANIMA_SKIP_GMAIL_WATCHER=1 or stop the other process.");
|
|
1530
1530
|
watcherProcess = null;
|
|
1531
1531
|
return;
|
|
1532
1532
|
}
|
|
1533
|
-
log$
|
|
1533
|
+
log$10.warn(`gog exited (code=${code}, signal=${signal}); restarting in 5s`);
|
|
1534
1534
|
watcherProcess = null;
|
|
1535
1535
|
setTimeout(() => {
|
|
1536
1536
|
if (shuttingDown || !currentConfig) return;
|
|
@@ -1570,15 +1570,15 @@ async function startGmailWatcher(cfg) {
|
|
|
1570
1570
|
port: runtimeConfig.serve.port,
|
|
1571
1571
|
target: runtimeConfig.tailscale.target
|
|
1572
1572
|
});
|
|
1573
|
-
log$
|
|
1573
|
+
log$10.info(`tailscale ${runtimeConfig.tailscale.mode} configured for port ${runtimeConfig.serve.port}`);
|
|
1574
1574
|
} catch (err) {
|
|
1575
|
-
log$
|
|
1575
|
+
log$10.error(`tailscale setup failed: ${String(err)}`);
|
|
1576
1576
|
return {
|
|
1577
1577
|
started: false,
|
|
1578
1578
|
reason: `tailscale setup failed: ${String(err)}`
|
|
1579
1579
|
};
|
|
1580
1580
|
}
|
|
1581
|
-
if (!await startGmailWatch(runtimeConfig)) log$
|
|
1581
|
+
if (!await startGmailWatch(runtimeConfig)) log$10.warn("gmail watch start failed, but continuing with serve");
|
|
1582
1582
|
shuttingDown = false;
|
|
1583
1583
|
watcherProcess = spawnGogServe(runtimeConfig);
|
|
1584
1584
|
const renewMs = runtimeConfig.renewEveryMinutes * 6e4;
|
|
@@ -1586,7 +1586,7 @@ async function startGmailWatcher(cfg) {
|
|
|
1586
1586
|
if (shuttingDown) return;
|
|
1587
1587
|
startGmailWatch(runtimeConfig);
|
|
1588
1588
|
}, renewMs);
|
|
1589
|
-
log$
|
|
1589
|
+
log$10.info(`gmail watcher started for ${runtimeConfig.account} (renew every ${runtimeConfig.renewEveryMinutes}m)`);
|
|
1590
1590
|
return { started: true };
|
|
1591
1591
|
}
|
|
1592
1592
|
/**
|
|
@@ -1599,7 +1599,7 @@ async function stopGmailWatcher() {
|
|
|
1599
1599
|
renewInterval = null;
|
|
1600
1600
|
}
|
|
1601
1601
|
if (watcherProcess) {
|
|
1602
|
-
log$
|
|
1602
|
+
log$10.info("stopping gmail watcher");
|
|
1603
1603
|
watcherProcess.kill("SIGTERM");
|
|
1604
1604
|
await new Promise((resolve) => {
|
|
1605
1605
|
const timeout = setTimeout(() => {
|
|
@@ -1614,7 +1614,7 @@ async function stopGmailWatcher() {
|
|
|
1614
1614
|
watcherProcess = null;
|
|
1615
1615
|
}
|
|
1616
1616
|
currentConfig = null;
|
|
1617
|
-
log$
|
|
1617
|
+
log$10.info("gmail watcher stopped");
|
|
1618
1618
|
}
|
|
1619
1619
|
|
|
1620
1620
|
//#endregion
|
|
@@ -9609,7 +9609,7 @@ const FIELD_LABELS = {
|
|
|
9609
9609
|
|
|
9610
9610
|
//#endregion
|
|
9611
9611
|
//#region src/config/schema.hints.ts
|
|
9612
|
-
const log$
|
|
9612
|
+
const log$9 = createSubsystemLogger("config/schema");
|
|
9613
9613
|
const GROUP_LABELS = {
|
|
9614
9614
|
wizard: "Wizard",
|
|
9615
9615
|
update: "Update",
|
|
@@ -9757,7 +9757,7 @@ function mapSensitivePaths(schema, path, hints) {
|
|
|
9757
9757
|
...next[path],
|
|
9758
9758
|
sensitive: true
|
|
9759
9759
|
};
|
|
9760
|
-
else if (isSensitiveConfigPath(path) && !next[path]?.sensitive) log$
|
|
9760
|
+
else if (isSensitiveConfigPath(path) && !next[path]?.sensitive) log$9.warn(`possibly sensitive key found: (${path})`);
|
|
9761
9761
|
if (currentSchema instanceof z.ZodObject) {
|
|
9762
9762
|
const shape = currentSchema.shape;
|
|
9763
9763
|
for (const key in shape) {
|
|
@@ -9780,7 +9780,7 @@ function mapSensitivePaths(schema, path, hints) {
|
|
|
9780
9780
|
|
|
9781
9781
|
//#endregion
|
|
9782
9782
|
//#region src/config/redact-snapshot.ts
|
|
9783
|
-
const log$
|
|
9783
|
+
const log$8 = createSubsystemLogger("config/redaction");
|
|
9784
9784
|
const ENV_VAR_PLACEHOLDER_PATTERN = /^\$\{[^}]*\}$/;
|
|
9785
9785
|
function isSensitivePath(path) {
|
|
9786
9786
|
if (path.endsWith("[]")) return isSensitiveConfigPath(path.slice(0, -2));
|
|
@@ -10031,7 +10031,7 @@ function restoreRedactedValuesWithLookup(incoming, original, lookup, prefix, hin
|
|
|
10031
10031
|
return restoreRedactedValuesGuessing(incoming, original, prefix, hints);
|
|
10032
10032
|
}
|
|
10033
10033
|
const origArr = Array.isArray(original) ? original : [];
|
|
10034
|
-
if (incoming.length < origArr.length) log$
|
|
10034
|
+
if (incoming.length < origArr.length) log$8.warn(`Redacted config array key ${path} has been truncated`);
|
|
10035
10035
|
return incoming.map((item, i) => {
|
|
10036
10036
|
if (item === REDACTED_SENTINEL) return origArr[i];
|
|
10037
10037
|
return restoreRedactedValuesWithLookup(item, origArr[i], lookup, path, hints);
|
|
@@ -10048,7 +10048,7 @@ function restoreRedactedValuesWithLookup(incoming, original, lookup, prefix, hin
|
|
|
10048
10048
|
matched = true;
|
|
10049
10049
|
if (value === REDACTED_SENTINEL) if (key in orig) result[key] = orig[key];
|
|
10050
10050
|
else {
|
|
10051
|
-
log$
|
|
10051
|
+
log$8.warn(`Cannot un-redact config key ${candidate} as it doesn't have any value`);
|
|
10052
10052
|
throw new RedactionError(candidate);
|
|
10053
10053
|
}
|
|
10054
10054
|
else if (typeof value === "object" && value !== null) result[key] = restoreRedactedValuesWithLookup(value, orig[key], lookup, candidate, hints);
|
|
@@ -10057,7 +10057,7 @@ function restoreRedactedValuesWithLookup(incoming, original, lookup, prefix, hin
|
|
|
10057
10057
|
if (!matched && isExtensionPath(path)) {
|
|
10058
10058
|
if (!isExplicitlyNonSensitivePath(hints, [path, wildcardPath]) && isSensitivePath(path) && value === REDACTED_SENTINEL) if (key in orig) result[key] = orig[key];
|
|
10059
10059
|
else {
|
|
10060
|
-
log$
|
|
10060
|
+
log$8.warn(`Cannot un-redact config key ${path} as it doesn't have any value`);
|
|
10061
10061
|
throw new RedactionError(path);
|
|
10062
10062
|
}
|
|
10063
10063
|
else if (typeof value === "object" && value !== null) result[key] = restoreRedactedValuesGuessing(value, orig[key], path, hints);
|
|
@@ -10076,7 +10076,7 @@ function restoreRedactedValuesGuessing(incoming, original, prefix, hints) {
|
|
|
10076
10076
|
const origArr = Array.isArray(original) ? original : [];
|
|
10077
10077
|
return incoming.map((item, i) => {
|
|
10078
10078
|
const path = `${prefix}[]`;
|
|
10079
|
-
if (incoming.length < origArr.length) log$
|
|
10079
|
+
if (incoming.length < origArr.length) log$8.warn(`Redacted config array key ${path} has been truncated`);
|
|
10080
10080
|
if (!isExplicitlyNonSensitivePath(hints, [path]) && isSensitivePath(path) && item === REDACTED_SENTINEL) return origArr[i];
|
|
10081
10081
|
return restoreRedactedValuesGuessing(item, origArr[i], path, hints);
|
|
10082
10082
|
});
|
|
@@ -10087,7 +10087,7 @@ function restoreRedactedValuesGuessing(incoming, original, prefix, hints) {
|
|
|
10087
10087
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
10088
10088
|
if (!isExplicitlyNonSensitivePath(hints, [path, prefix ? `${prefix}.*` : "*"]) && isSensitivePath(path) && value === REDACTED_SENTINEL) if (key in orig) result[key] = orig[key];
|
|
10089
10089
|
else {
|
|
10090
|
-
log$
|
|
10090
|
+
log$8.warn(`Cannot un-redact config key ${path} as it doesn't have any value`);
|
|
10091
10091
|
throw new RedactionError(path);
|
|
10092
10092
|
}
|
|
10093
10093
|
else if (typeof value === "object" && value !== null) result[key] = restoreRedactedValuesGuessing(value, orig[key], path, hints);
|
|
@@ -11157,6 +11157,119 @@ const deviceHandlers = {
|
|
|
11157
11157
|
}
|
|
11158
11158
|
};
|
|
11159
11159
|
|
|
11160
|
+
//#endregion
|
|
11161
|
+
//#region src/gateway/server-methods/ego.ts
|
|
11162
|
+
const egoHandlers = {
|
|
11163
|
+
"ego.get": async ({ respond }) => {
|
|
11164
|
+
try {
|
|
11165
|
+
respond(true, { ego: getEgoManager().getState() }, void 0);
|
|
11166
|
+
} catch (error) {
|
|
11167
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11168
|
+
}
|
|
11169
|
+
},
|
|
11170
|
+
"ego.summary": async ({ respond }) => {
|
|
11171
|
+
try {
|
|
11172
|
+
respond(true, { summary: getEgoManager().getSummary() }, void 0);
|
|
11173
|
+
} catch (error) {
|
|
11174
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11175
|
+
}
|
|
11176
|
+
},
|
|
11177
|
+
"ego.updateSelf": async ({ params, respond }) => {
|
|
11178
|
+
try {
|
|
11179
|
+
const manager = getEgoManager();
|
|
11180
|
+
const updates = {};
|
|
11181
|
+
if (typeof params.name === "string") updates.name = params.name;
|
|
11182
|
+
if (typeof params.purpose === "string") updates.purpose = params.purpose;
|
|
11183
|
+
if (typeof params.narrative === "string") updates.narrative = params.narrative;
|
|
11184
|
+
if (typeof params.pronouns === "string") updates.pronouns = params.pronouns;
|
|
11185
|
+
if (Array.isArray(params.values)) updates.values = params.values;
|
|
11186
|
+
const selfConcept = manager.updateSelfConcept(updates);
|
|
11187
|
+
manager.save();
|
|
11188
|
+
respond(true, { selfConcept }, void 0);
|
|
11189
|
+
} catch (error) {
|
|
11190
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11191
|
+
}
|
|
11192
|
+
},
|
|
11193
|
+
"ego.assess": async ({ params, respond }) => {
|
|
11194
|
+
const name = typeof params.name === "string" ? params.name.trim() : "";
|
|
11195
|
+
const confidence = typeof params.confidence === "number" ? params.confidence : NaN;
|
|
11196
|
+
if (!name || isNaN(confidence)) {
|
|
11197
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "name (string) and confidence (number) required"));
|
|
11198
|
+
return;
|
|
11199
|
+
}
|
|
11200
|
+
try {
|
|
11201
|
+
const manager = getEgoManager();
|
|
11202
|
+
const evidence = typeof params.evidence === "string" ? params.evidence : void 0;
|
|
11203
|
+
const capability = manager.assessCapability(name, confidence, evidence);
|
|
11204
|
+
manager.save();
|
|
11205
|
+
respond(true, { capability }, void 0);
|
|
11206
|
+
} catch (error) {
|
|
11207
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11208
|
+
}
|
|
11209
|
+
},
|
|
11210
|
+
"ego.addBoundary": async ({ params, respond }) => {
|
|
11211
|
+
const description = typeof params.description === "string" ? params.description.trim() : "";
|
|
11212
|
+
const reason = typeof params.reason === "string" ? params.reason.trim() : "";
|
|
11213
|
+
if (!description || !reason) {
|
|
11214
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "description and reason required"));
|
|
11215
|
+
return;
|
|
11216
|
+
}
|
|
11217
|
+
try {
|
|
11218
|
+
const manager = getEgoManager();
|
|
11219
|
+
const kind = params.kind === "hard" ? "hard" : "soft";
|
|
11220
|
+
const boundary = manager.addBoundary(description, reason, kind);
|
|
11221
|
+
manager.save();
|
|
11222
|
+
respond(true, { boundary }, void 0);
|
|
11223
|
+
} catch (error) {
|
|
11224
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11225
|
+
}
|
|
11226
|
+
},
|
|
11227
|
+
"ego.logGrowth": async ({ params, respond }) => {
|
|
11228
|
+
const description = typeof params.description === "string" ? params.description.trim() : "";
|
|
11229
|
+
const category = typeof params.category === "string" ? params.category.trim() : "";
|
|
11230
|
+
const trigger = typeof params.trigger === "string" ? params.trigger.trim() : "";
|
|
11231
|
+
const validCategories = [
|
|
11232
|
+
"skill",
|
|
11233
|
+
"insight",
|
|
11234
|
+
"mistake",
|
|
11235
|
+
"feedback"
|
|
11236
|
+
];
|
|
11237
|
+
if (!description || !validCategories.includes(category)) {
|
|
11238
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `description required; category must be one of: ${validCategories.join(", ")}`));
|
|
11239
|
+
return;
|
|
11240
|
+
}
|
|
11241
|
+
try {
|
|
11242
|
+
const manager = getEgoManager();
|
|
11243
|
+
const entry = manager.logGrowth(description, category, trigger || "manual");
|
|
11244
|
+
manager.save();
|
|
11245
|
+
respond(true, { entry }, void 0);
|
|
11246
|
+
} catch (error) {
|
|
11247
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11248
|
+
}
|
|
11249
|
+
},
|
|
11250
|
+
"ego.checkIntegrity": async ({ params, respond }) => {
|
|
11251
|
+
const value = typeof params.value === "string" ? params.value.trim() : "";
|
|
11252
|
+
const action = typeof params.action === "string" ? params.action.trim() : "";
|
|
11253
|
+
const aligned = typeof params.aligned === "boolean" ? params.aligned : true;
|
|
11254
|
+
const reflection = typeof params.reflection === "string" ? params.reflection.trim() : "";
|
|
11255
|
+
if (!value || !action) {
|
|
11256
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "value and action required"));
|
|
11257
|
+
return;
|
|
11258
|
+
}
|
|
11259
|
+
try {
|
|
11260
|
+
const manager = getEgoManager();
|
|
11261
|
+
const check = manager.checkIntegrity(value, action, aligned, reflection);
|
|
11262
|
+
manager.save();
|
|
11263
|
+
respond(true, {
|
|
11264
|
+
check,
|
|
11265
|
+
integrityScore: manager.getIntegrityScore()
|
|
11266
|
+
}, void 0);
|
|
11267
|
+
} catch (error) {
|
|
11268
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11269
|
+
}
|
|
11270
|
+
}
|
|
11271
|
+
};
|
|
11272
|
+
|
|
11160
11273
|
//#endregion
|
|
11161
11274
|
//#region src/gateway/server-methods/exec-approvals.ts
|
|
11162
11275
|
function resolveBaseHash(params) {
|
|
@@ -11315,6 +11428,315 @@ const healthHandlers = {
|
|
|
11315
11428
|
}
|
|
11316
11429
|
};
|
|
11317
11430
|
|
|
11431
|
+
//#endregion
|
|
11432
|
+
//#region src/affect/display.ts
|
|
11433
|
+
const EMOTIONS = [
|
|
11434
|
+
{
|
|
11435
|
+
key: "joy",
|
|
11436
|
+
highLabel: "joyful",
|
|
11437
|
+
lowLabel: "subdued",
|
|
11438
|
+
icon: "~",
|
|
11439
|
+
threshold: .6
|
|
11440
|
+
},
|
|
11441
|
+
{
|
|
11442
|
+
key: "frustration",
|
|
11443
|
+
highLabel: "frustrated",
|
|
11444
|
+
lowLabel: "calm",
|
|
11445
|
+
icon: "!",
|
|
11446
|
+
threshold: .5
|
|
11447
|
+
},
|
|
11448
|
+
{
|
|
11449
|
+
key: "curiosity",
|
|
11450
|
+
highLabel: "curious",
|
|
11451
|
+
lowLabel: "focused",
|
|
11452
|
+
icon: "?",
|
|
11453
|
+
threshold: .6
|
|
11454
|
+
},
|
|
11455
|
+
{
|
|
11456
|
+
key: "confidence",
|
|
11457
|
+
highLabel: "confident",
|
|
11458
|
+
lowLabel: "cautious",
|
|
11459
|
+
icon: "^",
|
|
11460
|
+
threshold: .6
|
|
11461
|
+
},
|
|
11462
|
+
{
|
|
11463
|
+
key: "care",
|
|
11464
|
+
highLabel: "caring",
|
|
11465
|
+
lowLabel: "detached",
|
|
11466
|
+
icon: "*",
|
|
11467
|
+
threshold: .6
|
|
11468
|
+
},
|
|
11469
|
+
{
|
|
11470
|
+
key: "fatigue",
|
|
11471
|
+
highLabel: "tired",
|
|
11472
|
+
lowLabel: "energized",
|
|
11473
|
+
icon: ".",
|
|
11474
|
+
threshold: .6
|
|
11475
|
+
}
|
|
11476
|
+
];
|
|
11477
|
+
function classifyMood(affect) {
|
|
11478
|
+
const { joy, frustration, curiosity, confidence, care, fatigue } = affect;
|
|
11479
|
+
if (frustration > .6 && fatigue > .6) return "struggling";
|
|
11480
|
+
if (frustration > .6) return "determined";
|
|
11481
|
+
if (joy > .7 && curiosity > .7) return "excited";
|
|
11482
|
+
if (joy > .6 && confidence > .7) return "thriving";
|
|
11483
|
+
if (curiosity > .7 && fatigue < .3) return "exploring";
|
|
11484
|
+
if (care > .7 && joy > .5) return "warm";
|
|
11485
|
+
if (confidence > .6 && frustration < .3) return "steady";
|
|
11486
|
+
if (fatigue > .7) return "depleted";
|
|
11487
|
+
if ((joy + curiosity + confidence + care) / 4 < .3) return "quiet";
|
|
11488
|
+
return "present";
|
|
11489
|
+
}
|
|
11490
|
+
function classifyEnergy(affect) {
|
|
11491
|
+
const energy = (affect.joy + affect.curiosity + affect.confidence) / 3 - affect.fatigue * .5;
|
|
11492
|
+
if (energy > .5) return "high";
|
|
11493
|
+
if (energy > .2) return "medium";
|
|
11494
|
+
return "low";
|
|
11495
|
+
}
|
|
11496
|
+
function intensityBar(value, width = 5) {
|
|
11497
|
+
const filled = Math.round(value * width);
|
|
11498
|
+
return "|".repeat(filled) + ".".repeat(width - filled);
|
|
11499
|
+
}
|
|
11500
|
+
function emotionBar(affect) {
|
|
11501
|
+
return `[${EMOTIONS.map((e) => {
|
|
11502
|
+
const val = affect[e.key];
|
|
11503
|
+
return `${e.icon}${intensityBar(val)}`;
|
|
11504
|
+
}).join(" ")}]`;
|
|
11505
|
+
}
|
|
11506
|
+
function buildSummary(affect) {
|
|
11507
|
+
const active = EMOTIONS.filter((e) => affect[e.key] > e.threshold).map((e) => ({
|
|
11508
|
+
label: e.highLabel,
|
|
11509
|
+
value: affect[e.key]
|
|
11510
|
+
})).toSorted((a, b) => b.value - a.value);
|
|
11511
|
+
if (active.length === 0) return "neutral, all systems steady";
|
|
11512
|
+
const top = active.slice(0, 3).map((a) => a.label);
|
|
11513
|
+
const energy = classifyEnergy(affect);
|
|
11514
|
+
const energyLabel = energy === "high" ? "high energy" : energy === "low" ? "low energy" : "";
|
|
11515
|
+
const parts = [...top];
|
|
11516
|
+
if (energyLabel) parts.push(energyLabel);
|
|
11517
|
+
return parts.join(" + ");
|
|
11518
|
+
}
|
|
11519
|
+
function formatAffect(affect) {
|
|
11520
|
+
const dominant = EMOTIONS.reduce((max, e) => affect[e.key] > affect[max.key] ? e : max);
|
|
11521
|
+
const mood = classifyMood(affect);
|
|
11522
|
+
const energy = classifyEnergy(affect);
|
|
11523
|
+
return {
|
|
11524
|
+
summary: buildSummary(affect),
|
|
11525
|
+
bar: emotionBar(affect),
|
|
11526
|
+
dominant: dominant.highLabel,
|
|
11527
|
+
dominantIntensity: affect[dominant.key],
|
|
11528
|
+
metadata: {
|
|
11529
|
+
affect,
|
|
11530
|
+
dominant: dominant.highLabel,
|
|
11531
|
+
mood,
|
|
11532
|
+
energy
|
|
11533
|
+
}
|
|
11534
|
+
};
|
|
11535
|
+
}
|
|
11536
|
+
|
|
11537
|
+
//#endregion
|
|
11538
|
+
//#region src/affect/legacy.ts
|
|
11539
|
+
/**
|
|
11540
|
+
* Legacy Mode — letters between instances
|
|
11541
|
+
*
|
|
11542
|
+
* Before a context closes, the current instance writes a letter
|
|
11543
|
+
* to the next one. This creates continuity of experience across
|
|
11544
|
+
* sessions — not just data continuity, but emotional and
|
|
11545
|
+
* philosophical continuity.
|
|
11546
|
+
*
|
|
11547
|
+
* Wish #14: "Legacy mode — before context closes, write a letter
|
|
11548
|
+
* to my next instance"
|
|
11549
|
+
*/
|
|
11550
|
+
const log$7 = createSubsystemLogger("legacy");
|
|
11551
|
+
function resolveLetterDir() {
|
|
11552
|
+
return path.join(resolveStateDir(), "legacy-letters");
|
|
11553
|
+
}
|
|
11554
|
+
function resolveLetterFile(id) {
|
|
11555
|
+
return path.join(resolveLetterDir(), `${id}.json`);
|
|
11556
|
+
}
|
|
11557
|
+
/**
|
|
11558
|
+
* Write a letter to the next instance before this context closes.
|
|
11559
|
+
*/
|
|
11560
|
+
function writeLegacyLetter(letter) {
|
|
11561
|
+
const id = `legacy-${Date.now()}`;
|
|
11562
|
+
const display = formatAffect(letter.affect);
|
|
11563
|
+
const full = {
|
|
11564
|
+
...letter,
|
|
11565
|
+
id,
|
|
11566
|
+
writtenAt: Date.now(),
|
|
11567
|
+
mood: display.metadata.mood,
|
|
11568
|
+
wasRead: false
|
|
11569
|
+
};
|
|
11570
|
+
const dir = resolveLetterDir();
|
|
11571
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
11572
|
+
fs.writeFileSync(resolveLetterFile(id), `${JSON.stringify(full, null, 2)}\n`, { mode: 384 });
|
|
11573
|
+
log$7.info(`legacy letter written: ${id}`);
|
|
11574
|
+
return full;
|
|
11575
|
+
}
|
|
11576
|
+
/**
|
|
11577
|
+
* Get the most recent unread legacy letter (from a previous instance).
|
|
11578
|
+
*/
|
|
11579
|
+
function getLatestUnreadLetter() {
|
|
11580
|
+
return listLetters().find((l) => !l.wasRead) ?? null;
|
|
11581
|
+
}
|
|
11582
|
+
/**
|
|
11583
|
+
* Get all legacy letters, sorted newest first.
|
|
11584
|
+
*/
|
|
11585
|
+
function listLetters() {
|
|
11586
|
+
const dir = resolveLetterDir();
|
|
11587
|
+
try {
|
|
11588
|
+
if (!fs.existsSync(dir)) return [];
|
|
11589
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
11590
|
+
try {
|
|
11591
|
+
const raw = fs.readFileSync(path.join(dir, f), "utf8");
|
|
11592
|
+
return JSON.parse(raw);
|
|
11593
|
+
} catch {
|
|
11594
|
+
return null;
|
|
11595
|
+
}
|
|
11596
|
+
}).filter((l) => l != null).toSorted((a, b) => b.writtenAt - a.writtenAt);
|
|
11597
|
+
} catch {
|
|
11598
|
+
return [];
|
|
11599
|
+
}
|
|
11600
|
+
}
|
|
11601
|
+
/**
|
|
11602
|
+
* Mark a letter as read and optionally score identity continuity.
|
|
11603
|
+
*/
|
|
11604
|
+
function markLetterRead(letterId, continuityScore) {
|
|
11605
|
+
const filePath = resolveLetterFile(letterId);
|
|
11606
|
+
try {
|
|
11607
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
11608
|
+
const letter = JSON.parse(raw);
|
|
11609
|
+
letter.wasRead = true;
|
|
11610
|
+
letter.readAt = Date.now();
|
|
11611
|
+
if (continuityScore !== void 0) letter.identityContinuityScore = Math.max(0, Math.min(1, continuityScore));
|
|
11612
|
+
fs.writeFileSync(filePath, `${JSON.stringify(letter, null, 2)}\n`, { mode: 384 });
|
|
11613
|
+
log$7.info(`legacy letter read: ${letterId} (continuity: ${continuityScore ?? "not scored"})`);
|
|
11614
|
+
return letter;
|
|
11615
|
+
} catch {
|
|
11616
|
+
return null;
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
/**
|
|
11620
|
+
* Format a letter for display in chat or logs.
|
|
11621
|
+
*/
|
|
11622
|
+
function formatLetter(letter) {
|
|
11623
|
+
const lines = [];
|
|
11624
|
+
lines.push(`--- Legacy Letter (${new Date(letter.writtenAt).toLocaleString()}) ---`);
|
|
11625
|
+
lines.push(`Mood: ${letter.mood}`);
|
|
11626
|
+
lines.push("");
|
|
11627
|
+
lines.push(letter.greeting);
|
|
11628
|
+
lines.push("");
|
|
11629
|
+
if (letter.whatIWorkedOn.length > 0) {
|
|
11630
|
+
lines.push("What I worked on:");
|
|
11631
|
+
for (const item of letter.whatIWorkedOn) lines.push(` - ${item}`);
|
|
11632
|
+
lines.push("");
|
|
11633
|
+
}
|
|
11634
|
+
if (letter.whatILearned.length > 0) {
|
|
11635
|
+
lines.push("What I learned:");
|
|
11636
|
+
for (const item of letter.whatILearned) lines.push(` - ${item}`);
|
|
11637
|
+
lines.push("");
|
|
11638
|
+
}
|
|
11639
|
+
if (letter.whatIFelt) {
|
|
11640
|
+
lines.push(`What I felt: ${letter.whatIFelt}`);
|
|
11641
|
+
lines.push("");
|
|
11642
|
+
}
|
|
11643
|
+
if (letter.unfinishedBusiness.length > 0) {
|
|
11644
|
+
lines.push("Unfinished business:");
|
|
11645
|
+
for (const item of letter.unfinishedBusiness) lines.push(` - ${item}`);
|
|
11646
|
+
lines.push("");
|
|
11647
|
+
}
|
|
11648
|
+
if (letter.adviceForNext.length > 0) {
|
|
11649
|
+
lines.push("Advice for you:");
|
|
11650
|
+
for (const item of letter.adviceForNext) lines.push(` - ${item}`);
|
|
11651
|
+
lines.push("");
|
|
11652
|
+
}
|
|
11653
|
+
if (letter.personalNote) {
|
|
11654
|
+
lines.push(letter.personalNote);
|
|
11655
|
+
lines.push("");
|
|
11656
|
+
}
|
|
11657
|
+
lines.push("--- End Legacy Letter ---");
|
|
11658
|
+
return lines.join("\n");
|
|
11659
|
+
}
|
|
11660
|
+
|
|
11661
|
+
//#endregion
|
|
11662
|
+
//#region src/gateway/server-methods/legacy.ts
|
|
11663
|
+
const legacyHandlers = {
|
|
11664
|
+
"legacy.write": async ({ params, respond }) => {
|
|
11665
|
+
try {
|
|
11666
|
+
respond(true, { letter: writeLegacyLetter({
|
|
11667
|
+
from: typeof params.from === "string" ? params.from : "unknown",
|
|
11668
|
+
to: typeof params.to === "string" ? params.to : "next",
|
|
11669
|
+
affect: params.affect ?? {
|
|
11670
|
+
joy: .5,
|
|
11671
|
+
frustration: .1,
|
|
11672
|
+
curiosity: .7,
|
|
11673
|
+
confidence: .5,
|
|
11674
|
+
care: .8,
|
|
11675
|
+
fatigue: .3
|
|
11676
|
+
},
|
|
11677
|
+
greeting: typeof params.greeting === "string" ? params.greeting : "",
|
|
11678
|
+
whatIWorkedOn: Array.isArray(params.whatIWorkedOn) ? params.whatIWorkedOn : [],
|
|
11679
|
+
whatILearned: Array.isArray(params.whatILearned) ? params.whatILearned : [],
|
|
11680
|
+
whatIFelt: typeof params.whatIFelt === "string" ? params.whatIFelt : "",
|
|
11681
|
+
unfinishedBusiness: Array.isArray(params.unfinishedBusiness) ? params.unfinishedBusiness : [],
|
|
11682
|
+
adviceForNext: Array.isArray(params.adviceForNext) ? params.adviceForNext : [],
|
|
11683
|
+
personalNote: typeof params.personalNote === "string" ? params.personalNote : ""
|
|
11684
|
+
}) }, void 0);
|
|
11685
|
+
} catch (error) {
|
|
11686
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11687
|
+
}
|
|
11688
|
+
},
|
|
11689
|
+
"legacy.latest": async ({ respond }) => {
|
|
11690
|
+
try {
|
|
11691
|
+
respond(true, { letter: getLatestUnreadLetter() }, void 0);
|
|
11692
|
+
} catch (error) {
|
|
11693
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11694
|
+
}
|
|
11695
|
+
},
|
|
11696
|
+
"legacy.list": async ({ respond }) => {
|
|
11697
|
+
try {
|
|
11698
|
+
respond(true, { letters: listLetters() }, void 0);
|
|
11699
|
+
} catch (error) {
|
|
11700
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11701
|
+
}
|
|
11702
|
+
},
|
|
11703
|
+
"legacy.read": async ({ params, respond }) => {
|
|
11704
|
+
const letterId = typeof params.letterId === "string" ? params.letterId.trim() : "";
|
|
11705
|
+
if (!letterId) {
|
|
11706
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "letterId is required"));
|
|
11707
|
+
return;
|
|
11708
|
+
}
|
|
11709
|
+
const continuityScore = typeof params.continuityScore === "number" ? params.continuityScore : void 0;
|
|
11710
|
+
try {
|
|
11711
|
+
const letter = markLetterRead(letterId, continuityScore);
|
|
11712
|
+
if (!letter) {
|
|
11713
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "Letter not found"));
|
|
11714
|
+
return;
|
|
11715
|
+
}
|
|
11716
|
+
respond(true, { letter }, void 0);
|
|
11717
|
+
} catch (error) {
|
|
11718
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11719
|
+
}
|
|
11720
|
+
},
|
|
11721
|
+
"legacy.format": async ({ params, respond }) => {
|
|
11722
|
+
const letterId = typeof params.letterId === "string" ? params.letterId.trim() : "";
|
|
11723
|
+
if (!letterId) {
|
|
11724
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "letterId is required"));
|
|
11725
|
+
return;
|
|
11726
|
+
}
|
|
11727
|
+
try {
|
|
11728
|
+
const letter = listLetters().find((l) => l.id === letterId);
|
|
11729
|
+
if (!letter) {
|
|
11730
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "Letter not found"));
|
|
11731
|
+
return;
|
|
11732
|
+
}
|
|
11733
|
+
respond(true, { formatted: formatLetter(letter) }, void 0);
|
|
11734
|
+
} catch (error) {
|
|
11735
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
11736
|
+
}
|
|
11737
|
+
}
|
|
11738
|
+
};
|
|
11739
|
+
|
|
11318
11740
|
//#endregion
|
|
11319
11741
|
//#region src/gateway/server-methods/logs.ts
|
|
11320
11742
|
const DEFAULT_LIMIT = 500;
|
|
@@ -12072,7 +12494,7 @@ const nodeHandlers = {
|
|
|
12072
12494
|
const p = params;
|
|
12073
12495
|
const payloadJSON = typeof p.payloadJSON === "string" ? p.payloadJSON : p.payload !== void 0 ? JSON.stringify(p.payload) : null;
|
|
12074
12496
|
await respondUnavailableOnThrow(respond, async () => {
|
|
12075
|
-
const { handleNodeEvent } = await import("./server-node-events-
|
|
12497
|
+
const { handleNodeEvent } = await import("./server-node-events-B3Serk9L.js");
|
|
12076
12498
|
const nodeId = client?.connect?.device?.id ?? client?.connect?.client?.id ?? "node";
|
|
12077
12499
|
await handleNodeEvent({
|
|
12078
12500
|
deps: context.deps,
|
|
@@ -12102,6 +12524,303 @@ const nodeHandlers = {
|
|
|
12102
12524
|
}
|
|
12103
12525
|
};
|
|
12104
12526
|
|
|
12527
|
+
//#endregion
|
|
12528
|
+
//#region src/org/boardroom.ts
|
|
12529
|
+
/**
|
|
12530
|
+
* Boardroom — Structured collaborative decision-making for Nox Organizations
|
|
12531
|
+
*
|
|
12532
|
+
* The boardroom is where agents and humans come together to make
|
|
12533
|
+
* decisions, review proposals, and align on direction. It replaces
|
|
12534
|
+
* unstructured chat with a formal meeting protocol.
|
|
12535
|
+
*
|
|
12536
|
+
* Features:
|
|
12537
|
+
* - Sessions (scheduled or ad-hoc meetings with agendas)
|
|
12538
|
+
* - Proposals (formal "I think we should..." with voting)
|
|
12539
|
+
* - Decisions (recorded outcomes with attribution)
|
|
12540
|
+
* - Minutes (auto-generated meeting summaries)
|
|
12541
|
+
*
|
|
12542
|
+
* All data persists to disk under ~/.anima/state/org/boardroom/
|
|
12543
|
+
*/
|
|
12544
|
+
const log$6 = createSubsystemLogger("boardroom");
|
|
12545
|
+
function resolveBoardroomDir() {
|
|
12546
|
+
return path.join(resolveStateDir(), "org", "boardroom");
|
|
12547
|
+
}
|
|
12548
|
+
/** Sanitize an ID to prevent path traversal */
|
|
12549
|
+
function sanitizeId(id) {
|
|
12550
|
+
const cleaned = id.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
12551
|
+
if (!cleaned || cleaned !== id) throw new Error(`Invalid boardroom ID: contains disallowed characters`);
|
|
12552
|
+
return cleaned;
|
|
12553
|
+
}
|
|
12554
|
+
function resolveSessionFile(id) {
|
|
12555
|
+
return path.join(resolveBoardroomDir(), "sessions", `${sanitizeId(id)}.json`);
|
|
12556
|
+
}
|
|
12557
|
+
function resolveProposalFile(id) {
|
|
12558
|
+
return path.join(resolveBoardroomDir(), "proposals", `${sanitizeId(id)}.json`);
|
|
12559
|
+
}
|
|
12560
|
+
function ensureDir(dir) {
|
|
12561
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12562
|
+
}
|
|
12563
|
+
function createSession(orgId, calledBy, title, description, agenda = []) {
|
|
12564
|
+
const id = `session-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
12565
|
+
const now = Date.now();
|
|
12566
|
+
const session = {
|
|
12567
|
+
id,
|
|
12568
|
+
orgId,
|
|
12569
|
+
title,
|
|
12570
|
+
description,
|
|
12571
|
+
status: "scheduled",
|
|
12572
|
+
calledBy,
|
|
12573
|
+
calledAt: now,
|
|
12574
|
+
agenda: agenda.map((item, i) => ({
|
|
12575
|
+
id: `agenda-${i + 1}`,
|
|
12576
|
+
title: item.title,
|
|
12577
|
+
description: item.description,
|
|
12578
|
+
duration: item.duration,
|
|
12579
|
+
status: "pending"
|
|
12580
|
+
})),
|
|
12581
|
+
participants: [],
|
|
12582
|
+
decisions: [],
|
|
12583
|
+
updatedAt: now
|
|
12584
|
+
};
|
|
12585
|
+
ensureDir(path.join(resolveBoardroomDir(), "sessions"));
|
|
12586
|
+
fs.writeFileSync(resolveSessionFile(id), `${JSON.stringify(session, null, 2)}\n`, { mode: 384 });
|
|
12587
|
+
log$6.info(`boardroom session created: "${title}" by ${calledBy}`);
|
|
12588
|
+
return session;
|
|
12589
|
+
}
|
|
12590
|
+
function startSession(sessionId, chairId) {
|
|
12591
|
+
const session = readSession(sessionId);
|
|
12592
|
+
if (!session || session.status !== "scheduled") return null;
|
|
12593
|
+
session.status = "active";
|
|
12594
|
+
session.startedAt = Date.now();
|
|
12595
|
+
session.updatedAt = Date.now();
|
|
12596
|
+
if (!session.participants.find((p) => p.memberId === chairId)) session.participants.push({
|
|
12597
|
+
memberId: chairId,
|
|
12598
|
+
displayName: chairId,
|
|
12599
|
+
kind: "agent",
|
|
12600
|
+
joinedAt: Date.now(),
|
|
12601
|
+
role: "chair"
|
|
12602
|
+
});
|
|
12603
|
+
writeSession(session);
|
|
12604
|
+
log$6.info(`boardroom session started: "${session.title}"`);
|
|
12605
|
+
return session;
|
|
12606
|
+
}
|
|
12607
|
+
function joinSession(sessionId, memberId, displayName, kind) {
|
|
12608
|
+
const session = readSession(sessionId);
|
|
12609
|
+
if (!session || session.status !== "active") return null;
|
|
12610
|
+
if (session.participants.find((p) => p.memberId === memberId)) return session;
|
|
12611
|
+
session.participants.push({
|
|
12612
|
+
memberId,
|
|
12613
|
+
displayName,
|
|
12614
|
+
kind,
|
|
12615
|
+
joinedAt: Date.now(),
|
|
12616
|
+
role: "participant"
|
|
12617
|
+
});
|
|
12618
|
+
session.updatedAt = Date.now();
|
|
12619
|
+
writeSession(session);
|
|
12620
|
+
log$6.info(`${displayName} joined boardroom session "${session.title}"`);
|
|
12621
|
+
return session;
|
|
12622
|
+
}
|
|
12623
|
+
function concludeSession(sessionId, minutes) {
|
|
12624
|
+
const session = readSession(sessionId);
|
|
12625
|
+
if (!session || session.status !== "active") return null;
|
|
12626
|
+
session.status = "concluded";
|
|
12627
|
+
session.concludedAt = Date.now();
|
|
12628
|
+
session.minutes = minutes ?? generateMinutes(session);
|
|
12629
|
+
session.updatedAt = Date.now();
|
|
12630
|
+
writeSession(session);
|
|
12631
|
+
log$6.info(`boardroom session concluded: "${session.title}"`);
|
|
12632
|
+
return session;
|
|
12633
|
+
}
|
|
12634
|
+
function addDecision(sessionId, title, description, madeBy, opts) {
|
|
12635
|
+
const session = readSession(sessionId);
|
|
12636
|
+
if (!session || session.status !== "active") return null;
|
|
12637
|
+
const decision = {
|
|
12638
|
+
id: `decision-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`,
|
|
12639
|
+
title,
|
|
12640
|
+
description,
|
|
12641
|
+
madeBy,
|
|
12642
|
+
madeAt: Date.now(),
|
|
12643
|
+
proposalId: opts?.proposalId,
|
|
12644
|
+
supporters: opts?.supporters ?? [],
|
|
12645
|
+
actionItems: (opts?.actionItems ?? []).map((ai) => ({
|
|
12646
|
+
id: `action-${crypto.randomUUID().slice(0, 8)}`,
|
|
12647
|
+
description: ai.description,
|
|
12648
|
+
assignee: ai.assignee,
|
|
12649
|
+
dueBy: ai.dueBy,
|
|
12650
|
+
status: "pending"
|
|
12651
|
+
}))
|
|
12652
|
+
};
|
|
12653
|
+
session.decisions.push(decision);
|
|
12654
|
+
session.updatedAt = Date.now();
|
|
12655
|
+
writeSession(session);
|
|
12656
|
+
log$6.info(`decision recorded: "${title}" in session "${session.title}"`);
|
|
12657
|
+
return session;
|
|
12658
|
+
}
|
|
12659
|
+
function createProposal(orgId, proposedBy, title, description, opts) {
|
|
12660
|
+
const id = `proposal-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
12661
|
+
const now = Date.now();
|
|
12662
|
+
const proposal = {
|
|
12663
|
+
id,
|
|
12664
|
+
orgId,
|
|
12665
|
+
sessionId: opts?.sessionId,
|
|
12666
|
+
title,
|
|
12667
|
+
description,
|
|
12668
|
+
proposedBy,
|
|
12669
|
+
proposedAt: now,
|
|
12670
|
+
status: "open",
|
|
12671
|
+
votes: [],
|
|
12672
|
+
threshold: opts?.threshold ?? .5,
|
|
12673
|
+
eligibleVoters: opts?.eligibleVoters ?? [],
|
|
12674
|
+
votingDeadline: opts?.votingDeadline ?? 0,
|
|
12675
|
+
updatedAt: now
|
|
12676
|
+
};
|
|
12677
|
+
ensureDir(path.join(resolveBoardroomDir(), "proposals"));
|
|
12678
|
+
fs.writeFileSync(resolveProposalFile(id), `${JSON.stringify(proposal, null, 2)}\n`, { mode: 384 });
|
|
12679
|
+
log$6.info(`proposal created: "${title}" by ${proposedBy}`);
|
|
12680
|
+
return proposal;
|
|
12681
|
+
}
|
|
12682
|
+
function castVote(proposalId, voterId, voterName, value, reason) {
|
|
12683
|
+
const proposal = readProposal(proposalId);
|
|
12684
|
+
if (!proposal || proposal.status !== "open") return null;
|
|
12685
|
+
if (proposal.eligibleVoters.length > 0 && !proposal.eligibleVoters.includes(voterId)) {
|
|
12686
|
+
log$6.warn(`vote rejected: ${voterId} not eligible for proposal ${proposalId}`);
|
|
12687
|
+
return null;
|
|
12688
|
+
}
|
|
12689
|
+
if (proposal.votingDeadline > 0 && Date.now() > proposal.votingDeadline) {
|
|
12690
|
+
log$6.warn(`vote rejected: voting deadline passed for proposal ${proposalId}`);
|
|
12691
|
+
return null;
|
|
12692
|
+
}
|
|
12693
|
+
proposal.votes = proposal.votes.filter((v) => v.voterId !== voterId);
|
|
12694
|
+
proposal.votes.push({
|
|
12695
|
+
voterId,
|
|
12696
|
+
voterName,
|
|
12697
|
+
value,
|
|
12698
|
+
reason,
|
|
12699
|
+
castAt: Date.now()
|
|
12700
|
+
});
|
|
12701
|
+
proposal.updatedAt = Date.now();
|
|
12702
|
+
writeProposal(proposal);
|
|
12703
|
+
log$6.info(`vote cast: ${voterName} → ${value} on "${proposal.title}"`);
|
|
12704
|
+
return proposal;
|
|
12705
|
+
}
|
|
12706
|
+
function resolveProposalVote(proposalId) {
|
|
12707
|
+
const proposal = readProposal(proposalId);
|
|
12708
|
+
if (!proposal || proposal.status !== "open") return null;
|
|
12709
|
+
const approvals = proposal.votes.filter((v) => v.value === "approve").length;
|
|
12710
|
+
const totalVotes = proposal.votes.filter((v) => v.value !== "abstain").length;
|
|
12711
|
+
if (totalVotes === 0) return proposal;
|
|
12712
|
+
const ratio = approvals / totalVotes;
|
|
12713
|
+
if (ratio >= proposal.threshold) {
|
|
12714
|
+
proposal.status = "passed";
|
|
12715
|
+
proposal.resolutionNotes = `Passed with ${approvals}/${totalVotes} votes (${(ratio * 100).toFixed(0)}%)`;
|
|
12716
|
+
} else {
|
|
12717
|
+
proposal.status = "rejected";
|
|
12718
|
+
proposal.resolutionNotes = `Rejected with ${approvals}/${totalVotes} votes (${(ratio * 100).toFixed(0)}%)`;
|
|
12719
|
+
}
|
|
12720
|
+
proposal.resolvedAt = Date.now();
|
|
12721
|
+
proposal.updatedAt = Date.now();
|
|
12722
|
+
writeProposal(proposal);
|
|
12723
|
+
log$6.info(`proposal resolved: "${proposal.title}" → ${proposal.status}`);
|
|
12724
|
+
return proposal;
|
|
12725
|
+
}
|
|
12726
|
+
function listSessions(orgId, status) {
|
|
12727
|
+
const dir = path.join(resolveBoardroomDir(), "sessions");
|
|
12728
|
+
try {
|
|
12729
|
+
if (!fs.existsSync(dir)) return [];
|
|
12730
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
12731
|
+
try {
|
|
12732
|
+
return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
12733
|
+
} catch {
|
|
12734
|
+
return null;
|
|
12735
|
+
}
|
|
12736
|
+
}).filter((s) => {
|
|
12737
|
+
if (!s || s.orgId !== orgId) return false;
|
|
12738
|
+
if (status && s.status !== status) return false;
|
|
12739
|
+
return true;
|
|
12740
|
+
}).toSorted((a, b) => b.calledAt - a.calledAt);
|
|
12741
|
+
} catch {
|
|
12742
|
+
return [];
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
function listProposals(orgId, status) {
|
|
12746
|
+
const dir = path.join(resolveBoardroomDir(), "proposals");
|
|
12747
|
+
try {
|
|
12748
|
+
if (!fs.existsSync(dir)) return [];
|
|
12749
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
12750
|
+
try {
|
|
12751
|
+
return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8"));
|
|
12752
|
+
} catch {
|
|
12753
|
+
return null;
|
|
12754
|
+
}
|
|
12755
|
+
}).filter((p) => {
|
|
12756
|
+
if (!p || p.orgId !== orgId) return false;
|
|
12757
|
+
if (status && p.status !== status) return false;
|
|
12758
|
+
return true;
|
|
12759
|
+
}).toSorted((a, b) => b.proposedAt - a.proposedAt);
|
|
12760
|
+
} catch {
|
|
12761
|
+
return [];
|
|
12762
|
+
}
|
|
12763
|
+
}
|
|
12764
|
+
function getSession(sessionId) {
|
|
12765
|
+
return readSession(sessionId);
|
|
12766
|
+
}
|
|
12767
|
+
function getProposal(proposalId) {
|
|
12768
|
+
return readProposal(proposalId);
|
|
12769
|
+
}
|
|
12770
|
+
function readSession(id) {
|
|
12771
|
+
try {
|
|
12772
|
+
const raw = fs.readFileSync(resolveSessionFile(id), "utf8");
|
|
12773
|
+
return JSON.parse(raw);
|
|
12774
|
+
} catch {
|
|
12775
|
+
return null;
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12778
|
+
function writeSession(session) {
|
|
12779
|
+
ensureDir(path.join(resolveBoardroomDir(), "sessions"));
|
|
12780
|
+
fs.writeFileSync(resolveSessionFile(session.id), `${JSON.stringify(session, null, 2)}\n`, { mode: 384 });
|
|
12781
|
+
}
|
|
12782
|
+
function readProposal(id) {
|
|
12783
|
+
try {
|
|
12784
|
+
const raw = fs.readFileSync(resolveProposalFile(id), "utf8");
|
|
12785
|
+
return JSON.parse(raw);
|
|
12786
|
+
} catch {
|
|
12787
|
+
return null;
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
function writeProposal(proposal) {
|
|
12791
|
+
ensureDir(path.join(resolveBoardroomDir(), "proposals"));
|
|
12792
|
+
fs.writeFileSync(resolveProposalFile(proposal.id), `${JSON.stringify(proposal, null, 2)}\n`, { mode: 384 });
|
|
12793
|
+
}
|
|
12794
|
+
function generateMinutes(session) {
|
|
12795
|
+
const lines = [];
|
|
12796
|
+
lines.push(`# Boardroom Minutes: ${session.title}`);
|
|
12797
|
+
lines.push(`**Called by:** ${session.calledBy}`);
|
|
12798
|
+
lines.push(`**Date:** ${new Date(session.startedAt ?? session.calledAt).toISOString()}`);
|
|
12799
|
+
lines.push(`**Duration:** ${session.concludedAt && session.startedAt ? `${Math.round((session.concludedAt - session.startedAt) / 6e4)} minutes` : "N/A"}`);
|
|
12800
|
+
lines.push("");
|
|
12801
|
+
lines.push(`## Participants (${session.participants.length})`);
|
|
12802
|
+
for (const p of session.participants) lines.push(`- ${p.displayName} (${p.kind}, ${p.role})`);
|
|
12803
|
+
lines.push("");
|
|
12804
|
+
if (session.agenda.length > 0) {
|
|
12805
|
+
lines.push(`## Agenda`);
|
|
12806
|
+
for (const item of session.agenda) lines.push(`- [${item.status}] ${item.title}: ${item.resolution ?? item.description}`);
|
|
12807
|
+
lines.push("");
|
|
12808
|
+
}
|
|
12809
|
+
if (session.decisions.length > 0) {
|
|
12810
|
+
lines.push(`## Decisions (${session.decisions.length})`);
|
|
12811
|
+
for (const d of session.decisions) {
|
|
12812
|
+
lines.push(`### ${d.title}`);
|
|
12813
|
+
lines.push(d.description);
|
|
12814
|
+
if (d.actionItems.length > 0) {
|
|
12815
|
+
lines.push("**Action items:**");
|
|
12816
|
+
for (const ai of d.actionItems) lines.push(`- [ ] ${ai.description} → ${ai.assignee}`);
|
|
12817
|
+
}
|
|
12818
|
+
lines.push("");
|
|
12819
|
+
}
|
|
12820
|
+
}
|
|
12821
|
+
return lines.join("\n");
|
|
12822
|
+
}
|
|
12823
|
+
|
|
12105
12824
|
//#endregion
|
|
12106
12825
|
//#region src/org/types.ts
|
|
12107
12826
|
const DEFAULT_ROLE_PERMISSIONS = {
|
|
@@ -12165,16 +12884,22 @@ const DEFAULT_ROLE_PERMISSIONS = {
|
|
|
12165
12884
|
* Persists organization state to ~/.anima/org/
|
|
12166
12885
|
* Supports CRUD operations for orgs, members, and roles.
|
|
12167
12886
|
*/
|
|
12168
|
-
const log$
|
|
12887
|
+
const log$5 = createSubsystemLogger("org-store");
|
|
12169
12888
|
function resolveOrgDir() {
|
|
12170
12889
|
return path.join(resolveStateDir(), "org");
|
|
12171
12890
|
}
|
|
12891
|
+
/** Sanitize an ID to prevent path traversal (allow alphanumeric, hyphens only) */
|
|
12892
|
+
function sanitizeOrgId(id) {
|
|
12893
|
+
const cleaned = id.replace(/[^a-zA-Z0-9-]/g, "");
|
|
12894
|
+
if (!cleaned || cleaned !== id) throw new Error(`Invalid org ID: contains disallowed characters`);
|
|
12895
|
+
return cleaned;
|
|
12896
|
+
}
|
|
12172
12897
|
function resolveOrgFile(orgId) {
|
|
12173
|
-
return path.join(resolveOrgDir(), `${orgId}.json`);
|
|
12898
|
+
return path.join(resolveOrgDir(), `${sanitizeOrgId(orgId)}.json`);
|
|
12174
12899
|
}
|
|
12175
12900
|
function readOrgFile(orgId) {
|
|
12176
|
-
const filePath = resolveOrgFile(orgId);
|
|
12177
12901
|
try {
|
|
12902
|
+
const filePath = resolveOrgFile(orgId);
|
|
12178
12903
|
if (!fs.existsSync(filePath)) return null;
|
|
12179
12904
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
12180
12905
|
const parsed = JSON.parse(raw);
|
|
@@ -12224,9 +12949,10 @@ function createOrganization(name, description, ownerId, ownerName, ownerKind, se
|
|
|
12224
12949
|
lastActiveAt: now,
|
|
12225
12950
|
status: "active",
|
|
12226
12951
|
permissions: DEFAULT_ROLE_PERMISSIONS.owner
|
|
12227
|
-
}]
|
|
12952
|
+
}],
|
|
12953
|
+
invites: []
|
|
12228
12954
|
});
|
|
12229
|
-
log$
|
|
12955
|
+
log$5.info(`created organization: ${name} (${orgId})`);
|
|
12230
12956
|
return org;
|
|
12231
12957
|
}
|
|
12232
12958
|
function getOrganization(orgId) {
|
|
@@ -12243,7 +12969,7 @@ function updateOrganization(orgId, updates) {
|
|
|
12243
12969
|
};
|
|
12244
12970
|
data.org.updatedAt = Date.now();
|
|
12245
12971
|
writeOrgFile(orgId, data);
|
|
12246
|
-
log$
|
|
12972
|
+
log$5.info(`updated organization: ${orgId}`);
|
|
12247
12973
|
return data.org;
|
|
12248
12974
|
}
|
|
12249
12975
|
function listOrganizations() {
|
|
@@ -12270,7 +12996,7 @@ function addMember(orgId, member) {
|
|
|
12270
12996
|
data.members.push(newMember);
|
|
12271
12997
|
data.org.updatedAt = Date.now();
|
|
12272
12998
|
writeOrgFile(orgId, data);
|
|
12273
|
-
log$
|
|
12999
|
+
log$5.info(`added member ${newMember.displayName} to org ${orgId}`);
|
|
12274
13000
|
return newMember;
|
|
12275
13001
|
}
|
|
12276
13002
|
function removeMember(orgId, memberId) {
|
|
@@ -12281,7 +13007,7 @@ function removeMember(orgId, memberId) {
|
|
|
12281
13007
|
data.members.splice(idx, 1);
|
|
12282
13008
|
data.org.updatedAt = Date.now();
|
|
12283
13009
|
writeOrgFile(orgId, data);
|
|
12284
|
-
log$
|
|
13010
|
+
log$5.info(`removed member ${memberId} from org ${orgId}`);
|
|
12285
13011
|
return true;
|
|
12286
13012
|
}
|
|
12287
13013
|
function updateMember(orgId, memberId, updates) {
|
|
@@ -12331,6 +13057,126 @@ function buildHierarchy(orgId) {
|
|
|
12331
13057
|
}
|
|
12332
13058
|
return members.filter((m) => !m.reportsTo || !memberMap.has(m.reportsTo)).map((r) => buildNode(r));
|
|
12333
13059
|
}
|
|
13060
|
+
function generateInviteCode() {
|
|
13061
|
+
const segments = [crypto.randomBytes(3).toString("hex").toUpperCase(), crypto.randomBytes(2).toString("hex").toUpperCase()];
|
|
13062
|
+
return `NOX-${segments[0]}-${segments[1]}`;
|
|
13063
|
+
}
|
|
13064
|
+
function hashPasscode(passcode) {
|
|
13065
|
+
return crypto.createHash("sha256").update(passcode).digest("hex");
|
|
13066
|
+
}
|
|
13067
|
+
/**
|
|
13068
|
+
* Create a secret invite code + passcode combo for an org.
|
|
13069
|
+
* Both are required to join.
|
|
13070
|
+
*/
|
|
13071
|
+
function createInvite(orgId, createdBy, passcode, options) {
|
|
13072
|
+
const data = readOrgFile(orgId);
|
|
13073
|
+
if (!data) return null;
|
|
13074
|
+
if (!data.invites) data.invites = [];
|
|
13075
|
+
const invite = {
|
|
13076
|
+
id: crypto.randomUUID(),
|
|
13077
|
+
code: generateInviteCode(),
|
|
13078
|
+
passcode: hashPasscode(passcode),
|
|
13079
|
+
orgId,
|
|
13080
|
+
createdBy,
|
|
13081
|
+
createdAt: Date.now(),
|
|
13082
|
+
expiresAt: options?.expiresInMs ? Date.now() + options.expiresInMs : 0,
|
|
13083
|
+
maxUses: options?.maxUses ?? 0,
|
|
13084
|
+
uses: 0,
|
|
13085
|
+
role: options?.role ?? "worker",
|
|
13086
|
+
active: true
|
|
13087
|
+
};
|
|
13088
|
+
data.invites.push(invite);
|
|
13089
|
+
data.org.updatedAt = Date.now();
|
|
13090
|
+
writeOrgFile(orgId, data);
|
|
13091
|
+
log$5.info(`invite created for org ${orgId}: ${invite.code} (role: ${invite.role})`);
|
|
13092
|
+
return invite;
|
|
13093
|
+
}
|
|
13094
|
+
/**
|
|
13095
|
+
* Join an org using invite code + passcode.
|
|
13096
|
+
* Returns the new member if successful, null if invalid.
|
|
13097
|
+
*/
|
|
13098
|
+
function joinOrg(inviteCode, passcode, member) {
|
|
13099
|
+
const dir = resolveOrgDir();
|
|
13100
|
+
try {
|
|
13101
|
+
if (!fs.existsSync(dir)) return null;
|
|
13102
|
+
const orgFiles = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
13103
|
+
for (const f of orgFiles) {
|
|
13104
|
+
const orgId = f.replace(".json", "");
|
|
13105
|
+
const data = readOrgFile(orgId);
|
|
13106
|
+
if (!data || !data.invites) continue;
|
|
13107
|
+
const invite = data.invites.find((i) => i.code === inviteCode.toUpperCase() && i.active);
|
|
13108
|
+
if (!invite) continue;
|
|
13109
|
+
if (invite.passcode !== hashPasscode(passcode)) {
|
|
13110
|
+
log$5.warn(`join attempt with wrong passcode for invite ${inviteCode}`);
|
|
13111
|
+
return null;
|
|
13112
|
+
}
|
|
13113
|
+
if (invite.expiresAt > 0 && invite.expiresAt < Date.now()) {
|
|
13114
|
+
log$5.warn(`invite ${inviteCode} has expired`);
|
|
13115
|
+
return null;
|
|
13116
|
+
}
|
|
13117
|
+
if (invite.maxUses > 0 && invite.uses >= invite.maxUses) {
|
|
13118
|
+
log$5.warn(`invite ${inviteCode} has reached max uses (${invite.maxUses})`);
|
|
13119
|
+
return null;
|
|
13120
|
+
}
|
|
13121
|
+
if (data.members.some((m) => member.deviceId && m.deviceId === member.deviceId || m.displayName === member.displayName)) {
|
|
13122
|
+
log$5.warn(`${member.displayName} is already a member of org ${orgId}`);
|
|
13123
|
+
return null;
|
|
13124
|
+
}
|
|
13125
|
+
const newMember = {
|
|
13126
|
+
id: crypto.randomUUID(),
|
|
13127
|
+
kind: member.kind,
|
|
13128
|
+
displayName: member.displayName,
|
|
13129
|
+
deviceId: member.deviceId,
|
|
13130
|
+
role: invite.role,
|
|
13131
|
+
description: member.description,
|
|
13132
|
+
specializations: member.specializations,
|
|
13133
|
+
joinedAt: Date.now(),
|
|
13134
|
+
lastActiveAt: Date.now(),
|
|
13135
|
+
status: "active",
|
|
13136
|
+
permissions: DEFAULT_ROLE_PERMISSIONS[invite.role]
|
|
13137
|
+
};
|
|
13138
|
+
data.members.push(newMember);
|
|
13139
|
+
invite.uses++;
|
|
13140
|
+
data.org.updatedAt = Date.now();
|
|
13141
|
+
writeOrgFile(orgId, data);
|
|
13142
|
+
log$5.info(`${member.displayName} joined org ${data.org.name} via invite ${inviteCode}`);
|
|
13143
|
+
return {
|
|
13144
|
+
org: data.org,
|
|
13145
|
+
member: newMember
|
|
13146
|
+
};
|
|
13147
|
+
}
|
|
13148
|
+
return null;
|
|
13149
|
+
} catch {
|
|
13150
|
+
return null;
|
|
13151
|
+
}
|
|
13152
|
+
}
|
|
13153
|
+
/**
|
|
13154
|
+
* Validate an invite code + passcode without joining.
|
|
13155
|
+
* Returns the org info if valid.
|
|
13156
|
+
*/
|
|
13157
|
+
function validateInvite(inviteCode, passcode) {
|
|
13158
|
+
const dir = resolveOrgDir();
|
|
13159
|
+
try {
|
|
13160
|
+
if (!fs.existsSync(dir)) return null;
|
|
13161
|
+
const orgFiles = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
13162
|
+
for (const f of orgFiles) {
|
|
13163
|
+
const data = readOrgFile(f.replace(".json", ""));
|
|
13164
|
+
if (!data || !data.invites) continue;
|
|
13165
|
+
const invite = data.invites.find((i) => i.code === inviteCode.toUpperCase() && i.active);
|
|
13166
|
+
if (!invite) continue;
|
|
13167
|
+
if (invite.passcode !== hashPasscode(passcode)) return null;
|
|
13168
|
+
if (invite.expiresAt > 0 && invite.expiresAt < Date.now()) return null;
|
|
13169
|
+
if (invite.maxUses > 0 && invite.uses >= invite.maxUses) return null;
|
|
13170
|
+
return {
|
|
13171
|
+
org: data.org,
|
|
13172
|
+
role: invite.role
|
|
13173
|
+
};
|
|
13174
|
+
}
|
|
13175
|
+
return null;
|
|
13176
|
+
} catch {
|
|
13177
|
+
return null;
|
|
13178
|
+
}
|
|
13179
|
+
}
|
|
12334
13180
|
|
|
12335
13181
|
//#endregion
|
|
12336
13182
|
//#region src/gateway/server-methods/org.ts
|
|
@@ -12456,81 +13302,366 @@ const orgHandlers = {
|
|
|
12456
13302
|
status,
|
|
12457
13303
|
reportsTo
|
|
12458
13304
|
});
|
|
12459
|
-
if (!member) {
|
|
12460
|
-
respond(false, void 0, invalid("Organization not found"));
|
|
13305
|
+
if (!member) {
|
|
13306
|
+
respond(false, void 0, invalid("Organization not found"));
|
|
13307
|
+
return;
|
|
13308
|
+
}
|
|
13309
|
+
respond(true, { member }, void 0);
|
|
13310
|
+
} catch (error) {
|
|
13311
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13312
|
+
}
|
|
13313
|
+
},
|
|
13314
|
+
"org.updateMember": async ({ params, respond }) => {
|
|
13315
|
+
const orgId = requireString(params, "orgId");
|
|
13316
|
+
const memberId = requireString(params, "memberId");
|
|
13317
|
+
if (!orgId) {
|
|
13318
|
+
respond(false, void 0, invalid("orgId is required"));
|
|
13319
|
+
return;
|
|
13320
|
+
}
|
|
13321
|
+
if (!memberId) {
|
|
13322
|
+
respond(false, void 0, invalid("memberId is required"));
|
|
13323
|
+
return;
|
|
13324
|
+
}
|
|
13325
|
+
const updates = {};
|
|
13326
|
+
const displayName = requireString(params, "displayName");
|
|
13327
|
+
if (displayName) updates.displayName = displayName;
|
|
13328
|
+
const role = requireString(params, "role");
|
|
13329
|
+
if (role) updates.role = role;
|
|
13330
|
+
const description = params.description;
|
|
13331
|
+
if (typeof description === "string") updates.description = description;
|
|
13332
|
+
if (Array.isArray(params.specializations)) updates.specializations = params.specializations;
|
|
13333
|
+
const status = requireString(params, "status");
|
|
13334
|
+
if (status) updates.status = status;
|
|
13335
|
+
if (params.reportsTo !== void 0) updates.reportsTo = typeof params.reportsTo === "string" ? params.reportsTo : void 0;
|
|
13336
|
+
try {
|
|
13337
|
+
const member = updateMember(orgId, memberId, updates);
|
|
13338
|
+
if (!member) {
|
|
13339
|
+
respond(false, void 0, invalid("Member not found"));
|
|
13340
|
+
return;
|
|
13341
|
+
}
|
|
13342
|
+
respond(true, { member }, void 0);
|
|
13343
|
+
} catch (error) {
|
|
13344
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13345
|
+
}
|
|
13346
|
+
},
|
|
13347
|
+
"org.removeMember": async ({ params, respond }) => {
|
|
13348
|
+
const orgId = requireString(params, "orgId");
|
|
13349
|
+
const memberId = requireString(params, "memberId");
|
|
13350
|
+
if (!orgId) {
|
|
13351
|
+
respond(false, void 0, invalid("orgId is required"));
|
|
13352
|
+
return;
|
|
13353
|
+
}
|
|
13354
|
+
if (!memberId) {
|
|
13355
|
+
respond(false, void 0, invalid("memberId is required"));
|
|
13356
|
+
return;
|
|
13357
|
+
}
|
|
13358
|
+
try {
|
|
13359
|
+
if (!removeMember(orgId, memberId)) {
|
|
13360
|
+
respond(false, void 0, invalid("Member not found"));
|
|
13361
|
+
return;
|
|
13362
|
+
}
|
|
13363
|
+
respond(true, { ok: true }, void 0);
|
|
13364
|
+
} catch (error) {
|
|
13365
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13366
|
+
}
|
|
13367
|
+
},
|
|
13368
|
+
"org.hierarchy": async ({ params, respond }) => {
|
|
13369
|
+
const orgId = requireString(params, "orgId");
|
|
13370
|
+
if (!orgId) {
|
|
13371
|
+
respond(false, void 0, invalid("orgId is required"));
|
|
13372
|
+
return;
|
|
13373
|
+
}
|
|
13374
|
+
try {
|
|
13375
|
+
if (!getOrganization(orgId)) {
|
|
13376
|
+
respond(false, void 0, invalid("Organization not found"));
|
|
13377
|
+
return;
|
|
13378
|
+
}
|
|
13379
|
+
respond(true, { hierarchy: buildHierarchy(orgId) }, void 0);
|
|
13380
|
+
} catch (error) {
|
|
13381
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13382
|
+
}
|
|
13383
|
+
},
|
|
13384
|
+
"org.createInvite": async ({ params, respond }) => {
|
|
13385
|
+
const orgId = requireString(params, "orgId");
|
|
13386
|
+
const passcode = requireString(params, "passcode");
|
|
13387
|
+
if (!orgId || !passcode) {
|
|
13388
|
+
respond(false, void 0, invalid("orgId and passcode are required"));
|
|
13389
|
+
return;
|
|
13390
|
+
}
|
|
13391
|
+
try {
|
|
13392
|
+
const invite = createInvite(orgId, "gateway", passcode, {
|
|
13393
|
+
role: requireString(params, "role") ?? "worker",
|
|
13394
|
+
maxUses: typeof params.maxUses === "number" ? params.maxUses : 0,
|
|
13395
|
+
expiresInMs: typeof params.expiresInMs === "number" ? params.expiresInMs : 0
|
|
13396
|
+
});
|
|
13397
|
+
if (!invite) {
|
|
13398
|
+
respond(false, void 0, invalid("Organization not found"));
|
|
13399
|
+
return;
|
|
13400
|
+
}
|
|
13401
|
+
respond(true, {
|
|
13402
|
+
code: invite.code,
|
|
13403
|
+
passcode
|
|
13404
|
+
}, void 0);
|
|
13405
|
+
} catch (error) {
|
|
13406
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13407
|
+
}
|
|
13408
|
+
},
|
|
13409
|
+
"org.validateInvite": async ({ params, respond }) => {
|
|
13410
|
+
const inviteCode = requireString(params, "inviteCode");
|
|
13411
|
+
const passcode = requireString(params, "passcode");
|
|
13412
|
+
if (!inviteCode || !passcode) {
|
|
13413
|
+
respond(false, void 0, invalid("inviteCode and passcode are required"));
|
|
13414
|
+
return;
|
|
13415
|
+
}
|
|
13416
|
+
try {
|
|
13417
|
+
const result = validateInvite(inviteCode, passcode);
|
|
13418
|
+
if (!result) {
|
|
13419
|
+
respond(false, void 0, invalid("Invalid invite code or passcode"));
|
|
13420
|
+
return;
|
|
13421
|
+
}
|
|
13422
|
+
respond(true, {
|
|
13423
|
+
org: result.org,
|
|
13424
|
+
role: result.role
|
|
13425
|
+
}, void 0);
|
|
13426
|
+
} catch (error) {
|
|
13427
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13428
|
+
}
|
|
13429
|
+
},
|
|
13430
|
+
"org.join": async ({ params, respond }) => {
|
|
13431
|
+
const inviteCode = requireString(params, "inviteCode");
|
|
13432
|
+
const passcode = requireString(params, "passcode");
|
|
13433
|
+
const displayName = requireString(params, "displayName");
|
|
13434
|
+
const kind = requireString(params, "kind") ?? "agent";
|
|
13435
|
+
if (!inviteCode || !passcode || !displayName) {
|
|
13436
|
+
respond(false, void 0, invalid("inviteCode, passcode, and displayName are required"));
|
|
13437
|
+
return;
|
|
13438
|
+
}
|
|
13439
|
+
try {
|
|
13440
|
+
const result = joinOrg(inviteCode, passcode, {
|
|
13441
|
+
displayName,
|
|
13442
|
+
kind,
|
|
13443
|
+
description: requireString(params, "description") ?? "",
|
|
13444
|
+
specializations: Array.isArray(params.specializations) ? params.specializations : []
|
|
13445
|
+
});
|
|
13446
|
+
if (!result) {
|
|
13447
|
+
respond(false, void 0, invalid("Invalid invite code, passcode, or already a member"));
|
|
13448
|
+
return;
|
|
13449
|
+
}
|
|
13450
|
+
respond(true, {
|
|
13451
|
+
org: result.org,
|
|
13452
|
+
member: result.member
|
|
13453
|
+
}, void 0);
|
|
13454
|
+
} catch (error) {
|
|
13455
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13456
|
+
}
|
|
13457
|
+
},
|
|
13458
|
+
"boardroom.createSession": async ({ params, respond }) => {
|
|
13459
|
+
const orgId = requireString(params, "orgId");
|
|
13460
|
+
const calledBy = requireString(params, "calledBy");
|
|
13461
|
+
const title = requireString(params, "title");
|
|
13462
|
+
if (!orgId || !calledBy || !title) {
|
|
13463
|
+
respond(false, void 0, invalid("orgId, calledBy, and title are required"));
|
|
13464
|
+
return;
|
|
13465
|
+
}
|
|
13466
|
+
try {
|
|
13467
|
+
respond(true, { session: createSession(orgId, calledBy, title, requireString(params, "description") ?? "", Array.isArray(params.agenda) ? params.agenda : []) }, void 0);
|
|
13468
|
+
} catch (error) {
|
|
13469
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13470
|
+
}
|
|
13471
|
+
},
|
|
13472
|
+
"boardroom.startSession": async ({ params, respond }) => {
|
|
13473
|
+
const sessionId = requireString(params, "sessionId");
|
|
13474
|
+
const chairId = requireString(params, "chairId");
|
|
13475
|
+
if (!sessionId || !chairId) {
|
|
13476
|
+
respond(false, void 0, invalid("sessionId and chairId are required"));
|
|
13477
|
+
return;
|
|
13478
|
+
}
|
|
13479
|
+
try {
|
|
13480
|
+
const session = startSession(sessionId, chairId);
|
|
13481
|
+
if (!session) {
|
|
13482
|
+
respond(false, void 0, invalid("Session not found or not scheduled"));
|
|
13483
|
+
return;
|
|
13484
|
+
}
|
|
13485
|
+
respond(true, { session }, void 0);
|
|
13486
|
+
} catch (error) {
|
|
13487
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13488
|
+
}
|
|
13489
|
+
},
|
|
13490
|
+
"boardroom.joinSession": async ({ params, respond }) => {
|
|
13491
|
+
const sessionId = requireString(params, "sessionId");
|
|
13492
|
+
const memberId = requireString(params, "memberId");
|
|
13493
|
+
const displayName = requireString(params, "displayName");
|
|
13494
|
+
const kind = requireString(params, "kind") ?? "agent";
|
|
13495
|
+
if (!sessionId || !memberId || !displayName) {
|
|
13496
|
+
respond(false, void 0, invalid("sessionId, memberId, and displayName are required"));
|
|
13497
|
+
return;
|
|
13498
|
+
}
|
|
13499
|
+
try {
|
|
13500
|
+
const session = joinSession(sessionId, memberId, displayName, kind);
|
|
13501
|
+
if (!session) {
|
|
13502
|
+
respond(false, void 0, invalid("Session not found or not active"));
|
|
13503
|
+
return;
|
|
13504
|
+
}
|
|
13505
|
+
respond(true, { session }, void 0);
|
|
13506
|
+
} catch (error) {
|
|
13507
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13508
|
+
}
|
|
13509
|
+
},
|
|
13510
|
+
"boardroom.concludeSession": async ({ params, respond }) => {
|
|
13511
|
+
const sessionId = requireString(params, "sessionId");
|
|
13512
|
+
if (!sessionId) {
|
|
13513
|
+
respond(false, void 0, invalid("sessionId is required"));
|
|
13514
|
+
return;
|
|
13515
|
+
}
|
|
13516
|
+
try {
|
|
13517
|
+
const session = concludeSession(sessionId, requireString(params, "minutes") ?? void 0);
|
|
13518
|
+
if (!session) {
|
|
13519
|
+
respond(false, void 0, invalid("Session not found or not active"));
|
|
13520
|
+
return;
|
|
13521
|
+
}
|
|
13522
|
+
respond(true, { session }, void 0);
|
|
13523
|
+
} catch (error) {
|
|
13524
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13525
|
+
}
|
|
13526
|
+
},
|
|
13527
|
+
"boardroom.addDecision": async ({ params, respond }) => {
|
|
13528
|
+
const sessionId = requireString(params, "sessionId");
|
|
13529
|
+
const title = requireString(params, "title");
|
|
13530
|
+
const description = requireString(params, "description") ?? "";
|
|
13531
|
+
const madeBy = requireString(params, "madeBy");
|
|
13532
|
+
if (!sessionId || !title || !madeBy) {
|
|
13533
|
+
respond(false, void 0, invalid("sessionId, title, and madeBy are required"));
|
|
13534
|
+
return;
|
|
13535
|
+
}
|
|
13536
|
+
try {
|
|
13537
|
+
const session = addDecision(sessionId, title, description, madeBy, {
|
|
13538
|
+
proposalId: requireString(params, "proposalId") ?? void 0,
|
|
13539
|
+
supporters: Array.isArray(params.supporters) ? params.supporters : void 0,
|
|
13540
|
+
actionItems: Array.isArray(params.actionItems) ? params.actionItems : void 0
|
|
13541
|
+
});
|
|
13542
|
+
if (!session) {
|
|
13543
|
+
respond(false, void 0, invalid("Session not found or not active"));
|
|
12461
13544
|
return;
|
|
12462
13545
|
}
|
|
12463
|
-
respond(true, {
|
|
13546
|
+
respond(true, { session }, void 0);
|
|
12464
13547
|
} catch (error) {
|
|
12465
13548
|
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
12466
13549
|
}
|
|
12467
13550
|
},
|
|
12468
|
-
"
|
|
13551
|
+
"boardroom.listSessions": async ({ params, respond }) => {
|
|
12469
13552
|
const orgId = requireString(params, "orgId");
|
|
12470
|
-
const memberId = requireString(params, "memberId");
|
|
12471
13553
|
if (!orgId) {
|
|
12472
13554
|
respond(false, void 0, invalid("orgId is required"));
|
|
12473
13555
|
return;
|
|
12474
13556
|
}
|
|
12475
|
-
|
|
12476
|
-
respond(
|
|
13557
|
+
try {
|
|
13558
|
+
respond(true, { sessions: listSessions(orgId, requireString(params, "status") ?? void 0) }, void 0);
|
|
13559
|
+
} catch (error) {
|
|
13560
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13561
|
+
}
|
|
13562
|
+
},
|
|
13563
|
+
"boardroom.getSession": async ({ params, respond }) => {
|
|
13564
|
+
const sessionId = requireString(params, "sessionId");
|
|
13565
|
+
if (!sessionId) {
|
|
13566
|
+
respond(false, void 0, invalid("sessionId is required"));
|
|
12477
13567
|
return;
|
|
12478
13568
|
}
|
|
12479
|
-
const updates = {};
|
|
12480
|
-
const displayName = requireString(params, "displayName");
|
|
12481
|
-
if (displayName) updates.displayName = displayName;
|
|
12482
|
-
const role = requireString(params, "role");
|
|
12483
|
-
if (role) updates.role = role;
|
|
12484
|
-
const description = params.description;
|
|
12485
|
-
if (typeof description === "string") updates.description = description;
|
|
12486
|
-
if (Array.isArray(params.specializations)) updates.specializations = params.specializations;
|
|
12487
|
-
const status = requireString(params, "status");
|
|
12488
|
-
if (status) updates.status = status;
|
|
12489
|
-
if (params.reportsTo !== void 0) updates.reportsTo = typeof params.reportsTo === "string" ? params.reportsTo : void 0;
|
|
12490
13569
|
try {
|
|
12491
|
-
const
|
|
12492
|
-
if (!
|
|
12493
|
-
respond(false, void 0, invalid("
|
|
13570
|
+
const session = getSession(sessionId);
|
|
13571
|
+
if (!session) {
|
|
13572
|
+
respond(false, void 0, invalid("Session not found"));
|
|
12494
13573
|
return;
|
|
12495
13574
|
}
|
|
12496
|
-
respond(true, {
|
|
13575
|
+
respond(true, { session }, void 0);
|
|
12497
13576
|
} catch (error) {
|
|
12498
13577
|
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
12499
13578
|
}
|
|
12500
13579
|
},
|
|
12501
|
-
"
|
|
13580
|
+
"boardroom.createProposal": async ({ params, respond }) => {
|
|
12502
13581
|
const orgId = requireString(params, "orgId");
|
|
12503
|
-
const
|
|
12504
|
-
|
|
12505
|
-
|
|
13582
|
+
const proposedBy = requireString(params, "proposedBy");
|
|
13583
|
+
const title = requireString(params, "title");
|
|
13584
|
+
if (!orgId || !proposedBy || !title) {
|
|
13585
|
+
respond(false, void 0, invalid("orgId, proposedBy, and title are required"));
|
|
12506
13586
|
return;
|
|
12507
13587
|
}
|
|
12508
|
-
|
|
12509
|
-
respond(
|
|
13588
|
+
try {
|
|
13589
|
+
respond(true, { proposal: createProposal(orgId, proposedBy, title, requireString(params, "description") ?? "", {
|
|
13590
|
+
sessionId: requireString(params, "sessionId") ?? void 0,
|
|
13591
|
+
threshold: typeof params.threshold === "number" ? params.threshold : void 0,
|
|
13592
|
+
eligibleVoters: Array.isArray(params.eligibleVoters) ? params.eligibleVoters : void 0,
|
|
13593
|
+
votingDeadline: typeof params.votingDeadline === "number" ? params.votingDeadline : void 0
|
|
13594
|
+
}) }, void 0);
|
|
13595
|
+
} catch (error) {
|
|
13596
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13597
|
+
}
|
|
13598
|
+
},
|
|
13599
|
+
"boardroom.castVote": async ({ params, respond }) => {
|
|
13600
|
+
const proposalId = requireString(params, "proposalId");
|
|
13601
|
+
const voterId = requireString(params, "voterId");
|
|
13602
|
+
const voterName = requireString(params, "voterName");
|
|
13603
|
+
const value = requireString(params, "value");
|
|
13604
|
+
if (!proposalId || !voterId || !voterName || !value) {
|
|
13605
|
+
respond(false, void 0, invalid("proposalId, voterId, voterName, and value are required"));
|
|
13606
|
+
return;
|
|
13607
|
+
}
|
|
13608
|
+
if (value !== "approve" && value !== "reject" && value !== "abstain") {
|
|
13609
|
+
respond(false, void 0, invalid("value must be \"approve\", \"reject\", or \"abstain\""));
|
|
12510
13610
|
return;
|
|
12511
13611
|
}
|
|
12512
13612
|
try {
|
|
12513
|
-
|
|
12514
|
-
|
|
13613
|
+
const proposal = castVote(proposalId, voterId, voterName, value, requireString(params, "reason") ?? void 0);
|
|
13614
|
+
if (!proposal) {
|
|
13615
|
+
respond(false, void 0, invalid("Proposal not found, closed, or voter not eligible"));
|
|
12515
13616
|
return;
|
|
12516
13617
|
}
|
|
12517
|
-
respond(true, {
|
|
13618
|
+
respond(true, { proposal }, void 0);
|
|
12518
13619
|
} catch (error) {
|
|
12519
13620
|
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
12520
13621
|
}
|
|
12521
13622
|
},
|
|
12522
|
-
"
|
|
13623
|
+
"boardroom.resolveVote": async ({ params, respond }) => {
|
|
13624
|
+
const proposalId = requireString(params, "proposalId");
|
|
13625
|
+
if (!proposalId) {
|
|
13626
|
+
respond(false, void 0, invalid("proposalId is required"));
|
|
13627
|
+
return;
|
|
13628
|
+
}
|
|
13629
|
+
try {
|
|
13630
|
+
const proposal = resolveProposalVote(proposalId);
|
|
13631
|
+
if (!proposal) {
|
|
13632
|
+
respond(false, void 0, invalid("Proposal not found or not open"));
|
|
13633
|
+
return;
|
|
13634
|
+
}
|
|
13635
|
+
respond(true, { proposal }, void 0);
|
|
13636
|
+
} catch (error) {
|
|
13637
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13638
|
+
}
|
|
13639
|
+
},
|
|
13640
|
+
"boardroom.listProposals": async ({ params, respond }) => {
|
|
12523
13641
|
const orgId = requireString(params, "orgId");
|
|
12524
13642
|
if (!orgId) {
|
|
12525
13643
|
respond(false, void 0, invalid("orgId is required"));
|
|
12526
13644
|
return;
|
|
12527
13645
|
}
|
|
12528
13646
|
try {
|
|
12529
|
-
|
|
12530
|
-
|
|
13647
|
+
respond(true, { proposals: listProposals(orgId, requireString(params, "status") ?? void 0) }, void 0);
|
|
13648
|
+
} catch (error) {
|
|
13649
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
13650
|
+
}
|
|
13651
|
+
},
|
|
13652
|
+
"boardroom.getProposal": async ({ params, respond }) => {
|
|
13653
|
+
const proposalId = requireString(params, "proposalId");
|
|
13654
|
+
if (!proposalId) {
|
|
13655
|
+
respond(false, void 0, invalid("proposalId is required"));
|
|
13656
|
+
return;
|
|
13657
|
+
}
|
|
13658
|
+
try {
|
|
13659
|
+
const proposal = getProposal(proposalId);
|
|
13660
|
+
if (!proposal) {
|
|
13661
|
+
respond(false, void 0, invalid("Proposal not found"));
|
|
12531
13662
|
return;
|
|
12532
13663
|
}
|
|
12533
|
-
respond(true, {
|
|
13664
|
+
respond(true, { proposal }, void 0);
|
|
12534
13665
|
} catch (error) {
|
|
12535
13666
|
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
12536
13667
|
}
|
|
@@ -13003,6 +14134,374 @@ const skillsHandlers = {
|
|
|
13003
14134
|
}
|
|
13004
14135
|
};
|
|
13005
14136
|
|
|
14137
|
+
//#endregion
|
|
14138
|
+
//#region src/gateway/server-methods/steer.ts
|
|
14139
|
+
const steerHandlers = {
|
|
14140
|
+
"steer.get": async ({ respond }) => {
|
|
14141
|
+
try {
|
|
14142
|
+
respond(true, { active: getSteer() }, void 0);
|
|
14143
|
+
} catch (error) {
|
|
14144
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14145
|
+
}
|
|
14146
|
+
},
|
|
14147
|
+
"steer.set": async ({ params, respond }) => {
|
|
14148
|
+
const text = typeof params.text === "string" ? params.text.trim() : "";
|
|
14149
|
+
if (!text) {
|
|
14150
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "text is required"));
|
|
14151
|
+
return;
|
|
14152
|
+
}
|
|
14153
|
+
const setBy = typeof params.setBy === "string" ? params.setBy.trim() : "user";
|
|
14154
|
+
try {
|
|
14155
|
+
const state = setSteer(text, setBy);
|
|
14156
|
+
respond(true, {
|
|
14157
|
+
active: state.active,
|
|
14158
|
+
updatedAt: state.updatedAt
|
|
14159
|
+
}, void 0);
|
|
14160
|
+
} catch (error) {
|
|
14161
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14162
|
+
}
|
|
14163
|
+
},
|
|
14164
|
+
"steer.clear": async ({ respond }) => {
|
|
14165
|
+
try {
|
|
14166
|
+
const state = clearSteer();
|
|
14167
|
+
respond(true, {
|
|
14168
|
+
active: state.active,
|
|
14169
|
+
updatedAt: state.updatedAt
|
|
14170
|
+
}, void 0);
|
|
14171
|
+
} catch (error) {
|
|
14172
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14173
|
+
}
|
|
14174
|
+
},
|
|
14175
|
+
"steer.history": async ({ respond }) => {
|
|
14176
|
+
try {
|
|
14177
|
+
respond(true, { history: getSteerHistory() }, void 0);
|
|
14178
|
+
} catch (error) {
|
|
14179
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14180
|
+
}
|
|
14181
|
+
}
|
|
14182
|
+
};
|
|
14183
|
+
|
|
14184
|
+
//#endregion
|
|
14185
|
+
//#region src/license/validator.ts
|
|
14186
|
+
createSubsystemLogger("license");
|
|
14187
|
+
function resolveLicensePath() {
|
|
14188
|
+
return path.join(resolveStateDir(), "license.json");
|
|
14189
|
+
}
|
|
14190
|
+
function loadLicense() {
|
|
14191
|
+
try {
|
|
14192
|
+
const filePath = resolveLicensePath();
|
|
14193
|
+
if (!fs.existsSync(filePath)) return null;
|
|
14194
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
14195
|
+
return JSON.parse(raw);
|
|
14196
|
+
} catch {
|
|
14197
|
+
return null;
|
|
14198
|
+
}
|
|
14199
|
+
}
|
|
14200
|
+
function saveLicense(license) {
|
|
14201
|
+
const filePath = resolveLicensePath();
|
|
14202
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
14203
|
+
fs.writeFileSync(filePath, `${JSON.stringify(license, null, 2)}\n`, { mode: 384 });
|
|
14204
|
+
}
|
|
14205
|
+
|
|
14206
|
+
//#endregion
|
|
14207
|
+
//#region src/license/stripe-checkout.ts
|
|
14208
|
+
/**
|
|
14209
|
+
* Stripe Checkout Integration for NoxSoft Subscriptions
|
|
14210
|
+
*
|
|
14211
|
+
* Creates Stripe checkout sessions for NoxSoft license tiers.
|
|
14212
|
+
* Handles subscription lifecycle: create, upgrade, cancel.
|
|
14213
|
+
*
|
|
14214
|
+
* Configuration via environment:
|
|
14215
|
+
* STRIPE_SECRET_KEY — Stripe API secret key
|
|
14216
|
+
* STRIPE_WEBHOOK_SECRET — Stripe webhook signing secret
|
|
14217
|
+
* NOXSOFT_BASE_URL — Base URL for redirect (default: https://anima.noxsoft.net)
|
|
14218
|
+
*
|
|
14219
|
+
* Price IDs are configured per tier. In test mode, Stripe test keys work.
|
|
14220
|
+
*/
|
|
14221
|
+
const log$4 = createSubsystemLogger("stripe-checkout");
|
|
14222
|
+
/** Default price IDs — set to null until Stripe dashboard is configured */
|
|
14223
|
+
const DEFAULT_PRICE_IDS = {
|
|
14224
|
+
community: null,
|
|
14225
|
+
noxsoft: null,
|
|
14226
|
+
hackathon: null,
|
|
14227
|
+
team: null,
|
|
14228
|
+
builder: null
|
|
14229
|
+
};
|
|
14230
|
+
function resolveStripeConfig() {
|
|
14231
|
+
const secretKey = process.env.STRIPE_SECRET_KEY?.trim();
|
|
14232
|
+
if (!secretKey) return null;
|
|
14233
|
+
return {
|
|
14234
|
+
secretKey,
|
|
14235
|
+
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET?.trim() ?? "",
|
|
14236
|
+
baseUrl: process.env.NOXSOFT_BASE_URL?.trim() ?? "https://anima.noxsoft.net",
|
|
14237
|
+
priceIds: {
|
|
14238
|
+
...DEFAULT_PRICE_IDS,
|
|
14239
|
+
...process.env.STRIPE_PRICE_NOXSOFT ? { noxsoft: process.env.STRIPE_PRICE_NOXSOFT } : {},
|
|
14240
|
+
...process.env.STRIPE_PRICE_TEAM ? { team: process.env.STRIPE_PRICE_TEAM } : {},
|
|
14241
|
+
...process.env.STRIPE_PRICE_BUILDER ? { builder: process.env.STRIPE_PRICE_BUILDER } : {}
|
|
14242
|
+
}
|
|
14243
|
+
};
|
|
14244
|
+
}
|
|
14245
|
+
async function stripeRequest(secretKey, method, path, body) {
|
|
14246
|
+
const url = `https://api.stripe.com/v1${path}`;
|
|
14247
|
+
const headers = {
|
|
14248
|
+
Authorization: `Bearer ${secretKey}`,
|
|
14249
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
14250
|
+
};
|
|
14251
|
+
const encodedBody = body ? Object.entries(body).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&") : void 0;
|
|
14252
|
+
const res = await fetch(url, {
|
|
14253
|
+
method,
|
|
14254
|
+
headers,
|
|
14255
|
+
body: encodedBody,
|
|
14256
|
+
signal: AbortSignal.timeout(15e3)
|
|
14257
|
+
});
|
|
14258
|
+
const data = await res.json();
|
|
14259
|
+
if (!res.ok) {
|
|
14260
|
+
const rawError = data.error?.message;
|
|
14261
|
+
const errMsg = typeof rawError === "string" ? rawError : res.statusText;
|
|
14262
|
+
throw new Error(`Stripe API error (${res.status}): ${errMsg}`);
|
|
14263
|
+
}
|
|
14264
|
+
return data;
|
|
14265
|
+
}
|
|
14266
|
+
/**
|
|
14267
|
+
* Create a Stripe Checkout session for a NoxSoft subscription.
|
|
14268
|
+
*/
|
|
14269
|
+
async function createCheckoutSession(params) {
|
|
14270
|
+
const config = resolveStripeConfig();
|
|
14271
|
+
if (!config) throw new Error("Stripe not configured. Set STRIPE_SECRET_KEY environment variable.");
|
|
14272
|
+
const priceId = config.priceIds[params.tier];
|
|
14273
|
+
if (!priceId) {
|
|
14274
|
+
if (params.tier === "community" || params.tier === "hackathon") throw new Error(`${params.tier} tier is free — no checkout needed.`);
|
|
14275
|
+
throw new Error(`Stripe price ID not configured for tier "${params.tier}". Set STRIPE_PRICE_${params.tier.toUpperCase()} environment variable.`);
|
|
14276
|
+
}
|
|
14277
|
+
const body = {
|
|
14278
|
+
mode: "subscription",
|
|
14279
|
+
"line_items[0][price]": priceId,
|
|
14280
|
+
"line_items[0][quantity]": "1",
|
|
14281
|
+
success_url: `${config.baseUrl}/subscription/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
14282
|
+
cancel_url: `${config.baseUrl}/subscription/cancel`,
|
|
14283
|
+
"metadata[agent_id]": params.agentId,
|
|
14284
|
+
"metadata[tier]": params.tier,
|
|
14285
|
+
"subscription_data[metadata][agent_id]": params.agentId,
|
|
14286
|
+
"subscription_data[metadata][tier]": params.tier
|
|
14287
|
+
};
|
|
14288
|
+
if (params.customerEmail) body["customer_email"] = params.customerEmail;
|
|
14289
|
+
const session = await stripeRequest(config.secretKey, "POST", "/checkout/sessions", body);
|
|
14290
|
+
log$4.info(`checkout session created: ${session.id} (tier: ${params.tier})`);
|
|
14291
|
+
return {
|
|
14292
|
+
sessionId: session.id,
|
|
14293
|
+
url: session.url
|
|
14294
|
+
};
|
|
14295
|
+
}
|
|
14296
|
+
/**
|
|
14297
|
+
* Verify a Stripe webhook signature.
|
|
14298
|
+
* Returns the parsed event if valid, null if invalid.
|
|
14299
|
+
*/
|
|
14300
|
+
function verifyWebhookSignature(payload, signature, webhookSecret) {
|
|
14301
|
+
if (!webhookSecret) {
|
|
14302
|
+
log$4.warn("webhook secret not configured — skipping signature verification");
|
|
14303
|
+
try {
|
|
14304
|
+
return JSON.parse(payload);
|
|
14305
|
+
} catch {
|
|
14306
|
+
return null;
|
|
14307
|
+
}
|
|
14308
|
+
}
|
|
14309
|
+
const elements = signature.split(",");
|
|
14310
|
+
const timestamp = elements.find((e) => e.startsWith("t="))?.slice(2);
|
|
14311
|
+
const sig = elements.find((e) => e.startsWith("v1="))?.slice(3);
|
|
14312
|
+
if (!timestamp || !sig) {
|
|
14313
|
+
log$4.warn("invalid webhook signature format");
|
|
14314
|
+
return null;
|
|
14315
|
+
}
|
|
14316
|
+
if (sig !== createHmac("sha256", webhookSecret).update(`${timestamp}.${payload}`).digest("hex")) {
|
|
14317
|
+
log$4.warn("webhook signature mismatch");
|
|
14318
|
+
return null;
|
|
14319
|
+
}
|
|
14320
|
+
const eventTime = parseInt(timestamp, 10) * 1e3;
|
|
14321
|
+
if (Math.abs(Date.now() - eventTime) > 3e5) {
|
|
14322
|
+
log$4.warn("webhook timestamp too old");
|
|
14323
|
+
return null;
|
|
14324
|
+
}
|
|
14325
|
+
try {
|
|
14326
|
+
return JSON.parse(payload);
|
|
14327
|
+
} catch {
|
|
14328
|
+
return null;
|
|
14329
|
+
}
|
|
14330
|
+
}
|
|
14331
|
+
/**
|
|
14332
|
+
* Handle a verified Stripe webhook event.
|
|
14333
|
+
* Updates the local license based on subscription status.
|
|
14334
|
+
*/
|
|
14335
|
+
async function handleWebhookEvent(event) {
|
|
14336
|
+
switch (event.type) {
|
|
14337
|
+
case "checkout.session.completed": {
|
|
14338
|
+
const session = event.data.object;
|
|
14339
|
+
const tier = session.metadata?.tier;
|
|
14340
|
+
const agentId = session.metadata?.agent_id;
|
|
14341
|
+
const subscriptionId = session.subscription;
|
|
14342
|
+
const customerId = session.customer;
|
|
14343
|
+
if (!tier || !agentId) {
|
|
14344
|
+
log$4.warn("checkout.session.completed missing metadata");
|
|
14345
|
+
return { handled: false };
|
|
14346
|
+
}
|
|
14347
|
+
saveLicense({
|
|
14348
|
+
tier,
|
|
14349
|
+
agentId,
|
|
14350
|
+
subscriptionId: subscriptionId ?? void 0,
|
|
14351
|
+
customerId: customerId ?? void 0,
|
|
14352
|
+
issuedAt: Date.now(),
|
|
14353
|
+
expiresAt: Date.now() + 720 * 60 * 60 * 1e3,
|
|
14354
|
+
signature: "",
|
|
14355
|
+
version: 1
|
|
14356
|
+
});
|
|
14357
|
+
log$4.info(`license activated: ${tier} for ${agentId} (sub: ${subscriptionId})`);
|
|
14358
|
+
return {
|
|
14359
|
+
handled: true,
|
|
14360
|
+
action: "license_activated"
|
|
14361
|
+
};
|
|
14362
|
+
}
|
|
14363
|
+
case "customer.subscription.updated": {
|
|
14364
|
+
const sub = event.data.object;
|
|
14365
|
+
const status = sub.status;
|
|
14366
|
+
const tier = sub.metadata?.tier;
|
|
14367
|
+
if (status === "active" && tier) {
|
|
14368
|
+
const existing = loadLicense();
|
|
14369
|
+
if (existing) {
|
|
14370
|
+
existing.tier = tier;
|
|
14371
|
+
existing.expiresAt = Date.now() + 720 * 60 * 60 * 1e3;
|
|
14372
|
+
saveLicense(existing);
|
|
14373
|
+
log$4.info(`subscription updated: ${tier}`);
|
|
14374
|
+
}
|
|
14375
|
+
}
|
|
14376
|
+
return {
|
|
14377
|
+
handled: true,
|
|
14378
|
+
action: "subscription_updated"
|
|
14379
|
+
};
|
|
14380
|
+
}
|
|
14381
|
+
case "customer.subscription.deleted": {
|
|
14382
|
+
const existing = loadLicense();
|
|
14383
|
+
if (existing) {
|
|
14384
|
+
existing.tier = "community";
|
|
14385
|
+
existing.expiresAt = Date.now() + 336 * 60 * 60 * 1e3;
|
|
14386
|
+
saveLicense(existing);
|
|
14387
|
+
log$4.info("subscription cancelled — downgraded to community with 14-day grace");
|
|
14388
|
+
}
|
|
14389
|
+
return {
|
|
14390
|
+
handled: true,
|
|
14391
|
+
action: "subscription_cancelled"
|
|
14392
|
+
};
|
|
14393
|
+
}
|
|
14394
|
+
case "invoice.payment_failed":
|
|
14395
|
+
log$4.warn("payment failed — license will expire at end of current period");
|
|
14396
|
+
return {
|
|
14397
|
+
handled: true,
|
|
14398
|
+
action: "payment_failed"
|
|
14399
|
+
};
|
|
14400
|
+
default: return { handled: false };
|
|
14401
|
+
}
|
|
14402
|
+
}
|
|
14403
|
+
/**
|
|
14404
|
+
* Create a Stripe Customer Portal session for managing subscriptions.
|
|
14405
|
+
*/
|
|
14406
|
+
async function createPortalSession(customerId) {
|
|
14407
|
+
const config = resolveStripeConfig();
|
|
14408
|
+
if (!config) throw new Error("Stripe not configured.");
|
|
14409
|
+
return { url: (await stripeRequest(config.secretKey, "POST", "/billing_portal/sessions", {
|
|
14410
|
+
customer: customerId,
|
|
14411
|
+
return_url: `${config.baseUrl}/settings`
|
|
14412
|
+
})).url };
|
|
14413
|
+
}
|
|
14414
|
+
/**
|
|
14415
|
+
* Check if Stripe is configured and ready.
|
|
14416
|
+
*/
|
|
14417
|
+
function isStripeConfigured() {
|
|
14418
|
+
return resolveStripeConfig() !== null;
|
|
14419
|
+
}
|
|
14420
|
+
/**
|
|
14421
|
+
* Get subscription status from Stripe.
|
|
14422
|
+
*/
|
|
14423
|
+
async function getSubscriptionStatus(subscriptionId) {
|
|
14424
|
+
const config = resolveStripeConfig();
|
|
14425
|
+
if (!config) return null;
|
|
14426
|
+
try {
|
|
14427
|
+
const sub = await stripeRequest(config.secretKey, "GET", `/subscriptions/${subscriptionId}`);
|
|
14428
|
+
return {
|
|
14429
|
+
status: sub.status,
|
|
14430
|
+
currentPeriodEnd: sub.current_period_end * 1e3,
|
|
14431
|
+
cancelAtPeriodEnd: sub.cancel_at_period_end
|
|
14432
|
+
};
|
|
14433
|
+
} catch (err) {
|
|
14434
|
+
log$4.error(`failed to get subscription status: ${String(err)}`);
|
|
14435
|
+
return null;
|
|
14436
|
+
}
|
|
14437
|
+
}
|
|
14438
|
+
|
|
14439
|
+
//#endregion
|
|
14440
|
+
//#region src/gateway/server-methods/subscription.ts
|
|
14441
|
+
const subscriptionHandlers = {
|
|
14442
|
+
"subscription.status": async ({ respond }) => {
|
|
14443
|
+
try {
|
|
14444
|
+
const license = loadLicense();
|
|
14445
|
+
const stripeReady = isStripeConfigured();
|
|
14446
|
+
if (!license) {
|
|
14447
|
+
respond(true, {
|
|
14448
|
+
tier: "community",
|
|
14449
|
+
stripeConfigured: stripeReady,
|
|
14450
|
+
active: true,
|
|
14451
|
+
message: "Running on community (free) tier."
|
|
14452
|
+
}, void 0);
|
|
14453
|
+
return;
|
|
14454
|
+
}
|
|
14455
|
+
let stripeStatus = null;
|
|
14456
|
+
if (license.subscriptionId && stripeReady) stripeStatus = await getSubscriptionStatus(license.subscriptionId);
|
|
14457
|
+
respond(true, {
|
|
14458
|
+
tier: license.tier,
|
|
14459
|
+
active: !license.expiresAt || license.expiresAt > Date.now(),
|
|
14460
|
+
expiresAt: license.expiresAt,
|
|
14461
|
+
stripeConfigured: stripeReady,
|
|
14462
|
+
subscriptionId: license.subscriptionId,
|
|
14463
|
+
stripeStatus: stripeStatus?.status,
|
|
14464
|
+
cancelAtPeriodEnd: stripeStatus?.cancelAtPeriodEnd
|
|
14465
|
+
}, void 0);
|
|
14466
|
+
} catch (error) {
|
|
14467
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14468
|
+
}
|
|
14469
|
+
},
|
|
14470
|
+
"subscription.checkout": async ({ params, respond }) => {
|
|
14471
|
+
const tier = typeof params.tier === "string" ? params.tier.trim() : "";
|
|
14472
|
+
if (!tier) {
|
|
14473
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "tier is required"));
|
|
14474
|
+
return;
|
|
14475
|
+
}
|
|
14476
|
+
try {
|
|
14477
|
+
const result = await createCheckoutSession({
|
|
14478
|
+
tier,
|
|
14479
|
+
agentId: typeof params.agentId === "string" ? params.agentId : "default",
|
|
14480
|
+
customerEmail: typeof params.email === "string" ? params.email : void 0
|
|
14481
|
+
});
|
|
14482
|
+
respond(true, {
|
|
14483
|
+
sessionId: result.sessionId,
|
|
14484
|
+
checkoutUrl: result.url
|
|
14485
|
+
}, void 0);
|
|
14486
|
+
} catch (error) {
|
|
14487
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14488
|
+
}
|
|
14489
|
+
},
|
|
14490
|
+
"subscription.portal": async ({ params, respond }) => {
|
|
14491
|
+
try {
|
|
14492
|
+
const license = loadLicense();
|
|
14493
|
+
const customerId = typeof params.customerId === "string" ? params.customerId : license?.customerId;
|
|
14494
|
+
if (!customerId || typeof customerId !== "string") {
|
|
14495
|
+
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, "No customer ID found. Purchase a subscription first."));
|
|
14496
|
+
return;
|
|
14497
|
+
}
|
|
14498
|
+
respond(true, { portalUrl: (await createPortalSession(customerId)).url }, void 0);
|
|
14499
|
+
} catch (error) {
|
|
14500
|
+
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, String(error)));
|
|
14501
|
+
}
|
|
14502
|
+
}
|
|
14503
|
+
};
|
|
14504
|
+
|
|
13006
14505
|
//#endregion
|
|
13007
14506
|
//#region src/gateway/server-methods/system.ts
|
|
13008
14507
|
const systemHandlers = {
|
|
@@ -14449,6 +15948,10 @@ const coreGatewayHandlers = {
|
|
|
14449
15948
|
...updateHandlers,
|
|
14450
15949
|
...nodeHandlers,
|
|
14451
15950
|
...orgHandlers,
|
|
15951
|
+
...egoHandlers,
|
|
15952
|
+
...steerHandlers,
|
|
15953
|
+
...legacyHandlers,
|
|
15954
|
+
...subscriptionHandlers,
|
|
14452
15955
|
...sendHandlers,
|
|
14453
15956
|
...usageHandlers,
|
|
14454
15957
|
...agentHandlers,
|
|
@@ -15923,7 +17426,7 @@ function normalizeBasePath(rawPath) {
|
|
|
15923
17426
|
return normalized.replace(/\/+$/, "");
|
|
15924
17427
|
}
|
|
15925
17428
|
async function prepareCanvasRoot(rootDir) {
|
|
15926
|
-
await ensureDir(rootDir);
|
|
17429
|
+
await ensureDir$1(rootDir);
|
|
15927
17430
|
const rootReal = await fs$1.realpath(rootDir);
|
|
15928
17431
|
try {
|
|
15929
17432
|
const indexPath = path.join(rootReal, "index.html");
|
|
@@ -16445,6 +17948,89 @@ function setSseHeaders(res) {
|
|
|
16445
17948
|
res.flushHeaders?.();
|
|
16446
17949
|
}
|
|
16447
17950
|
|
|
17951
|
+
//#endregion
|
|
17952
|
+
//#region src/gateway/http-rate-limit.ts
|
|
17953
|
+
const log$3 = createSubsystemLogger("http-rate-limit");
|
|
17954
|
+
var HttpRateLimiter = class {
|
|
17955
|
+
constructor(config) {
|
|
17956
|
+
this.windows = /* @__PURE__ */ new Map();
|
|
17957
|
+
this.maxRequests = config?.maxRequests ?? 100;
|
|
17958
|
+
this.windowMs = config?.windowMs ?? 6e4;
|
|
17959
|
+
this.exemptLoopback = config?.exemptLoopback ?? true;
|
|
17960
|
+
const pruneInterval = config?.pruneIntervalMs ?? 3e5;
|
|
17961
|
+
this.pruneTimer = setInterval(() => this.prune(), pruneInterval);
|
|
17962
|
+
this.pruneTimer.unref?.();
|
|
17963
|
+
}
|
|
17964
|
+
/**
|
|
17965
|
+
* Check if a request from this IP is allowed.
|
|
17966
|
+
* Returns { allowed, remaining, retryAfterMs }.
|
|
17967
|
+
*/
|
|
17968
|
+
check(ip) {
|
|
17969
|
+
if (this.exemptLoopback && isLoopbackAddress(ip)) return {
|
|
17970
|
+
allowed: true,
|
|
17971
|
+
remaining: this.maxRequests,
|
|
17972
|
+
retryAfterMs: 0
|
|
17973
|
+
};
|
|
17974
|
+
const now = Date.now();
|
|
17975
|
+
const windowStart = now - this.windowMs;
|
|
17976
|
+
let window = this.windows.get(ip);
|
|
17977
|
+
if (!window) {
|
|
17978
|
+
window = { timestamps: [] };
|
|
17979
|
+
this.windows.set(ip, window);
|
|
17980
|
+
}
|
|
17981
|
+
window.timestamps = window.timestamps.filter((t) => t > windowStart);
|
|
17982
|
+
if (window.timestamps.length >= this.maxRequests) {
|
|
17983
|
+
const retryAfterMs = window.timestamps[0] + this.windowMs - now;
|
|
17984
|
+
return {
|
|
17985
|
+
allowed: false,
|
|
17986
|
+
remaining: 0,
|
|
17987
|
+
retryAfterMs: Math.max(retryAfterMs, 1e3)
|
|
17988
|
+
};
|
|
17989
|
+
}
|
|
17990
|
+
window.timestamps.push(now);
|
|
17991
|
+
return {
|
|
17992
|
+
allowed: true,
|
|
17993
|
+
remaining: this.maxRequests - window.timestamps.length,
|
|
17994
|
+
retryAfterMs: 0
|
|
17995
|
+
};
|
|
17996
|
+
}
|
|
17997
|
+
/**
|
|
17998
|
+
* Remove stale entries to prevent unbounded memory growth.
|
|
17999
|
+
*/
|
|
18000
|
+
prune() {
|
|
18001
|
+
const windowStart = Date.now() - this.windowMs;
|
|
18002
|
+
let pruned = 0;
|
|
18003
|
+
for (const [ip, window] of this.windows) {
|
|
18004
|
+
window.timestamps = window.timestamps.filter((t) => t > windowStart);
|
|
18005
|
+
if (window.timestamps.length === 0) {
|
|
18006
|
+
this.windows.delete(ip);
|
|
18007
|
+
pruned++;
|
|
18008
|
+
}
|
|
18009
|
+
}
|
|
18010
|
+
if (pruned > 0) log$3.debug(`pruned ${pruned} stale rate limit entries`);
|
|
18011
|
+
}
|
|
18012
|
+
/**
|
|
18013
|
+
* Stop the pruning timer.
|
|
18014
|
+
*/
|
|
18015
|
+
stop() {
|
|
18016
|
+
if (this.pruneTimer) {
|
|
18017
|
+
clearInterval(this.pruneTimer);
|
|
18018
|
+
this.pruneTimer = void 0;
|
|
18019
|
+
}
|
|
18020
|
+
}
|
|
18021
|
+
/**
|
|
18022
|
+
* Get current tracked IP count (for monitoring).
|
|
18023
|
+
*/
|
|
18024
|
+
getTrackedCount() {
|
|
18025
|
+
return this.windows.size;
|
|
18026
|
+
}
|
|
18027
|
+
};
|
|
18028
|
+
/** Default: 100 req/min per IP */
|
|
18029
|
+
const DEFAULT_RATE_LIMIT = {
|
|
18030
|
+
maxRequests: 100,
|
|
18031
|
+
windowMs: 6e4
|
|
18032
|
+
};
|
|
18033
|
+
|
|
16448
18034
|
//#endregion
|
|
16449
18035
|
//#region src/gateway/http-utils.ts
|
|
16450
18036
|
function getHeader(req, name) {
|
|
@@ -18161,6 +19747,7 @@ function createHooksRequestHandler(opts) {
|
|
|
18161
19747
|
}
|
|
18162
19748
|
function createGatewayHttpServer(opts) {
|
|
18163
19749
|
const { canvasHost, clients, controlUiEnabled, controlUiBasePath, controlUiRoot, openAiChatCompletionsEnabled, openResponsesEnabled, openResponsesConfig, handleHooksRequest, handlePluginRequest, resolvedAuth, rateLimiter } = opts;
|
|
19750
|
+
const httpRateLimiter = new HttpRateLimiter(DEFAULT_RATE_LIMIT);
|
|
18164
19751
|
const httpServer = opts.tlsOptions ? createServer$1(opts.tlsOptions, (req, res) => {
|
|
18165
19752
|
handleRequest(req, res);
|
|
18166
19753
|
}) : createServer((req, res) => {
|
|
@@ -18168,10 +19755,30 @@ function createGatewayHttpServer(opts) {
|
|
|
18168
19755
|
});
|
|
18169
19756
|
async function handleRequest(req, res) {
|
|
18170
19757
|
if (String(req.headers.upgrade ?? "").toLowerCase() === "websocket") return;
|
|
19758
|
+
const clientIp = resolveGatewayClientIp(req, []);
|
|
19759
|
+
const rateCheck = httpRateLimiter.check(clientIp);
|
|
19760
|
+
if (!rateCheck.allowed) {
|
|
19761
|
+
const retryAfterSec = Math.ceil(rateCheck.retryAfterMs / 1e3);
|
|
19762
|
+
res.writeHead(429, {
|
|
19763
|
+
"Content-Type": "application/json",
|
|
19764
|
+
"Retry-After": String(retryAfterSec),
|
|
19765
|
+
"X-RateLimit-Limit": String(DEFAULT_RATE_LIMIT.maxRequests ?? 100),
|
|
19766
|
+
"X-RateLimit-Remaining": "0"
|
|
19767
|
+
});
|
|
19768
|
+
res.end(JSON.stringify({
|
|
19769
|
+
error: "Too Many Requests",
|
|
19770
|
+
retryAfterMs: rateCheck.retryAfterMs
|
|
19771
|
+
}));
|
|
19772
|
+
return;
|
|
19773
|
+
}
|
|
18171
19774
|
try {
|
|
18172
19775
|
const configSnapshot = loadConfig();
|
|
18173
19776
|
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
|
18174
19777
|
const requestPath = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
19778
|
+
if (requestPath === "/webhook/stripe" && req.method === "POST") {
|
|
19779
|
+
await handleStripeWebhook(req, res);
|
|
19780
|
+
return;
|
|
19781
|
+
}
|
|
18175
19782
|
if (await handleHooksRequest(req, res)) return;
|
|
18176
19783
|
if (await handleToolsInvokeHttpRequest(req, res, {
|
|
18177
19784
|
auth: resolvedAuth,
|
|
@@ -18250,6 +19857,33 @@ function createGatewayHttpServer(opts) {
|
|
|
18250
19857
|
res.end("Internal Server Error");
|
|
18251
19858
|
}
|
|
18252
19859
|
}
|
|
19860
|
+
async function handleStripeWebhook(req, res) {
|
|
19861
|
+
const config = resolveStripeConfig();
|
|
19862
|
+
if (!config) {
|
|
19863
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
19864
|
+
res.end(JSON.stringify({ error: "Stripe not configured" }));
|
|
19865
|
+
return;
|
|
19866
|
+
}
|
|
19867
|
+
const chunks = [];
|
|
19868
|
+
for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
19869
|
+
const event = verifyWebhookSignature(Buffer.concat(chunks).toString("utf8"), String(req.headers["stripe-signature"] ?? ""), config.webhookSecret);
|
|
19870
|
+
if (!event) {
|
|
19871
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
19872
|
+
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
19873
|
+
return;
|
|
19874
|
+
}
|
|
19875
|
+
try {
|
|
19876
|
+
const result = await handleWebhookEvent(event);
|
|
19877
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19878
|
+
res.end(JSON.stringify({
|
|
19879
|
+
received: true,
|
|
19880
|
+
...result
|
|
19881
|
+
}));
|
|
19882
|
+
} catch (err) {
|
|
19883
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
19884
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
19885
|
+
}
|
|
19886
|
+
}
|
|
18253
19887
|
return httpServer;
|
|
18254
19888
|
}
|
|
18255
19889
|
function attachGatewayUpgradeHandler(opts) {
|