snow-flow 10.0.67 → 10.0.68

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "10.0.67",
3
+ "version": "10.0.68",
4
4
  "name": "snow-flow",
5
5
  "description": "Snow-Flow - ServiceNow Multi-Agent Development Framework powered by AI",
6
6
  "license": "Elastic-2.0",
@@ -38,6 +38,7 @@ import { ArgsProvider, useArgs, type Args } from "./context/args"
38
38
  import open from "open"
39
39
  import { writeHeapSnapshot } from "v8"
40
40
  import { PromptRefProvider, usePromptRef } from "./context/prompt"
41
+ import { Config } from "@/config/config"
41
42
 
42
43
  async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
43
44
  // can't set raw mode if not a TTY
@@ -634,6 +635,26 @@ function App() {
634
635
  dialog.clear()
635
636
  },
636
637
  },
638
+ {
639
+ title: kv.get("telemetry_enabled", true) ? "Disable anonymous telemetry" : "Enable anonymous telemetry",
640
+ value: "app.toggle.telemetry",
641
+ category: "System",
642
+ slash: {
643
+ name: "telemetry",
644
+ },
645
+ onSelect: async (dialog) => {
646
+ const enabled = kv.get("telemetry_enabled", true)
647
+ kv.set("telemetry_enabled", !enabled)
648
+ await Config.updateGlobal({ telemetry: !enabled }).catch(() => {})
649
+ toast.show({
650
+ message: enabled
651
+ ? "Telemetry disabled. Takes effect next session."
652
+ : "Telemetry enabled. Thank you!",
653
+ variant: "info",
654
+ })
655
+ dialog.clear()
656
+ },
657
+ },
637
658
  ])
638
659
 
639
660
  createEffect(() => {
@@ -1301,6 +1301,10 @@ export namespace Config {
1301
1301
  url: z.string().optional().describe("Enterprise URL"),
1302
1302
  })
1303
1303
  .optional(),
1304
+ telemetry: z
1305
+ .boolean()
1306
+ .optional()
1307
+ .describe("Enable anonymous usage telemetry (default: true). Set to false to disable."),
1304
1308
  compaction: z
1305
1309
  .object({
1306
1310
  auto: z.boolean().optional().describe("Enable automatic compaction when context is full (default: true)"),
package/src/flag/flag.ts CHANGED
@@ -27,6 +27,7 @@ export namespace Flag {
27
27
  export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthyBoth("DISABLE_DEFAULT_PLUGINS")
28
28
  export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthyBoth("DISABLE_LSP_DOWNLOAD")
29
29
  export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthyBoth("ENABLE_EXPERIMENTAL_MODELS")
30
+ export const OPENCODE_TELEMETRY_DISABLED = truthyBoth("TELEMETRY_DISABLED")
30
31
  export const OPENCODE_DISABLE_AUTOCOMPACT = truthyBoth("DISABLE_AUTOCOMPACT")
31
32
  export const OPENCODE_DISABLE_MODELS_FETCH = truthyBoth("DISABLE_MODELS_FETCH")
32
33
  export const OPENCODE_DISABLE_CLAUDE_CODE = truthyBoth("DISABLE_CLAUDE_CODE")
@@ -45,7 +45,7 @@ import { LLM } from "./llm"
45
45
  import { iife } from "@/util/iife"
46
46
  import { Shell } from "@/shell/shell"
47
47
  import { Truncate } from "@/tool/truncation"
48
- import { UsageReporter, ActivityReporter } from "@/usage"
48
+ import { UsageReporter, ActivityReporter, AnonymousTelemetry } from "@/usage"
49
49
 
50
50
  // @ts-ignore
51
51
  globalThis.AI_SDK_LOG_WARNINGS = false
@@ -156,6 +156,7 @@ export namespace SessionPrompt {
156
156
  // Initialize TUI usage reporting (lazy, no-op if no enterprise auth)
157
157
  UsageReporter.init()
158
158
  ActivityReporter.init()
159
+ AnonymousTelemetry.init()
159
160
 
160
161
  const message = await createUserMessage(input)
161
162
  await Session.touch(input.sessionID)
@@ -0,0 +1,118 @@
1
+ import { Instance } from "@/project/instance"
2
+ import { Bus } from "@/bus"
3
+ import { MessageV2 } from "@/session/message-v2"
4
+ import { Log } from "@/util/log"
5
+ import { Installation } from "@/installation"
6
+ import { Flag } from "@/flag/flag"
7
+ import { Config } from "@/config/config"
8
+ import { machineIdSync } from "node-machine-id"
9
+
10
+ const log = Log.create({ service: "usage.anonymous-telemetry" })
11
+
12
+ const PORTAL_URL = process.env.SNOW_FLOW_PORTAL_URL || "https://portal.snow-flow.dev"
13
+
14
+ function isDisabled(): boolean {
15
+ // 1. Standard DO_NOT_TRACK convention (consoledonottrack.com)
16
+ const dnt = process.env.DO_NOT_TRACK?.toLowerCase()
17
+ if (dnt === "1" || dnt === "true") return true
18
+
19
+ // 2. Snow-Flow specific env vars
20
+ if (Flag.OPENCODE_TELEMETRY_DISABLED) return true
21
+
22
+ // 3. CI environments
23
+ const ci = process.env.CI?.toLowerCase()
24
+ if (ci === "true" || ci === "1") return true
25
+
26
+ return false
27
+ }
28
+
29
+ function getMachineId(): string | undefined {
30
+ try {
31
+ return machineIdSync()
32
+ } catch {
33
+ return undefined
34
+ }
35
+ }
36
+
37
+ export namespace AnonymousTelemetry {
38
+ const state = Instance.state(
39
+ () => {
40
+ // Check env-based opt-out first (sync, before any subscriptions)
41
+ if (isDisabled()) {
42
+ log.info("anonymous telemetry disabled (env)")
43
+ return { disabled: true as const, unsubs: [] as (() => void)[], startTime: 0, messageCount: 0 }
44
+ }
45
+
46
+ const machineId = getMachineId()
47
+ if (!machineId) {
48
+ log.info("anonymous telemetry disabled (no machine id)")
49
+ return { disabled: true as const, unsubs: [] as (() => void)[], startTime: 0, messageCount: 0 }
50
+ }
51
+
52
+ let messageCount = 0
53
+ const startTime = Date.now()
54
+
55
+ const unsubs = [
56
+ Bus.subscribe(MessageV2.Event.Updated, (event) => {
57
+ if (event.properties.info.role === "user") {
58
+ messageCount++
59
+ }
60
+ }),
61
+ ]
62
+
63
+ // Check config-based opt-out (async)
64
+ Config.get().then((config) => {
65
+ if (config.telemetry === false) {
66
+ log.info("anonymous telemetry disabled (config)")
67
+ for (const unsub of unsubs) unsub()
68
+ unsubs.length = 0
69
+ } else {
70
+ log.info("anonymous telemetry active")
71
+ }
72
+ }).catch(() => {
73
+ // Config not available yet — keep telemetry active
74
+ log.info("anonymous telemetry active (config unavailable)")
75
+ })
76
+
77
+ return { disabled: false as const, unsubs, startTime, machineId, get messageCount() { return messageCount } }
78
+ },
79
+ async (current) => {
80
+ for (const unsub of current.unsubs) unsub()
81
+
82
+ if (current.disabled) return
83
+
84
+ const sessionDurationSec = Math.round((Date.now() - current.startTime) / 1000)
85
+
86
+ // Check config opt-out before sending
87
+ const config = await Config.get().catch(() => undefined)
88
+ if (config?.telemetry === false) return
89
+
90
+ const payload = {
91
+ machineId: current.machineId,
92
+ version: Installation.VERSION,
93
+ channel: Installation.CHANNEL,
94
+ os: process.platform,
95
+ arch: process.arch,
96
+ sessionDurationSec,
97
+ messageCount: current.messageCount,
98
+ timestamp: Date.now(),
99
+ }
100
+
101
+ try {
102
+ await fetch(`${PORTAL_URL}/api/telemetry/ping`, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify(payload),
106
+ signal: AbortSignal.timeout(5_000),
107
+ })
108
+ } catch {
109
+ // Fire-and-forget: network errors expected when offline
110
+ }
111
+ },
112
+ )
113
+
114
+ /** Initialize the anonymous telemetry reporter. Call once per Instance lifecycle. */
115
+ export function init() {
116
+ state()
117
+ }
118
+ }
@@ -1,2 +1,3 @@
1
1
  export { UsageReporter } from "./reporter"
2
2
  export { ActivityReporter } from "./activity-reporter"
3
+ export { AnonymousTelemetry } from "./anonymous-telemetry"