@unerr-ai/unerr 0.2.0 → 0.2.2
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 +6 -0
- package/dist/cli.js +37236 -35793
- package/package.json +6 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proxy Lifecycle State Machine — XState v5 formalization of the boot sequence.
|
|
3
|
-
*
|
|
4
|
-
* States: idle → detecting → setup|indexing → ready → running → shutdown
|
|
5
|
-
* ↘ error → shutdown
|
|
6
|
-
*
|
|
7
|
-
* This machine is the source of truth for proxy lifecycle. Every state transition
|
|
8
|
-
* emits events that higher layers (Layer 1 temporal intelligence, Layer 2 UI) can
|
|
9
|
-
* subscribe to without modifying the core boot flow.
|
|
10
|
-
*
|
|
11
|
-
* Design: pure machine definition (no side effects). Side effects are injected
|
|
12
|
-
* via the `actions` map when creating the actor in lifecycle-actor.ts.
|
|
13
|
-
*/
|
|
14
|
-
import { assign, setup } from "xstate";
|
|
15
|
-
export const proxyMachine = setup({
|
|
16
|
-
types: {
|
|
17
|
-
context: {},
|
|
18
|
-
events: {},
|
|
19
|
-
input: {},
|
|
20
|
-
},
|
|
21
|
-
actions: {
|
|
22
|
-
recordError: assign({
|
|
23
|
-
error: (_, params) => params.message,
|
|
24
|
-
}),
|
|
25
|
-
markGraphLoaded: assign({ graphLoaded: true }),
|
|
26
|
-
markMcpReady: assign({ mcpReady: true }),
|
|
27
|
-
setRepoId: assign({
|
|
28
|
-
repoId: (_, params) => params.repoId,
|
|
29
|
-
}),
|
|
30
|
-
recordStateEntry: assign({ stateEnteredAt: () => Date.now() }),
|
|
31
|
-
},
|
|
32
|
-
guards: {
|
|
33
|
-
needsSetup: (_, params) => params.needsSetup,
|
|
34
|
-
isReady: ({ context }) => context.graphLoaded,
|
|
35
|
-
},
|
|
36
|
-
}).createMachine({
|
|
37
|
-
id: "proxy",
|
|
38
|
-
initial: "idle",
|
|
39
|
-
context: ({ input }) => ({
|
|
40
|
-
repoPath: input.repoPath,
|
|
41
|
-
repoId: undefined,
|
|
42
|
-
graphLoaded: false,
|
|
43
|
-
mcpReady: false,
|
|
44
|
-
error: undefined,
|
|
45
|
-
bootStartedAt: Date.now(),
|
|
46
|
-
stateEnteredAt: Date.now(),
|
|
47
|
-
}),
|
|
48
|
-
states: {
|
|
49
|
-
idle: {
|
|
50
|
-
on: {
|
|
51
|
-
START_DETECT: {
|
|
52
|
-
target: "detecting",
|
|
53
|
-
actions: [{ type: "recordStateEntry" }],
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
detecting: {
|
|
58
|
-
on: {
|
|
59
|
-
DETECT_COMPLETE: [
|
|
60
|
-
{
|
|
61
|
-
guard: {
|
|
62
|
-
type: "needsSetup",
|
|
63
|
-
params: ({ event }) => ({ needsSetup: event.needsSetup }),
|
|
64
|
-
},
|
|
65
|
-
target: "setup",
|
|
66
|
-
actions: [{ type: "recordStateEntry" }],
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
target: "indexing",
|
|
70
|
-
actions: [
|
|
71
|
-
{ type: "recordStateEntry" },
|
|
72
|
-
{
|
|
73
|
-
type: "setRepoId",
|
|
74
|
-
params: ({ event }) => ({ repoId: event.repoId ?? "" }),
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
],
|
|
79
|
-
ERROR: {
|
|
80
|
-
target: "error",
|
|
81
|
-
actions: [
|
|
82
|
-
{
|
|
83
|
-
type: "recordError",
|
|
84
|
-
params: ({ event }) => ({ message: event.message }),
|
|
85
|
-
},
|
|
86
|
-
{ type: "recordStateEntry" },
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
setup: {
|
|
92
|
-
on: {
|
|
93
|
-
SETUP_COMPLETE: {
|
|
94
|
-
target: "indexing",
|
|
95
|
-
actions: [
|
|
96
|
-
{
|
|
97
|
-
type: "setRepoId",
|
|
98
|
-
params: ({ event }) => ({ repoId: event.repoId }),
|
|
99
|
-
},
|
|
100
|
-
{ type: "recordStateEntry" },
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
ERROR: {
|
|
104
|
-
target: "error",
|
|
105
|
-
actions: [
|
|
106
|
-
{
|
|
107
|
-
type: "recordError",
|
|
108
|
-
params: ({ event }) => ({ message: event.message }),
|
|
109
|
-
},
|
|
110
|
-
{ type: "recordStateEntry" },
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
SHUTDOWN: { target: "shutdown" },
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
indexing: {
|
|
117
|
-
on: {
|
|
118
|
-
GRAPH_LOADED: {
|
|
119
|
-
actions: [{ type: "markGraphLoaded" }],
|
|
120
|
-
},
|
|
121
|
-
INDEX_COMPLETE: {
|
|
122
|
-
target: "ready",
|
|
123
|
-
actions: [{ type: "recordStateEntry" }],
|
|
124
|
-
},
|
|
125
|
-
ERROR: {
|
|
126
|
-
target: "error",
|
|
127
|
-
actions: [
|
|
128
|
-
{
|
|
129
|
-
type: "recordError",
|
|
130
|
-
params: ({ event }) => ({ message: event.message }),
|
|
131
|
-
},
|
|
132
|
-
{ type: "recordStateEntry" },
|
|
133
|
-
],
|
|
134
|
-
},
|
|
135
|
-
SHUTDOWN: { target: "shutdown" },
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
ready: {
|
|
139
|
-
on: {
|
|
140
|
-
MCP_READY: {
|
|
141
|
-
target: "running",
|
|
142
|
-
actions: [{ type: "markMcpReady" }, { type: "recordStateEntry" }],
|
|
143
|
-
},
|
|
144
|
-
ERROR: {
|
|
145
|
-
target: "error",
|
|
146
|
-
actions: [
|
|
147
|
-
{
|
|
148
|
-
type: "recordError",
|
|
149
|
-
params: ({ event }) => ({ message: event.message }),
|
|
150
|
-
},
|
|
151
|
-
{ type: "recordStateEntry" },
|
|
152
|
-
],
|
|
153
|
-
},
|
|
154
|
-
SHUTDOWN: { target: "shutdown" },
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
running: {
|
|
158
|
-
on: {
|
|
159
|
-
ERROR: {
|
|
160
|
-
target: "error",
|
|
161
|
-
actions: [
|
|
162
|
-
{
|
|
163
|
-
type: "recordError",
|
|
164
|
-
params: ({ event }) => ({ message: event.message }),
|
|
165
|
-
},
|
|
166
|
-
{ type: "recordStateEntry" },
|
|
167
|
-
],
|
|
168
|
-
},
|
|
169
|
-
SHUTDOWN: {
|
|
170
|
-
target: "shutdown",
|
|
171
|
-
actions: [{ type: "recordStateEntry" }],
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
error: {
|
|
176
|
-
on: {
|
|
177
|
-
SHUTDOWN: {
|
|
178
|
-
target: "shutdown",
|
|
179
|
-
actions: [{ type: "recordStateEntry" }],
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
shutdown: {
|
|
184
|
-
type: "final",
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
});
|
package/dist/proxy/log-tailer.js
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Log Tailer — relays new metric/log events from child processes to the
|
|
3
|
-
* proxy console via startupLog.
|
|
4
|
-
*
|
|
5
|
-
* Three streams (compression, token_flow, file_read) live in `.unerr/metrics.db`
|
|
6
|
-
* and are polled by `id > lastSeen` every `pollIntervalMs` (default 500ms).
|
|
7
|
-
* One stream (`logs/unerr.jsonl`) stays JSONL and is tailed via `fs.watch`.
|
|
8
|
-
*
|
|
9
|
-
* Why this shape: SQLite gives ordered-by-id polling for free, so we no
|
|
10
|
-
* longer have to track byte offsets or worry about partial writes. The
|
|
11
|
-
* remaining JSONL file is structured log output that doesn't need indexed
|
|
12
|
-
* aggregation, so leaving it as a flat file keeps `tail -f .../unerr.jsonl`
|
|
13
|
-
* useful for debugging.
|
|
14
|
-
*/
|
|
15
|
-
import { closeSync, existsSync, openSync, readSync, statSync, watch, } from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
import { openMetricsStore, } from "../tracking/metrics-store.js";
|
|
18
|
-
import { startupLog } from "../utils/startup-log.js";
|
|
19
|
-
function formatSize(bytes) {
|
|
20
|
-
if (bytes >= 1024 * 1024)
|
|
21
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
22
|
-
if (bytes >= 1024)
|
|
23
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
24
|
-
return `${bytes}B`;
|
|
25
|
-
}
|
|
26
|
-
/** Read new lines from a file starting at the given byte offset. */
|
|
27
|
-
function readNewLines(filePath, fromOffset) {
|
|
28
|
-
try {
|
|
29
|
-
const stat = statSync(filePath);
|
|
30
|
-
if (stat.size <= fromOffset)
|
|
31
|
-
return { lines: [], newOffset: fromOffset };
|
|
32
|
-
const bytesToRead = stat.size - fromOffset;
|
|
33
|
-
const buf = Buffer.alloc(bytesToRead);
|
|
34
|
-
const fd = openSync(filePath, "r");
|
|
35
|
-
try {
|
|
36
|
-
readSync(fd, buf, 0, bytesToRead, fromOffset);
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
closeSync(fd);
|
|
40
|
-
}
|
|
41
|
-
const text = buf.toString("utf-8");
|
|
42
|
-
const rawLines = text.split("\n").filter((l) => l.trim());
|
|
43
|
-
return { lines: rawLines, newOffset: stat.size };
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
return { lines: [], newOffset: fromOffset };
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Format and print a compression log entry. */
|
|
50
|
-
function printCompressionEntry(entry) {
|
|
51
|
-
const cmd = String(entry.command ?? "");
|
|
52
|
-
const cmdSlug = cmd.length > 40 ? `${cmd.slice(0, 37)}...` : cmd;
|
|
53
|
-
const category = String(entry.category ?? "unknown");
|
|
54
|
-
const savedPct = Number(entry.savedPct ?? 0);
|
|
55
|
-
const rawBytes = Number(entry.rawBytes ?? 0);
|
|
56
|
-
const compressedBytes = Number(entry.compressedBytes ?? 0);
|
|
57
|
-
const omniFallback = Boolean(entry.omniFallback);
|
|
58
|
-
const strategy = omniFallback ? `omni(${category})` : category;
|
|
59
|
-
if (savedPct === 0)
|
|
60
|
-
return; // skip no-op compressions on console
|
|
61
|
-
startupLog.step(`${startupLog.fmt.muted("[exec]")} ${startupLog.fmt.dim(`"${cmdSlug}"`)} ${startupLog.fmt.muted("→")} ${startupLog.fmt.cyan(strategy)} ${startupLog.fmt.muted(`(${formatSize(rawBytes)} → ${formatSize(compressedBytes)}, ${savedPct}% saved)`)}`);
|
|
62
|
-
}
|
|
63
|
-
/** Format and print a token-flow.jsonl entry from child processes. */
|
|
64
|
-
function printTokenFlowEntry(entry) {
|
|
65
|
-
const pid = Number(entry.pid ?? 0);
|
|
66
|
-
if (pid === process.pid)
|
|
67
|
-
return;
|
|
68
|
-
const tokensSaved = Number(entry.tokens_saved ?? 0);
|
|
69
|
-
if (tokensSaved <= 0)
|
|
70
|
-
return;
|
|
71
|
-
startupLog.tokenFlow({
|
|
72
|
-
turn: Number(entry.turn ?? 0),
|
|
73
|
-
tool: entry.tool != null ? String(entry.tool) : null,
|
|
74
|
-
mechanism: String(entry.mechanism ?? "unknown"),
|
|
75
|
-
tokensSaved,
|
|
76
|
-
tokensDelivered: Number(entry.tokens_with ?? 0),
|
|
77
|
-
sessionTotal: 0,
|
|
78
|
-
pid,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
/** Format and print a file-reads.jsonl entry from child processes. */
|
|
82
|
-
function printFileReadEntry(entry) {
|
|
83
|
-
const savedPct = Number(entry.savedPct ?? 0);
|
|
84
|
-
if (savedPct === 0)
|
|
85
|
-
return; // skip full reads with no savings
|
|
86
|
-
const file = String(entry.file ?? "");
|
|
87
|
-
const fileSlug = file.length > 40 ? `...${file.slice(-37)}` : file;
|
|
88
|
-
const mode = String(entry.mode ?? "full");
|
|
89
|
-
const totalLines = Number(entry.totalLines ?? 0);
|
|
90
|
-
const returnedLines = Number(entry.returnedLines ?? 0);
|
|
91
|
-
startupLog.step(`${startupLog.fmt.muted("[mcp]")} ${startupLog.fmt.dim(fileSlug)} ${startupLog.fmt.muted("→")} ${startupLog.fmt.cyan(mode)} ${startupLog.fmt.muted(`(${totalLines} → ${returnedLines} lines, ${savedPct}% saved)`)}`);
|
|
92
|
-
}
|
|
93
|
-
/** Format and print a general unerr.jsonl entry from child processes. */
|
|
94
|
-
function printGeneralEntry(entry) {
|
|
95
|
-
const pid = Number(entry.pid ?? 0);
|
|
96
|
-
// Only relay entries from OTHER processes (not our own PID)
|
|
97
|
-
if (pid === process.pid)
|
|
98
|
-
return;
|
|
99
|
-
const level = String(entry.level ?? "");
|
|
100
|
-
const msg = String(entry.msg ?? "");
|
|
101
|
-
// Skip noisy low-value entries
|
|
102
|
-
if (!msg || level === "step" || level === "ready")
|
|
103
|
-
return;
|
|
104
|
-
const prefix = startupLog.fmt.muted(`[pid:${pid}]`);
|
|
105
|
-
switch (level) {
|
|
106
|
-
case "error":
|
|
107
|
-
startupLog.error(`${prefix} ${msg}`);
|
|
108
|
-
break;
|
|
109
|
-
case "warn":
|
|
110
|
-
startupLog.warn(`${prefix} ${msg}`);
|
|
111
|
-
break;
|
|
112
|
-
case "done":
|
|
113
|
-
startupLog.done(`${prefix} ${msg}`, typeof entry.ms === "number" ? entry.ms : undefined);
|
|
114
|
-
break;
|
|
115
|
-
case "metric":
|
|
116
|
-
startupLog.step(`${prefix} ${startupLog.fmt.cyan(msg)}: ${startupLog.fmt.bold(String(entry.value))} ${startupLog.fmt.muted(String(entry.unit ?? ""))}`);
|
|
117
|
-
break;
|
|
118
|
-
default:
|
|
119
|
-
startupLog.step(`${prefix} ${msg}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function tailFile(state, handler) {
|
|
123
|
-
const { lines, newOffset } = readNewLines(state.path, state.offset);
|
|
124
|
-
state.offset = newOffset;
|
|
125
|
-
for (const line of lines) {
|
|
126
|
-
try {
|
|
127
|
-
const entry = JSON.parse(line);
|
|
128
|
-
handler(entry);
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// malformed line — skip
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// ── Row → wire-shape adapters ────────────────────────────────────────
|
|
136
|
-
// The console printers expect the old JSONL field names (camelCase).
|
|
137
|
-
// These keep the printers untouched while the storage backend swaps.
|
|
138
|
-
function compressionRowToEntry(r) {
|
|
139
|
-
return {
|
|
140
|
-
ts: r.ts_iso,
|
|
141
|
-
command: r.command,
|
|
142
|
-
category: r.category,
|
|
143
|
-
confidence: r.confidence,
|
|
144
|
-
rawBytes: r.raw_bytes,
|
|
145
|
-
compressedBytes: r.compressed_bytes,
|
|
146
|
-
savedPct: r.saved_pct,
|
|
147
|
-
omniFallback: r.omni_fallback === 1,
|
|
148
|
-
teeFile: r.tee_file,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
function fileReadRowToEntry(r) {
|
|
152
|
-
return {
|
|
153
|
-
ts: r.ts_iso,
|
|
154
|
-
file: r.file,
|
|
155
|
-
mode: r.mode,
|
|
156
|
-
totalLines: r.total_lines,
|
|
157
|
-
returnedLines: r.returned_lines,
|
|
158
|
-
savedPct: r.saved_pct,
|
|
159
|
-
entity: r.entity,
|
|
160
|
-
tokenEstimate: r.token_estimate,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function tokenFlowRowToEntry(r) {
|
|
164
|
-
return {
|
|
165
|
-
id: r.id,
|
|
166
|
-
ts: r.ts_iso,
|
|
167
|
-
session_id: r.session_id,
|
|
168
|
-
pid: r.pid,
|
|
169
|
-
turn: r.turn,
|
|
170
|
-
mechanism: r.mechanism,
|
|
171
|
-
tool: r.tool,
|
|
172
|
-
tokens_without: r.tokens_without,
|
|
173
|
-
tokens_with: r.tokens_with,
|
|
174
|
-
tokens_saved: r.tokens_saved,
|
|
175
|
-
detail: r.detail ? JSON.parse(r.detail) : undefined,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Start tailing the proxy's child-process activity.
|
|
180
|
-
*
|
|
181
|
-
* - compression / token_flow / file_read: polled from `.unerr/metrics.db`
|
|
182
|
-
* via `id > lastSeen` (no offsets, no partial-read races).
|
|
183
|
-
* - unerr.jsonl: still `fs.watch`-tailed — it's a flat structured log,
|
|
184
|
-
* not a metric stream.
|
|
185
|
-
*/
|
|
186
|
-
export function startLogTailer(cwd, options) {
|
|
187
|
-
const unerrDir = join(cwd, ".unerr");
|
|
188
|
-
const logsDir = join(unerrDir, "logs");
|
|
189
|
-
const generalPath = join(logsDir, "unerr.jsonl");
|
|
190
|
-
const pollIntervalMs = options?.pollIntervalMs ?? 500;
|
|
191
|
-
// ── JSONL path: unerr.jsonl via fs.watch + offset ────────────────
|
|
192
|
-
const generalState = {
|
|
193
|
-
path: generalPath,
|
|
194
|
-
offset: existsSync(generalPath) ? statSync(generalPath).size : 0,
|
|
195
|
-
watcher: null,
|
|
196
|
-
};
|
|
197
|
-
function setupWatcher(state, handler) {
|
|
198
|
-
try {
|
|
199
|
-
state.watcher = watch(state.path, () => {
|
|
200
|
-
tailFile(state, handler);
|
|
201
|
-
});
|
|
202
|
-
state.watcher.unref();
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
/* file may not exist yet — handled by the JSONL poll fallback below */
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
setupWatcher(generalState, printGeneralEntry);
|
|
209
|
-
// ── SQLite path: compression / token_flow / file_read ────────────
|
|
210
|
-
// Initialize lastSeen to the current max id so we only relay *new* events.
|
|
211
|
-
const store = openMetricsStore(unerrDir);
|
|
212
|
-
const initial = store.lastIds();
|
|
213
|
-
let lastCompressionId = initial.compression;
|
|
214
|
-
let lastFileReadId = initial.fileRead;
|
|
215
|
-
let lastTokenFlowId = initial.tokenFlow;
|
|
216
|
-
const sqlPoll = setInterval(() => {
|
|
217
|
-
try {
|
|
218
|
-
const cRows = store.compressionSince(lastCompressionId);
|
|
219
|
-
for (const r of cRows) {
|
|
220
|
-
printCompressionEntry(compressionRowToEntry(r));
|
|
221
|
-
lastCompressionId = r.id;
|
|
222
|
-
}
|
|
223
|
-
const fRows = store.fileReadsSince(lastFileReadId);
|
|
224
|
-
for (const r of fRows) {
|
|
225
|
-
printFileReadEntry(fileReadRowToEntry(r));
|
|
226
|
-
lastFileReadId = r.id;
|
|
227
|
-
}
|
|
228
|
-
const tRows = store.tokenFlowSince(lastTokenFlowId);
|
|
229
|
-
for (const r of tRows) {
|
|
230
|
-
const entry = tokenFlowRowToEntry(r);
|
|
231
|
-
printTokenFlowEntry(entry);
|
|
232
|
-
options?.onTokenFlowEvent?.(entry);
|
|
233
|
-
lastTokenFlowId = r.id;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
/* poll round failed — try again next tick */
|
|
238
|
-
}
|
|
239
|
-
}, pollIntervalMs);
|
|
240
|
-
sqlPoll.unref();
|
|
241
|
-
// ── JSONL poll fallback (unerr.jsonl appears after startup) ──────
|
|
242
|
-
const jsonlPoll = setInterval(() => {
|
|
243
|
-
if (!generalState.watcher && existsSync(generalState.path)) {
|
|
244
|
-
try {
|
|
245
|
-
generalState.offset = 0;
|
|
246
|
-
generalState.watcher = watch(generalState.path, () => {
|
|
247
|
-
tailFile(generalState, printGeneralEntry);
|
|
248
|
-
});
|
|
249
|
-
generalState.watcher.unref();
|
|
250
|
-
}
|
|
251
|
-
catch {
|
|
252
|
-
/* retry next poll */
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
tailFile(generalState, printGeneralEntry);
|
|
256
|
-
}, 3000);
|
|
257
|
-
jsonlPoll.unref();
|
|
258
|
-
return {
|
|
259
|
-
close() {
|
|
260
|
-
clearInterval(sqlPoll);
|
|
261
|
-
clearInterval(jsonlPoll);
|
|
262
|
-
generalState.watcher?.close();
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model Pricing Table + Dollar Savings Calculator.
|
|
3
|
-
*
|
|
4
|
-
* T.2: Per-model input/output token pricing (USD per million tokens).
|
|
5
|
-
* T.3: Dollar savings = tokens_saved × input_rate (we save on agent input).
|
|
6
|
-
*
|
|
7
|
-
* All pricing is input-side (tokens we prevent the agent from consuming).
|
|
8
|
-
*/
|
|
9
|
-
const MODEL_RATES = [
|
|
10
|
-
{
|
|
11
|
-
id: "claude-sonnet-4-20250514",
|
|
12
|
-
name: "Claude Sonnet 4",
|
|
13
|
-
inputPerMillion: 3,
|
|
14
|
-
outputPerMillion: 15,
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
id: "claude-opus-4-20250514",
|
|
18
|
-
name: "Claude Opus 4",
|
|
19
|
-
inputPerMillion: 15,
|
|
20
|
-
outputPerMillion: 75,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: "claude-haiku-4-20250506",
|
|
24
|
-
name: "Claude Haiku 4",
|
|
25
|
-
inputPerMillion: 0.8,
|
|
26
|
-
outputPerMillion: 4,
|
|
27
|
-
},
|
|
28
|
-
{ id: "gpt-4o", name: "GPT-4o", inputPerMillion: 2.5, outputPerMillion: 10 },
|
|
29
|
-
{
|
|
30
|
-
id: "gpt-4o-mini",
|
|
31
|
-
name: "GPT-4o Mini",
|
|
32
|
-
inputPerMillion: 0.15,
|
|
33
|
-
outputPerMillion: 0.6,
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "gemini-2.0-flash",
|
|
37
|
-
name: "Gemini 2.0 Flash",
|
|
38
|
-
inputPerMillion: 0.1,
|
|
39
|
-
outputPerMillion: 0.4,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
id: "gemini-1.5-pro",
|
|
43
|
-
name: "Gemini 1.5 Pro",
|
|
44
|
-
inputPerMillion: 1.25,
|
|
45
|
-
outputPerMillion: 5,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: "ollama",
|
|
49
|
-
name: "Ollama (local)",
|
|
50
|
-
inputPerMillion: 0,
|
|
51
|
-
outputPerMillion: 0,
|
|
52
|
-
},
|
|
53
|
-
];
|
|
54
|
-
const DEFAULT_MODEL = "claude-sonnet-4-20250514";
|
|
55
|
-
/**
|
|
56
|
-
* Get the pricing rate for a model. Returns default (Sonnet) for unknown models.
|
|
57
|
-
*/
|
|
58
|
-
export function getModelRate(modelId) {
|
|
59
|
-
const id = modelId ?? DEFAULT_MODEL;
|
|
60
|
-
return MODEL_RATES.find((m) => m.id === id) ?? MODEL_RATES[0];
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Calculate dollar savings from tokens saved.
|
|
64
|
-
* Savings are on input tokens (what the agent would have consumed).
|
|
65
|
-
*/
|
|
66
|
-
export function calculateDollarSavings(tokensSaved, modelId) {
|
|
67
|
-
const rate = getModelRate(modelId);
|
|
68
|
-
return (tokensSaved * rate.inputPerMillion) / 1_000_000;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Format dollar amount for display.
|
|
72
|
-
*/
|
|
73
|
-
export function formatDollars(amount) {
|
|
74
|
-
if (amount >= 1)
|
|
75
|
-
return `$${amount.toFixed(2)}`;
|
|
76
|
-
if (amount >= 0.01)
|
|
77
|
-
return `$${amount.toFixed(2)}`;
|
|
78
|
-
if (amount >= 0.001)
|
|
79
|
-
return `$${amount.toFixed(3)}`;
|
|
80
|
-
return `$${amount.toFixed(4)}`;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Format a complete savings summary string.
|
|
84
|
-
*/
|
|
85
|
-
export function formatSavingsSummary(tokensSaved, modelId) {
|
|
86
|
-
const dollars = calculateDollarSavings(tokensSaved, modelId);
|
|
87
|
-
const rate = getModelRate(modelId);
|
|
88
|
-
const tokensStr = tokensSaved >= 1000
|
|
89
|
-
? `${(tokensSaved / 1000).toFixed(1)}K`
|
|
90
|
-
: String(tokensSaved);
|
|
91
|
-
return `${tokensStr} tokens saved (${formatDollars(dollars)} at ${rate.name} rates)`;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get all supported models.
|
|
95
|
-
*/
|
|
96
|
-
export function getAllModelRates() {
|
|
97
|
-
return [...MODEL_RATES];
|
|
98
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NetworkFirewall — Zero-leakage network boundary for True Local Mode.
|
|
3
|
-
*
|
|
4
|
-
* When sealed, intercepts globalThis.fetch and rejects any outbound
|
|
5
|
-
* request that targets a non-localhost address. This is the belt-and-
|
|
6
|
-
* suspenders safety net: even if a code path accidentally imports a
|
|
7
|
-
* cloud service, the firewall catches it.
|
|
8
|
-
*
|
|
9
|
-
* Localhost addresses (Ollama, LM Studio, CozoDB) are always allowed.
|
|
10
|
-
* The user's configured BYO-LLM baseUrl is also allowlisted.
|
|
11
|
-
*
|
|
12
|
-
* Allowlist is built BEFORE seal() via addAllowedHost(). Once seal()
|
|
13
|
-
* is called the allowlist is frozen — no runtime additions permitted.
|
|
14
|
-
*
|
|
15
|
-
* If Level 2 (fetch interception) ever fires in production, it
|
|
16
|
-
* indicates a bug in Level 1 (conditional service instantiation)
|
|
17
|
-
* that must be fixed.
|
|
18
|
-
*/
|
|
19
|
-
const LOCALHOST_HOSTNAMES = new Set([
|
|
20
|
-
"localhost",
|
|
21
|
-
"127.0.0.1",
|
|
22
|
-
"::1",
|
|
23
|
-
"0.0.0.0",
|
|
24
|
-
]);
|
|
25
|
-
/** Parsed URL hostname cache for allowlisted BYO-LLM endpoints. */
|
|
26
|
-
const allowlistedHosts = new Set();
|
|
27
|
-
let sealed = false;
|
|
28
|
-
let blockedCallCount = 0;
|
|
29
|
-
// Capture the original fetch before anything can monkey-patch it.
|
|
30
|
-
const originalFetch = globalThis.fetch;
|
|
31
|
-
/**
|
|
32
|
-
* Extract hostname from a fetch input (string, URL, or Request).
|
|
33
|
-
* Returns null if the input cannot be parsed.
|
|
34
|
-
*/
|
|
35
|
-
function extractHostname(input) {
|
|
36
|
-
try {
|
|
37
|
-
if (typeof input === "string") {
|
|
38
|
-
return new URL(input).hostname;
|
|
39
|
-
}
|
|
40
|
-
if (input instanceof URL) {
|
|
41
|
-
return input.hostname;
|
|
42
|
-
}
|
|
43
|
-
// Request object
|
|
44
|
-
if (input && typeof input.url === "string") {
|
|
45
|
-
return new URL(input.url).hostname;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
// Malformed URL — treat as blocked
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
function isLocalhost(hostname) {
|
|
54
|
-
return (LOCALHOST_HOSTNAMES.has(hostname) ||
|
|
55
|
-
allowlistedHosts.has(hostname) ||
|
|
56
|
-
hostname.endsWith(".local"));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Add a hostname to the firewall allowlist.
|
|
60
|
-
*
|
|
61
|
-
* Must be called BEFORE seal(). Throws if called after seal() to
|
|
62
|
-
* enforce immutability of the allowlist at runtime.
|
|
63
|
-
*/
|
|
64
|
-
export function addAllowedHost(hostname) {
|
|
65
|
-
if (sealed) {
|
|
66
|
-
throw new Error(`[NetworkFirewall] Cannot add host "${hostname}" — firewall is already sealed. All allowlist entries must be added before seal().`);
|
|
67
|
-
}
|
|
68
|
-
allowlistedHosts.add(hostname);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Add a URL's hostname to the firewall allowlist.
|
|
72
|
-
* Convenience wrapper — extracts hostname from a URL string.
|
|
73
|
-
* Must be called BEFORE seal().
|
|
74
|
-
*/
|
|
75
|
-
export function addAllowedUrl(url) {
|
|
76
|
-
let hostname;
|
|
77
|
-
try {
|
|
78
|
-
hostname = new URL(url).hostname;
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return; // Skip malformed URLs
|
|
82
|
-
}
|
|
83
|
-
addAllowedHost(hostname);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Seal the network firewall. All non-localhost fetch() calls will be
|
|
87
|
-
* rejected with a NetworkFirewallError.
|
|
88
|
-
*
|
|
89
|
-
* @param allowedBaseUrls - Additional URLs to allowlist (e.g., BYO-LLM baseUrl).
|
|
90
|
-
* Only the hostname is extracted; paths are ignored.
|
|
91
|
-
* These are added to any hosts previously registered via addAllowedHost().
|
|
92
|
-
*/
|
|
93
|
-
export function seal(allowedBaseUrls) {
|
|
94
|
-
if (sealed)
|
|
95
|
-
return;
|
|
96
|
-
// Populate allowlist from user-configured endpoints
|
|
97
|
-
if (allowedBaseUrls) {
|
|
98
|
-
for (const url of allowedBaseUrls) {
|
|
99
|
-
try {
|
|
100
|
-
allowlistedHosts.add(new URL(url).hostname);
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// Skip malformed URLs
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
sealed = true;
|
|
108
|
-
globalThis.fetch = ((input, init) => {
|
|
109
|
-
const hostname = extractHostname(input);
|
|
110
|
-
if (hostname && !isLocalhost(hostname)) {
|
|
111
|
-
blockedCallCount++;
|
|
112
|
-
process.stderr.write(`[unerr] FIREWALL: Blocked outbound call to ${hostname} (Local Mode active)\n`);
|
|
113
|
-
return Promise.reject(new Error(`[NetworkFirewall] Outbound network call to ${hostname} blocked — Local Mode active. This indicates a code path that should be gated on mode !== 'local'.`));
|
|
114
|
-
}
|
|
115
|
-
return originalFetch(input, init);
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Unseal the firewall, restoring the original fetch.
|
|
120
|
-
* Used in tests and for mode transitions.
|
|
121
|
-
*/
|
|
122
|
-
export function unseal() {
|
|
123
|
-
sealed = false;
|
|
124
|
-
globalThis.fetch = originalFetch;
|
|
125
|
-
allowlistedHosts.clear();
|
|
126
|
-
}
|
|
127
|
-
/** Returns true if the firewall is currently sealed. */
|
|
128
|
-
export function isSealed() {
|
|
129
|
-
return sealed;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Number of outbound calls blocked since the firewall was sealed.
|
|
133
|
-
* If this is >0 in production, a Level 1 gate is missing.
|
|
134
|
-
*/
|
|
135
|
-
export function getBlockedCount() {
|
|
136
|
-
return blockedCallCount;
|
|
137
|
-
}
|
|
138
|
-
/** Reset the blocked call counter (for testing). */
|
|
139
|
-
export function resetBlockedCount() {
|
|
140
|
-
blockedCallCount = 0;
|
|
141
|
-
}
|