agentworkshop 0.5.3 → 0.5.4
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/.output/nitro.json +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/70bd10c7-55cf-4e49-94de-8aa1b9edf33a.json +1 -0
- package/.output/server/chunks/_/nitro.mjs +712 -625
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/package.json +1 -1
- package/package.json +1 -1
- package/scripts/_dbg-retention-test.ts +25 -0
- package/scripts/dev-guard.mjs +33 -0
- package/scripts/start.mjs +11 -0
- package/server/plugins/backup.ts +21 -11
- package/server/plugins/dev-stability-guard.ts +13 -1
- package/server/plugins/retention.ts +57 -0
- package/server/services/workshop/daq/daq-controller.ts +4 -2
- package/server/services/workshop/db/retention.ts +50 -0
- package/server/services/workshop/dcw/dcw-controller.ts +4 -2
- package/shared/config/single-instance.mjs +86 -0
- package/.output/public/_nuxt/builds/meta/fb769eda-b1db-437b-adc7-ecf71eb3b30b.json +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nitro.mjs","sources":["../../../../node_modules/.pnpm/uncrypto@0.1.3/node_modules/uncrypto/dist/crypto.node.mjs","../../../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs","../../../../node_modules/.pnpm/ufo@1.6.4/node_modules/ufo/dist/index.mjs","../../../../node_modules/.pnpm/cookie-es@1.2.3/node_modules/cookie-es/dist/index.mjs","../../../../node_modules/.pnpm/radix3@1.1.2/node_modules/radix3/dist/index.mjs","../../../../node_modules/.pnpm/defu@6.1.7/node_modules/defu/dist/defu.mjs","../../../../node_modules/.pnpm/iron-webcrypto@1.2.1/node_modules/iron-webcrypto/dist/index.js","../../../../node_modules/.pnpm/node-mock-http@1.0.5/node_modules/node-mock-http/dist/index.mjs","../../../../node_modules/.pnpm/h3@1.15.11/node_modules/h3/dist/index.mjs","../../../../node_modules/.pnpm/hookable@5.5.3/node_modules/hookable/dist/index.mjs","../../../../node_modules/.pnpm/node-fetch-native@1.6.7/node_modules/node-fetch-native/dist/native.mjs","../../../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/shared/ofetch.CWycOUEr.mjs","../../../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/node.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/dist/shared/unstorage.zVDD2mZo.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/dist/index.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/utils/index.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/utils/node-fs.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/fs-lite.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/storage.mjs","../../../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/shared/ohash.D__AXeF1.mjs","../../../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/crypto/node/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/hash.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/cache.mjs","../../../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/dist/index.mjs","../../../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/utils.env.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/config.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/route-rules-utils.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/route-rules.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/utils.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/utils/error.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/handlers/error.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/error/utils.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/error/prod.mjs","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/plugins/oauth.js","../../../../node_modules/.pnpm/hookable@6.1.1/node_modules/hookable/dist/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/plugin.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/renderer.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/utils/paths.mjs","../../../../server/utils/errors.ts","../../../../sdk/hooks.mjs","../../../../sdk/api.mjs","../../../../sdk/context.mjs","../../../../server/services/workshop/plugins/host.mjs","../../../../server/services/workshop/scene-events.ts","../../../../server/services/workshop/ops/ops.ts","../../../../server/utils/approval-gate.ts","../../../../server/utils/auth.ts","../../../../server/utils/config.ts","../../../../server/utils/response.ts","../../../../server/utils/validate.ts","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/utils/session.js","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/plugins/ws.js","../../../../server/plugins/aw-plugins.ts","../../../../shared/config/home.mjs","../../../../server/plugins/backup.ts","../../../../server/services/workshop/logger.ts","../../../../server/services/workshop/daq/infra.ts","../../../../server/services/workshop/daq/storage/sqlite.adapter.ts","../../../../server/services/workshop/daq/storage/timescale.adapter.ts","../../../../server/services/workshop/daq/storage/index.ts","../../../../server/services/workshop/daq/bus/inproc.adapter.ts","../../../../server/services/workshop/daq/bus/mqtt.adapter.ts","../../../../server/services/workshop/daq/bus/index.ts","../../../../shared/daq-protocol.ts","../../../../server/services/workshop/daq/drivers.ts","../../../../server/services/workshop/daq/daq-templates.ts","../../../../server/services/workshop/daq/daq-node.ts","../../../../server/services/workshop/daq/daq-runtime.ts","../../../../server/services/workshop/daq/daq-node.repo.ts","../../../../server/services/workshop/daq/host-ports.ts","../../../../server/services/workshop/daq/alarm-notify.ts","../../../../server/services/workshop/daq/daq-controller.ts","../../../../server/plugins/daq.ts","../../../../server/plugins/dev-stability-guard.ts","../../../../server/plugins/security.ts","../../../../shared/config/engine.mjs","../../../../server/services/system-config.ts","../../../../server/plugins/system-config.ts","../../../../server/services/workshop/db/database.ts","../../../../server/services/workshop/db/channel.repo.ts","../../../../server/services/workshop/db/agent.repo.ts","../../../../server/services/workshop/db/channel-agent.repo.ts","../../../../server/services/workshop/db/team.repo.ts","../../../../server/services/workshop/db/team-member.repo.ts","../../../../server/services/workshop/db/channel-template.repo.ts","../../../../server/services/workshop/db/task.repo.ts","../../../../server/services/workshop/db/message.repo.ts","../../../../server/services/workshop/db/subscription.repo.ts","../../../../server/services/workshop/db/memory.repo.ts","../../../../server/services/workshop/db/user.repo.ts","../../../../server/services/workshop/db/channel-event.repo.ts","../../../../server/services/workshop/db/ops.repo.ts","../../../../server/repositories/user.repository.ts","../../../../server/services/user.service.ts","../../../../server/services/workshop/runtime/mailbox.ts","../../../../server/api/workshop/ws.ts","../../../../server/services/workshop/runtime/execution-mode.ts","../../../../server/services/workshop/agents/mock-agent.ts","../../../../server/services/workshop/agents/harness-process.ts","../../../../server/services/workshop/agents/adapters/omp-rpc-client.ts","../../../../shared/terminal-protocol.ts","../../../../server/services/workshop/agents/harness-terminal.ts","../../../../server/services/workshop/prompts/loader.ts","../../../../server/services/workshop/agents/node-bindings.repo.ts","../../../../server/services/workshop/agents/tool-approvals.ts","../../../../shared/dcw-protocol.ts","../../../../server/services/workshop/dcw/drivers.ts","../../../../server/services/workshop/dcw/dcw-templates.ts","../../../../server/services/workshop/assets/device-twin.repo.ts","../../../../server/services/workshop/dcw/dcw-node.ts","../../../../server/services/workshop/dcw/dcw-node.repo.ts","../../../../server/services/workshop/dcw/dcw-runtime.ts","../../../../server/services/workshop/dcw/dcw-product.repo.ts","../../../../server/services/workshop/dcw/dcw-recipe.repo.ts","../../../../server/services/workshop/dcw/dcw-line.repo.ts","../../../../server/services/workshop/dcw/line-run.ts","../../../../server/services/workshop/dcw/recipe-rollback.repo.ts","../../../../server/services/workshop/dcw/recipe-rollback-manager.ts","../../../../server/services/workshop/dcw/dcw-controller.ts","../../../../server/services/workshop/agents/industrial-context.ts","../../../../server/services/workshop/agents/industrial-tools.ts","../../../../server/services/workshop/agents/omp-agent.ts","../../../../server/services/workshop/agents/claude-agent.ts","../../../../server/services/workshop/agents/factory.ts","../../../../server/services/workshop/types/task.ts","../../../../server/services/workshop/runtime/agent-runtime.ts","../../../../server/services/workshop/runtime/channel-runtime.ts","../../../../server/services/workshop/runtime/scheduler-loop.ts","../../../../server/services/workshop/runtime/task-engine.ts","../../../../server/services/workshop/runtime/memory.ts","../../../../server/services/workshop/runtime/embedding-provider.ts","../../../../server/services/workshop/runtime/manager.ts","../../../../server/plugins/workshop.ts","../../../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/static.mjs","../../../../server/middleware/request-log.ts","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/api/session.delete.js","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/api/session.get.js","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/app.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/lib/http-graceful-shutdown.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/shutdown.mjs"],"names":["decode","getQuery","parse","serialize","createRouter","f","h","c","i","l","g","createError","process","mergeHeaders","crypto","flatHooks","createTask","serialTaskCaller","parallelTaskCaller","callEachWith","createHooks","Hookable","s","callHooks","nodeFetch","fetch","Headers","Headers$1","AbortController","AbortController$1","normalizeKey","defineDriver","DRIVER_NAME","dirname","fsPromises","resolve","fsp","_inlineAppConfig","createRadixRouter","__publicField","log","watch","AEP_VERSION","randomUUID","_a","DB_PATH","rows","_b","_c","_d","reqNative","sleep","REGISTRY","bad","load","normalize","singleton","require","SCHEMA_SQL","COLS","randomPassword","randomBytes","hubGlobal","hub","criteria","worker","registry","frame","save","DATA_DIR","COOLDOWN_MS","_e","fresh","partsToText","pid","seen","modeInfo","rowToTask","TaskEngineImpl","callNodeRequestHandler","fetchNodeRequestHandler","gracefulShutdown"],"mappings":"","x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,51,52,134,135,137,138,139,140,141]}
|
|
1
|
+
{"version":3,"file":"nitro.mjs","sources":["../../../../node_modules/.pnpm/uncrypto@0.1.3/node_modules/uncrypto/dist/crypto.node.mjs","../../../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs","../../../../node_modules/.pnpm/ufo@1.6.4/node_modules/ufo/dist/index.mjs","../../../../node_modules/.pnpm/cookie-es@1.2.3/node_modules/cookie-es/dist/index.mjs","../../../../node_modules/.pnpm/radix3@1.1.2/node_modules/radix3/dist/index.mjs","../../../../node_modules/.pnpm/defu@6.1.7/node_modules/defu/dist/defu.mjs","../../../../node_modules/.pnpm/iron-webcrypto@1.2.1/node_modules/iron-webcrypto/dist/index.js","../../../../node_modules/.pnpm/node-mock-http@1.0.5/node_modules/node-mock-http/dist/index.mjs","../../../../node_modules/.pnpm/h3@1.15.11/node_modules/h3/dist/index.mjs","../../../../node_modules/.pnpm/hookable@5.5.3/node_modules/hookable/dist/index.mjs","../../../../node_modules/.pnpm/node-fetch-native@1.6.7/node_modules/node-fetch-native/dist/native.mjs","../../../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/shared/ofetch.CWycOUEr.mjs","../../../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/node.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/dist/shared/unstorage.zVDD2mZo.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/dist/index.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/utils/index.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/utils/node-fs.mjs","../../../../node_modules/.pnpm/unstorage@1.17.5_db0@0.3.4_ioredis@5.11.1/node_modules/unstorage/drivers/fs-lite.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/storage.mjs","../../../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/shared/ohash.D__AXeF1.mjs","../../../../node_modules/.pnpm/ohash@2.0.11/node_modules/ohash/dist/crypto/node/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/hash.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/cache.mjs","../../../../node_modules/.pnpm/klona@2.0.6/node_modules/klona/dist/index.mjs","../../../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/utils.env.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/config.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/route-rules-utils.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/route-rules.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/utils.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/utils/error.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/handlers/error.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/error/utils.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/error/prod.mjs","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/plugins/oauth.js","../../../../node_modules/.pnpm/hookable@6.1.1/node_modules/hookable/dist/index.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/plugin.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/renderer.mjs","../../../../node_modules/.pnpm/@nuxt+nitro-server@4.5.2_2b2a73a5ea3a6c1cac65dd2aa61911d0/node_modules/@nuxt/nitro-server/dist/runtime/utils/paths.mjs","../../../../server/utils/errors.ts","../../../../sdk/hooks.mjs","../../../../sdk/api.mjs","../../../../sdk/context.mjs","../../../../server/services/workshop/plugins/host.mjs","../../../../server/services/workshop/scene-events.ts","../../../../server/services/workshop/ops/ops.ts","../../../../server/utils/approval-gate.ts","../../../../server/utils/auth.ts","../../../../server/utils/config.ts","../../../../server/utils/response.ts","../../../../server/utils/validate.ts","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/utils/session.js","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/plugins/ws.js","../../../../server/plugins/aw-plugins.ts","../../../../shared/config/home.mjs","../../../../server/plugins/backup.ts","../../../../server/services/workshop/logger.ts","../../../../server/services/workshop/daq/infra.ts","../../../../server/services/workshop/daq/storage/sqlite.adapter.ts","../../../../server/services/workshop/daq/storage/timescale.adapter.ts","../../../../server/services/workshop/daq/storage/index.ts","../../../../server/services/workshop/daq/bus/inproc.adapter.ts","../../../../server/services/workshop/daq/bus/mqtt.adapter.ts","../../../../server/services/workshop/daq/bus/index.ts","../../../../shared/daq-protocol.ts","../../../../server/services/workshop/daq/drivers.ts","../../../../server/services/workshop/daq/daq-templates.ts","../../../../server/services/workshop/daq/daq-node.ts","../../../../server/services/workshop/daq/daq-runtime.ts","../../../../server/services/workshop/daq/daq-node.repo.ts","../../../../server/services/workshop/daq/host-ports.ts","../../../../server/services/workshop/daq/alarm-notify.ts","../../../../server/services/workshop/daq/daq-controller.ts","../../../../server/plugins/daq.ts","../../../../server/plugins/dev-stability-guard.ts","../../../../server/services/workshop/db/retention.ts","../../../../server/plugins/retention.ts","../../../../server/plugins/security.ts","../../../../shared/config/engine.mjs","../../../../server/services/system-config.ts","../../../../server/plugins/system-config.ts","../../../../server/services/workshop/db/database.ts","../../../../server/services/workshop/db/channel.repo.ts","../../../../server/services/workshop/db/agent.repo.ts","../../../../server/services/workshop/db/channel-agent.repo.ts","../../../../server/services/workshop/db/team.repo.ts","../../../../server/services/workshop/db/team-member.repo.ts","../../../../server/services/workshop/db/channel-template.repo.ts","../../../../server/services/workshop/db/task.repo.ts","../../../../server/services/workshop/db/message.repo.ts","../../../../server/services/workshop/db/subscription.repo.ts","../../../../server/services/workshop/db/memory.repo.ts","../../../../server/services/workshop/db/user.repo.ts","../../../../server/services/workshop/db/channel-event.repo.ts","../../../../server/services/workshop/db/ops.repo.ts","../../../../server/repositories/user.repository.ts","../../../../server/services/user.service.ts","../../../../server/services/workshop/runtime/mailbox.ts","../../../../server/api/workshop/ws.ts","../../../../server/services/workshop/runtime/execution-mode.ts","../../../../server/services/workshop/agents/mock-agent.ts","../../../../server/services/workshop/agents/harness-process.ts","../../../../server/services/workshop/agents/adapters/omp-rpc-client.ts","../../../../shared/terminal-protocol.ts","../../../../server/services/workshop/agents/harness-terminal.ts","../../../../server/services/workshop/prompts/loader.ts","../../../../server/services/workshop/agents/node-bindings.repo.ts","../../../../server/services/workshop/agents/tool-approvals.ts","../../../../shared/dcw-protocol.ts","../../../../server/services/workshop/dcw/drivers.ts","../../../../server/services/workshop/dcw/dcw-templates.ts","../../../../server/services/workshop/assets/device-twin.repo.ts","../../../../server/services/workshop/dcw/dcw-node.ts","../../../../server/services/workshop/dcw/dcw-node.repo.ts","../../../../server/services/workshop/dcw/dcw-runtime.ts","../../../../server/services/workshop/dcw/dcw-product.repo.ts","../../../../server/services/workshop/dcw/dcw-recipe.repo.ts","../../../../server/services/workshop/dcw/dcw-line.repo.ts","../../../../server/services/workshop/dcw/line-run.ts","../../../../server/services/workshop/dcw/recipe-rollback.repo.ts","../../../../server/services/workshop/dcw/recipe-rollback-manager.ts","../../../../server/services/workshop/dcw/dcw-controller.ts","../../../../server/services/workshop/agents/industrial-context.ts","../../../../server/services/workshop/agents/industrial-tools.ts","../../../../server/services/workshop/agents/omp-agent.ts","../../../../server/services/workshop/agents/claude-agent.ts","../../../../server/services/workshop/agents/factory.ts","../../../../server/services/workshop/types/task.ts","../../../../server/services/workshop/runtime/agent-runtime.ts","../../../../server/services/workshop/runtime/channel-runtime.ts","../../../../server/services/workshop/runtime/scheduler-loop.ts","../../../../server/services/workshop/runtime/task-engine.ts","../../../../server/services/workshop/runtime/memory.ts","../../../../server/services/workshop/runtime/embedding-provider.ts","../../../../server/services/workshop/runtime/manager.ts","../../../../server/plugins/workshop.ts","../../../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/static.mjs","../../../../server/middleware/request-log.ts","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/api/session.delete.js","../../../../node_modules/.pnpm/nuxt-auth-utils@0.5.30_magi_25cfea03c37d60c613297752f2919dae/node_modules/nuxt-auth-utils/dist/runtime/server/api/session.get.js","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/app.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/lib/http-graceful-shutdown.mjs","../../../../node_modules/.pnpm/nitropack@2.13.4_oxc-parser_b1d7d58181e96cbc69b3295f5f958520/node_modules/nitropack/dist/runtime/internal/shutdown.mjs"],"names":["decode","getQuery","parse","serialize","createRouter","f","h","c","i","l","g","createError","process","mergeHeaders","crypto","flatHooks","createTask","serialTaskCaller","parallelTaskCaller","callEachWith","createHooks","Hookable","s","callHooks","nodeFetch","fetch","Headers","Headers$1","AbortController","AbortController$1","normalizeKey","defineDriver","DRIVER_NAME","dirname","fsPromises","resolve","fsp","_inlineAppConfig","createRadixRouter","__publicField","log","watch","AEP_VERSION","randomUUID","_a","DB_PATH","rows","_b","_c","_d","reqNative","sleep","REGISTRY","bad","load","normalize","singleton","require","SCHEMA_SQL","COLS","randomPassword","randomBytes","hubGlobal","hub","criteria","worker","registry","frame","save","DATA_DIR","COOLDOWN_MS","_e","fresh","partsToText","pid","seen","modeInfo","rowToTask","TaskEngineImpl","callNodeRequestHandler","fetchNodeRequestHandler","gracefulShutdown"],"mappings":"","x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,51,52,136,137,139,140,141,142,143]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 保留策略单元验证(hardening GROW-1 / AC-5):临时库种过期行 → sweep → 断言。
|
|
3
|
+
* 用法:npx tsx scripts/_dbg-retention-test.ts
|
|
4
|
+
*/
|
|
5
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
6
|
+
import { runRetentionSweep } from '../server/services/workshop/db/retention'
|
|
7
|
+
|
|
8
|
+
const db = new DatabaseSync(':memory:')
|
|
9
|
+
db.exec('CREATE TABLE messages (id TEXT PRIMARY KEY, created_at TEXT NOT NULL)')
|
|
10
|
+
db.exec('CREATE TABLE audit_log (id INTEGER PRIMARY KEY AUTOINCREMENT, at TEXT NOT NULL)')
|
|
11
|
+
db.exec('CREATE TABLE approval_history (id TEXT PRIMARY KEY, created_at TEXT NOT NULL)')
|
|
12
|
+
const old = new Date(Date.now() - 40 * 86_400_000).toISOString()
|
|
13
|
+
const ancient = new Date(Date.now() - 120 * 86_400_000).toISOString()
|
|
14
|
+
const fresh = new Date(Date.now() - 1000).toISOString()
|
|
15
|
+
for (let i = 0; i < 12; i++) {
|
|
16
|
+
db.prepare('INSERT INTO messages VALUES (?,?)').run(`m${i}`, i < 8 ? old : fresh)
|
|
17
|
+
db.prepare('INSERT INTO audit_log (at) VALUES (?)').run(i < 6 ? ancient : fresh)
|
|
18
|
+
db.prepare('INSERT INTO approval_history VALUES (?,?)').run(`a${i}`, fresh)
|
|
19
|
+
}
|
|
20
|
+
console.log('SWEEP =', JSON.stringify(runRetentionSweep(db)))
|
|
21
|
+
const c = (t: string) => String((db.prepare(`SELECT COUNT(*) c FROM ${t}`).get() as { c: number }).c)
|
|
22
|
+
console.log(`剩量 messages=${c('messages')}(期望4) audit=${c('audit_log')}(期望6) approval=${c('approval_history')}(期望12)`)
|
|
23
|
+
|
|
24
|
+
process.env.AW_RETENTION_DISABLED = '1'
|
|
25
|
+
console.log('逃生门 =', JSON.stringify(runRetentionSweep(db)), '(期望 [])')
|
package/scripts/dev-guard.mjs
CHANGED
|
@@ -73,6 +73,39 @@ const effectivePort = cliPort ?? devPort
|
|
|
73
73
|
process.env.PORT = String(effectivePort)
|
|
74
74
|
console.log(`[config] worker PORT env -> ${effectivePort}`)
|
|
75
75
|
|
|
76
|
+
// --- 单实例互斥 + 端口占用显式报错(hardening ST-1/ST-4):不再静默换端口 ---
|
|
77
|
+
{
|
|
78
|
+
const { acquireLock, checkPort } = await import('../shared/config/single-instance.mjs')
|
|
79
|
+
const lock = acquireLock(resolve(root, '.AgentWorkShop'), { mode: 'dev', port: effectivePort })
|
|
80
|
+
if (!lock.ok) {
|
|
81
|
+
const h = lock.holder
|
|
82
|
+
console.error(`✖ 已有实例在运行(pid=${h.pid}${h.port ? `,端口=${h.port}` : ''},启动于 ${h.startedAt ?? '未知'},模式=${h.mode ?? '未知'})`)
|
|
83
|
+
console.error(` › dev/start 共享同一数据目录,禁止双开;如需并行请用 AW_HOME 隔离配置根`)
|
|
84
|
+
console.error(` › 如确认是残留锁文件,可删除 ${lock.lockPath} 后重试`)
|
|
85
|
+
process.exit(2)
|
|
86
|
+
}
|
|
87
|
+
const probeHost = devHost === '0.0.0.0' || devHost === '::' ? '127.0.0.1' : devHost
|
|
88
|
+
const occupied = await checkPort(probeHost, effectivePort)
|
|
89
|
+
if (occupied) {
|
|
90
|
+
let pidHint = ''
|
|
91
|
+
try {
|
|
92
|
+
const { execSync } = await import('node:child_process')
|
|
93
|
+
const isWin = process.platform === 'win32'
|
|
94
|
+
const out = execSync(
|
|
95
|
+
isWin ? `netstat -ano | findstr :${effectivePort}` : `lsof -ti :${effectivePort} 2>/dev/null | head -1`,
|
|
96
|
+
{ encoding: 'utf8', shell: true },
|
|
97
|
+
)
|
|
98
|
+
const m = isWin ? out.split('\n').find(l => l.includes('LISTENING')) : out.trim()
|
|
99
|
+
pidHint = isWin ? (m?.trim().split(/\s+/).pop() ?? '') : m
|
|
100
|
+
if (pidHint) pidHint = `(占用 pid=${pidHint})`
|
|
101
|
+
}
|
|
102
|
+
catch { /* 探测失败不阻断报错 */ }
|
|
103
|
+
console.error(`✖ 开发端口 ${effectivePort} 已被占用${pidHint}`)
|
|
104
|
+
console.error(` › 可显式指定其他端口: aw dev --port ${effectivePort + 1}`)
|
|
105
|
+
process.exit(2)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
76
109
|
const inject = [...rest]
|
|
77
110
|
if (!hasPort) inject.push('--port', String(devPort))
|
|
78
111
|
if (!hasHost) inject.push('--host', devHost)
|
package/scripts/start.mjs
CHANGED
|
@@ -50,6 +50,17 @@ const host = eff.effective['server.host'] ?? '0.0.0.0'
|
|
|
50
50
|
const prodPort = eff.effective['server.prod.port'] ?? 3000
|
|
51
51
|
const portSource = eff.sources['server.prod.port']
|
|
52
52
|
|
|
53
|
+
// ---- 单实例互斥(hardening ST-1):同配置根双开直接退出码 2,防 SQLite 锁崩溃 ----
|
|
54
|
+
const { acquireLock } = await import('../shared/config/single-instance.mjs')
|
|
55
|
+
const lock = acquireLock(rm.configRoot, { mode: `prod:${rm.mode}`, port: argPort ? Number(argPort) : prodPort })
|
|
56
|
+
if (!lock.ok) {
|
|
57
|
+
const h = lock.holder
|
|
58
|
+
console.error(`✖ 已有实例在运行(pid=${h.pid}${h.port ? `,端口=${h.port}` : ''},启动于 ${h.startedAt ?? '未知'},模式=${h.mode ?? '未知'})`)
|
|
59
|
+
console.error(` › 配置根: ${rm.configRoot}`)
|
|
60
|
+
console.error(` › 如确认是残留锁文件,可删除 ${lock.lockPath} 后重试`)
|
|
61
|
+
process.exit(2)
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
process.env.HOST = process.env.HOST || process.env.NITRO_HOST || String(host)
|
|
54
65
|
process.env.PORT = process.env.PORT || process.env.NITRO_PORT || String(prodPort)
|
|
55
66
|
process.env.NITRO_HOST = process.env.HOST
|
package/server/plugins/backup.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 备份定时插件(S6,production-readiness-plan)。
|
|
3
3
|
*
|
|
4
|
-
* 对 data/ 下三库(workshop/users/daq-timeseries)每日
|
|
4
|
+
* 对 data/ 下三库(workshop/users/daq-timeseries)每日 serialize 镜像快照到
|
|
5
5
|
* data/backups/,按 BACKUP_KEEP(默认 7)轮转;调度模式复用 ws.ts 保留期清理的
|
|
6
6
|
* setInterval + globalThis key 防 HMR 重复 + unref()。dev 与生产均生效
|
|
7
7
|
* (备份无副作用,始终开启;BACKUP_DISABLED=1 可关)。
|
|
8
8
|
*/
|
|
9
9
|
import { DatabaseSync } from 'node:sqlite'
|
|
10
|
-
import { existsSync, mkdirSync, readdirSync, rmSync } from 'node:fs'
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
11
11
|
import { resolve } from 'node:path'
|
|
12
12
|
import { ensureDataDir } from '@/shared/config/home.mjs'
|
|
13
13
|
|
|
@@ -15,7 +15,23 @@ const g = globalThis as typeof globalThis & { __awBackupTimer?: NodeJS.Timeout,
|
|
|
15
15
|
|
|
16
16
|
const DB_FILES = ['workshop.sqlite', 'users.sqlite', 'daq-timeseries.sqlite'] as const
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 单库在线快照(hardening ST-2):node:sqlite serialize() 一致性数据库镜像 → 落盘。
|
|
20
|
+
* 纯读操作,不与主写者抢锁(VACUUM INTO 会与写事务竞争 SQLITE_BUSY,已实测);
|
|
21
|
+
* 产物为标准 SQLite 文件,与历史 .bak 结构兼容。失败只记录不抛出。
|
|
22
|
+
*/
|
|
23
|
+
async function backupOne(src: string, target: string): Promise<void> {
|
|
24
|
+
const db = new DatabaseSync(src)
|
|
25
|
+
try {
|
|
26
|
+
const image = db.serialize()
|
|
27
|
+
writeFileSync(target, image)
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
db.close()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function backupOnce(dataDir: string): Promise<void> {
|
|
19
35
|
const backupDir = resolve(dataDir, 'backups')
|
|
20
36
|
mkdirSync(backupDir, { recursive: true })
|
|
21
37
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
@@ -24,14 +40,8 @@ function backupOnce(dataDir: string): void {
|
|
|
24
40
|
if (!existsSync(src)) continue
|
|
25
41
|
const target = resolve(backupDir, `${file}.${stamp}.bak`)
|
|
26
42
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// VACUUM INTO:在线一致性快照(WAL 下安全),不阻塞长事务
|
|
30
|
-
db.exec(`VACUUM INTO '${target.replace(/\\/g, '/')}'`)
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
db.close()
|
|
34
|
-
}
|
|
43
|
+
await backupOne(src, target)
|
|
44
|
+
console.log('[backup]', file, '快照完成(serialize)')
|
|
35
45
|
}
|
|
36
46
|
catch (err) {
|
|
37
47
|
console.error(`[backup] ${file} 快照失败:`, err instanceof Error ? err.message : err)
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* 依赖异常中止进程,绝不能被护栏吞掉。
|
|
8
8
|
*/
|
|
9
9
|
const SOCKET_NOISE = /ECONNRESET|EPIPE|ECONNABORTED|ETIMEDOUT|ERR_STREAM_PREMATURE_CLOSE/
|
|
10
|
+
// SQLite 忙/锁(hardening ST-1):多写竞争的瞬态错误,记录并继续;
|
|
11
|
+
// 真正的库损坏/磁盘错误不经此路径,仍走致命语义。
|
|
12
|
+
const SQLITE_BUSY = /SQLITE_BUSY|SQLITE_LOCKED|database is locked/i
|
|
10
13
|
|
|
11
14
|
function isSocketNoise(reason: unknown): boolean {
|
|
12
15
|
if (reason == null) return false
|
|
@@ -22,11 +25,16 @@ function describe(reason: unknown): string {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export default defineNitroPlugin(() => {
|
|
28
|
+
const isTransientDbBusy = (reason: unknown): boolean => SQLITE_BUSY.test(describe(reason))
|
|
25
29
|
process.on('unhandledRejection', (reason) => {
|
|
26
30
|
if (isSocketNoise(reason)) {
|
|
27
31
|
console.warn(`[stability-guard] socket noise suppressed: ${describe(reason).slice(0, 120)}`)
|
|
28
32
|
return
|
|
29
33
|
}
|
|
34
|
+
if (isTransientDbBusy(reason)) {
|
|
35
|
+
console.warn(`[stability-guard] sqlite busy(瞬态,不退出): ${describe(reason).slice(0, 120)}`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
30
38
|
console.error(`[stability-guard] fatal unhandledRejection, exiting:\n${describe(reason)}`)
|
|
31
39
|
process.exit(1)
|
|
32
40
|
})
|
|
@@ -35,8 +43,12 @@ export default defineNitroPlugin(() => {
|
|
|
35
43
|
console.warn(`[stability-guard] socket noise suppressed: ${describe(err).slice(0, 120)}`)
|
|
36
44
|
return
|
|
37
45
|
}
|
|
46
|
+
if (isTransientDbBusy(err)) {
|
|
47
|
+
console.warn(`[stability-guard] sqlite busy(瞬态,不退出): ${describe(err).slice(0, 120)}`)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
38
50
|
console.error(`[stability-guard] fatal uncaughtException, exiting:\n${describe(err)}`)
|
|
39
51
|
process.exit(1)
|
|
40
52
|
})
|
|
41
|
-
console.warn('[stability-guard] armed (socket
|
|
53
|
+
console.warn('[stability-guard] armed (socket 断连与 sqlite 瞬态忙 → 记录继续;真实错误 → exit 1)')
|
|
42
54
|
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 保留期定时插件(hardening GROW-1)。
|
|
3
|
+
*
|
|
4
|
+
* 每 24h 对 workshop.sqlite 的 messages/audit_log/approval_history 做分批保留
|
|
5
|
+
* 清理(策略见 db/retention.ts;RETENTION_DISABLED=1 可整体关闭)。
|
|
6
|
+
* 调度模式同 backup.ts:setInterval + globalThis key 防 HMR 重复 + unref()。
|
|
7
|
+
* 启动 90s 后首跑(避开启动风暴,且错峰于 backup 的 30s 首备)。
|
|
8
|
+
*/
|
|
9
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
10
|
+
import { existsSync } from 'node:fs'
|
|
11
|
+
import { resolve } from 'node:path'
|
|
12
|
+
import { runRetentionSweep } from '@/server/services/workshop/db/retention'
|
|
13
|
+
import { ensureDataDir } from '@/shared/config/home.mjs'
|
|
14
|
+
|
|
15
|
+
const g = globalThis as typeof globalThis & { __awRetentionTimer?: NodeJS.Timeout }
|
|
16
|
+
|
|
17
|
+
function sweepOnce(dataDir: string): void {
|
|
18
|
+
const dbPath = resolve(dataDir, 'workshop.sqlite')
|
|
19
|
+
if (!existsSync(dbPath)) return
|
|
20
|
+
const db = new DatabaseSync(dbPath)
|
|
21
|
+
try {
|
|
22
|
+
const results = runRetentionSweep(db)
|
|
23
|
+
for (const r of results) {
|
|
24
|
+
if (r.removed > 0) console.log(`[retention] ${r.table} 清理 ${r.removed} 行(>${r.days}d)`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
db.close()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function retentionPlugin() {
|
|
33
|
+
if (process.env.RETENTION_DISABLED === '1' || process.env.AW_RETENTION_DISABLED === '1') return
|
|
34
|
+
if (g.__awRetentionTimer) return
|
|
35
|
+
const dataDir = ensureDataDir()
|
|
36
|
+
|
|
37
|
+
const first = setTimeout(() => {
|
|
38
|
+
try {
|
|
39
|
+
sweepOnce(dataDir)
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
console.error('[retention] 首轮清理失败:', err instanceof Error ? err.message : err)
|
|
43
|
+
}
|
|
44
|
+
}, 90_000)
|
|
45
|
+
first.unref?.()
|
|
46
|
+
|
|
47
|
+
const timer = setInterval(() => {
|
|
48
|
+
try {
|
|
49
|
+
sweepOnce(dataDir)
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error('[retention] 定时清理失败:', err instanceof Error ? err.message : err)
|
|
53
|
+
}
|
|
54
|
+
}, 24 * 3_600_000)
|
|
55
|
+
timer.unref?.()
|
|
56
|
+
g.__awRetentionTimer = timer
|
|
57
|
+
}
|
|
@@ -476,12 +476,14 @@ class DaqController {
|
|
|
476
476
|
controllerState(): AepDaqControllerState & { produced?: number, consumed?: number, dropped?: number, samplesStored?: number, tsdbDropped?: number } {
|
|
477
477
|
// 丢弃 = 队列层真实丢弃(inproc 拥塞/mqtt 断连)+ 消费侧乱序迟到帧(诚实可见)
|
|
478
478
|
const queueLost = g_queueLost()
|
|
479
|
+
// 单次遍历(hardening PERF-1):status 高频路径不再对全表多次 all()
|
|
480
|
+
const nodes = this.repo.all()
|
|
479
481
|
return {
|
|
480
482
|
running: this.running,
|
|
481
483
|
defaultIntervalMs: this.defaultIntervalMs,
|
|
482
484
|
defaultPublishIntervalMs: this.defaultPublishIntervalMs,
|
|
483
|
-
nodesTotal:
|
|
484
|
-
nodesOnline: this.
|
|
485
|
+
nodesTotal: nodes.length,
|
|
486
|
+
nodesOnline: this.running ? nodes.filter(n => n.enabled).length : 0,
|
|
485
487
|
produced: this.producedCount,
|
|
486
488
|
consumed: this.consumedCount,
|
|
487
489
|
dropped: Math.max(0, this.producedCount - this.consumedCount) + queueLost + this.lateDropped,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 增长治理保留策略(hardening GROW-1)。
|
|
3
|
+
*
|
|
4
|
+
* 为无保留机制的只增表做分批清理(每批 ≤5000,循环到 0,避免长事务持锁):
|
|
5
|
+
* messages 默认 30d (AW_MESSAGES_RETENTION_D)
|
|
6
|
+
* audit_log 默认 90d (AW_AUDIT_RETENTION_D;合规观察期,从严保留)
|
|
7
|
+
* approval_history 默认 180d (AW_APPROVAL_RETENTION_D;历史裁决主张不可变,放宽)
|
|
8
|
+
* AW_RETENTION_DISABLED=1 整体逃生门。时间列均为 TEXT ISO,可字典序比较。
|
|
9
|
+
* channel_events 与 daq_samples 已有各自保留机制,此处不重复覆盖。
|
|
10
|
+
*/
|
|
11
|
+
type DatabaseSyncLike = {
|
|
12
|
+
prepare: (sql: string) => { run: (...args: unknown[]) => { changes: number | bigint } }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DAY_MS = 86_400_000
|
|
16
|
+
const BATCH = 5000
|
|
17
|
+
|
|
18
|
+
const TARGETS = [
|
|
19
|
+
{ table: 'messages', column: 'created_at', days: 30, envKey: 'AW_MESSAGES_RETENTION_D' },
|
|
20
|
+
{ table: 'audit_log', column: 'at', days: 90, envKey: 'AW_AUDIT_RETENTION_D' },
|
|
21
|
+
{ table: 'approval_history', column: 'created_at', days: 180, envKey: 'AW_APPROVAL_RETENTION_D' },
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
function retentionDays(envKey: string, fallback: number): number {
|
|
25
|
+
const n = Number(process.env[envKey])
|
|
26
|
+
return Number.isFinite(n) && n > 0 ? n : fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RetentionResult { table: string, removed: number, days: number }
|
|
30
|
+
|
|
31
|
+
/** 对单库执行一轮保留清理;返回各表删除行数(调用方负责 try/catch 与日志)。 */
|
|
32
|
+
export function runRetentionSweep(db: DatabaseSyncLike): RetentionResult[] {
|
|
33
|
+
if (process.env.AW_RETENTION_DISABLED === '1') return []
|
|
34
|
+
const out: RetentionResult[] = []
|
|
35
|
+
for (const t of TARGETS) {
|
|
36
|
+
const days = retentionDays(t.envKey, t.days)
|
|
37
|
+
const before = new Date(Date.now() - days * DAY_MS).toISOString()
|
|
38
|
+
let removed = 0
|
|
39
|
+
for (;;) {
|
|
40
|
+
const res = db.prepare(
|
|
41
|
+
`DELETE FROM ${t.table} WHERE rowid IN (SELECT rowid FROM ${t.table} WHERE ${t.column} < ? LIMIT ${BATCH})`,
|
|
42
|
+
).run(before)
|
|
43
|
+
const n = Number(res.changes)
|
|
44
|
+
removed += n
|
|
45
|
+
if (n < BATCH) break
|
|
46
|
+
}
|
|
47
|
+
out.push({ table: t.table, removed, days })
|
|
48
|
+
}
|
|
49
|
+
return out
|
|
50
|
+
}
|
|
@@ -218,10 +218,12 @@ class DcwController {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
controllerState() {
|
|
221
|
+
// 单次遍历(hardening PERF-1):status 高频路径不再对全表多次 all()
|
|
222
|
+
const nodes = this.repo.all()
|
|
221
223
|
return {
|
|
222
224
|
running: this.running,
|
|
223
|
-
nodesTotal:
|
|
224
|
-
nodesOnline: this.running ?
|
|
225
|
+
nodesTotal: nodes.length,
|
|
226
|
+
nodesOnline: this.running ? nodes.filter(n => n.enabled).length : 0,
|
|
225
227
|
writesTotal: this.writesTotal,
|
|
226
228
|
writesFailed: this.writesFailed,
|
|
227
229
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 单实例互斥(hardening ST-1)—— 零依赖,CLI / 启动脚本通用。
|
|
3
|
+
// 锁文件 <configRoot>/.runtime/aw.lock 原子创建(wx):
|
|
4
|
+
// { pid, startedAt, mode, port }
|
|
5
|
+
// 已存在且 PID 存活 → 视为已有实例(返回冲突信息,调用方以退出码 2 退出);
|
|
6
|
+
// PID 已死(崩溃/强杀残留) → 自动接管重写。进程退出时 releaseLock 兜底清理。
|
|
7
|
+
// ============================================================
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
function pidAlive(pid) {
|
|
12
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
13
|
+
try {
|
|
14
|
+
// signal 0 探活:进程存在但不发信号;ESRCH=不存在,EPERM=存在(他人进程)
|
|
15
|
+
process.kill(pid, 0)
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
return err?.code === 'EPERM'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 尝试获取单实例锁。
|
|
25
|
+
* @returns {{ ok: true, lockPath: string, release: () => void }
|
|
26
|
+
* | { ok: false, lockPath: string, holder: { pid: number, startedAt: string, mode: string, port: number | null } }}
|
|
27
|
+
*/
|
|
28
|
+
export function acquireLock(configRoot, { mode = 'prod', port = null } = {}) {
|
|
29
|
+
const runtimeDir = join(configRoot, '.runtime')
|
|
30
|
+
const lockPath = join(runtimeDir, 'aw.lock')
|
|
31
|
+
mkdirSync(runtimeDir, { recursive: true })
|
|
32
|
+
if (existsSync(lockPath)) {
|
|
33
|
+
let holder = null
|
|
34
|
+
try {
|
|
35
|
+
holder = JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
36
|
+
}
|
|
37
|
+
catch { /* 损坏锁文件按残留处理 */ }
|
|
38
|
+
if (holder && pidAlive(holder.pid)) {
|
|
39
|
+
return { ok: false, lockPath, holder: { ...holder, port: holder.port ?? null } }
|
|
40
|
+
}
|
|
41
|
+
try { rmSync(lockPath) } catch { /* 抢不掉则下方 wx 再失败 */ }
|
|
42
|
+
}
|
|
43
|
+
const me = { pid: process.pid, startedAt: new Date().toISOString(), mode, port }
|
|
44
|
+
try {
|
|
45
|
+
writeFileSync(lockPath, JSON.stringify(me, null, 2), { flag: 'wx' })
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
// 与并发启动者竞态:按冲突处理
|
|
49
|
+
let holder = null
|
|
50
|
+
try { holder = JSON.parse(readFileSync(lockPath, 'utf8')) } catch { /* ignore */ }
|
|
51
|
+
if (holder && pidAlive(holder.pid)) {
|
|
52
|
+
return { ok: false, lockPath, holder: { ...holder, port: holder.port ?? null } }
|
|
53
|
+
}
|
|
54
|
+
throw err
|
|
55
|
+
}
|
|
56
|
+
let released = false
|
|
57
|
+
const release = () => {
|
|
58
|
+
if (released) return
|
|
59
|
+
released = true
|
|
60
|
+
try {
|
|
61
|
+
const cur = JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
62
|
+
if (cur?.pid !== process.pid) return // 锁已被他人接管,不误删
|
|
63
|
+
rmSync(lockPath)
|
|
64
|
+
}
|
|
65
|
+
catch { /* 已不存在/损坏,忽略 */ }
|
|
66
|
+
}
|
|
67
|
+
process.once('exit', release)
|
|
68
|
+
process.once('SIGINT', () => { release(); process.exit(130) })
|
|
69
|
+
process.once('SIGTERM', () => { release(); process.exit(143) })
|
|
70
|
+
return { ok: true, lockPath, release }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 端口占用探测:返回占用进程 PID(未占用返回 null)。TCP try-listen 后立刻释放。 */
|
|
74
|
+
export async function checkPort(host, port) {
|
|
75
|
+
const net = await import('node:net')
|
|
76
|
+
return new Promise((resolveProbe) => {
|
|
77
|
+
const srv = net.createServer()
|
|
78
|
+
srv.once('error', (err) => {
|
|
79
|
+
resolveProbe(err?.code === 'EADDRINUSE' ? port : null)
|
|
80
|
+
srv.close()
|
|
81
|
+
})
|
|
82
|
+
srv.listen(port, host, () => {
|
|
83
|
+
srv.close(() => resolveProbe(null))
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"fb769eda-b1db-437b-adc7-ecf71eb3b30b","timestamp":1788339632657,"prerendered":[]}
|