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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fsp from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { codexAppServerExecutablePath } from '../codex-control/codex-app-server-runtime-env.js';
|
|
5
|
+
import { ensureDir, exists, globalSksRoot, readJson, runProcess, writeTextAtomic } from '../fsx.js';
|
|
5
6
|
export const TELEGRAM_HUB_LAUNCH_LABEL = 'com.sneakoscope.telegram-hub';
|
|
6
7
|
export function telegramHubServicePaths(globalRoot = globalSksRoot(), home = process.env.HOME || os.homedir()) {
|
|
7
8
|
const root = path.resolve(globalRoot);
|
|
@@ -10,10 +11,17 @@ export function telegramHubServicePaths(globalRoot = globalSksRoot(), home = pro
|
|
|
10
11
|
global_root: root,
|
|
11
12
|
launch_agent_path: path.join(path.resolve(home), 'Library', 'LaunchAgents', `${TELEGRAM_HUB_LAUNCH_LABEL}.plist`),
|
|
12
13
|
stdout_log_path: path.join(logs, 'hub.out.log'),
|
|
13
|
-
stderr_log_path: path.join(logs, 'hub.err.log')
|
|
14
|
+
stderr_log_path: path.join(logs, 'hub.err.log'),
|
|
15
|
+
receive_health_path: path.join(root, 'telegram', 'receive-health.json')
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
export function telegramHubLaunchAgentSource(input) {
|
|
19
|
+
const home = path.dirname(path.dirname(path.dirname(input.paths.launch_agent_path)));
|
|
20
|
+
const executablePath = codexAppServerExecutablePath({
|
|
21
|
+
nodeBin: input.nodeBin,
|
|
22
|
+
home,
|
|
23
|
+
inheritedPath: ''
|
|
24
|
+
});
|
|
17
25
|
const args = [
|
|
18
26
|
'/usr/bin/caffeinate',
|
|
19
27
|
'-i',
|
|
@@ -24,6 +32,7 @@ export function telegramHubLaunchAgentSource(input) {
|
|
|
24
32
|
'run',
|
|
25
33
|
'--project-root',
|
|
26
34
|
path.resolve(input.projectRoot),
|
|
35
|
+
'--service-managed',
|
|
27
36
|
'--json'
|
|
28
37
|
];
|
|
29
38
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -35,6 +44,7 @@ export function telegramHubLaunchAgentSource(input) {
|
|
|
35
44
|
<key>KeepAlive</key><dict><key>SuccessfulExit</key><false/></dict>
|
|
36
45
|
<key>ProcessType</key><string>Background</string>
|
|
37
46
|
<key>ThrottleInterval</key><integer>5</integer>
|
|
47
|
+
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${xml(executablePath)}</string></dict>
|
|
38
48
|
<key>StandardOutPath</key><string>${xml(input.paths.stdout_log_path)}</string>
|
|
39
49
|
<key>StandardErrorPath</key><string>${xml(input.paths.stderr_log_path)}</string>
|
|
40
50
|
</dict></plist>
|
|
@@ -116,23 +126,91 @@ export async function telegramHubServiceStatus(options) {
|
|
|
116
126
|
timedOut: false
|
|
117
127
|
}));
|
|
118
128
|
const text = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
119
|
-
const
|
|
129
|
+
const launchState = text.match(/^[ \t]*state = ([^\n]+)/m)?.[1]?.trim() ?? null;
|
|
120
130
|
const pidText = text.match(/^[ \t]*pid = (\d+)/m)?.[1] ?? null;
|
|
121
|
-
const
|
|
131
|
+
const launchPid = pidText ? Number(pidText) : null;
|
|
122
132
|
const loaded = result.code === 0;
|
|
123
|
-
const
|
|
133
|
+
const launchRunning = loaded && launchState === 'running' && Boolean(launchPid);
|
|
134
|
+
const receiveHealth = await readJson(paths.receive_health_path, null);
|
|
135
|
+
const currentReceiveHealth = receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
136
|
+
? receiveHealth
|
|
137
|
+
: null;
|
|
138
|
+
const owner = await readJson(path.join(paths.global_root, 'telegram', 'owner.lock'), null);
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const isPidAlive = options.isPidAlive ?? localPidAlive;
|
|
141
|
+
const directOwnerFresh = Boolean(owner?.schema === 'sks.telegram-owner.v1'
|
|
142
|
+
&& owner.host === os.hostname()
|
|
143
|
+
&& Number.isInteger(owner.pid)
|
|
144
|
+
&& owner.pid > 0
|
|
145
|
+
&& Number.isFinite(Date.parse(owner.heartbeat_at))
|
|
146
|
+
&& now - Date.parse(owner.heartbeat_at) <= 90_000
|
|
147
|
+
&& isPidAlive(owner.pid));
|
|
148
|
+
const directIdentity = directOwnerFresh && owner
|
|
149
|
+
? await (options.inspectDirectHub ?? inspectCenterSupervisedDirectHub)(owner.pid, options.projectRoot)
|
|
150
|
+
.catch(() => null)
|
|
151
|
+
: null;
|
|
152
|
+
const directRunning = Boolean(directOwnerFresh
|
|
153
|
+
&& directIdentity
|
|
154
|
+
&& directIdentity.parent_pid === directIdentity.supervisor_pid
|
|
155
|
+
&& isPidAlive(directIdentity.supervisor_pid));
|
|
156
|
+
const launchHealthMatches = Boolean(launchRunning
|
|
157
|
+
&& currentReceiveHealth
|
|
158
|
+
&& currentReceiveHealth.pid === launchPid);
|
|
159
|
+
const mode = launchHealthMatches
|
|
160
|
+
? 'launch_agent'
|
|
161
|
+
: directRunning
|
|
162
|
+
? 'direct'
|
|
163
|
+
: launchRunning
|
|
164
|
+
? 'launch_agent'
|
|
165
|
+
: null;
|
|
166
|
+
const running = launchRunning || directRunning;
|
|
167
|
+
const pid = mode === 'direct' ? owner?.pid ?? null : launchPid;
|
|
168
|
+
const state = mode === 'direct' ? 'direct' : launchState;
|
|
169
|
+
const matchingReceiveHealth = Boolean(running
|
|
170
|
+
&& currentReceiveHealth
|
|
171
|
+
&& currentReceiveHealth.pid === pid);
|
|
172
|
+
const receiveReady = Boolean(matchingReceiveHealth
|
|
173
|
+
&& currentReceiveHealth?.state === 'ready'
|
|
174
|
+
&& currentReceiveHealth.last_successful_poll_at
|
|
175
|
+
&& Date.now() - Date.parse(currentReceiveHealth.last_successful_poll_at) <= 90_000
|
|
176
|
+
&& currentReceiveHealth.dead_letter_ingress === 0);
|
|
124
177
|
return {
|
|
125
178
|
schema: 'sks.telegram-hub-service.v1',
|
|
126
|
-
ok:
|
|
179
|
+
ok: receiveReady,
|
|
127
180
|
supported: true,
|
|
128
181
|
installed,
|
|
129
182
|
loaded,
|
|
130
183
|
running,
|
|
184
|
+
mode,
|
|
131
185
|
state,
|
|
132
186
|
pid,
|
|
187
|
+
receive_ready: receiveReady,
|
|
188
|
+
receive_state: receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
189
|
+
? receiveHealth.state
|
|
190
|
+
: null,
|
|
191
|
+
receive_generation: receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
192
|
+
? receiveHealth.generation
|
|
193
|
+
: null,
|
|
194
|
+
last_successful_poll_at: receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
195
|
+
? receiveHealth.last_successful_poll_at
|
|
196
|
+
: null,
|
|
197
|
+
pending_ingress: receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
198
|
+
? receiveHealth.pending_ingress
|
|
199
|
+
: 0,
|
|
200
|
+
dead_letter_ingress: receiveHealth?.schema === 'sks.telegram-receive-health.v1'
|
|
201
|
+
? receiveHealth.dead_letter_ingress
|
|
202
|
+
: 0,
|
|
133
203
|
service,
|
|
134
204
|
launch_agent_path: paths.launch_agent_path,
|
|
135
|
-
error:
|
|
205
|
+
error: receiveReady
|
|
206
|
+
? null
|
|
207
|
+
: running
|
|
208
|
+
? matchingReceiveHealth && Number(currentReceiveHealth?.dead_letter_ingress) > 0
|
|
209
|
+
? 'telegram_ingress_dead_letter_present'
|
|
210
|
+
: 'telegram_receive_not_ready'
|
|
211
|
+
: loaded
|
|
212
|
+
? 'telegram_hub_not_running'
|
|
213
|
+
: boundedError(result.stderr || result.stdout || 'telegram_hub_not_loaded')
|
|
136
214
|
};
|
|
137
215
|
}
|
|
138
216
|
export async function removeTelegramHubService(options) {
|
|
@@ -150,8 +228,15 @@ function unsupportedStatus(paths, error, uid) {
|
|
|
150
228
|
installed: false,
|
|
151
229
|
loaded: false,
|
|
152
230
|
running: false,
|
|
231
|
+
mode: null,
|
|
153
232
|
state: null,
|
|
154
233
|
pid: null,
|
|
234
|
+
receive_ready: false,
|
|
235
|
+
receive_state: null,
|
|
236
|
+
receive_generation: null,
|
|
237
|
+
last_successful_poll_at: null,
|
|
238
|
+
pending_ingress: 0,
|
|
239
|
+
dead_letter_ingress: 0,
|
|
155
240
|
service: `${launchDomain(uid)}/${TELEGRAM_HUB_LAUNCH_LABEL}`,
|
|
156
241
|
launch_agent_path: paths.launch_agent_path,
|
|
157
242
|
error
|
|
@@ -159,7 +244,7 @@ function unsupportedStatus(paths, error, uid) {
|
|
|
159
244
|
}
|
|
160
245
|
async function waitForTelegramHubService(options) {
|
|
161
246
|
let status = await telegramHubServiceStatus(options);
|
|
162
|
-
for (let attempt = 0; attempt <
|
|
247
|
+
for (let attempt = 0; attempt < 80 && !status.ok; attempt += 1) {
|
|
163
248
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
164
249
|
status = await telegramHubServiceStatus(options);
|
|
165
250
|
}
|
|
@@ -171,6 +256,50 @@ function launchDomain(uid = typeof process.getuid === 'function' ? process.getui
|
|
|
171
256
|
function boundedError(value) {
|
|
172
257
|
return String(value || '').replace(/(?:\/Users|\/home)\/[^\s]+/g, '[path-redacted]').slice(0, 500);
|
|
173
258
|
}
|
|
259
|
+
function localPidAlive(pid) {
|
|
260
|
+
try {
|
|
261
|
+
process.kill(pid, 0);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function inspectCenterSupervisedDirectHub(pid, projectRoot) {
|
|
269
|
+
const result = await runProcess('/bin/ps', [
|
|
270
|
+
'-p',
|
|
271
|
+
String(pid),
|
|
272
|
+
'-o',
|
|
273
|
+
'ppid=',
|
|
274
|
+
'-o',
|
|
275
|
+
'command='
|
|
276
|
+
], {
|
|
277
|
+
timeoutMs: 1_000,
|
|
278
|
+
maxOutputBytes: 16 * 1024
|
|
279
|
+
});
|
|
280
|
+
if (result.code !== 0 || result.timedOut)
|
|
281
|
+
return null;
|
|
282
|
+
const match = result.stdout.trim().match(/^(\d+)\s+([\s\S]+)$/);
|
|
283
|
+
if (!match)
|
|
284
|
+
return null;
|
|
285
|
+
const parentText = match[1];
|
|
286
|
+
const command = match[2];
|
|
287
|
+
if (!parentText || !command)
|
|
288
|
+
return null;
|
|
289
|
+
const parentPid = Number(parentText);
|
|
290
|
+
const supervisorMatch = command.match(/(?:^|\s)--center-supervisor-pid\s+([1-9]\d*)(?:\s|$)/);
|
|
291
|
+
if (!Number.isSafeInteger(parentPid)
|
|
292
|
+
|| !supervisorMatch
|
|
293
|
+
|| !/(?:^|\s)telegram\s+hub\s+run(?:\s|$)/.test(command)
|
|
294
|
+
|| !/(?:^|\s)--center-supervised(?:\s|$)/.test(command)
|
|
295
|
+
|| !command.includes(`--project-root ${path.resolve(projectRoot)}`)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const supervisorPid = Number(supervisorMatch[1]);
|
|
299
|
+
if (!Number.isSafeInteger(supervisorPid))
|
|
300
|
+
return null;
|
|
301
|
+
return { parent_pid: parentPid, supervisor_pid: supervisorPid };
|
|
302
|
+
}
|
|
174
303
|
function xml(value) {
|
|
175
304
|
return value
|
|
176
305
|
.replace(/&/g, '&')
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
1
2
|
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
+
import { errorCodeOf } from '../errors/message.js';
|
|
5
|
+
import { ensureDir, globalSksRoot, nowIso, readJson, runProcess, sha256, which, writeBinaryAtomic, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
6
|
import { REMOTE_MACHINE_REGISTRY_SCHEMA, REMOTE_SESSION_INDEX_SCHEMA, RemoteCodexSessionBindingStore, remoteCodexSessionBindingsPath, remoteMachineRegistryPath, remoteSessionIndexPath, validateAllowedRoot, validateRemoteMachineRegistry, validateRemoteSessionIndex } from '../remote/index.js';
|
|
5
|
-
import { TelegramBotApiClient } from './bot-api.js';
|
|
6
|
-
import { isPositiveTelegramId, validateTelegramBotToken } from './config.js';
|
|
7
|
+
import { probeTelegramBotReadiness, TelegramBotApiClient, TelegramBotApiError } from './bot-api.js';
|
|
8
|
+
import { buildTelegramConfigV2, isPositiveTelegramId, readTelegramKeychainToken, readTelegramPreviousConfig, telegramTokenFingerprint, validateTelegramBotToken } from './config.js';
|
|
7
9
|
import { telegramHubPaths } from './hub.js';
|
|
10
|
+
import { TelegramIngressSpool } from './ingress-spool.js';
|
|
11
|
+
import { TelegramTopicRegistry } from './ledgers.js';
|
|
12
|
+
import { TelegramOwnerConflictError, TelegramOwnerLock } from './owner-lock.js';
|
|
8
13
|
const KEYCHAIN_SERVICE = 'com.sneakoscope.telegram.bot';
|
|
9
14
|
const KEYCHAIN_WRITER_SWIFT = `import Foundation
|
|
10
15
|
import Security
|
|
@@ -15,6 +20,12 @@ let v:[String:Any]=[kSecValueData as String:Data(k.utf8)]
|
|
|
15
20
|
var r=SecItemUpdate(q as CFDictionary,v as CFDictionary)
|
|
16
21
|
if r==errSecItemNotFound{var n=q;n[kSecValueData as String]=Data(k.utf8);r=SecItemAdd(n as CFDictionary,nil)}
|
|
17
22
|
if r != errSecSuccess{FileHandle.standardError.write(Data("keychain_status=\\(r)\\n".utf8));exit(1)}`;
|
|
23
|
+
const KEYCHAIN_DELETE_SWIFT = `import Foundation
|
|
24
|
+
import Security
|
|
25
|
+
let a=CommandLine.arguments[1],s=CommandLine.arguments[2]
|
|
26
|
+
let q:[String:Any]=[kSecClass as String:kSecClassGenericPassword,kSecAttrAccount as String:a,kSecAttrService as String:s]
|
|
27
|
+
let r=SecItemDelete(q as CFDictionary)
|
|
28
|
+
if r != errSecSuccess && r != errSecItemNotFound{FileHandle.standardError.write(Data("keychain_status=\\(r)\\n".utf8));exit(1)}`;
|
|
18
29
|
export async function setupTelegramLocalCoding(input) {
|
|
19
30
|
const token = validateTelegramBotToken(input.token.trim());
|
|
20
31
|
const explicitPair = normalizeExplicitPair(input.pairedChatId, input.pairedUserId);
|
|
@@ -23,93 +34,228 @@ export async function setupTelegramLocalCoding(input) {
|
|
|
23
34
|
if (rootIssue)
|
|
24
35
|
throw new Error(`telegram_project_root_invalid:${rootIssue}`);
|
|
25
36
|
const globalRoot = path.resolve(input.globalRoot ?? globalSksRoot());
|
|
37
|
+
const setupOwner = new TelegramOwnerLock({
|
|
38
|
+
lockPath: telegramHubPaths(globalRoot).owner,
|
|
39
|
+
tokenFingerprint: telegramTokenFingerprint(token)
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
await setupOwner.acquire();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof TelegramOwnerConflictError) {
|
|
46
|
+
throw new Error('telegram_hub_must_be_stopped_before_setup');
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return await setupTelegramLocalCodingLocked(input, token, explicitPair, projectRoot, globalRoot);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await setupOwner.release();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function setupTelegramLocalCodingLocked(input, token, explicitPair, projectRoot, globalRoot) {
|
|
26
58
|
const api = input.api ?? new TelegramBotApiClient(token, { timeoutMs: 12_000, maxRetries: 0 });
|
|
27
|
-
const
|
|
28
|
-
|
|
59
|
+
const readiness = await probeTelegramBotReadiness(api).catch(throwTelegramSetupApiError);
|
|
60
|
+
if (readiness.webhook_configured) {
|
|
61
|
+
throw new Error('telegram_webhook_conflict:remove_webhook_before_long_polling');
|
|
62
|
+
}
|
|
63
|
+
const detectedPair = explicitPair === null
|
|
64
|
+
? await detectPrivateStart(api, readiness.bot.username, false).catch(throwTelegramSetupApiError)
|
|
65
|
+
: null;
|
|
66
|
+
const pairing = explicitPair ?? detectedPair;
|
|
29
67
|
const account = input.account?.trim() || process.env.USER || os.userInfo().username || 'sks';
|
|
30
|
-
await (input.keychainWriter ?? writeTelegramKeychain)(token, KEYCHAIN_SERVICE, account);
|
|
31
68
|
const hostname = input.hostname ?? os.hostname();
|
|
32
69
|
const machineId = `local-${sha256(hostname).slice(0, 12)}`;
|
|
33
70
|
const projectId = `project-${sha256(projectRoot).slice(0, 12)}`;
|
|
34
71
|
const sessionId = `telegram-${sha256(`${machineId}:${projectId}`).slice(0, 12)}`;
|
|
35
|
-
const
|
|
72
|
+
const bindingPath = remoteCodexSessionBindingsPath(projectRoot);
|
|
73
|
+
const machinePath = remoteMachineRegistryPath(globalRoot);
|
|
74
|
+
const indexPath = remoteSessionIndexPath(projectRoot);
|
|
75
|
+
const hubPaths = telegramHubPaths(globalRoot);
|
|
76
|
+
const configPath = hubPaths.config;
|
|
77
|
+
const stateSnapshots = await Promise.all([
|
|
78
|
+
snapshotSetupFile(bindingPath),
|
|
79
|
+
snapshotSetupFile(machinePath),
|
|
80
|
+
snapshotSetupFile(indexPath),
|
|
81
|
+
snapshotSetupFile(hubPaths.topics),
|
|
82
|
+
snapshotSetupFile(hubPaths.projection),
|
|
83
|
+
snapshotSetupFile(hubPaths.idempotency),
|
|
84
|
+
snapshotSetupFile(hubPaths.ingress),
|
|
85
|
+
snapshotSetupFile(hubPaths.receive_health),
|
|
86
|
+
snapshotSetupFile(configPath)
|
|
87
|
+
]);
|
|
88
|
+
const keychainWriter = input.keychainWriter ?? writeTelegramKeychain;
|
|
89
|
+
const keychainReader = input.keychainReader
|
|
90
|
+
?? (input.keychainWriter ? async () => null : readTelegramKeychain);
|
|
91
|
+
const keychainDeleter = input.keychainDeleter
|
|
92
|
+
?? (input.keychainWriter
|
|
93
|
+
? async () => { throw new Error('telegram_keychain_rollback_unavailable'); }
|
|
94
|
+
: deleteTelegramKeychain);
|
|
95
|
+
const previousToken = await keychainReader(KEYCHAIN_SERVICE, account);
|
|
96
|
+
const previousConfig = await readPreviousTelegramConfig(configPath);
|
|
97
|
+
const tokenFingerprint = telegramTokenFingerprint(token);
|
|
98
|
+
const samePersistedIdentity = previousConfig?.schema === 'sks.telegram-config.v2'
|
|
99
|
+
&& previousConfig.bot_user_id === readiness.bot.id
|
|
100
|
+
&& previousConfig.bot_token_fingerprint === tokenFingerprint
|
|
101
|
+
&& previousConfig.paired_chat_id === pairing.chatId
|
|
102
|
+
&& previousConfig.paired_user_id === pairing.userId
|
|
103
|
+
&& input.resetSession !== true;
|
|
104
|
+
const configGeneration = previousConfig?.schema === 'sks.telegram-config.v2'
|
|
105
|
+
? samePersistedIdentity
|
|
106
|
+
? previousConfig.config_generation
|
|
107
|
+
: previousConfig.config_generation + 1
|
|
108
|
+
: 1;
|
|
109
|
+
const config = buildTelegramConfigV2({
|
|
110
|
+
botTokenRef: { type: 'keychain', service: KEYCHAIN_SERVICE, account },
|
|
111
|
+
botUserId: readiness.bot.id,
|
|
112
|
+
botTokenFingerprint: tokenFingerprint,
|
|
113
|
+
pairedChatId: pairing.chatId,
|
|
114
|
+
pairedUserId: pairing.userId,
|
|
115
|
+
configGeneration,
|
|
116
|
+
longPollTimeoutSec: 25,
|
|
117
|
+
ownerStaleMs: 60_000,
|
|
118
|
+
protectContent: true,
|
|
119
|
+
silentNotifications: false
|
|
120
|
+
});
|
|
121
|
+
const identityChanged = previousConfig?.schema !== 'sks.telegram-config.v2'
|
|
122
|
+
|| previousConfig.identity_fingerprint !== config.identity_fingerprint
|
|
123
|
+
|| input.resetSession === true;
|
|
124
|
+
const ingress = new TelegramIngressSpool(hubPaths.ingress, {
|
|
125
|
+
identityFingerprint: config.identity_fingerprint,
|
|
126
|
+
...(previousToken
|
|
127
|
+
? { legacyTokenFingerprint: telegramTokenFingerprint(previousToken) }
|
|
128
|
+
: {})
|
|
129
|
+
});
|
|
130
|
+
if (identityChanged)
|
|
131
|
+
await ingress.assertIdentityRotationSafe();
|
|
132
|
+
const bindingStore = new RemoteCodexSessionBindingStore(bindingPath);
|
|
36
133
|
const existingBinding = await bindingStore.find(sessionId);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
134
|
+
let credentialWriteAttempted = false;
|
|
135
|
+
try {
|
|
136
|
+
credentialWriteAttempted = true;
|
|
137
|
+
await keychainWriter(token, KEYCHAIN_SERVICE, account);
|
|
138
|
+
if (input.keychainWriter === undefined) {
|
|
139
|
+
await verifyTelegramKeychainReadback(token, KEYCHAIN_SERVICE, account, keychainReader);
|
|
140
|
+
}
|
|
141
|
+
const binding = existingBinding && input.resetSession !== true
|
|
142
|
+
? existingBinding
|
|
143
|
+
: await bindingStore.upsert({
|
|
144
|
+
session_id: sessionId,
|
|
145
|
+
machine_id: machineId,
|
|
146
|
+
project_id: projectId,
|
|
147
|
+
project_root: projectRoot,
|
|
148
|
+
codex_thread_id: null,
|
|
149
|
+
last_turn_id: null,
|
|
150
|
+
last_turn_status: null
|
|
151
|
+
});
|
|
152
|
+
const registry = await upsertLocalMachine(machinePath, {
|
|
153
|
+
id: machineId,
|
|
154
|
+
display_name: `This Mac (${hostname})`.slice(0, 120),
|
|
155
|
+
transport: 'local',
|
|
156
|
+
allowed_roots: [projectRoot],
|
|
157
|
+
enabled: true
|
|
158
|
+
});
|
|
159
|
+
await upsertLocalTarget(indexPath, registry, {
|
|
41
160
|
machine_id: machineId,
|
|
42
161
|
project_id: projectId,
|
|
43
|
-
project_root: projectRoot
|
|
44
|
-
codex_thread_id: null,
|
|
45
|
-
last_turn_id: null,
|
|
46
|
-
last_turn_status: null
|
|
162
|
+
project_root: projectRoot
|
|
47
163
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
164
|
+
if (identityChanged) {
|
|
165
|
+
await new TelegramTopicRegistry(hubPaths.topics, {
|
|
166
|
+
identityFingerprint: config.identity_fingerprint
|
|
167
|
+
}).invalidateAll();
|
|
168
|
+
await ingress.resetForIdentity();
|
|
169
|
+
await writeJsonAtomic(hubPaths.projection, {
|
|
170
|
+
schema: 'sks.telegram-projection-state.v1',
|
|
171
|
+
sessions: {}
|
|
172
|
+
});
|
|
173
|
+
await writeTextAtomic(hubPaths.idempotency, '', { mode: 0o600 });
|
|
174
|
+
await fsp.unlink(hubPaths.receive_health).catch((error) => {
|
|
175
|
+
if (errorCodeOf(error) !== 'ENOENT')
|
|
176
|
+
throw error;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
await writeJsonAtomic(configPath, config);
|
|
180
|
+
if (detectedPair) {
|
|
181
|
+
await acknowledgePrivateStart(api, detectedPair.updateId).catch(throwTelegramSetupApiError);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
schema: 'sks.telegram-setup.v1',
|
|
185
|
+
ok: true,
|
|
186
|
+
bot: readiness.bot,
|
|
187
|
+
pairing: {
|
|
188
|
+
chat_id: pairing.chatId,
|
|
189
|
+
user_id: pairing.userId,
|
|
190
|
+
detected: explicitPair === null
|
|
191
|
+
},
|
|
192
|
+
machine_id: machineId,
|
|
193
|
+
project_id: projectId,
|
|
194
|
+
project_root: projectRoot,
|
|
195
|
+
session_id: sessionId,
|
|
196
|
+
codex_thread_id: binding.codex_thread_id,
|
|
197
|
+
codex_thread_state: binding.codex_thread_id ? 'ready' : 'pending_first_turn',
|
|
198
|
+
token_storage: 'macos-keychain',
|
|
199
|
+
config_path: configPath,
|
|
200
|
+
machine_registry_path: machinePath,
|
|
201
|
+
session_index_path: indexPath
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const rollbackErrors = [];
|
|
206
|
+
for (const snapshot of [...stateSnapshots].reverse()) {
|
|
207
|
+
await restoreSetupFile(snapshot).catch((rollbackError) => {
|
|
208
|
+
rollbackErrors.push(publicSetupError(rollbackError));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (credentialWriteAttempted) {
|
|
212
|
+
const restoreCredential = previousToken === null
|
|
213
|
+
? keychainDeleter(KEYCHAIN_SERVICE, account)
|
|
214
|
+
: keychainWriter(previousToken, KEYCHAIN_SERVICE, account);
|
|
215
|
+
await restoreCredential.catch((rollbackError) => {
|
|
216
|
+
rollbackErrors.push(publicSetupError(rollbackError));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (rollbackErrors.length > 0) {
|
|
220
|
+
throw new Error(`telegram_setup_rollback_failed:${publicSetupError(error)}:${rollbackErrors.join(',')}`);
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
97
224
|
}
|
|
98
|
-
|
|
225
|
+
async function readPreviousTelegramConfig(file) {
|
|
226
|
+
const raw = await readJson(file, null);
|
|
227
|
+
if (raw === null)
|
|
228
|
+
return null;
|
|
229
|
+
const config = readTelegramPreviousConfig(raw);
|
|
230
|
+
if (!config)
|
|
231
|
+
throw new Error('telegram_config_invalid:unsupported_or_malformed');
|
|
232
|
+
return config;
|
|
233
|
+
}
|
|
234
|
+
export async function detectPrivateStart(api, botUsername = null, consume = true) {
|
|
99
235
|
const updates = await api.getUpdates({ timeout: 0, allowed_updates: ['message'] });
|
|
100
|
-
const
|
|
236
|
+
const candidates = updates.filter((update) => isPrivateStart(update, botUsername));
|
|
237
|
+
const identities = new Set(candidates.map((update) => (`${String(update.message.chat.id)}:${String(update.message.from.id)}`)));
|
|
238
|
+
if (identities.size > 1) {
|
|
239
|
+
throw new Error('telegram_pairing_ambiguous:retry_with_explicit_private_chat_and_user_ids');
|
|
240
|
+
}
|
|
241
|
+
const match = candidates.at(-1);
|
|
101
242
|
if (!match?.message?.from) {
|
|
102
243
|
throw new Error('telegram_pairing_start_not_found:send_/start_to_the_bot_then_retry');
|
|
103
244
|
}
|
|
245
|
+
if (consume)
|
|
246
|
+
await acknowledgePrivateStart(api, match.update_id);
|
|
247
|
+
return {
|
|
248
|
+
chatId: String(match.message.chat.id),
|
|
249
|
+
userId: String(match.message.from.id),
|
|
250
|
+
updateId: match.update_id
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async function acknowledgePrivateStart(api, updateId) {
|
|
104
254
|
await api.getUpdates({
|
|
105
|
-
offset:
|
|
255
|
+
offset: updateId + 1,
|
|
106
256
|
timeout: 0,
|
|
107
257
|
allowed_updates: ['message']
|
|
108
258
|
});
|
|
109
|
-
return {
|
|
110
|
-
chatId: String(match.message.chat.id),
|
|
111
|
-
userId: String(match.message.from.id)
|
|
112
|
-
};
|
|
113
259
|
}
|
|
114
260
|
export async function writeTelegramKeychain(token, service, account) {
|
|
115
261
|
if (process.platform !== 'darwin')
|
|
@@ -123,6 +269,65 @@ export async function writeTelegramKeychain(token, service, account) {
|
|
|
123
269
|
if (result.code !== 0 || result.timedOut)
|
|
124
270
|
throw new Error('telegram_keychain_store_failed');
|
|
125
271
|
}
|
|
272
|
+
export async function readTelegramKeychain(service, account) {
|
|
273
|
+
return readTelegramKeychainToken(service, account, { timeoutMs: 5_000 })
|
|
274
|
+
.catch((error) => {
|
|
275
|
+
if (error instanceof Error && error.message === 'telegram_keychain_requires_macos')
|
|
276
|
+
throw error;
|
|
277
|
+
throw new Error('telegram_keychain_snapshot_failed');
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
export async function verifyTelegramKeychainReadback(expectedToken, service, account, reader = readTelegramKeychain) {
|
|
281
|
+
const storedToken = await reader(service, account).catch(() => null);
|
|
282
|
+
if (storedToken !== expectedToken)
|
|
283
|
+
throw new Error('telegram_keychain_readback_failed');
|
|
284
|
+
}
|
|
285
|
+
export async function deleteTelegramKeychain(service, account) {
|
|
286
|
+
if (process.platform !== 'darwin')
|
|
287
|
+
throw new Error('telegram_keychain_requires_macos');
|
|
288
|
+
const swift = await which('swift').catch(() => null) || '/usr/bin/swift';
|
|
289
|
+
const result = await runProcess(swift, ['-e', KEYCHAIN_DELETE_SWIFT, account, service], {
|
|
290
|
+
timeoutMs: 30_000,
|
|
291
|
+
maxOutputBytes: 8 * 1024
|
|
292
|
+
});
|
|
293
|
+
if (result.code !== 0 || result.timedOut)
|
|
294
|
+
throw new Error('telegram_keychain_delete_failed');
|
|
295
|
+
}
|
|
296
|
+
async function snapshotSetupFile(file) {
|
|
297
|
+
const stat = await fsp.lstat(file).catch((error) => {
|
|
298
|
+
if (errorCodeOf(error) === 'ENOENT')
|
|
299
|
+
return null;
|
|
300
|
+
throw error;
|
|
301
|
+
});
|
|
302
|
+
if (stat === null)
|
|
303
|
+
return { file, contents: null, mode: null };
|
|
304
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
305
|
+
throw new Error('telegram_setup_state_snapshot_invalid');
|
|
306
|
+
return {
|
|
307
|
+
file,
|
|
308
|
+
contents: await fsp.readFile(file),
|
|
309
|
+
mode: stat.mode & 0o777
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function restoreSetupFile(snapshot) {
|
|
313
|
+
if (snapshot.contents === null) {
|
|
314
|
+
await fsp.unlink(snapshot.file).catch((error) => {
|
|
315
|
+
if (errorCodeOf(error) !== 'ENOENT')
|
|
316
|
+
throw error;
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
await writeBinaryAtomic(snapshot.file, snapshot.contents, {
|
|
321
|
+
...(snapshot.mode === null ? {} : { mode: snapshot.mode })
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
function publicSetupError(error) {
|
|
325
|
+
return (error instanceof Error ? error.message : String(error))
|
|
326
|
+
.replace(/\b\d{5,}:[A-Za-z0-9_-]{20,}\b/g, '[redacted]')
|
|
327
|
+
.replace(/(?:\/Users|\/home)\/[^\s]+/g, '[path-redacted]')
|
|
328
|
+
.replace(/[:,]+/g, '_')
|
|
329
|
+
.slice(0, 160);
|
|
330
|
+
}
|
|
126
331
|
async function upsertLocalMachine(file, machine) {
|
|
127
332
|
await ensureDir(path.dirname(file));
|
|
128
333
|
const raw = await readJson(file, null);
|
|
@@ -206,11 +411,24 @@ function normalizeExplicitPair(chatId, userId) {
|
|
|
206
411
|
}
|
|
207
412
|
return { chatId: chat, userId: user };
|
|
208
413
|
}
|
|
209
|
-
function isPrivateStart(update) {
|
|
414
|
+
function isPrivateStart(update, botUsername) {
|
|
210
415
|
const message = update.message;
|
|
211
416
|
if (!message || message.chat.type !== 'private' || !message.from)
|
|
212
417
|
return false;
|
|
213
|
-
|
|
418
|
+
const command = /^\/start(?:@([A-Za-z0-9_]+))?(?:\s|$)/i.exec(String(message.text || '').trim());
|
|
419
|
+
if (!command)
|
|
420
|
+
return false;
|
|
421
|
+
const addressedBot = command[1];
|
|
422
|
+
return !addressedBot || (botUsername !== null && addressedBot.toLowerCase() === botUsername.toLowerCase());
|
|
423
|
+
}
|
|
424
|
+
function throwTelegramSetupApiError(error) {
|
|
425
|
+
if (error instanceof TelegramBotApiError && (error.errorCode === 401 || error.errorCode === 404)) {
|
|
426
|
+
throw new Error('telegram_bot_auth_failed');
|
|
427
|
+
}
|
|
428
|
+
if (error instanceof TelegramBotApiError && error.errorCode === 409) {
|
|
429
|
+
throw new Error('telegram_409_conflict:stop_other_poller_and_retry');
|
|
430
|
+
}
|
|
431
|
+
throw error;
|
|
214
432
|
}
|
|
215
433
|
export function telegramSetupKeychainReference(account = process.env.USER || os.userInfo().username || 'sks') {
|
|
216
434
|
return { type: 'keychain', service: KEYCHAIN_SERVICE, account };
|