@xopcai/xopc 0.0.89 → 0.0.91
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/README.md +36 -12
- package/README.zh-CN.md +36 -12
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +41 -0
- package/dist/gateway/static/root/assets/agents-bVWUlrlD.js +222 -0
- package/dist/gateway/static/root/assets/apps-page-CIC8bmvZ.js +1 -0
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-CpyoFbs4.js → attachment-preview-renderer-DBAxQXb-.js} +2 -2
- package/dist/gateway/static/root/assets/{attachment-process-heavy-CqVriadb.js → attachment-process-heavy-Csq3TrrP.js} +4 -4
- package/dist/gateway/static/root/assets/channels-settings-C8G8RAAP.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-DaHGkRF1.js → channels-status-swr-CYWL5DLD.js} +1 -1
- package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +1 -0
- package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +1 -0
- package/dist/gateway/static/root/assets/cron-api-TVqLlGAC.js +1 -0
- package/dist/gateway/static/root/assets/cron-dreaming-jobs-Ip703-qM.js +2 -0
- package/dist/gateway/static/root/assets/cron-page-BtcFYlvv.js +1 -0
- package/dist/gateway/static/root/assets/dist-CUV1uY5f.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-CtuKJ9tE.js → extension-debug-page-mTLHRDp1.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-ykzjOkR5.js → extension-page-iI8BI7WK.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-Ce2qrdpO.js → extension-settings-page-ByXcdubM.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-C9FFJjuH.js → fetch-BWtQq_Ys.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-BFcrNeTU.js → field-primitives-BsZ-4VT5.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-CEg4Vr9R.js → heartbeat-config-api-WjTsRLCU.js} +1 -1
- package/dist/gateway/static/root/assets/{index-CZfy9oxs.js → index-CKkR-v9U.js} +101 -97
- package/dist/gateway/static/root/assets/index-VlELBY99.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-ClnIpxfd.js +1 -0
- package/dist/gateway/static/root/assets/note-detail-page-B91pLkEI.css +1 -0
- package/dist/gateway/static/root/assets/note-detail-page-DJ2Mb4x7.js +179 -0
- package/dist/gateway/static/root/assets/note-time-JLBPSLzK.js +1 -0
- package/dist/gateway/static/root/assets/notes-page-BE-75qz9.js +1 -0
- package/dist/gateway/static/root/assets/{pdf-BnEvgIXZ.js → pdf-epILhEOn.js} +1 -1
- package/dist/gateway/static/root/assets/preload-helper-zJ_50EbN.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-bJJkWtTl.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-BqdzA28u.js → settings-form-section-DSYCknxM.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-WcMXLq2U.js +3 -0
- package/dist/gateway/static/root/assets/share-preview-page-awRqs4hV.js +2 -0
- package/dist/gateway/static/root/assets/skills-page-Lu-i1JG7.js +2 -0
- package/dist/gateway/static/root/assets/{theme-store-CNqbmTNV.js → theme-store-BC-42BoZ.js} +1 -1
- package/dist/gateway/static/root/assets/toast-z0toXu32.js +1 -0
- package/dist/gateway/static/root/assets/url-CY1RQKTU.js +3 -0
- package/dist/gateway/static/root/assets/{utils-BWm2tG2w.js → utils-DX3TQuap.js} +1 -1
- package/dist/gateway/static/root/assets/vendor-codemirror-DYoKfS8f.js +45 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-B5uKlDqA.js +1 -0
- package/dist/gateway/static/root/assets/workflow-page.utils-ClC37yEp.js +1 -0
- package/dist/gateway/static/root/assets/workflows-page-C7VhIXtR.js +27 -0
- package/dist/gateway/static/root/index.html +11 -7
- package/dist/package.js +1 -1
- package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js +20 -18
- package/dist/src/agent/skills/marketplace/adapters/skillhub/adapter.js.map +1 -1
- package/dist/src/agent/tools/cronjob-tool.d.ts +6 -0
- package/dist/src/agent/tools/cronjob-tool.js +74 -9
- package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
- package/dist/src/agent/tools/edit.d.ts +5 -1
- package/dist/src/agent/tools/edit.js +7 -5
- package/dist/src/agent/tools/edit.js.map +1 -1
- package/dist/src/agent/tools/factory.js +2 -2
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/write.d.ts +5 -1
- package/dist/src/agent/tools/write.js +7 -5
- package/dist/src/agent/tools/write.js.map +1 -1
- package/dist/src/agent/workflow/agent-progress.js +2 -0
- package/dist/src/agent/workflow/agent-progress.js.map +1 -1
- package/dist/src/agent/workflow/builtins/client-proposal.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/client-proposal.js +155 -0
- package/dist/src/agent/workflow/builtins/client-proposal.js.map +1 -0
- package/dist/src/agent/workflow/builtins/competitor-scan.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/competitor-scan.js +150 -0
- package/dist/src/agent/workflow/builtins/competitor-scan.js.map +1 -0
- package/dist/src/agent/workflow/builtins/content-draft.d.ts +13 -0
- package/dist/src/agent/workflow/builtins/content-draft.js +146 -0
- package/dist/src/agent/workflow/builtins/content-draft.js.map +1 -0
- package/dist/src/agent/workflow/builtins/content-repurpose.d.ts +11 -0
- package/dist/src/agent/workflow/builtins/content-repurpose.js +137 -0
- package/dist/src/agent/workflow/builtins/content-repurpose.js.map +1 -0
- package/dist/src/agent/workflow/builtins/decision-compare.d.ts +13 -0
- package/dist/src/agent/workflow/builtins/decision-compare.js +173 -0
- package/dist/src/agent/workflow/builtins/decision-compare.js.map +1 -0
- package/dist/src/agent/workflow/builtins/inbox-triage.d.ts +11 -0
- package/dist/src/agent/workflow/builtins/inbox-triage.js +148 -0
- package/dist/src/agent/workflow/builtins/inbox-triage.js.map +1 -0
- package/dist/src/agent/workflow/builtins/index.d.ts +10 -1
- package/dist/src/agent/workflow/builtins/index.js +46 -1
- package/dist/src/agent/workflow/builtins/index.js.map +1 -1
- package/dist/src/agent/workflow/builtins/meeting-prep.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/meeting-prep.js +144 -0
- package/dist/src/agent/workflow/builtins/meeting-prep.js.map +1 -0
- package/dist/src/agent/workflow/builtins/offer-design.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/offer-design.js +161 -0
- package/dist/src/agent/workflow/builtins/offer-design.js.map +1 -0
- package/dist/src/agent/workflow/builtins/weekly-review.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/weekly-review.js +131 -0
- package/dist/src/agent/workflow/builtins/weekly-review.js.map +1 -0
- package/dist/src/agent/workflow/step-labels.js +2 -2
- package/dist/src/agent/workflow/step-labels.js.map +1 -1
- package/dist/src/agent/workflow/subagent-runner.js +3 -1
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
- package/dist/src/agent/workflow/types.d.ts +4 -0
- package/dist/src/chat-commands/agent-edit.d.ts +4 -0
- package/dist/src/chat-commands/agent-edit.js +136 -0
- package/dist/src/chat-commands/agent-edit.js.map +1 -0
- package/dist/src/chat-commands/index.d.ts +1 -0
- package/dist/src/chat-commands/index.js +3 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/cli/bin.js +2 -0
- package/dist/src/cli/bin.js.map +1 -1
- package/dist/src/cli/commands/cron.js +42 -3
- package/dist/src/cli/commands/cron.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +79 -56
- package/dist/src/cli/commands/doctor/checks/session-integrity.js.map +1 -1
- package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
- package/dist/src/cli/commands/update.js +86 -79
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/commands/agents.config.d.ts +3 -2
- package/dist/src/commands/agents.config.js +5 -2
- package/dist/src/commands/agents.config.js.map +1 -1
- package/dist/src/config/agent-typed-models.d.ts +2 -7
- package/dist/src/config/agent-typed-models.js +3 -14
- package/dist/src/config/agent-typed-models.js.map +1 -1
- package/dist/src/config/localized-text.d.ts +6 -0
- package/dist/src/config/localized-text.js +42 -0
- package/dist/src/config/localized-text.js.map +1 -0
- package/dist/src/config/models-json.d.ts +6 -6
- package/dist/src/config/schema.d.ts +6 -21
- package/dist/src/config/schema.js +4 -4
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/cron/executor.d.ts +2 -0
- package/dist/src/cron/executor.js +111 -1
- package/dist/src/cron/executor.js.map +1 -1
- package/dist/src/cron/types.d.ts +8 -1
- package/dist/src/cron/validation.d.ts +4 -0
- package/dist/src/cron/validation.js +4 -3
- package/dist/src/cron/validation.js.map +1 -1
- package/dist/src/cron/workflow-run-completion.d.ts +23 -0
- package/dist/src/cron/workflow-run-completion.js +72 -0
- package/dist/src/cron/workflow-run-completion.js.map +1 -0
- package/dist/src/extensions/update.d.ts +51 -0
- package/dist/src/extensions/update.js +260 -0
- package/dist/src/extensions/update.js.map +1 -0
- package/dist/src/gateway/agents-admin.d.ts +15 -8
- package/dist/src/gateway/agents-admin.js +77 -28
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +6 -0
- package/dist/src/gateway/hono/lib/config-payload.js +3 -1
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/middleware/auth.d.ts +2 -0
- package/dist/src/gateway/hono/middleware/auth.js +11 -7
- package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
- package/dist/src/gateway/hono/routes/agents.js +55 -12
- package/dist/src/gateway/hono/routes/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/agents.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/gateway.d.ts +2 -2
- package/dist/src/gateway/hono/routes/config-patch/gateway.js +12 -0
- package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/notes.d.ts +3 -0
- package/dist/src/gateway/hono/routes/notes.js +274 -0
- package/dist/src/gateway/hono/routes/notes.js.map +1 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/update.js +55 -107
- package/dist/src/gateway/hono/routes/update.js.map +1 -1
- package/dist/src/gateway/hono/routes/workflows.js +3 -1
- package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
- package/dist/src/gateway/server.js +2 -0
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +3 -0
- package/dist/src/gateway/service.js +12 -1
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/workspace-ripgrep.d.ts +6 -0
- package/dist/src/gateway/workspace-ripgrep.js +62 -11
- package/dist/src/gateway/workspace-ripgrep.js.map +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/infra/brew.d.ts +4 -0
- package/dist/src/infra/brew.js +20 -0
- package/dist/src/infra/brew.js.map +1 -0
- package/dist/src/infra/package-json.d.ts +2 -0
- package/dist/src/infra/package-json.js +23 -0
- package/dist/src/infra/package-json.js.map +1 -0
- package/dist/src/infra/package-update-steps.d.ts +35 -0
- package/dist/src/infra/package-update-steps.js +304 -0
- package/dist/src/infra/package-update-steps.js.map +1 -0
- package/dist/src/infra/path-env.d.ts +11 -0
- package/dist/src/infra/path-env.js +90 -0
- package/dist/src/infra/path-env.js.map +1 -0
- package/dist/src/infra/path-prepend.d.ts +7 -0
- package/dist/src/infra/path-prepend.js +44 -0
- package/dist/src/infra/path-prepend.js.map +1 -0
- package/dist/src/infra/stable-node-path.d.ts +2 -0
- package/dist/src/infra/stable-node-path.js +28 -0
- package/dist/src/infra/stable-node-path.js.map +1 -0
- package/dist/src/infra/update-global.d.ts +30 -23
- package/dist/src/infra/update-global.js +113 -64
- package/dist/src/infra/update-global.js.map +1 -1
- package/dist/src/infra/update-log.d.ts +1 -0
- package/dist/src/infra/update-log.js +12 -0
- package/dist/src/infra/update-log.js.map +1 -0
- package/dist/src/infra/update-restart.d.ts +20 -0
- package/dist/src/infra/update-restart.js +165 -0
- package/dist/src/infra/update-restart.js.map +1 -0
- package/dist/src/infra/update-runner.d.ts +89 -1
- package/dist/src/infra/update-runner.js +604 -173
- package/dist/src/infra/update-runner.js.map +1 -1
- package/dist/src/infra/update-startup.d.ts +3 -0
- package/dist/src/infra/update-startup.js +8 -4
- package/dist/src/infra/update-startup.js.map +1 -1
- package/dist/src/notes/attachment-ref.d.ts +9 -0
- package/dist/src/notes/attachment-ref.js +27 -0
- package/dist/src/notes/attachment-ref.js.map +1 -0
- package/dist/src/notes/index.d.ts +4 -0
- package/dist/src/notes/index.js +4 -0
- package/dist/src/notes/note-attachment-sync.d.ts +7 -0
- package/dist/src/notes/note-attachment-sync.js +46 -0
- package/dist/src/notes/note-attachment-sync.js.map +1 -0
- package/dist/src/notes/note-index-meta.d.ts +14 -0
- package/dist/src/notes/note-index-meta.js +87 -0
- package/dist/src/notes/note-index-meta.js.map +1 -0
- package/dist/src/notes/paths.d.ts +5 -0
- package/dist/src/notes/paths.js +23 -0
- package/dist/src/notes/paths.js.map +1 -0
- package/dist/src/notes/service.d.ts +42 -0
- package/dist/src/notes/service.js +331 -0
- package/dist/src/notes/service.js.map +1 -0
- package/dist/src/notes/store.d.ts +33 -0
- package/dist/src/notes/store.js +317 -0
- package/dist/src/notes/store.js.map +1 -0
- package/dist/src/notes/types.d.ts +162 -0
- package/dist/src/notes/types.js +1 -0
- package/dist/src/routing/resolve-route.d.ts +3 -1
- package/dist/src/routing/resolve-route.js.map +1 -1
- package/dist/src/session/store.d.ts +5 -3
- package/dist/src/session/store.js +66 -20
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/utils/logger/stats.d.ts +1 -1
- package/dist/src/workflows/domain/event.d.ts +3 -0
- package/dist/src/workflows/domain/run.d.ts +3 -0
- package/dist/src/workflows/domain/run.js.map +1 -1
- package/dist/src/workflows/engine/projector.js +17 -0
- package/dist/src/workflows/engine/projector.js.map +1 -1
- package/dist/src/workflows/engine/workflow-engine.js +127 -0
- package/dist/src/workflows/engine/workflow-engine.js.map +1 -1
- package/dist/src/workflows/index.js +1 -1
- package/dist/src/workflows/service/run-view-to-snapshot.js +3 -1
- package/dist/src/workflows/service/run-view-to-snapshot.js.map +1 -1
- package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
- package/dist/src/workflows/service/workflow-run-service.js +4 -1
- package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
- package/dist/src/workflows/service/workflow-session-bridge.js +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-B6PJB07W.js +0 -222
- package/dist/gateway/static/root/assets/apps-page-BOr0B1wv.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BelUKggl.js +0 -1
- package/dist/gateway/static/root/assets/cron-api-CjOg-BIj.js +0 -1
- package/dist/gateway/static/root/assets/cron-dreaming-jobs-DueM3rBz.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-DhoZmZXb.js +0 -1
- package/dist/gateway/static/root/assets/dist-6LecgDx5.js +0 -1
- package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +0 -45
- package/dist/gateway/static/root/assets/index-CiN1cQiQ.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-BwWLfqvd.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DV5WN8uk.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-CfOBRbPX.js +0 -3
- package/dist/gateway/static/root/assets/share-preview-page-Di5Bzh4g.js +0 -2
- package/dist/gateway/static/root/assets/skills-page-D0H5Kaxg.js +0 -2
- package/dist/gateway/static/root/assets/url-aYn-Rj1C.js +0 -7
- package/dist/gateway/static/root/assets/vendor-codemirror-D0yxdRpg.js +0 -58
- package/dist/gateway/static/root/assets/voice-api-key-field-X2UfnHeq.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-BOPpO3NG.js +0 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts\n\nimport { spawn } from 'node:child_process';\nimport { access, readdir, unlink } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport type { UpdateChannel } from './update-channels.js';\n\nconst log = createLogger('UpdateRunner');\n\nconst AUTO_UPDATE_TIMEOUT_MS = 45 * 60 * 1000; // 45 minutes\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n};\n\ntype SpawnUpdateParams = {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n};\n\nfunction createLineEmitter(\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>,\n) {\n let bufOut = '';\n let bufErr = '';\n const flush = (buf: string, source: 'stdout' | 'stderr'): string => {\n const parts = buf.split('\\n');\n const rest = parts.pop() ?? '';\n for (const line of parts) {\n if (line.length) void onProgress?.(line, source);\n }\n return rest;\n };\n return {\n pushStdout(chunk: string) {\n bufOut += chunk;\n bufOut = flush(bufOut, 'stdout');\n },\n pushStderr(chunk: string) {\n bufErr += chunk;\n bufErr = flush(bufErr, 'stderr');\n },\n flushEnd() {\n if (bufOut.length) void onProgress?.(bufOut, 'stdout');\n if (bufErr.length) void onProgress?.(bufErr, 'stderr');\n },\n };\n}\n\nasync function spawnUpdateCommand(params: SpawnUpdateParams): Promise<AutoUpdateResult> {\n const timeoutMs = params.timeoutMs ?? AUTO_UPDATE_TIMEOUT_MS;\n const baseArgs = ['update', '--yes', '--channel', params.channel, '--json'];\n const argv = await resolveUpdateCommandArgv(baseArgs, params.root ?? null);\n\n return new Promise<AutoUpdateResult>((resolve) => {\n const child = spawn(argv[0], argv.slice(1), {\n env: {\n ...process.env,\n XOPC_AUTO_UPDATE: '1',\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n detached: false,\n });\n\n let stdout = '';\n let stderr = '';\n let stdoutTruncated = false;\n let stderrTruncated = false;\n\n const lineEmitter = createLineEmitter(params.onProgress);\n\n const timeoutId = setTimeout(() => {\n child.kill('SIGTERM');\n }, timeoutMs);\n\n const finish = (result: AutoUpdateResult) => {\n clearTimeout(timeoutId);\n lineEmitter.flushEnd();\n resolve(result);\n };\n\n child.stdout?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stdout += text;\n lineEmitter.pushStdout(text);\n if (stdout.length > 64_000) {\n if (!stdoutTruncated) {\n log.warn('Update command stdout exceeded 64KB; truncating');\n stdoutTruncated = true;\n }\n stdout = stdout.slice(-32_000);\n }\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n const text = chunk.toString();\n stderr += text;\n lineEmitter.pushStderr(text);\n if (stderr.length > 64_000) {\n if (!stderrTruncated) {\n log.warn('Update command stderr exceeded 64KB; truncating');\n stderrTruncated = true;\n }\n stderr = stderr.slice(-32_000);\n }\n });\n\n child.on('error', (err) => {\n log.error({ err }, `Update subprocess spawn error: ${err.message}`);\n finish({ ok: false, exitCode: null, reason: err.message, stdout, stderr });\n });\n\n child.on('exit', (code, signal) => {\n if (signal === 'SIGTERM' || code === 143) {\n log.warn({ code, signal }, 'Update subprocess timed out; attempting lock file cleanup');\n void cleanupNpmLockFiles(params.root).catch((cleanupErr) => {\n log.warn({ err: cleanupErr }, 'Failed to clean npm lock files after timeout');\n });\n finish({ ok: false, exitCode: code, reason: 'timeout', stdout, stderr });\n return;\n }\n finish({\n ok: code === 0,\n exitCode: code,\n reason: code === 0 ? undefined : 'non-zero-exit',\n stdout,\n stderr,\n });\n });\n });\n}\n\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nexport async function runAutoUpdateCommandWithProgress(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n}): Promise<AutoUpdateResult> {\n return spawnUpdateCommand(params);\n}\n\nasync function cleanupNpmLockFiles(root: string | null | undefined): Promise<void> {\n if (!root) return;\n let entries: string[];\n try {\n entries = await readdir(root);\n } catch {\n return;\n }\n for (const entry of entries) {\n if (entry.startsWith('.package-lock')) {\n try {\n await unlink(join(root, entry));\n } catch {\n // best-effort\n }\n }\n }\n}\n\n/**\n * Resolve the argv array for spawning the update command.\n *\n * Priority:\n * 1. process.execPath + process.argv[1] (current runtime + entry point)\n * 2. process.execPath + known dist entry points in root\n * 3. Fallback to bare `xopc` (assumes global install)\n */\nasync function resolveUpdateCommandArgv(\n baseArgs: string[],\n root: string | null,\n): Promise<string[]> {\n const execPath = process.execPath?.trim();\n const argv1 = process.argv[1]?.trim();\n\n if (execPath && argv1) {\n return [execPath, argv1, ...baseArgs];\n }\n\n if (execPath && root) {\n const candidates = [join(root, 'dist/src/cli/bin.js'), join(root, 'dist/index.js')];\n for (const candidate of candidates) {\n try {\n await access(candidate);\n return [execPath, candidate, ...baseArgs];\n } catch {\n // try next\n }\n }\n }\n\n log.warn('Falling back to bare `xopc` command — version mismatch possible');\n try {\n const { execSync } = await import('node:child_process');\n const cmd = process.platform === 'win32' ? 'where xopc' : 'which xopc';\n const whichResult = execSync(cmd, { encoding: 'utf-8', timeout: 3000 }).trim();\n if (whichResult) {\n log.info({ resolvedPath: whichResult.split(/\\r?\\n/)[0]?.trim() }, 'Resolved xopc via PATH');\n }\n } catch {\n log.warn('Could not resolve `xopc` in PATH; update command may fail');\n }\n\n return ['xopc', ...baseArgs];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"update-runner.js","names":[],"sources":["../../../src/infra/update-runner.ts"],"sourcesContent":["// src/infra/update-runner.ts — unified gateway/CLI update runner (OpenClaw-aligned)\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { readPackageName, readPackageVersion } from './package-json.js';\nimport { runGlobalPackageUpdateSteps, type PackageUpdateStepResult } from './package-update-steps.js';\nimport { createDefaultCommandRunner, runCommandWithTimeout } from './run-command.js';\nimport { resolveStableNodePath } from './stable-node-path.js';\nimport { trimLogTail } from './update-log.js';\nimport { DEFAULT_PACKAGE_CHANNEL, type UpdateChannel } from './update-channels.js';\nimport { compareSemver, resolveNpmChannelTag } from './update-check.js';\nimport { runPostUpdateExtensionSync, type ExtensionPostUpdateResult } from '../extensions/update.js';\nimport {\n cleanupGlobalRenameDirs,\n createGlobalInstallEnv,\n detectGlobalInstallManagerForRoot,\n resolveGlobalInstallSpec,\n resolveGlobalInstallTarget,\n XOPC_PACKAGE_NAME,\n type GlobalInstallManager,\n} from './update-global.js';\nimport {\n maybeRestartGatewayAfterUpdate,\n type InProcessRestartTrigger,\n type UpdateRestartResult,\n} from './update-restart.js';\n\nconst DEFAULT_TIMEOUT_MS = 45 * 60 * 1000;\nconst MAX_LOG_CHARS = 8000;\nconst CORE_PACKAGE_NAMES = new Set([XOPC_PACKAGE_NAME]);\n\nexport type UpdateStepResult = {\n name: string;\n command: string;\n cwd: string;\n durationMs: number;\n exitCode: number | null;\n stdoutTail?: string | null;\n stderrTail?: string | null;\n};\n\nexport type UpdatePostUpdateResult = {\n extensions?: ExtensionPostUpdateResult;\n restart?: UpdateRestartResult;\n};\n\nexport type UpdateRunResult = {\n status: 'ok' | 'error' | 'skipped';\n mode: 'git' | 'pnpm' | 'npm' | 'unknown';\n root?: string;\n reason?: string;\n before?: { sha?: string | null; version?: string | null };\n after?: { sha?: string | null; version?: string | null };\n steps: UpdateStepResult[];\n durationMs: number;\n postUpdate?: UpdatePostUpdateResult;\n};\n\nexport type UpdateStepInfo = {\n name: string;\n command: string;\n index: number;\n total: number;\n};\n\nexport type UpdateStepCompletion = UpdateStepInfo & {\n durationMs: number;\n exitCode: number | null;\n stderrTail?: string | null;\n};\n\nexport type UpdateStepProgress = {\n onStepStart?: (step: UpdateStepInfo) => void;\n onStepComplete?: (step: UpdateStepCompletion) => void;\n};\n\nexport type UpdateInstallSurface =\n | { kind: 'git'; mode: 'git'; root: string; packageRoot: string }\n | { kind: 'global'; mode: GlobalInstallManager; root: string; packageRoot: string }\n | { kind: 'package-root'; mode: 'unknown'; root: string; packageRoot: string }\n | { kind: 'missing'; mode: 'unknown'; root?: string; packageRoot?: undefined };\n\ntype UpdateRunnerOptions = {\n cwd?: string;\n argv1?: string;\n channel?: UpdateChannel;\n timeoutMs?: number;\n progress?: UpdateStepProgress;\n skipExtensionSync?: boolean;\n shouldRestart?: boolean;\n triggerInProcessRestart?: InProcessRestartTrigger;\n};\n\ntype CommandRunner = (\n argv: string[],\n options: { timeoutMs: number; cwd?: string; env?: NodeJS.ProcessEnv },\n) => Promise<{ stdout: string; stderr: string; code: number | null }>;\n\nfunction normalizeDir(value?: string | null): string | null {\n if (!value) return null;\n const trimmed = value.trim();\n return trimmed ? path.resolve(trimmed) : null;\n}\n\nfunction resolveNodeModulesBinPackageRoot(argv1: string): string | null {\n const normalized = path.resolve(argv1);\n const parts = normalized.split(path.sep);\n const binIndex = parts.lastIndexOf('.bin');\n if (binIndex <= 0 || parts[binIndex - 1] !== 'node_modules') return null;\n return path.join(parts.slice(0, binIndex).join(path.sep), path.basename(normalized));\n}\n\nfunction buildStartDirs(opts: UpdateRunnerOptions): string[] {\n const dirs: string[] = [];\n const cwd = normalizeDir(opts.cwd);\n if (cwd) dirs.push(cwd);\n const argv1 = normalizeDir(opts.argv1);\n if (argv1) {\n dirs.push(path.dirname(argv1));\n const packageRoot = resolveNodeModulesBinPackageRoot(argv1);\n if (packageRoot) dirs.push(packageRoot);\n }\n const proc = normalizeDir(process.cwd());\n if (proc) dirs.push(proc);\n return Array.from(new Set(dirs));\n}\n\nasync function resolveComparablePath(target: string): Promise<string> {\n return fs.realpath(target).catch(() => path.resolve(target));\n}\n\nasync function pathsReferToSameLocation(left: string, right: string): Promise<boolean> {\n return (await resolveComparablePath(left)) === (await resolveComparablePath(right));\n}\n\nasync function looksLikeGitCheckout(root: string): Promise<boolean> {\n try {\n await fs.access(path.join(root, '.git'));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function resolveGitRoot(\n runCommand: CommandRunner,\n candidates: string[],\n timeoutMs: number,\n): Promise<string | null> {\n for (const dir of candidates) {\n const res = await runCommand(['git', '-C', dir, 'rev-parse', '--show-toplevel'], {\n timeoutMs,\n }).catch(() => null);\n if (!res || res.code !== 0) continue;\n const root = res.stdout.trim();\n if (root) return root;\n }\n return null;\n}\n\nasync function findPackageRoot(candidates: string[]): Promise<string | null> {\n for (const dir of candidates) {\n let current = dir;\n for (let i = 0; i < 12; i += 1) {\n const pkgPath = path.join(current, 'package.json');\n try {\n const raw = await fs.readFile(pkgPath, 'utf-8');\n const parsed = JSON.parse(raw) as { name?: string };\n const name = parsed?.name?.trim();\n if (name && CORE_PACKAGE_NAMES.has(name)) return current;\n } catch {\n // ignore\n }\n const parent = path.dirname(current);\n if (parent === current) break;\n current = parent;\n }\n }\n return null;\n}\n\nfunction mergeCommandEnvironments(\n baseEnv: NodeJS.ProcessEnv | undefined,\n overrideEnv: NodeJS.ProcessEnv | undefined,\n): NodeJS.ProcessEnv | undefined {\n if (!baseEnv) return overrideEnv;\n if (!overrideEnv) return baseEnv;\n return { ...baseEnv, ...overrideEnv };\n}\n\nasync function buildUpdateCommandRunner(): Promise<{\n defaultCommandEnv: NodeJS.ProcessEnv | undefined;\n runCommand: CommandRunner;\n}> {\n const defaultCommandEnv = await createGlobalInstallEnv();\n return {\n defaultCommandEnv,\n runCommand: async (argv, options) => {\n const res = await runCommandWithTimeout(argv, {\n ...options,\n env: mergeCommandEnvironments(defaultCommandEnv, options.env),\n });\n return { stdout: res.stdout, stderr: res.stderr, code: res.code };\n },\n };\n}\n\ntype RunStepOptions = {\n runCommand: CommandRunner;\n name: string;\n argv: string[];\n cwd: string;\n timeoutMs: number;\n env?: NodeJS.ProcessEnv;\n progress?: UpdateStepProgress;\n stepIndex: number;\n totalSteps: number;\n};\n\nasync function runStep(opts: RunStepOptions): Promise<UpdateStepResult> {\n const { runCommand, name, argv, cwd, timeoutMs, env, progress, stepIndex, totalSteps } = opts;\n const command = argv.join(' ');\n const stepInfo: UpdateStepInfo = { name, command, index: stepIndex, total: totalSteps };\n progress?.onStepStart?.(stepInfo);\n const started = Date.now();\n const result = await runCommand(argv, { cwd, timeoutMs, env });\n const durationMs = Date.now() - started;\n const stderrTail = trimLogTail(result.stderr, MAX_LOG_CHARS);\n progress?.onStepComplete?.({ ...stepInfo, durationMs, exitCode: result.code, stderrTail });\n return {\n name,\n command,\n cwd,\n durationMs,\n exitCode: result.code,\n stdoutTail: trimLogTail(result.stdout, MAX_LOG_CHARS),\n stderrTail,\n };\n}\n\nfunction toUpdateStepResult(step: PackageUpdateStepResult): UpdateStepResult {\n return {\n name: step.name,\n command: step.command,\n cwd: step.cwd,\n durationMs: step.durationMs,\n exitCode: step.exitCode,\n stdoutTail: step.stdoutTail,\n stderrTail: step.stderrTail,\n };\n}\n\nexport async function resolveUpdateInstallSurface(\n opts: Pick<UpdateRunnerOptions, 'cwd' | 'argv1' | 'timeoutMs'> = {},\n): Promise<UpdateInstallSurface> {\n const { runCommand } = await buildUpdateCommandRunner();\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const candidates = buildStartDirs(opts);\n const pkgRoot = await findPackageRoot(candidates);\n\n let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs);\n if (gitRoot && pkgRoot && !(await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n gitRoot = null;\n }\n if (gitRoot && !pkgRoot) {\n return { kind: 'missing', mode: 'unknown', root: gitRoot };\n }\n if (gitRoot && pkgRoot && (await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n return { kind: 'git', mode: 'git', root: gitRoot, packageRoot: pkgRoot };\n }\n if (!pkgRoot) {\n return { kind: 'missing', mode: 'unknown' };\n }\n\n const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs);\n if (globalManager) {\n return { kind: 'global', mode: globalManager, root: pkgRoot, packageRoot: pkgRoot };\n }\n\n return { kind: 'package-root', mode: 'unknown', root: pkgRoot, packageRoot: pkgRoot };\n}\n\nasync function runGitUpdate(params: {\n gitRoot: string;\n runCommand: CommandRunner;\n timeoutMs: number;\n progress?: UpdateStepProgress;\n defaultCommandEnv?: NodeJS.ProcessEnv;\n}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const steps: UpdateStepResult[] = [];\n const { gitRoot, runCommand, timeoutMs, progress, defaultCommandEnv } = params;\n const totalSteps = 7;\n let stepIndex = 0;\n const step = (name: string, argv: string[], cwd: string, env?: NodeJS.ProcessEnv) =>\n runStep({\n runCommand,\n name,\n argv,\n cwd,\n timeoutMs,\n env,\n progress,\n stepIndex: stepIndex++,\n totalSteps,\n });\n\n const beforeShaResult = await runCommand(['git', '-C', gitRoot, 'rev-parse', 'HEAD'], {\n cwd: gitRoot,\n timeoutMs,\n });\n const beforeSha = beforeShaResult.stdout.trim() || null;\n const beforeVersion = await readPackageVersion(gitRoot);\n\n const statusCheck = await step(\n 'clean check',\n ['git', '-C', gitRoot, 'status', '--porcelain'],\n gitRoot,\n );\n steps.push(statusCheck);\n if (statusCheck.stdoutTail?.trim()) {\n return {\n status: 'skipped',\n mode: 'git',\n root: gitRoot,\n reason: 'dirty',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const fetchStep = await step(\n 'git fetch',\n ['git', '-C', gitRoot, 'fetch', '--all', '--prune', '--tags'],\n gitRoot,\n );\n steps.push(fetchStep);\n if (fetchStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'fetch-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const upstreamStep = await step(\n 'upstream check',\n ['git', '-C', gitRoot, 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'],\n gitRoot,\n );\n steps.push(upstreamStep);\n if (upstreamStep.exitCode !== 0) {\n return {\n status: 'skipped',\n mode: 'git',\n root: gitRoot,\n reason: 'no-upstream',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const rebaseStep = await step(\n 'git rebase',\n ['git', '-C', gitRoot, 'rebase', '@{upstream}'],\n gitRoot,\n );\n steps.push(rebaseStep);\n if (rebaseStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'rebase-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const depsStep = await step('deps install', ['pnpm', 'install'], gitRoot, defaultCommandEnv);\n steps.push(depsStep);\n if (depsStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'deps-install-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const buildStep = await step('build', ['pnpm', 'run', 'build'], gitRoot, defaultCommandEnv);\n steps.push(buildStep);\n if (buildStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'build-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n\n const doctorEntry = path.join(gitRoot, 'dist/src/cli/bin.js');\n const doctorEntryExists = await fs.stat(doctorEntry).then(() => true).catch(() => false);\n if (doctorEntryExists) {\n const doctorNodePath = await resolveStableNodePath(process.execPath);\n const doctorStep = await step(\n 'xopc doctor',\n [doctorNodePath, doctorEntry, 'doctor', '--fix'],\n gitRoot,\n { ...defaultCommandEnv, XOPC_UPDATE_IN_PROGRESS: '1' },\n );\n steps.push(doctorStep);\n if (doctorStep.exitCode !== 0) {\n return {\n status: 'error',\n mode: 'git',\n root: gitRoot,\n reason: 'doctor-failed',\n before: { sha: beforeSha, version: beforeVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n }\n }\n\n const afterShaStep = await step(\n 'git rev-parse HEAD (after)',\n ['git', '-C', gitRoot, 'rev-parse', 'HEAD'],\n gitRoot,\n );\n steps.push(afterShaStep);\n const afterVersion = await readPackageVersion(gitRoot);\n\n return {\n status: 'ok',\n mode: 'git',\n root: gitRoot,\n before: { sha: beforeSha, version: beforeVersion },\n after: { sha: afterShaStep.stdoutTail?.trim() ?? null, version: afterVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n}\n\nasync function runGlobalUpdate(params: {\n pkgRoot: string;\n globalManager: GlobalInstallManager;\n channel: UpdateChannel;\n runCommand: CommandRunner;\n timeoutMs: number;\n defaultCommandEnv?: NodeJS.ProcessEnv;\n progress?: UpdateStepProgress;\n}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const beforeVersion = await readPackageVersion(params.pkgRoot);\n const resolved = await resolveNpmChannelTag({ channel: params.channel, timeoutMs: 10_000 });\n if (!resolved.version) {\n return {\n status: 'error',\n mode: params.globalManager,\n root: params.pkgRoot,\n reason: 'registry-unreachable',\n before: { version: beforeVersion },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const comparison = compareSemver(beforeVersion ?? '0.0.0', resolved.version);\n if (comparison !== null && comparison >= 0) {\n return {\n status: 'skipped',\n mode: params.globalManager,\n root: params.pkgRoot,\n reason: 'up-to-date',\n before: { version: beforeVersion },\n after: { version: beforeVersion },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const installTarget = await resolveGlobalInstallTarget({\n manager: params.globalManager,\n runCommand: params.runCommand,\n timeoutMs: params.timeoutMs,\n pkgRoot: params.pkgRoot,\n });\n const packageName = (await readPackageName(params.pkgRoot)) ?? XOPC_PACKAGE_NAME;\n if (installTarget.globalRoot) {\n await cleanupGlobalRenameDirs({ globalRoot: installTarget.globalRoot, packageName });\n }\n\n const spec = resolveGlobalInstallSpec({\n version: resolved.version,\n env: params.defaultCommandEnv,\n });\n\n let stepIndex = 0;\n const packageUpdate = await runGlobalPackageUpdateSteps({\n installTarget,\n installSpec: spec,\n packageName,\n packageRoot: params.pkgRoot,\n runCommand: params.runCommand,\n timeoutMs: params.timeoutMs,\n ...(params.defaultCommandEnv === undefined ? {} : { env: params.defaultCommandEnv }),\n installCwd: params.pkgRoot,\n runStep: async (stepParams) => {\n const result = await runStep({\n runCommand: params.runCommand,\n name: stepParams.name,\n argv: stepParams.argv,\n cwd: stepParams.cwd ?? params.pkgRoot,\n timeoutMs: stepParams.timeoutMs,\n env: stepParams.env,\n progress: params.progress,\n stepIndex: stepIndex++,\n totalSteps: 3,\n });\n return {\n name: result.name,\n command: result.command,\n cwd: result.cwd,\n durationMs: result.durationMs,\n exitCode: result.exitCode,\n stdoutTail: result.stdoutTail,\n stderrTail: result.stderrTail,\n };\n },\n });\n\n const steps = packageUpdate.steps.map(toUpdateStepResult);\n return {\n status: packageUpdate.failedStep ? 'error' : 'ok',\n mode: params.globalManager,\n root: packageUpdate.verifiedPackageRoot ?? params.pkgRoot,\n reason: packageUpdate.failedStep ? 'global-install-failed' : undefined,\n before: { version: beforeVersion },\n after: { version: packageUpdate.afterVersion },\n steps,\n durationMs: Date.now() - startedAt,\n };\n}\n\nexport async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<UpdateRunResult> {\n const startedAt = Date.now();\n const { defaultCommandEnv, runCommand } = await buildUpdateCommandRunner();\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL;\n const candidates = buildStartDirs(opts);\n const pkgRoot = await findPackageRoot(candidates);\n\n let gitRoot = await resolveGitRoot(runCommand, candidates, timeoutMs);\n if (!gitRoot && pkgRoot) {\n const cwdRoot = normalizeDir(opts.cwd);\n if (\n cwdRoot &&\n (await pathsReferToSameLocation(cwdRoot, pkgRoot)) &&\n (await looksLikeGitCheckout(cwdRoot))\n ) {\n gitRoot = await resolveComparablePath(cwdRoot);\n }\n }\n if (gitRoot && pkgRoot && !(await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n gitRoot = null;\n }\n\n if (gitRoot && pkgRoot && (await pathsReferToSameLocation(gitRoot, pkgRoot))) {\n return runGitUpdate({\n gitRoot,\n runCommand,\n timeoutMs,\n progress: opts.progress,\n defaultCommandEnv,\n });\n }\n\n if (!pkgRoot) {\n return {\n status: 'error',\n mode: 'unknown',\n reason: 'not-xopc-root',\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n }\n\n const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs);\n if (globalManager) {\n return runGlobalUpdate({\n pkgRoot,\n globalManager,\n channel,\n runCommand,\n timeoutMs,\n defaultCommandEnv,\n progress: opts.progress,\n });\n }\n\n return {\n status: 'skipped',\n mode: 'unknown',\n root: pkgRoot,\n reason: 'not-global-install',\n before: { version: await readPackageVersion(pkgRoot) },\n steps: [],\n durationMs: Date.now() - startedAt,\n };\n}\n\nexport async function runGatewayUpdateWithPostSteps(\n opts: UpdateRunnerOptions = {},\n): Promise<UpdateRunResult> {\n const channel = opts.channel ?? DEFAULT_PACKAGE_CHANNEL;\n const result = await runGatewayUpdate(opts);\n\n if (result.status !== 'ok') {\n return result;\n }\n\n const postUpdate: UpdatePostUpdateResult = {};\n\n if (!opts.skipExtensionSync) {\n const extensions = await runPostUpdateExtensionSync({\n channel,\n timeoutMs: opts.timeoutMs,\n });\n postUpdate.extensions = extensions;\n if (extensions.status === 'error') {\n return {\n ...result,\n status: 'error',\n reason: 'post-update-extensions',\n postUpdate,\n };\n }\n }\n\n const restart = await maybeRestartGatewayAfterUpdate({\n shouldRestart: opts.shouldRestart,\n expectedVersion: result.after?.version ?? undefined,\n triggerInProcessRestart: opts.triggerInProcessRestart,\n });\n postUpdate.restart = restart;\n\n return { ...result, postUpdate };\n}\n\nexport function formatUpdateApiResult(result: UpdateRunResult, channel: UpdateChannel): Record<string, unknown> {\n if (result.status === 'skipped' && result.reason === 'up-to-date') {\n return {\n status: 'up-to-date',\n currentVersion: result.before?.version ?? null,\n latestVersion: result.before?.version ?? null,\n channel,\n mode: result.mode,\n };\n }\n if (result.status === 'ok') {\n return {\n status: 'ok',\n previousVersion: result.before?.version ?? null,\n installedVersion: result.after?.version ?? null,\n channel,\n mode: result.mode,\n steps: result.steps.length,\n postUpdate: result.postUpdate ?? undefined,\n };\n }\n const failed = result.steps.find((step) => step.exitCode !== 0);\n return {\n status: 'error',\n reason: result.reason ?? 'update-failed',\n mode: result.mode,\n message: failed?.stderrTail ?? failed?.stdoutTail ?? result.reason,\n stderrTail: failed?.stderrTail ?? undefined,\n };\n}\n\nexport type AutoUpdateResult = {\n ok: boolean;\n exitCode: number | null;\n reason?: string;\n stdout?: string;\n stderr?: string;\n result?: UpdateRunResult;\n};\n\nexport async function runAutoUpdateCommand(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): Promise<AutoUpdateResult> {\n const result = await runGatewayUpdateWithPostSteps({\n channel: params.channel,\n cwd: params.root ?? undefined,\n argv1: process.argv[1],\n timeoutMs: params.timeoutMs,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n const payload = formatUpdateApiResult(result, params.channel);\n const stdout = JSON.stringify(payload);\n return {\n ok: result.status === 'ok' || (result.status === 'skipped' && result.reason === 'up-to-date'),\n exitCode: result.status === 'error' ? 1 : 0,\n reason: result.reason,\n stdout,\n stderr: result.steps.find((s) => s.exitCode !== 0)?.stderrTail ?? undefined,\n result,\n };\n}\n\nexport async function runAutoUpdateCommandWithProgress(params: {\n channel: UpdateChannel;\n root?: string | null;\n timeoutMs?: number;\n triggerInProcessRestart?: InProcessRestartTrigger;\n onProgress?: (line: string, source: 'stdout' | 'stderr') => void | Promise<void>;\n}): Promise<AutoUpdateResult> {\n const result = await runGatewayUpdateWithPostSteps({\n channel: params.channel,\n cwd: params.root ?? undefined,\n argv1: process.argv[1],\n timeoutMs: params.timeoutMs,\n triggerInProcessRestart: params.triggerInProcessRestart,\n progress: {\n onStepStart: (step) => void params.onProgress?.(`[${step.index + 1}/${step.total}] ${step.name}`, 'stdout'),\n onStepComplete: (step) => {\n if (step.exitCode !== 0 && step.stderrTail) {\n void params.onProgress?.(step.stderrTail, 'stderr');\n }\n },\n },\n });\n const payload = formatUpdateApiResult(result, params.channel);\n return {\n ok: result.status === 'ok' || (result.status === 'skipped' && result.reason === 'up-to-date'),\n exitCode: result.status === 'error' ? 1 : 0,\n reason: result.reason,\n stdout: JSON.stringify(payload),\n stderr: result.steps.find((s) => s.exitCode !== 0)?.stderrTail ?? undefined,\n result,\n };\n}\n\n// Legacy export for tests that import createDefaultCommandRunner path\nexport { createDefaultCommandRunner };\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Config } from '../config/schema.js';
|
|
2
2
|
import { type UpdateAvailable } from './update-check.js';
|
|
3
|
+
import type { InProcessRestartTrigger } from './update-restart.js';
|
|
3
4
|
/** Get the cached update-available state (populated after startup check). */
|
|
4
5
|
export declare function getUpdateAvailable(): UpdateAvailable | null;
|
|
5
6
|
/**
|
|
@@ -8,6 +9,7 @@ export declare function getUpdateAvailable(): UpdateAvailable | null;
|
|
|
8
9
|
export declare function runGatewayUpdateCheck(params: {
|
|
9
10
|
config: Config;
|
|
10
11
|
onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;
|
|
12
|
+
triggerInProcessRestart?: InProcessRestartTrigger;
|
|
11
13
|
/** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */
|
|
12
14
|
force?: boolean;
|
|
13
15
|
}): Promise<void>;
|
|
@@ -17,4 +19,5 @@ export declare function runGatewayUpdateCheck(params: {
|
|
|
17
19
|
export declare function scheduleGatewayUpdateCheck(params: {
|
|
18
20
|
config: Config;
|
|
19
21
|
onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;
|
|
22
|
+
triggerInProcessRestart?: InProcessRestartTrigger;
|
|
20
23
|
}): () => void;
|
|
@@ -136,7 +136,8 @@ async function runGatewayUpdateCheck(params) {
|
|
|
136
136
|
nextState,
|
|
137
137
|
now,
|
|
138
138
|
root,
|
|
139
|
-
config
|
|
139
|
+
config,
|
|
140
|
+
triggerInProcessRestart: params.triggerInProcessRestart
|
|
140
141
|
});
|
|
141
142
|
} else {
|
|
142
143
|
delete nextState.lastAvailableVersion;
|
|
@@ -203,16 +204,19 @@ async function handleAutoUpdate(params) {
|
|
|
203
204
|
const { runAutoUpdateCommand } = await import("./update-runner.js");
|
|
204
205
|
const result = await runAutoUpdateCommand({
|
|
205
206
|
channel,
|
|
206
|
-
root
|
|
207
|
+
root,
|
|
208
|
+
triggerInProcessRestart: params.triggerInProcessRestart
|
|
207
209
|
});
|
|
208
210
|
if (result.ok) {
|
|
209
211
|
nextState.autoLastSuccessVersion = version;
|
|
210
212
|
nextState.autoLastSuccessAt = new Date(now).toISOString();
|
|
213
|
+
const restartMode = result.result?.postUpdate?.restart?.mode;
|
|
211
214
|
log.info({
|
|
212
215
|
channel,
|
|
213
216
|
version,
|
|
214
|
-
tag
|
|
215
|
-
|
|
217
|
+
tag,
|
|
218
|
+
restartMode
|
|
219
|
+
}, restartMode === "in-process" ? "Auto-update applied; gateway restart scheduled" : "Auto-update applied successfully");
|
|
216
220
|
} else log.warn({
|
|
217
221
|
channel,
|
|
218
222
|
version,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { writeTextAtomic } from './write-file-atomic.js';\nimport type { Config } from '../config/schema.js';\nimport { resolveUpdateCheckStatePath } from '../config/paths-state.js';\nimport { acquireUpdateLock } from './update-lock.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n if (!parsed || typeof parsed !== 'object') {\n log.warn({ statePath }, 'Update check state file contains non-object; resetting');\n return {};\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn({ err, statePath }, 'Failed to read update check state; resetting');\n }\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await writeTextAtomic(statePath, JSON.stringify(state, null, 2));\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const statePath = resolveUpdateCheckStatePath();\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n const lock = await acquireUpdateLock('auto');\n if (!lock) {\n log.info({ version, tag }, 'Auto-update skipped: another update is in progress');\n return;\n }\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({ channel, root });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n log.info({ channel, version, tag }, 'Auto-update applied successfully; restart gateway to refresh browser extension artifacts');\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"update-startup.js","names":[],"sources":["../../../src/infra/update-startup.ts"],"sourcesContent":["// src/infra/update-startup.ts\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { writeTextAtomic } from './write-file-atomic.js';\nimport type { Config } from '../config/schema.js';\nimport { resolveUpdateCheckStatePath } from '../config/paths-state.js';\nimport { acquireUpdateLock } from './update-lock.js';\nimport { PACKAGE_VERSION } from '../package-version.js';\nimport { createLogger } from '../utils/logger.js';\n\nimport { normalizeUpdateChannel, DEFAULT_PACKAGE_CHANNEL } from './update-channels.js';\nimport {\n compareSemver,\n resolveNpmChannelTag,\n detectInstallKind,\n resolvePackageRoot,\n type InstallKind,\n type UpdateAvailable,\n} from './update-check.js';\nimport type { InProcessRestartTrigger } from './update-restart.js';\n\nconst log = createLogger('UpdateCheck');\n\n// --- State persistence ---\n\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\ntype UpdateCheckState = {\n /** `package.json` version at the last successful registry check (used to bypass 24h throttle when you bump the local version). */\n lastCheckPackageVersion?: string;\n lastCheckedAt?: string;\n lastAvailableVersion?: string;\n lastAvailableTag?: string;\n lastNotifiedVersion?: string;\n lastNotifiedTag?: string;\n autoInstallId?: string;\n autoFirstSeenVersion?: string;\n autoFirstSeenTag?: string;\n autoFirstSeenAt?: string;\n autoLastAttemptVersion?: string;\n autoLastAttemptAt?: string;\n autoLastSuccessVersion?: string;\n autoLastSuccessAt?: string;\n};\n\n// --- In-memory cache ---\n\nlet updateAvailableCache: UpdateAvailable | null = null;\n\n/** Get the cached update-available state (populated after startup check). */\nexport function getUpdateAvailable(): UpdateAvailable | null {\n return updateAvailableCache;\n}\n\n// --- Core logic ---\n\nasync function readState(statePath: string): Promise<UpdateCheckState> {\n try {\n const raw = await readFile(statePath, 'utf-8');\n const parsed = JSON.parse(raw) as UpdateCheckState;\n if (!parsed || typeof parsed !== 'object') {\n log.warn({ statePath }, 'Update check state file contains non-object; resetting');\n return {};\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n log.warn({ err, statePath }, 'Failed to read update check state; resetting');\n }\n return {};\n }\n}\n\nasync function writeState(statePath: string, state: UpdateCheckState): Promise<void> {\n await writeTextAtomic(statePath, JSON.stringify(state, null, 2));\n}\n\nfunction resolveCheckIntervalMs(config: Config): number {\n const auto = config.update?.auto;\n if (!auto?.enabled) return CHECK_INTERVAL_MS;\n\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n if (channel === 'beta') {\n const hours = auto.betaCheckIntervalHours ?? 1;\n return Math.max(ONE_HOUR_MS / 4, Math.floor(hours * ONE_HOUR_MS));\n }\n return ONE_HOUR_MS;\n}\n\n/**\n * Compute a deterministic delay for stable auto-update rollout,\n * based on a per-installation hash to spread updates over time.\n */\nfunction resolveStableJitterMs(\n installId: string,\n version: string,\n tag: string,\n jitterWindowMs: number,\n): number {\n if (jitterWindowMs <= 0) return 0;\n const hash = createHash('sha256').update(`${installId}:${version}:${tag}`).digest();\n const bucket = hash.readUInt32BE(0);\n return bucket % (Math.floor(jitterWindowMs) + 1);\n}\n\n/**\n * Main startup update check. Called once when the gateway starts (or on demand).\n */\nexport async function runGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n triggerInProcessRestart?: InProcessRestartTrigger;\n /** When true, bypass checkOnStart/auto-disabled early exit and throttle (for POST /api/update/check). */\n force?: boolean;\n}): Promise<void> {\n const { config, force } = params;\n\n const autoEnabled = config.update?.auto?.enabled ?? false;\n const shouldCheckHints = config.update?.checkOnStart !== false;\n if (!force && !shouldCheckHints && !autoEnabled) return;\n\n const statePath = resolveUpdateCheckStatePath();\n const state = await readState(statePath);\n const now = Date.now();\n\n // Hydrate from persisted state if within throttle window\n const lastCheckedAt = state.lastCheckedAt ? Date.parse(state.lastCheckedAt) : null;\n if (state.lastAvailableVersion && (shouldCheckHints || force)) {\n const comparison = compareSemver(PACKAGE_VERSION, state.lastAvailableVersion);\n if (comparison !== null && comparison < 0) {\n const cached: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: state.lastAvailableVersion,\n channel: state.lastAvailableTag ?? 'latest',\n };\n updateAvailableCache = cached;\n params.onUpdateAvailableChange?.(cached);\n }\n }\n\n const checkIntervalMs = resolveCheckIntervalMs(config);\n // Re-check npm when the local package version changed (e.g. after editing package.json) even within 24h.\n const shouldBypassThrottleForVersion =\n state.lastCheckPackageVersion === undefined || state.lastCheckPackageVersion !== PACKAGE_VERSION;\n if (\n !force &&\n !shouldBypassThrottleForVersion &&\n lastCheckedAt &&\n Number.isFinite(lastCheckedAt) &&\n now - lastCheckedAt < checkIntervalMs\n ) {\n return; // Within throttle window\n }\n\n // Install kind: auto-install only for npm global installs, but we still query npm in git\n // so the Web UI / CLI can show \"newer on registry\" and the top reminder bar.\n const root = await resolvePackageRoot();\n let installKind: InstallKind = 'unknown';\n if (root) {\n installKind = await detectInstallKind(root);\n if (installKind === 'git') {\n log.info('Update check: git checkout (hint-only; use git pull to update, no auto npm install)');\n }\n }\n\n // Query npm registry\n const channel = normalizeUpdateChannel(config.update?.channel) ?? DEFAULT_PACKAGE_CHANNEL;\n const resolved = await resolveNpmChannelTag({ channel, timeoutMs: 2500 });\n\n const nextState: UpdateCheckState = {\n ...state,\n lastCheckedAt: new Date(now).toISOString(),\n };\n\n if (!resolved.version) {\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n return;\n }\n\n const comparison = compareSemver(PACKAGE_VERSION, resolved.version);\n if (comparison !== null && comparison < 0) {\n // Update available\n const updateInfo: UpdateAvailable = {\n currentVersion: PACKAGE_VERSION,\n latestVersion: resolved.version,\n channel: resolved.tag,\n };\n\n if (shouldCheckHints || force) {\n updateAvailableCache = updateInfo;\n params.onUpdateAvailableChange?.(updateInfo);\n }\n\n nextState.lastAvailableVersion = resolved.version;\n nextState.lastAvailableTag = resolved.tag;\n\n // Log notification (once per version)\n const shouldNotify =\n state.lastNotifiedVersion !== resolved.version || state.lastNotifiedTag !== resolved.tag;\n if ((shouldCheckHints || force) && shouldNotify) {\n log.info(\n { currentVersion: PACKAGE_VERSION, latestVersion: resolved.version, tag: resolved.tag },\n `Update available (${resolved.tag}): v${resolved.version} (current v${PACKAGE_VERSION}). Run: xopc update`,\n );\n nextState.lastNotifiedVersion = resolved.version;\n nextState.lastNotifiedTag = resolved.tag;\n }\n\n // Auto-update logic (never from a git worktree)\n if (\n autoEnabled &&\n (channel === 'stable' || channel === 'beta') &&\n installKind !== 'git'\n ) {\n await handleAutoUpdate({\n channel,\n version: resolved.version,\n tag: resolved.tag,\n state,\n nextState,\n now,\n root,\n config,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n }\n } else {\n // Current version is up to date or newer\n delete nextState.lastAvailableVersion;\n delete nextState.lastAvailableTag;\n updateAvailableCache = null;\n params.onUpdateAvailableChange?.(null);\n }\n\n nextState.lastCheckPackageVersion = PACKAGE_VERSION;\n await writeState(statePath, nextState);\n}\n\nasync function handleAutoUpdate(params: {\n channel: 'stable' | 'beta';\n version: string;\n tag: string;\n state: UpdateCheckState;\n nextState: UpdateCheckState;\n now: number;\n root: string | null;\n config: Config;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): Promise<void> {\n const { channel, version, tag, state, nextState, now, root, config } = params;\n const auto = config.update?.auto;\n if (!auto) return;\n\n const stableDelayHours = auto.stableDelayHours ?? 6;\n const stableJitterHours = auto.stableJitterHours ?? 12;\n const betaCheckIntervalHours = auto.betaCheckIntervalHours ?? 1;\n\n // Rate limit: don't re-attempt same version within interval\n const attemptIntervalMs =\n channel === 'beta'\n ? Math.max(ONE_HOUR_MS / 4, Math.floor(betaCheckIntervalHours * ONE_HOUR_MS))\n : ONE_HOUR_MS;\n const lastAttemptAt = state.autoLastAttemptAt ? Date.parse(state.autoLastAttemptAt) : null;\n const recentAttempt =\n state.autoLastAttemptVersion === version &&\n lastAttemptAt !== null &&\n Number.isFinite(lastAttemptAt) &&\n now - lastAttemptAt < attemptIntervalMs;\n\n if (recentAttempt) {\n log.info({ version, tag }, 'Auto-update deferred: recent attempt exists');\n return;\n }\n\n // Stable rollout delay + jitter\n if (channel === 'stable') {\n if (!nextState.autoInstallId) {\n nextState.autoInstallId = state.autoInstallId?.trim() || randomUUID();\n }\n // Track first-seen time for this version\n if (state.autoFirstSeenVersion !== version || state.autoFirstSeenTag !== tag) {\n nextState.autoFirstSeenVersion = version;\n nextState.autoFirstSeenTag = tag;\n nextState.autoFirstSeenAt = new Date(now).toISOString();\n } else {\n nextState.autoFirstSeenAt = state.autoFirstSeenAt;\n }\n\n const firstSeenMs = nextState.autoFirstSeenAt ? Date.parse(nextState.autoFirstSeenAt) : now;\n const baseDelayMs = Math.max(0, stableDelayHours) * ONE_HOUR_MS;\n const jitterWindowMs = Math.max(0, stableJitterHours) * ONE_HOUR_MS;\n const jitterMs = resolveStableJitterMs(nextState.autoInstallId, version, tag, jitterWindowMs);\n const applyAfterMs = firstSeenMs + baseDelayMs + jitterMs;\n\n if (now < applyAfterMs) {\n log.info(\n { version, tag, applyAfter: new Date(applyAfterMs).toISOString() },\n 'Auto-update deferred: stable rollout window not yet due',\n );\n return;\n }\n }\n\n // Execute auto-update\n nextState.autoLastAttemptVersion = version;\n nextState.autoLastAttemptAt = new Date(now).toISOString();\n\n log.info({ channel, version, tag }, 'Starting auto-update');\n\n const lock = await acquireUpdateLock('auto');\n if (!lock) {\n log.info({ version, tag }, 'Auto-update skipped: another update is in progress');\n return;\n }\n try {\n const { runAutoUpdateCommand } = await import('./update-runner.js');\n const result = await runAutoUpdateCommand({\n channel,\n root,\n triggerInProcessRestart: params.triggerInProcessRestart,\n });\n if (result.ok) {\n nextState.autoLastSuccessVersion = version;\n nextState.autoLastSuccessAt = new Date(now).toISOString();\n const restartMode = result.result?.postUpdate?.restart?.mode;\n log.info(\n { channel, version, tag, restartMode },\n restartMode === 'in-process'\n ? 'Auto-update applied; gateway restart scheduled'\n : 'Auto-update applied successfully',\n );\n } else {\n log.warn(\n { channel, version, tag, exitCode: result.exitCode, reason: result.reason },\n `Auto-update attempt failed: ${result.reason ?? `exit ${result.exitCode}`}`,\n );\n }\n } catch (err) {\n log.error({ err, channel, version }, 'Auto-update command threw');\n } finally {\n await lock.release();\n }\n}\n\n/**\n * Schedule periodic update checks. Returns a cleanup function to stop the timer.\n */\nexport function scheduleGatewayUpdateCheck(params: {\n config: Config;\n onUpdateAvailableChange?: (update: UpdateAvailable | null) => void;\n triggerInProcessRestart?: InProcessRestartTrigger;\n}): () => void {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const tick = async () => {\n if (stopped) return;\n try {\n await runGatewayUpdateCheck(params);\n } catch (err) {\n log.warn({ err }, 'Periodic update check failed');\n }\n if (!stopped) {\n const intervalMs = resolveCheckIntervalMs(params.config);\n timer = setTimeout(() => void tick(), intervalMs);\n }\n };\n\n // Initial check after a short delay (don't block startup)\n timer = setTimeout(() => void tick(), 5000);\n\n return () => {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Canonical persisted reference: xopc-attachment://notes/{noteId}/{attachmentId} */
|
|
2
|
+
export declare const NOTE_ATTACHMENT_SCHEME = "xopc-attachment";
|
|
3
|
+
export type ParsedNoteAttachmentRef = {
|
|
4
|
+
noteId: string;
|
|
5
|
+
attachmentId: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildNoteAttachmentRef(noteId: string, attachmentId: string): string;
|
|
8
|
+
export declare function parseNoteAttachmentTarget(target: string, expectedNoteId?: string): ParsedNoteAttachmentRef | null;
|
|
9
|
+
export declare function attachmentIdFromTarget(target: string, noteId: string): string | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/notes/attachment-ref.ts
|
|
2
|
+
/** Canonical persisted reference: xopc-attachment://notes/{noteId}/{attachmentId} */
|
|
3
|
+
const NOTE_ATTACHMENT_SCHEME = "xopc-attachment";
|
|
4
|
+
const CANONICAL_REF = /^xopc-attachment:\/\/notes\/([^/]+)\/([^/?#]+)$/i;
|
|
5
|
+
function buildNoteAttachmentRef(noteId, attachmentId) {
|
|
6
|
+
return `${NOTE_ATTACHMENT_SCHEME}://notes/${encodeURIComponent(noteId)}/${encodeURIComponent(attachmentId)}`;
|
|
7
|
+
}
|
|
8
|
+
function parseNoteAttachmentTarget(target, expectedNoteId) {
|
|
9
|
+
const trimmed = target.trim();
|
|
10
|
+
if (!trimmed) return null;
|
|
11
|
+
const canonical = trimmed.split("?")[0].match(CANONICAL_REF);
|
|
12
|
+
if (!canonical) return null;
|
|
13
|
+
const noteId = decodeURIComponent(canonical[1]);
|
|
14
|
+
const attachmentId = decodeURIComponent(canonical[2]);
|
|
15
|
+
if (expectedNoteId && noteId !== expectedNoteId) return null;
|
|
16
|
+
return {
|
|
17
|
+
noteId,
|
|
18
|
+
attachmentId
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function attachmentIdFromTarget(target, noteId) {
|
|
22
|
+
return parseNoteAttachmentTarget(target, noteId)?.attachmentId;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { NOTE_ATTACHMENT_SCHEME, attachmentIdFromTarget, buildNoteAttachmentRef, parseNoteAttachmentTarget };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=attachment-ref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-ref.js","names":[],"sources":["../../../src/notes/attachment-ref.ts"],"sourcesContent":["/** Canonical persisted reference: xopc-attachment://notes/{noteId}/{attachmentId} */\nexport const NOTE_ATTACHMENT_SCHEME = 'xopc-attachment';\n\nconst CANONICAL_REF =\n /^xopc-attachment:\\/\\/notes\\/([^/]+)\\/([^/?#]+)$/i;\n\nexport type ParsedNoteAttachmentRef = {\n noteId: string;\n attachmentId: string;\n};\n\nexport function buildNoteAttachmentRef(noteId: string, attachmentId: string): string {\n return `${NOTE_ATTACHMENT_SCHEME}://notes/${encodeURIComponent(noteId)}/${encodeURIComponent(attachmentId)}`;\n}\n\nexport function parseNoteAttachmentTarget(\n target: string,\n expectedNoteId?: string,\n): ParsedNoteAttachmentRef | null {\n const trimmed = target.trim();\n if (!trimmed) return null;\n\n const canonical = trimmed.split('?')[0].match(CANONICAL_REF);\n if (!canonical) return null;\n\n const noteId = decodeURIComponent(canonical[1]);\n const attachmentId = decodeURIComponent(canonical[2]);\n if (expectedNoteId && noteId !== expectedNoteId) return null;\n return { noteId, attachmentId };\n}\n\nexport function attachmentIdFromTarget(target: string, noteId: string): string | undefined {\n return parseNoteAttachmentTarget(target, noteId)?.attachmentId;\n}\n"],"mappings":";;AACA,MAAa,yBAAyB;AAEtC,MAAM,gBACJ;AAOF,SAAgB,uBAAuB,QAAgB,cAA8B;AACnF,QAAO,GAAG,uBAAuB,WAAW,mBAAmB,OAAO,CAAC,GAAG,mBAAmB,aAAa;;AAG5G,SAAgB,0BACd,QACA,gBACgC;CAChC,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,CAAC,QAAS,QAAO;CAErB,MAAM,YAAY,QAAQ,MAAM,IAAI,CAAC,GAAG,MAAM,cAAc;AAC5D,KAAI,CAAC,UAAW,QAAO;CAEvB,MAAM,SAAS,mBAAmB,UAAU,GAAG;CAC/C,MAAM,eAAe,mBAAmB,UAAU,GAAG;AACrD,KAAI,kBAAkB,WAAW,eAAgB,QAAO;AACxD,QAAO;EAAE;EAAQ;EAAc;;AAGjC,SAAgB,uBAAuB,QAAgB,QAAoC;AACzF,QAAO,0BAA0B,QAAQ,OAAO,EAAE"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { NotesStore } from './store.js';
|
|
2
|
+
export { NotesService } from './service.js';
|
|
3
|
+
export { resolveNotesDir, resolveNotesIndexPath, resolveNoteItemPath, resolveNoteMediaDir, resolveNoteHistoryDir } from './paths.js';
|
|
4
|
+
export type { Note, NoteKind, NoteStatus, NoteAttachment, NoteAiMeta, NoteAiDeepMeta, NoteIndexEntry, NoteSnapshot, NoteSnapshotEntry, NotesIndexFile, NotesListQuery, CaptureSource, CaptureChannel, CreateNoteParams, SnapshotTrigger, } from './types.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath } from "./paths.js";
|
|
2
|
+
import { NotesStore } from "./store.js";
|
|
3
|
+
import { NotesService } from "./service.js";
|
|
4
|
+
export { NotesService, NotesStore, resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Note, NoteAttachment } from './types.js';
|
|
2
|
+
/** Attachment ids referenced in note markdown, blocks, or plain text. */
|
|
3
|
+
export declare function collectReferencedAttachmentIds(note: Pick<Note, 'id' | 'text' | 'blocks'>): Set<string>;
|
|
4
|
+
export declare function partitionAttachmentsByReference(note: Pick<Note, 'id' | 'text' | 'blocks' | 'attachments'>): {
|
|
5
|
+
kept: NoteAttachment[];
|
|
6
|
+
removed: NoteAttachment[];
|
|
7
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { attachmentIdFromTarget } from "./attachment-ref.js";
|
|
2
|
+
import { notePlainText } from "./note-index-meta.js";
|
|
3
|
+
//#region src/notes/note-attachment-sync.ts
|
|
4
|
+
const MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
5
|
+
const MARKDOWN_LINK = /(?<!!)\[([^\]]*)\]\(([^)]+)\)/g;
|
|
6
|
+
const BARE_ATTACHMENT_REF = /xopc-attachment:\/\/notes\/([^/]+)\/([^/?#"'\s)]+)/gi;
|
|
7
|
+
/** Attachment ids referenced in note markdown, blocks, or plain text. */
|
|
8
|
+
function collectReferencedAttachmentIds(note) {
|
|
9
|
+
const ids = /* @__PURE__ */ new Set();
|
|
10
|
+
for (const block of note.blocks ?? []) if (block.type === "image") ids.add(block.attachmentId);
|
|
11
|
+
const source = notePlainText(note);
|
|
12
|
+
if (!source.trim()) return ids;
|
|
13
|
+
for (const match of source.matchAll(MARKDOWN_IMAGE)) {
|
|
14
|
+
const target = match[2];
|
|
15
|
+
if (typeof target !== "string") continue;
|
|
16
|
+
const attachmentId = attachmentIdFromTarget(target, note.id);
|
|
17
|
+
if (attachmentId) ids.add(attachmentId);
|
|
18
|
+
}
|
|
19
|
+
for (const match of source.matchAll(MARKDOWN_LINK)) {
|
|
20
|
+
const target = match[2];
|
|
21
|
+
if (typeof target !== "string") continue;
|
|
22
|
+
const attachmentId = attachmentIdFromTarget(target, note.id);
|
|
23
|
+
if (attachmentId) ids.add(attachmentId);
|
|
24
|
+
}
|
|
25
|
+
for (const match of source.matchAll(BARE_ATTACHMENT_REF)) {
|
|
26
|
+
if (decodeURIComponent(match[1]) !== note.id) continue;
|
|
27
|
+
ids.add(decodeURIComponent(match[2]));
|
|
28
|
+
}
|
|
29
|
+
return ids;
|
|
30
|
+
}
|
|
31
|
+
function partitionAttachmentsByReference(note) {
|
|
32
|
+
const referenced = collectReferencedAttachmentIds(note);
|
|
33
|
+
const attachments = note.attachments ?? [];
|
|
34
|
+
const kept = [];
|
|
35
|
+
const removed = [];
|
|
36
|
+
for (const attachment of attachments) if (referenced.has(attachment.id)) kept.push(attachment);
|
|
37
|
+
else removed.push(attachment);
|
|
38
|
+
return {
|
|
39
|
+
kept,
|
|
40
|
+
removed
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { collectReferencedAttachmentIds, partitionAttachmentsByReference };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=note-attachment-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-attachment-sync.js","names":[],"sources":["../../../src/notes/note-attachment-sync.ts"],"sourcesContent":["import type { Note, NoteAttachment } from './types.js';\nimport { attachmentIdFromTarget } from './attachment-ref.js';\nimport { notePlainText } from './note-index-meta.js';\n\nconst MARKDOWN_IMAGE = /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\nconst MARKDOWN_LINK = /(?<!!)\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\nconst BARE_ATTACHMENT_REF = /xopc-attachment:\\/\\/notes\\/([^/]+)\\/([^/?#\"'\\s)]+)/gi;\n\n/** Attachment ids referenced in note markdown, blocks, or plain text. */\nexport function collectReferencedAttachmentIds(\n note: Pick<Note, 'id' | 'text' | 'blocks'>,\n): Set<string> {\n const ids = new Set<string>();\n\n for (const block of note.blocks ?? []) {\n if (block.type === 'image') ids.add(block.attachmentId);\n }\n\n const source = notePlainText(note);\n if (!source.trim()) return ids;\n\n for (const match of source.matchAll(MARKDOWN_IMAGE)) {\n const target = match[2];\n if (typeof target !== 'string') continue;\n const attachmentId = attachmentIdFromTarget(target, note.id);\n if (attachmentId) ids.add(attachmentId);\n }\n\n for (const match of source.matchAll(MARKDOWN_LINK)) {\n const target = match[2];\n if (typeof target !== 'string') continue;\n const attachmentId = attachmentIdFromTarget(target, note.id);\n if (attachmentId) ids.add(attachmentId);\n }\n\n for (const match of source.matchAll(BARE_ATTACHMENT_REF)) {\n const noteId = decodeURIComponent(match[1]);\n if (noteId !== note.id) continue;\n ids.add(decodeURIComponent(match[2]));\n }\n\n return ids;\n}\n\nexport function partitionAttachmentsByReference(\n note: Pick<Note, 'id' | 'text' | 'blocks' | 'attachments'>,\n): { kept: NoteAttachment[]; removed: NoteAttachment[] } {\n const referenced = collectReferencedAttachmentIds(note);\n const attachments = note.attachments ?? [];\n const kept: NoteAttachment[] = [];\n const removed: NoteAttachment[] = [];\n\n for (const attachment of attachments) {\n if (referenced.has(attachment.id)) {\n kept.push(attachment);\n } else {\n removed.push(attachment);\n }\n }\n\n return { kept, removed };\n}\n"],"mappings":";;;AAIA,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AACtB,MAAM,sBAAsB;;AAG5B,SAAgB,+BACd,MACa;CACb,MAAM,sBAAM,IAAI,KAAa;AAE7B,MAAK,MAAM,SAAS,KAAK,UAAU,EAAE,CACnC,KAAI,MAAM,SAAS,QAAS,KAAI,IAAI,MAAM,aAAa;CAGzD,MAAM,SAAS,cAAc,KAAK;AAClC,KAAI,CAAC,OAAO,MAAM,CAAE,QAAO;AAE3B,MAAK,MAAM,SAAS,OAAO,SAAS,eAAe,EAAE;EACnD,MAAM,SAAS,MAAM;AACrB,MAAI,OAAO,WAAW,SAAU;EAChC,MAAM,eAAe,uBAAuB,QAAQ,KAAK,GAAG;AAC5D,MAAI,aAAc,KAAI,IAAI,aAAa;;AAGzC,MAAK,MAAM,SAAS,OAAO,SAAS,cAAc,EAAE;EAClD,MAAM,SAAS,MAAM;AACrB,MAAI,OAAO,WAAW,SAAU;EAChC,MAAM,eAAe,uBAAuB,QAAQ,KAAK,GAAG;AAC5D,MAAI,aAAc,KAAI,IAAI,aAAa;;AAGzC,MAAK,MAAM,SAAS,OAAO,SAAS,oBAAoB,EAAE;AAExD,MADe,mBAAmB,MAAM,GAC9B,KAAK,KAAK,GAAI;AACxB,MAAI,IAAI,mBAAmB,MAAM,GAAG,CAAC;;AAGvC,QAAO;;AAGT,SAAgB,gCACd,MACuD;CACvD,MAAM,aAAa,+BAA+B,KAAK;CACvD,MAAM,cAAc,KAAK,eAAe,EAAE;CAC1C,MAAM,OAAyB,EAAE;CACjC,MAAM,UAA4B,EAAE;AAEpC,MAAK,MAAM,cAAc,YACvB,KAAI,WAAW,IAAI,WAAW,GAAG,CAC/B,MAAK,KAAK,WAAW;KAErB,SAAQ,KAAK,WAAW;AAI5B,QAAO;EAAE;EAAM;EAAS"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Note } from './types.js';
|
|
2
|
+
export { parseNoteAttachmentTarget, buildNoteAttachmentRef } from './attachment-ref.js';
|
|
3
|
+
export declare function notePlainText(note: Pick<Note, 'id' | 'text' | 'blocks'>): string;
|
|
4
|
+
export declare function stripMediaFromPlainText(text: string): string;
|
|
5
|
+
export declare function extractAttachmentFileNames(note: Pick<Note, 'attachments'>): string[] | undefined;
|
|
6
|
+
export declare function extractCoverAttachmentId(note: Note): string | undefined;
|
|
7
|
+
export declare function buildNoteSnippet(note: Pick<Note, 'id' | 'text' | 'blocks'>): string | undefined;
|
|
8
|
+
export declare function buildNoteIndexMeta(note: Note): {
|
|
9
|
+
snippet?: string;
|
|
10
|
+
coverAttachmentId?: string;
|
|
11
|
+
voiceAttachmentId?: string;
|
|
12
|
+
voiceDurationSec?: number;
|
|
13
|
+
attachmentNames?: string[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { attachmentIdFromTarget, buildNoteAttachmentRef, parseNoteAttachmentTarget } from "./attachment-ref.js";
|
|
2
|
+
//#region src/notes/note-index-meta.ts
|
|
3
|
+
const SNIPPET_LENGTH = 100;
|
|
4
|
+
const MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
5
|
+
const MARKDOWN_LINK = /(?<!!)\[([^\]]*)\]\(([^)]+)\)/g;
|
|
6
|
+
const BARE_ATTACHMENT_REF = /xopc-attachment:\/\/notes\/[^/]+\/[^/?#"'\s)]+/gi;
|
|
7
|
+
function notePlainText(note) {
|
|
8
|
+
if (note.text?.trim()) return note.text;
|
|
9
|
+
return note.blocks?.map((block) => {
|
|
10
|
+
if (block.type === "divider") return "";
|
|
11
|
+
if (block.type === "todo") return block.text;
|
|
12
|
+
if (block.type === "image") return `})`;
|
|
13
|
+
return block.text;
|
|
14
|
+
}).join(" ") ?? "";
|
|
15
|
+
}
|
|
16
|
+
function isNoteMediaTarget(target) {
|
|
17
|
+
return parseNoteAttachmentTarget(target) !== null;
|
|
18
|
+
}
|
|
19
|
+
function stripMediaFromPlainText(text) {
|
|
20
|
+
let result = text.replace(MARKDOWN_IMAGE, " ");
|
|
21
|
+
result = result.replace(MARKDOWN_LINK, (full, _alt, target) => {
|
|
22
|
+
if (typeof target !== "string") return full;
|
|
23
|
+
return isNoteMediaTarget(target) ? " " : full;
|
|
24
|
+
});
|
|
25
|
+
result = result.replace(BARE_ATTACHMENT_REF, " ");
|
|
26
|
+
return result.replace(/\s+/g, " ").trim();
|
|
27
|
+
}
|
|
28
|
+
function extractAttachmentFileNames(note) {
|
|
29
|
+
const names = note.attachments?.map((item) => item.fileName.trim().toLowerCase()).filter(Boolean);
|
|
30
|
+
if (!names?.length) return void 0;
|
|
31
|
+
return [...new Set(names)];
|
|
32
|
+
}
|
|
33
|
+
function extractCoverAttachmentId(note) {
|
|
34
|
+
for (const block of note.blocks ?? []) {
|
|
35
|
+
if (block.type !== "image") continue;
|
|
36
|
+
const attachment = note.attachments?.find((item) => item.id === block.attachmentId);
|
|
37
|
+
if (!attachment || attachment.type === "image") return block.attachmentId;
|
|
38
|
+
}
|
|
39
|
+
const text = notePlainText(note);
|
|
40
|
+
if (!text.trim()) return void 0;
|
|
41
|
+
for (const match of text.matchAll(MARKDOWN_IMAGE)) {
|
|
42
|
+
const target = match[2];
|
|
43
|
+
if (typeof target !== "string") continue;
|
|
44
|
+
const attachmentId = attachmentIdFromTarget(target, note.id);
|
|
45
|
+
if (!attachmentId) continue;
|
|
46
|
+
const attachment = note.attachments?.find((item) => item.id === attachmentId);
|
|
47
|
+
if (!attachment || attachment.type === "image") return attachmentId;
|
|
48
|
+
}
|
|
49
|
+
for (const match of text.matchAll(BARE_ATTACHMENT_REF)) {
|
|
50
|
+
const parsed = parseNoteAttachmentTarget(match[0], note.id);
|
|
51
|
+
if (!parsed) continue;
|
|
52
|
+
const attachment = note.attachments?.find((item) => item.id === parsed.attachmentId);
|
|
53
|
+
if (!attachment || attachment.type === "image") return parsed.attachmentId;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function buildNoteSnippet(note) {
|
|
57
|
+
const text = notePlainText(note);
|
|
58
|
+
if (!text.trim()) return void 0;
|
|
59
|
+
const clean = stripMediaFromPlainText(text);
|
|
60
|
+
if (!clean) return void 0;
|
|
61
|
+
return clean.length > SNIPPET_LENGTH ? `${clean.slice(0, SNIPPET_LENGTH)}…` : clean;
|
|
62
|
+
}
|
|
63
|
+
function extractVoiceAttachment(note) {
|
|
64
|
+
const audio = note.attachments?.find((a) => a.type === "audio");
|
|
65
|
+
if (!audio) return {};
|
|
66
|
+
return {
|
|
67
|
+
voiceAttachmentId: audio.id,
|
|
68
|
+
voiceDurationSec: audio.duration
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildNoteIndexMeta(note) {
|
|
72
|
+
const coverAttachmentId = extractCoverAttachmentId(note);
|
|
73
|
+
const snippet = buildNoteSnippet(note);
|
|
74
|
+
const attachmentNames = extractAttachmentFileNames(note);
|
|
75
|
+
const { voiceAttachmentId, voiceDurationSec } = extractVoiceAttachment(note);
|
|
76
|
+
return {
|
|
77
|
+
snippet,
|
|
78
|
+
coverAttachmentId,
|
|
79
|
+
voiceAttachmentId,
|
|
80
|
+
voiceDurationSec,
|
|
81
|
+
attachmentNames
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { buildNoteAttachmentRef, buildNoteIndexMeta, buildNoteSnippet, extractAttachmentFileNames, extractCoverAttachmentId, notePlainText, parseNoteAttachmentTarget, stripMediaFromPlainText };
|
|
86
|
+
|
|
87
|
+
//# sourceMappingURL=note-index-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-index-meta.js","names":[],"sources":["../../../src/notes/note-index-meta.ts"],"sourcesContent":["import type { Note } from './types.js';\nimport {\n attachmentIdFromTarget,\n buildNoteAttachmentRef,\n parseNoteAttachmentTarget,\n} from './attachment-ref.js';\n\nconst SNIPPET_LENGTH = 100;\nconst MARKDOWN_IMAGE = /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\nconst MARKDOWN_LINK = /(?<!!)\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\nconst BARE_ATTACHMENT_REF = /xopc-attachment:\\/\\/notes\\/[^/]+\\/[^/?#\"'\\s)]+/gi;\n\nexport { parseNoteAttachmentTarget, buildNoteAttachmentRef } from './attachment-ref.js';\n\nexport function notePlainText(note: Pick<Note, 'id' | 'text' | 'blocks'>): string {\n if (note.text?.trim()) return note.text;\n return (\n note.blocks\n ?.map((block) => {\n if (block.type === 'divider') return '';\n if (block.type === 'todo') return block.text;\n if (block.type === 'image') {\n const alt = block.alt ?? '';\n return `})`;\n }\n return block.text;\n })\n .join(' ') ?? ''\n );\n}\n\nfunction isNoteMediaTarget(target: string): boolean {\n return parseNoteAttachmentTarget(target) !== null;\n}\n\nexport function stripMediaFromPlainText(text: string): string {\n let result = text.replace(MARKDOWN_IMAGE, ' ');\n result = result.replace(MARKDOWN_LINK, (full, _alt, target) => {\n if (typeof target !== 'string') return full;\n return isNoteMediaTarget(target) ? ' ' : full;\n });\n result = result.replace(BARE_ATTACHMENT_REF, ' ');\n return result.replace(/\\s+/g, ' ').trim();\n}\n\nexport function extractAttachmentFileNames(note: Pick<Note, 'attachments'>): string[] | undefined {\n const names = note.attachments?.map((item) => item.fileName.trim().toLowerCase()).filter(Boolean);\n if (!names?.length) return undefined;\n return [...new Set(names)];\n}\n\nexport function extractCoverAttachmentId(note: Note): string | undefined {\n for (const block of note.blocks ?? []) {\n if (block.type !== 'image') continue;\n const attachment = note.attachments?.find((item) => item.id === block.attachmentId);\n if (!attachment || attachment.type === 'image') return block.attachmentId;\n }\n\n const text = notePlainText(note);\n if (!text.trim()) return undefined;\n\n for (const match of text.matchAll(MARKDOWN_IMAGE)) {\n const target = match[2];\n if (typeof target !== 'string') continue;\n const attachmentId = attachmentIdFromTarget(target, note.id);\n if (!attachmentId) continue;\n const attachment = note.attachments?.find((item) => item.id === attachmentId);\n if (!attachment || attachment.type === 'image') return attachmentId;\n }\n\n for (const match of text.matchAll(BARE_ATTACHMENT_REF)) {\n const parsed = parseNoteAttachmentTarget(match[0], note.id);\n if (!parsed) continue;\n const attachment = note.attachments?.find((item) => item.id === parsed.attachmentId);\n if (!attachment || attachment.type === 'image') return parsed.attachmentId;\n }\n\n return undefined;\n}\n\nexport function buildNoteSnippet(note: Pick<Note, 'id' | 'text' | 'blocks'>): string | undefined {\n const text = notePlainText(note);\n if (!text.trim()) return undefined;\n const clean = stripMediaFromPlainText(text);\n if (!clean) return undefined;\n return clean.length > SNIPPET_LENGTH ? `${clean.slice(0, SNIPPET_LENGTH)}…` : clean;\n}\n\nfunction extractVoiceAttachment(note: Note): { voiceAttachmentId?: string; voiceDurationSec?: number } {\n const audio = note.attachments?.find((a) => a.type === 'audio');\n if (!audio) return {};\n return { voiceAttachmentId: audio.id, voiceDurationSec: audio.duration };\n}\n\nexport function buildNoteIndexMeta(note: Note): {\n snippet?: string;\n coverAttachmentId?: string;\n voiceAttachmentId?: string;\n voiceDurationSec?: number;\n attachmentNames?: string[];\n} {\n const coverAttachmentId = extractCoverAttachmentId(note);\n const snippet = buildNoteSnippet(note);\n const attachmentNames = extractAttachmentFileNames(note);\n const { voiceAttachmentId, voiceDurationSec } = extractVoiceAttachment(note);\n return {\n snippet,\n coverAttachmentId,\n voiceAttachmentId,\n voiceDurationSec,\n attachmentNames,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function resolveNotesDir(): string;
|
|
2
|
+
export declare function resolveNotesIndexPath(): string;
|
|
3
|
+
export declare function resolveNoteItemPath(noteId: string): string;
|
|
4
|
+
export declare function resolveNoteMediaDir(noteId: string): string;
|
|
5
|
+
export declare function resolveNoteHistoryDir(noteId: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { init_paths_state, resolveStateDir } from "../config/paths-state.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/notes/paths.ts
|
|
4
|
+
init_paths_state();
|
|
5
|
+
function resolveNotesDir() {
|
|
6
|
+
return join(resolveStateDir(), "notes");
|
|
7
|
+
}
|
|
8
|
+
function resolveNotesIndexPath() {
|
|
9
|
+
return join(resolveNotesDir(), "index.json");
|
|
10
|
+
}
|
|
11
|
+
function resolveNoteItemPath(noteId) {
|
|
12
|
+
return join(resolveNotesDir(), "items", `${noteId}.json`);
|
|
13
|
+
}
|
|
14
|
+
function resolveNoteMediaDir(noteId) {
|
|
15
|
+
return join(resolveNotesDir(), "media", noteId);
|
|
16
|
+
}
|
|
17
|
+
function resolveNoteHistoryDir(noteId) {
|
|
18
|
+
return join(resolveNotesDir(), "history", noteId);
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { resolveNoteHistoryDir, resolveNoteItemPath, resolveNoteMediaDir, resolveNotesDir, resolveNotesIndexPath };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","names":[],"sources":["../../../src/notes/paths.ts"],"sourcesContent":["import { join } from 'node:path';\n\nimport { resolveStateDir } from '../config/paths-state.js';\n\nexport function resolveNotesDir(): string {\n return join(resolveStateDir(), 'notes');\n}\n\nexport function resolveNotesIndexPath(): string {\n return join(resolveNotesDir(), 'index.json');\n}\n\nexport function resolveNoteItemPath(noteId: string): string {\n return join(resolveNotesDir(), 'items', `${noteId}.json`);\n}\n\nexport function resolveNoteMediaDir(noteId: string): string {\n return join(resolveNotesDir(), 'media', noteId);\n}\n\nexport function resolveNoteHistoryDir(noteId: string): string {\n return join(resolveNotesDir(), 'history', noteId);\n}\n"],"mappings":";;;kBAE2D;AAE3D,SAAgB,kBAA0B;AACxC,QAAO,KAAK,iBAAiB,EAAE,QAAQ;;AAGzC,SAAgB,wBAAgC;AAC9C,QAAO,KAAK,iBAAiB,EAAE,aAAa;;AAG9C,SAAgB,oBAAoB,QAAwB;AAC1D,QAAO,KAAK,iBAAiB,EAAE,SAAS,GAAG,OAAO,OAAO;;AAG3D,SAAgB,oBAAoB,QAAwB;AAC1D,QAAO,KAAK,iBAAiB,EAAE,SAAS,OAAO;;AAGjD,SAAgB,sBAAsB,QAAwB;AAC5D,QAAO,KAAK,iBAAiB,EAAE,WAAW,OAAO"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NotesStore } from './store.js';
|
|
2
|
+
import type { CaptureSource, CreateNoteParams, Note, NoteAiPatch, NoteAttachment, NoteBlock, NoteIndexEntry, NoteSnapshot, NoteSnapshotEntry, NotesListQuery, SnapshotTrigger } from './types.js';
|
|
3
|
+
export declare class NotesService {
|
|
4
|
+
private store;
|
|
5
|
+
private lastSnapshotAt;
|
|
6
|
+
constructor(store: NotesStore);
|
|
7
|
+
initialize(): Promise<void>;
|
|
8
|
+
quickCapture(text: string, source: CaptureSource): Promise<Note>;
|
|
9
|
+
createNote(params: CreateNoteParams): Promise<Note>;
|
|
10
|
+
getNote(id: string): Promise<Note | null>;
|
|
11
|
+
updateNote(id: string, patch: Partial<Note>, trigger?: SnapshotTrigger): Promise<Note | null>;
|
|
12
|
+
private reconcileAttachments;
|
|
13
|
+
syncNote(id: string, patch: Partial<Note>, baseRemoteVersion?: number): Promise<{
|
|
14
|
+
note: Note | null;
|
|
15
|
+
conflict: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
createAiEditPatch(id: string, instruction: string, blocks?: NoteBlock[]): Promise<{
|
|
18
|
+
message: string;
|
|
19
|
+
patch: NoteAiPatch;
|
|
20
|
+
} | null>;
|
|
21
|
+
deleteNote(id: string): Promise<boolean>;
|
|
22
|
+
listNotes(query?: NotesListQuery): Promise<{
|
|
23
|
+
items: NoteIndexEntry[];
|
|
24
|
+
total: number;
|
|
25
|
+
}>;
|
|
26
|
+
addAttachment(noteId: string, file: {
|
|
27
|
+
name: string;
|
|
28
|
+
buffer: Buffer;
|
|
29
|
+
mimeType: string;
|
|
30
|
+
duration?: number;
|
|
31
|
+
}): Promise<NoteAttachment | null>;
|
|
32
|
+
getAttachmentPath(noteId: string, attachmentId: string): Promise<{
|
|
33
|
+
filePath: string;
|
|
34
|
+
mimeType: string;
|
|
35
|
+
fileName: string;
|
|
36
|
+
} | null>;
|
|
37
|
+
listNoteHistory(noteId: string): Promise<NoteSnapshotEntry[]>;
|
|
38
|
+
getNoteSnapshot(noteId: string, timestamp: number): Promise<NoteSnapshot | null>;
|
|
39
|
+
restoreNoteSnapshot(noteId: string, timestamp: number): Promise<Note | null>;
|
|
40
|
+
flush(): Promise<void>;
|
|
41
|
+
private maybeSaveSnapshot;
|
|
42
|
+
}
|