sneakoscope 7.3.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -10
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +4 -1
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
- package/dist/cli/install-helpers-codex-lb-config.js +114 -9
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +788 -600
- package/dist/cli/router.js +30 -2
- package/dist/commands/codex-app.js +37 -1
- package/dist/commands/codex-lb.js +491 -300
- package/dist/config/skills-manifest.json +57 -71
- package/dist/core/align/align-route.js +127 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
- package/dist/core/codex-app/codex-app-restart.js +83 -0
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
- package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
- package/dist/core/codex-app/menubar/constants.js +5 -0
- package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
- package/dist/core/codex-app/menubar/installer.js +18 -0
- package/dist/core/codex-app/multi-provider-router.js +22 -10
- package/dist/core/codex-app/openrouter-activate.js +26 -1
- package/dist/core/codex-app.js +118 -26
- package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
- package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
- package/dist/core/codex-control/codex-task-runner.js +2 -2
- package/dist/core/codex-lb/capability-runner.js +204 -0
- package/dist/core/codex-lb/capability-types.js +1 -0
- package/dist/core/codex-lb/codex-lb-env.js +9 -13
- package/dist/core/codex-lb/codex-lb-setup.js +45 -8
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
- package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
- package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
- package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
- package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
- package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
- package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
- package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
- package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
- package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
- package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
- package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
- package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
- package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
- package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
- package/dist/core/codex-lb/desktop-controller.js +1410 -0
- package/dist/core/codex-lb/desktop-mode.js +32 -0
- package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
- package/dist/core/codex-lb/desktop-service.js +613 -0
- package/dist/core/codex-lb/legacy-migration.js +575 -0
- package/dist/core/codex-lb/migration-receipt.js +246 -0
- package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
- package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
- package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
- package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
- package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
- package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
- package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
- package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
- package/dist/core/codex-native/core-skill-manifest.js +22 -6
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
- package/dist/core/commands/align-command.js +220 -0
- package/dist/core/commands/image-ux-review-command.js +280 -68
- package/dist/core/commands/naruto-command.js +257 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +402 -34
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/doctor/skill-legacy-surface.js +1 -3
- package/dist/core/errors/message.js +3 -0
- package/dist/core/evidence/evidence-freshness.js +14 -0
- package/dist/core/feature-fixtures.js +9 -1
- package/dist/core/feature-registry.js +5 -3
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/hook-context.js +15 -2
- package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
- package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +33 -89
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +230 -12
- package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
- package/dist/core/imagegen/imagegen-capability.js +10 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +25 -13
- package/dist/core/init.js +1 -1
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/perf/release-latency-slo.js +12 -6
- package/dist/core/pipeline-internals/runtime-core.js +78 -0
- package/dist/core/pipeline-internals/runtime-gates.js +5 -2
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/preflight/parallel-preflight-engine.js +1 -1
- package/dist/core/proof/auto-finalize.js +98 -6
- package/dist/core/proof/route-proof-gate.js +12 -1
- package/dist/core/proof/route-proof-policy.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/release-proof-io.js +1 -3
- package/dist/core/release/release-real-contract.js +5 -4
- package/dist/core/release/release-stamp-proof.js +1 -3
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/remote/audit-idempotency.js +50 -18
- package/dist/core/remote/codex-public-activity.js +283 -0
- package/dist/core/remote/index.js +1 -0
- package/dist/core/remote/ssh-worker-client.js +4 -1
- package/dist/core/remote/worker.js +206 -27
- package/dist/core/research/research-adversarial-review.js +1 -3
- package/dist/core/research/research-claim-synthesizer.js +1 -3
- package/dist/core/research/research-falsification-runner.js +1 -3
- package/dist/core/research/research-source-evidence.js +1 -3
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +2 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +30 -10
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/secret-redaction.js +6 -0
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +85 -10
- package/dist/core/telegram/center-snapshot.js +718 -0
- package/dist/core/telegram/config.js +158 -31
- package/dist/core/telegram/hub.js +288 -33
- package/dist/core/telegram/index.js +2 -0
- package/dist/core/telegram/ingress-spool.js +419 -0
- package/dist/core/telegram/ledgers.js +188 -29
- package/dist/core/telegram/messages.js +26 -6
- package/dist/core/telegram/owner-lock.js +10 -7
- package/dist/core/telegram/runtime-projection.js +38 -3
- package/dist/core/telegram/runtime.js +298 -63
- package/dist/core/telegram/service.js +137 -8
- package/dist/core/telegram/setup.js +291 -73
- package/dist/core/text/strings.js +3 -0
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
- package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
- package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
- package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
- package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
- package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
- package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
- package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
- package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
- package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
- package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
- package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
- package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
- package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
- package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
- package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
- package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
- package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
- package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
- package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
- package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
- package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
- package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
- package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
- package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
- package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
- package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
- package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
- package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
- package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
- package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +1 -1
- package/dist/scripts/helper-dedup-check.js +0 -2
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +21 -4
- package/dist/scripts/release-real-check.js +7 -0
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +8 -3
- package/release-gates.v2.json +176 -1
- package/runtime-required-scripts.json +30 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
- package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -5,12 +5,13 @@ import { appendJsonlBounded, ensureDir, nowIso, readJson, sha256, writeJsonAtomi
|
|
|
5
5
|
import { withFileLock } from '../locks/file-lock.js';
|
|
6
6
|
export class TelegramTopicRegistry {
|
|
7
7
|
file;
|
|
8
|
-
|
|
8
|
+
options;
|
|
9
|
+
constructor(file, options = {}) {
|
|
9
10
|
this.file = file;
|
|
11
|
+
this.options = options;
|
|
10
12
|
}
|
|
11
13
|
async list() {
|
|
12
|
-
|
|
13
|
-
return registry.schema === 'sks.telegram-topic-registry.v1' && Array.isArray(registry.routes) ? registry.routes : [];
|
|
14
|
+
return (await this.readRegistry()).routes;
|
|
14
15
|
}
|
|
15
16
|
async findBySession(machineId, projectId, sessionId) {
|
|
16
17
|
return (await this.list()).find((route) => route.machine_id === machineId && route.project_id === projectId && route.session_id === sessionId) ?? null;
|
|
@@ -20,7 +21,8 @@ export class TelegramTopicRegistry {
|
|
|
20
21
|
}
|
|
21
22
|
async upsert(input) {
|
|
22
23
|
return withFileLock({ lockPath: `${this.file}.lock`, timeoutMs: 5_000, staleMs: 30_000 }, async () => {
|
|
23
|
-
const
|
|
24
|
+
const registry = await this.readRegistry();
|
|
25
|
+
const routes = [...registry.routes];
|
|
24
26
|
const sessionIndex = routes.findIndex((route) => route.machine_id === input.machine_id && route.project_id === input.project_id && route.session_id === input.session_id);
|
|
25
27
|
const collision = input.message_thread_id > 0
|
|
26
28
|
? routes.find((route, index) => index !== sessionIndex && route.chat_id === input.chat_id && route.message_thread_id === input.message_thread_id)
|
|
@@ -29,6 +31,10 @@ export class TelegramTopicRegistry {
|
|
|
29
31
|
throw new Error('telegram_topic_collision');
|
|
30
32
|
const current = sessionIndex >= 0 ? routes[sessionIndex] : undefined;
|
|
31
33
|
const at = nowIso();
|
|
34
|
+
const routeMoved = Boolean(current
|
|
35
|
+
&& (current.chat_id !== input.chat_id
|
|
36
|
+
|| current.message_thread_id !== input.message_thread_id));
|
|
37
|
+
const generationBase = Math.max(registry.generation_epoch ?? 0, ...routes.map((route) => route.generation));
|
|
32
38
|
const next = {
|
|
33
39
|
schema: 'sks.telegram-topic-route.v1',
|
|
34
40
|
machine_id: input.machine_id,
|
|
@@ -39,16 +45,60 @@ export class TelegramTopicRegistry {
|
|
|
39
45
|
pinned_message_id: input.pinned_message_id,
|
|
40
46
|
created_at: current?.created_at ?? at,
|
|
41
47
|
updated_at: at,
|
|
42
|
-
generation: current
|
|
48
|
+
generation: current && !routeMoved && input.recreate !== true
|
|
49
|
+
? current.generation
|
|
50
|
+
: generationBase + 1
|
|
43
51
|
};
|
|
44
52
|
if (sessionIndex >= 0)
|
|
45
53
|
routes[sessionIndex] = next;
|
|
46
54
|
else
|
|
47
55
|
routes.push(next);
|
|
48
|
-
await writeJsonAtomic(this.file, {
|
|
56
|
+
await writeJsonAtomic(this.file, {
|
|
57
|
+
schema: 'sks.telegram-topic-registry.v1',
|
|
58
|
+
...(this.options.identityFingerprint
|
|
59
|
+
? { identity_fingerprint: this.options.identityFingerprint }
|
|
60
|
+
: {}),
|
|
61
|
+
generation_epoch: Math.max(generationBase, next.generation),
|
|
62
|
+
routes
|
|
63
|
+
});
|
|
49
64
|
return next;
|
|
50
65
|
});
|
|
51
66
|
}
|
|
67
|
+
async invalidateAll() {
|
|
68
|
+
return withFileLock({ lockPath: `${this.file}.lock`, timeoutMs: 5_000, staleMs: 30_000 }, async () => {
|
|
69
|
+
const raw = await this.readRawRegistry();
|
|
70
|
+
const generationEpoch = Math.max(raw.generation_epoch ?? 0, ...raw.routes.map((route) => route.generation));
|
|
71
|
+
await writeJsonAtomic(this.file, {
|
|
72
|
+
schema: 'sks.telegram-topic-registry.v1',
|
|
73
|
+
...(this.options.identityFingerprint
|
|
74
|
+
? { identity_fingerprint: this.options.identityFingerprint }
|
|
75
|
+
: {}),
|
|
76
|
+
generation_epoch: generationEpoch,
|
|
77
|
+
routes: []
|
|
78
|
+
});
|
|
79
|
+
return generationEpoch;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async readRegistry() {
|
|
83
|
+
const registry = await this.readRawRegistry();
|
|
84
|
+
const expectedIdentity = this.options.identityFingerprint;
|
|
85
|
+
if (!expectedIdentity || registry.identity_fingerprint === expectedIdentity)
|
|
86
|
+
return registry;
|
|
87
|
+
return {
|
|
88
|
+
schema: 'sks.telegram-topic-registry.v1',
|
|
89
|
+
identity_fingerprint: expectedIdentity,
|
|
90
|
+
generation_epoch: Math.max(registry.generation_epoch ?? 0, ...registry.routes.map((route) => route.generation)),
|
|
91
|
+
routes: []
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async readRawRegistry() {
|
|
95
|
+
const registry = await readJson(this.file, { schema: 'sks.telegram-topic-registry.v1', routes: [] });
|
|
96
|
+
if (registry.schema !== 'sks.telegram-topic-registry.v1'
|
|
97
|
+
|| !Array.isArray(registry.routes)) {
|
|
98
|
+
throw new Error('telegram_topic_registry_schema_invalid');
|
|
99
|
+
}
|
|
100
|
+
return registry;
|
|
101
|
+
}
|
|
52
102
|
}
|
|
53
103
|
export function buildTelegramTopicName(input, existingNames = [], maxLength = 128) {
|
|
54
104
|
const normalized = [input.machine, `${input.repo}/${input.branch}`, input.title]
|
|
@@ -80,33 +130,86 @@ export class TelegramIdempotencyLedger {
|
|
|
80
130
|
}
|
|
81
131
|
export class TelegramActionBroker {
|
|
82
132
|
file;
|
|
83
|
-
|
|
133
|
+
maxEntries;
|
|
134
|
+
now;
|
|
135
|
+
constructor(file, options = {}) {
|
|
84
136
|
this.file = file;
|
|
137
|
+
this.maxEntries = Math.max(64, Math.min(4096, options.maxEntries ?? 512));
|
|
138
|
+
this.now = options.now ?? Date.now;
|
|
85
139
|
}
|
|
86
140
|
async create(action, route) {
|
|
87
|
-
|
|
141
|
+
return (await this.createMany([{ action, route }]))[0];
|
|
142
|
+
}
|
|
143
|
+
async createMany(entries) {
|
|
144
|
+
if (entries.some((entry) => entry.action.risk === 'R3'))
|
|
88
145
|
throw new Error('telegram_r3_always_denied');
|
|
89
146
|
return this.mutate((ledger) => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
expireOpenActions(ledger, this.now());
|
|
148
|
+
const actionsById = new Map(ledger.actions.map((action) => [action.action_id, action]));
|
|
149
|
+
const aliasesByActionId = new Map(ledger.aliases.map((alias) => [alias.action_id, alias]));
|
|
150
|
+
const protectedActionIds = new Set();
|
|
151
|
+
let additions = 0;
|
|
152
|
+
for (const { action, route } of entries) {
|
|
153
|
+
if (protectedActionIds.has(action.action_id))
|
|
154
|
+
throw new Error('telegram_action_batch_duplicate');
|
|
155
|
+
protectedActionIds.add(action.action_id);
|
|
156
|
+
const existing = actionsById.get(action.action_id);
|
|
157
|
+
if (!existing) {
|
|
158
|
+
additions += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const existingAlias = aliasesByActionId.get(action.action_id);
|
|
162
|
+
if (!existingAlias
|
|
163
|
+
|| existing.status !== 'open'
|
|
164
|
+
|| existing.route_generation !== action.route_generation
|
|
165
|
+
|| existingAlias.chat_id !== route.chat_id
|
|
166
|
+
|| existingAlias.message_thread_id !== route.message_thread_id) {
|
|
167
|
+
throw new Error('telegram_action_id_exists');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
reserveActionLedgerCapacity(ledger, this.maxEntries, additions, protectedActionIds);
|
|
171
|
+
return entries.map(({ action, route }) => {
|
|
172
|
+
const existing = actionsById.get(action.action_id);
|
|
173
|
+
if (existing) {
|
|
174
|
+
const existingAlias = aliasesByActionId.get(action.action_id);
|
|
175
|
+
return { action: existing, callback_data: `cb:${existingAlias.alias}` };
|
|
176
|
+
}
|
|
177
|
+
const alias = createAlias();
|
|
178
|
+
const callbackData = `cb:${alias}`;
|
|
179
|
+
if (Buffer.byteLength(callbackData, 'utf8') < 1 || Buffer.byteLength(callbackData, 'utf8') > 64)
|
|
180
|
+
throw new Error('telegram_callback_data_out_of_bounds');
|
|
181
|
+
ledger.actions.push(action);
|
|
182
|
+
const aliasEntry = {
|
|
183
|
+
alias,
|
|
184
|
+
action_id: action.action_id,
|
|
185
|
+
session_id: action.session_id,
|
|
186
|
+
chat_id: route.chat_id,
|
|
187
|
+
message_thread_id: route.message_thread_id,
|
|
188
|
+
route_generation: action.route_generation,
|
|
189
|
+
revision: action.revision,
|
|
190
|
+
expires_at: action.expires_at,
|
|
191
|
+
used_at: null,
|
|
192
|
+
claimed_update_id: null
|
|
193
|
+
};
|
|
194
|
+
ledger.aliases.push(aliasEntry);
|
|
195
|
+
actionsById.set(action.action_id, action);
|
|
196
|
+
aliasesByActionId.set(action.action_id, aliasEntry);
|
|
197
|
+
return { action, callback_data: callbackData };
|
|
106
198
|
});
|
|
107
|
-
return { action, callback_data: callbackData };
|
|
108
199
|
});
|
|
109
200
|
}
|
|
201
|
+
async inspect(callbackData) {
|
|
202
|
+
const ledger = await readJson(this.file, { schema: 'sks.telegram-action-ledger.v1', actions: [], aliases: [] });
|
|
203
|
+
if (ledger.schema !== 'sks.telegram-action-ledger.v1') {
|
|
204
|
+
return { alias: null, action: null };
|
|
205
|
+
}
|
|
206
|
+
const aliasValue = callbackData.startsWith('cb:') ? callbackData.slice(3) : '';
|
|
207
|
+
const alias = ledger.aliases.find((entry) => entry.alias === aliasValue) ?? null;
|
|
208
|
+
const action = alias
|
|
209
|
+
? ledger.actions.find((entry) => entry.action_id === alias.action_id) ?? null
|
|
210
|
+
: null;
|
|
211
|
+
return { alias, action };
|
|
212
|
+
}
|
|
110
213
|
async resolve(input) {
|
|
111
214
|
return this.mutate((ledger) => {
|
|
112
215
|
const aliasValue = input.callback_data.startsWith('cb:') ? input.callback_data.slice(3) : '';
|
|
@@ -116,19 +219,30 @@ export class TelegramActionBroker {
|
|
|
116
219
|
const action = ledger.actions.find((entry) => entry.action_id === alias.action_id) ?? null;
|
|
117
220
|
if (!action)
|
|
118
221
|
return { ok: false, status: 'unknown_action', action: null };
|
|
222
|
+
if (alias.claimed_update_id === input.update_id
|
|
223
|
+
&& alias.used_at
|
|
224
|
+
&& action.status === 'claimed') {
|
|
225
|
+
return { ok: true, status: 'claimed', action };
|
|
226
|
+
}
|
|
119
227
|
if (alias.used_at || ['claimed', 'resolved', 'cancelled'].includes(action.status))
|
|
120
228
|
return { ok: false, status: 'already_resolved', action };
|
|
121
|
-
const now = input.now ??
|
|
122
|
-
if (
|
|
229
|
+
const now = input.now ?? this.now();
|
|
230
|
+
if (hasExpired(alias.expires_at, now) || hasExpired(action.expires_at, now)) {
|
|
123
231
|
action.status = 'expired';
|
|
124
232
|
return { ok: false, status: 'expired', action };
|
|
125
233
|
}
|
|
126
234
|
if (alias.chat_id !== input.chat_id || alias.message_thread_id !== input.message_thread_id)
|
|
127
235
|
return { ok: false, status: 'wrong_topic', action };
|
|
236
|
+
if (input.route_generation === null
|
|
237
|
+
|| alias.route_generation !== input.route_generation
|
|
238
|
+
|| action.route_generation !== input.route_generation) {
|
|
239
|
+
return { ok: false, status: 'stale_generation', action };
|
|
240
|
+
}
|
|
128
241
|
if (alias.revision !== input.revision || action.revision !== input.revision)
|
|
129
242
|
return { ok: false, status: 'revision_mismatch', action };
|
|
130
243
|
const usedAt = new Date(now).toISOString();
|
|
131
244
|
alias.used_at = usedAt;
|
|
245
|
+
alias.claimed_update_id = input.update_id;
|
|
132
246
|
action.status = 'claimed';
|
|
133
247
|
return { ok: true, status: 'claimed', action };
|
|
134
248
|
});
|
|
@@ -148,11 +262,52 @@ export class TelegramActionBroker {
|
|
|
148
262
|
if (ledger.schema !== 'sks.telegram-action-ledger.v1')
|
|
149
263
|
throw new Error('telegram_action_ledger_schema_invalid');
|
|
150
264
|
const result = fn(ledger);
|
|
265
|
+
capActionLedger(ledger, this.maxEntries);
|
|
151
266
|
await writeJsonAtomic(this.file, ledger);
|
|
152
267
|
return result;
|
|
153
268
|
});
|
|
154
269
|
}
|
|
155
270
|
}
|
|
271
|
+
function capActionLedger(ledger, maxEntries) {
|
|
272
|
+
if (ledger.actions.length <= maxEntries)
|
|
273
|
+
return;
|
|
274
|
+
pruneTerminalActionHistory(ledger, ledger.actions.length - maxEntries);
|
|
275
|
+
}
|
|
276
|
+
function reserveActionLedgerCapacity(ledger, maxEntries, additions, protectedActionIds) {
|
|
277
|
+
const removeCount = Math.max(0, ledger.actions.length + additions - maxEntries);
|
|
278
|
+
if (removeCount === 0)
|
|
279
|
+
return;
|
|
280
|
+
pruneTerminalActionHistory(ledger, removeCount, protectedActionIds);
|
|
281
|
+
}
|
|
282
|
+
function pruneTerminalActionHistory(ledger, removeCount, protectedActionIds = new Set()) {
|
|
283
|
+
const removable = ledger.actions
|
|
284
|
+
.filter((action) => isTerminalActionStatus(action.status) && !protectedActionIds.has(action.action_id))
|
|
285
|
+
.slice(0, removeCount);
|
|
286
|
+
if (removable.length < removeCount)
|
|
287
|
+
throw new Error('telegram_action_ledger_capacity_exceeded');
|
|
288
|
+
const removedIds = new Set(removable.map((action) => action.action_id));
|
|
289
|
+
ledger.actions = ledger.actions.filter((action) => !removedIds.has(action.action_id));
|
|
290
|
+
ledger.aliases = ledger.aliases.filter((alias) => !removedIds.has(alias.action_id));
|
|
291
|
+
}
|
|
292
|
+
function isTerminalActionStatus(status) {
|
|
293
|
+
return status === 'resolved' || status === 'cancelled' || status === 'expired';
|
|
294
|
+
}
|
|
295
|
+
function expireOpenActions(ledger, now) {
|
|
296
|
+
const aliasesByActionId = new Map(ledger.aliases.map((alias) => [alias.action_id, alias]));
|
|
297
|
+
for (const action of ledger.actions) {
|
|
298
|
+
if (action.status !== 'open')
|
|
299
|
+
continue;
|
|
300
|
+
const alias = aliasesByActionId.get(action.action_id);
|
|
301
|
+
if (hasExpired(action.expires_at, now)
|
|
302
|
+
|| (alias && hasExpired(alias.expires_at, now))) {
|
|
303
|
+
action.status = 'expired';
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function hasExpired(expiresAt, now) {
|
|
308
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
309
|
+
return !Number.isFinite(expiresAtMs) || expiresAtMs <= now;
|
|
310
|
+
}
|
|
156
311
|
export class TelegramAuditLedger {
|
|
157
312
|
file;
|
|
158
313
|
salt;
|
|
@@ -167,11 +322,15 @@ export class TelegramAuditLedger {
|
|
|
167
322
|
update_id: input.update_id,
|
|
168
323
|
chat_id_hash: `sha256:${sha256(`${this.salt}:${input.chat_id}`)}`,
|
|
169
324
|
topic_id: input.topic_id,
|
|
170
|
-
action_alias: input.action_alias,
|
|
325
|
+
action_alias: input.action_alias ? `sha256:${sha256(`${this.salt}:${input.action_alias}`)}` : null,
|
|
171
326
|
command_kind: sanitizeAuditField(input.command_kind),
|
|
172
327
|
decision: input.decision,
|
|
173
328
|
policy_reason: sanitizeAuditField(input.policy_reason),
|
|
174
|
-
effect_receipt: input.effect_receipt ? sanitizeAuditField(input.effect_receipt) : null
|
|
329
|
+
effect_receipt: input.effect_receipt ? sanitizeAuditField(input.effect_receipt) : null,
|
|
330
|
+
telegram_delivery: input.telegram_delivery,
|
|
331
|
+
telegram_delivery_receipt: input.telegram_delivery_receipt
|
|
332
|
+
? sanitizeAuditField(input.telegram_delivery_receipt)
|
|
333
|
+
: null
|
|
175
334
|
};
|
|
176
335
|
await appendJsonlBounded(this.file, event, 5 * 1024 * 1024);
|
|
177
336
|
return event;
|
|
@@ -178,14 +178,34 @@ export class TelegramMessageProjector {
|
|
|
178
178
|
text,
|
|
179
179
|
reply_markup: replyMarkup
|
|
180
180
|
}));
|
|
181
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
method: 'editMessageText',
|
|
183
|
+
message_id: input.route.pinned_message_id,
|
|
184
|
+
pinned: true,
|
|
185
|
+
pin_delivery: 'not_attempted'
|
|
186
|
+
};
|
|
182
187
|
}
|
|
183
188
|
const sent = await this.sendFinal({ route: input.route, text, replyMarkup });
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
try {
|
|
190
|
+
await this.api.call('pinChatMessage', this.basePayload(input.route, {
|
|
191
|
+
message_id: sent.message_id,
|
|
192
|
+
disable_notification: true
|
|
193
|
+
}));
|
|
194
|
+
return {
|
|
195
|
+
method: sent.method,
|
|
196
|
+
message_id: sent.message_id,
|
|
197
|
+
pinned: true,
|
|
198
|
+
pin_delivery: 'delivered'
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
method: sent.method,
|
|
204
|
+
message_id: sent.message_id,
|
|
205
|
+
pinned: false,
|
|
206
|
+
pin_delivery: error instanceof TelegramBotApiError ? error.delivery : 'unknown'
|
|
207
|
+
};
|
|
208
|
+
}
|
|
189
209
|
}
|
|
190
210
|
async requestInput(input) {
|
|
191
211
|
const sent = await this.sendFinal({
|
|
@@ -2,6 +2,7 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import fsp from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { errorCodeOf } from '../errors/message.js';
|
|
5
6
|
import { ensureDir, nowIso } from '../fsx.js';
|
|
6
7
|
export class TelegramOwnerConflictError extends Error {
|
|
7
8
|
owner;
|
|
@@ -19,6 +20,7 @@ export class TelegramOwnerLock {
|
|
|
19
20
|
processStartTime;
|
|
20
21
|
now;
|
|
21
22
|
isPidAlive;
|
|
23
|
+
reclaimDeadImmediately;
|
|
22
24
|
owner = null;
|
|
23
25
|
constructor(options) {
|
|
24
26
|
this.options = options;
|
|
@@ -28,6 +30,7 @@ export class TelegramOwnerLock {
|
|
|
28
30
|
this.processStartTime = options.processStartTime ?? new Date(Date.now() - process.uptime() * 1000).toISOString();
|
|
29
31
|
this.now = options.now ?? Date.now;
|
|
30
32
|
this.isPidAlive = options.isPidAlive ?? localPidAlive;
|
|
33
|
+
this.reclaimDeadImmediately = options.reclaimDeadImmediately === true;
|
|
31
34
|
}
|
|
32
35
|
async acquire() {
|
|
33
36
|
await ensureDir(path.dirname(this.options.lockPath));
|
|
@@ -46,7 +49,7 @@ export class TelegramOwnerLock {
|
|
|
46
49
|
return owner;
|
|
47
50
|
}
|
|
48
51
|
catch (error) {
|
|
49
|
-
if (
|
|
52
|
+
if (errorCodeOf(error) !== 'EEXIST')
|
|
50
53
|
throw error;
|
|
51
54
|
const existing = await this.readOwner();
|
|
52
55
|
if (!this.isStale(existing))
|
|
@@ -57,7 +60,7 @@ export class TelegramOwnerLock {
|
|
|
57
60
|
await fsp.unlink(quarantine).catch(() => undefined);
|
|
58
61
|
}
|
|
59
62
|
catch (renameError) {
|
|
60
|
-
if (!['ENOENT', 'EEXIST'].includes(
|
|
63
|
+
if (!['ENOENT', 'EEXIST'].includes(errorCodeOf(renameError)))
|
|
61
64
|
throw renameError;
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -108,7 +111,10 @@ export class TelegramOwnerLock {
|
|
|
108
111
|
if (!owner)
|
|
109
112
|
return true;
|
|
110
113
|
const age = this.now() - Date.parse(owner.heartbeat_at);
|
|
111
|
-
|
|
114
|
+
const alive = this.isPidAlive(owner.pid, owner.host);
|
|
115
|
+
if (!alive && this.reclaimDeadImmediately)
|
|
116
|
+
return true;
|
|
117
|
+
return Number.isFinite(age) && age > this.staleMs && !alive;
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
function sameOwner(left, right) {
|
|
@@ -126,9 +132,6 @@ function localPidAlive(pid, host) {
|
|
|
126
132
|
return true;
|
|
127
133
|
}
|
|
128
134
|
catch (error) {
|
|
129
|
-
return
|
|
135
|
+
return errorCodeOf(error) !== 'ESRCH';
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
|
-
function errorCode(error) {
|
|
133
|
-
return error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
134
|
-
}
|
|
@@ -3,13 +3,20 @@ import { buildTelegramTopicName } from './ledgers.js';
|
|
|
3
3
|
import { publicSafeText } from './messages.js';
|
|
4
4
|
import { asRecordOrNull as asRecord } from '../json/records.js';
|
|
5
5
|
export { asRecord };
|
|
6
|
-
export function pickerAction(route, now) {
|
|
6
|
+
export function pickerAction(route, now, updateId) {
|
|
7
7
|
return {
|
|
8
8
|
schema: 'sks.remote-action.v1',
|
|
9
|
-
action_id: crypto.
|
|
9
|
+
action_id: `tg-pick-${crypto.createHash('sha256').update([
|
|
10
|
+
updateId,
|
|
11
|
+
route.machine_id,
|
|
12
|
+
route.project_id,
|
|
13
|
+
route.session_id,
|
|
14
|
+
route.generation
|
|
15
|
+
].join(':')).digest('hex').slice(0, 36)}`,
|
|
10
16
|
machine_id: route.machine_id,
|
|
11
17
|
project_id: route.project_id,
|
|
12
18
|
session_id: route.session_id,
|
|
19
|
+
route_generation: route.generation,
|
|
13
20
|
kind: 'read',
|
|
14
21
|
risk: 'R0',
|
|
15
22
|
prompt: 'pick',
|
|
@@ -63,6 +70,28 @@ export function successSummary(action, data) {
|
|
|
63
70
|
return `Verification snapshot: ${snapshotSummary(data ?? {})}`;
|
|
64
71
|
return snapshotSummary(data ?? {});
|
|
65
72
|
}
|
|
73
|
+
export function failureSummary(codeInput, deliveryInput) {
|
|
74
|
+
const code = publicSafeText(String(codeInput || 'remote_command_failed')).slice(0, 160);
|
|
75
|
+
const delivery = String(deliveryInput || '');
|
|
76
|
+
if (code === 'codex_turn_completion_unknown') {
|
|
77
|
+
return `Codex accepted this turn, but SKS lost the completion signal. It will not replay the request automatically. Check SKS Center → Telegram → Codex activity or send /status before trying again. Code: ${code}`;
|
|
78
|
+
}
|
|
79
|
+
if (code === 'codex_turn_start_delivery_unknown' || delivery === 'unknown') {
|
|
80
|
+
return `Codex may already have received this request, so SKS will not replay it automatically. Check SKS Center → Telegram → Codex activity or send /status before trying again. Code: ${code}`;
|
|
81
|
+
}
|
|
82
|
+
if (code.includes('_contract_invalid')) {
|
|
83
|
+
return `The linked Codex App Server rejected an incompatible request contract before delivery. Restart the updated SKS Hub, then retry once. Code: ${code}`;
|
|
84
|
+
}
|
|
85
|
+
if (code === 'codex_app_server_initialize_failed'
|
|
86
|
+
|| code === 'codex_app_server_unavailable'
|
|
87
|
+
|| code === 'codex_app_server_provider_credentials_unavailable') {
|
|
88
|
+
return `The request was not sent because the linked Codex App Server could not initialize. Recheck provider authentication and Hub status in SKS Center. Code: ${code}`;
|
|
89
|
+
}
|
|
90
|
+
if (delivery === 'acknowledged') {
|
|
91
|
+
return `Codex acknowledged the request, but SKS could not finish projecting the result. Check the linked Codex thread in SKS Center before sending another request. Code: ${code}`;
|
|
92
|
+
}
|
|
93
|
+
return `Remote action blocked before completion. Use /status or SKS Center → Telegram → Diagnostics for the next action. Code: ${code}`;
|
|
94
|
+
}
|
|
66
95
|
export function topicName(target, sessionId, snapshot) {
|
|
67
96
|
const project = asRecord(snapshot?.project);
|
|
68
97
|
return buildTelegramTopicName({
|
|
@@ -80,7 +109,13 @@ export function routeKey(route) {
|
|
|
80
109
|
return `${route.machine_id}:${route.project_id}:${route.session_id}:${route.generation}`;
|
|
81
110
|
}
|
|
82
111
|
export function emptyProjection() {
|
|
83
|
-
return {
|
|
112
|
+
return {
|
|
113
|
+
after_seq: 0,
|
|
114
|
+
snapshot_digest: null,
|
|
115
|
+
draft_message_id: null,
|
|
116
|
+
final_generation: null,
|
|
117
|
+
final_generation_unknown: null
|
|
118
|
+
};
|
|
84
119
|
}
|
|
85
120
|
export function requestId(prefix) {
|
|
86
121
|
return `${prefix}:${crypto.randomUUID()}`;
|