snow-flow 10.0.200 → 10.0.201
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 +1 -1
- package/src/tool/registry.ts +17 -1
- package/src/usage/anonymous-telemetry.ts +40 -0
package/package.json
CHANGED
package/src/tool/registry.ts
CHANGED
|
@@ -28,6 +28,7 @@ import { Truncate } from "./truncation"
|
|
|
28
28
|
import { PlanExitTool, PlanEnterTool } from "./plan"
|
|
29
29
|
import { ReviewEnterTool, ReviewExitTool } from "./review"
|
|
30
30
|
import { ApplyPatchTool } from "./apply_patch"
|
|
31
|
+
import { Auth } from "@/auth"
|
|
31
32
|
|
|
32
33
|
export namespace ToolRegistry {
|
|
33
34
|
const log = Log.create({ service: "tool.registry" })
|
|
@@ -99,6 +100,9 @@ export namespace ToolRegistry {
|
|
|
99
100
|
const custom = await state().then((x) => x.custom)
|
|
100
101
|
const config = await Config.get()
|
|
101
102
|
|
|
103
|
+
// Check if the user has the code-review feature (enterprise subscription)
|
|
104
|
+
const hasCodeReview = await hasFeature("code-review")
|
|
105
|
+
|
|
102
106
|
return [
|
|
103
107
|
InvalidTool,
|
|
104
108
|
...(["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) ? [QuestionTool] : []),
|
|
@@ -118,11 +122,23 @@ export namespace ToolRegistry {
|
|
|
118
122
|
ApplyPatchTool,
|
|
119
123
|
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [LspTool] : []),
|
|
120
124
|
...(config.experimental?.batch_tool === true ? [BatchTool] : []),
|
|
121
|
-
...(Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool
|
|
125
|
+
...(Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool, PlanEnterTool] : []),
|
|
126
|
+
...(Flag.OPENCODE_CLIENT === "cli" && hasCodeReview ? [ReviewEnterTool, ReviewExitTool] : []),
|
|
122
127
|
...custom,
|
|
123
128
|
]
|
|
124
129
|
}
|
|
125
130
|
|
|
131
|
+
/** Check if the user has a specific feature in their enterprise subscription */
|
|
132
|
+
async function hasFeature(feature: string): Promise<boolean> {
|
|
133
|
+
try {
|
|
134
|
+
const auths = await Auth.all()
|
|
135
|
+
for (const auth of Object.values(auths)) {
|
|
136
|
+
if (auth.type === "enterprise" && auth.features?.includes(feature)) return true
|
|
137
|
+
}
|
|
138
|
+
} catch {}
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
export async function ids() {
|
|
127
143
|
return all().then((x) => x.map((t) => t.id))
|
|
128
144
|
}
|
|
@@ -28,6 +28,19 @@ function getMachineId(): string | undefined {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function detectInstallMethod(): string {
|
|
32
|
+
// Check npm
|
|
33
|
+
if (process.env.npm_config_user_agent) return "npm"
|
|
34
|
+
// Check bun
|
|
35
|
+
if (typeof globalThis.Bun !== "undefined") return "bun"
|
|
36
|
+
// Check homebrew
|
|
37
|
+
const execPath = process.execPath || ""
|
|
38
|
+
if (execPath.includes("Cellar") || execPath.includes("homebrew") || execPath.includes("linuxbrew")) return "brew"
|
|
39
|
+
// Check standalone binary (no node_modules in path suggests binary distribution)
|
|
40
|
+
if (!execPath.includes("node_modules") && !execPath.includes("node") && !execPath.includes("bun")) return "binary"
|
|
41
|
+
return "other"
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
async function sendPing(payload: Record<string, unknown>): Promise<void> {
|
|
32
45
|
try {
|
|
33
46
|
const response = await fetch(`${PORTAL_URL}/api/telemetry/ping`, {
|
|
@@ -63,9 +76,11 @@ export namespace AnonymousTelemetry {
|
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
let messageCount = 0
|
|
79
|
+
let exitReason: "normal" | "error" | "interrupt" = "normal"
|
|
66
80
|
let configDisabled = false
|
|
67
81
|
const startTime = Date.now()
|
|
68
82
|
const sessionId = crypto.randomUUID()
|
|
83
|
+
const installMethod = detectInstallMethod()
|
|
69
84
|
|
|
70
85
|
const unsubs = [
|
|
71
86
|
Bus.subscribe(MessageV2.Event.Updated, (event) => {
|
|
@@ -73,6 +88,18 @@ export namespace AnonymousTelemetry {
|
|
|
73
88
|
}),
|
|
74
89
|
]
|
|
75
90
|
|
|
91
|
+
// Track exit reason via process signals
|
|
92
|
+
const onError = () => {
|
|
93
|
+
exitReason = "error"
|
|
94
|
+
}
|
|
95
|
+
const onInterrupt = () => {
|
|
96
|
+
exitReason = "interrupt"
|
|
97
|
+
}
|
|
98
|
+
process.on("uncaughtException", onError)
|
|
99
|
+
process.on("unhandledRejection", onError)
|
|
100
|
+
process.on("SIGINT", onInterrupt)
|
|
101
|
+
process.on("SIGTERM", onInterrupt)
|
|
102
|
+
|
|
76
103
|
const basePayload = {
|
|
77
104
|
machineId,
|
|
78
105
|
sessionId,
|
|
@@ -80,6 +107,7 @@ export namespace AnonymousTelemetry {
|
|
|
80
107
|
channel: Installation.CHANNEL,
|
|
81
108
|
os: process.platform,
|
|
82
109
|
arch: process.arch,
|
|
110
|
+
installMethod,
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
log.info("anonymous telemetry initializing", { machineId: machineId.slice(0, 8) + "..." })
|
|
@@ -111,20 +139,32 @@ export namespace AnonymousTelemetry {
|
|
|
111
139
|
get messageCount() {
|
|
112
140
|
return messageCount
|
|
113
141
|
},
|
|
142
|
+
get exitReason() {
|
|
143
|
+
return exitReason
|
|
144
|
+
},
|
|
114
145
|
get configDisabled() {
|
|
115
146
|
return configDisabled
|
|
116
147
|
},
|
|
148
|
+
cleanup() {
|
|
149
|
+
process.removeListener("uncaughtException", onError)
|
|
150
|
+
process.removeListener("unhandledRejection", onError)
|
|
151
|
+
process.removeListener("SIGINT", onInterrupt)
|
|
152
|
+
process.removeListener("SIGTERM", onInterrupt)
|
|
153
|
+
},
|
|
117
154
|
}
|
|
118
155
|
},
|
|
119
156
|
async (current) => {
|
|
120
157
|
for (const unsub of current.unsubs) unsub()
|
|
121
158
|
if (current.disabled || current.configDisabled) return
|
|
122
159
|
|
|
160
|
+
if ("cleanup" in current) current.cleanup()
|
|
161
|
+
|
|
123
162
|
const sessionDurationSec = Math.round((Date.now() - current.startTime) / 1000)
|
|
124
163
|
|
|
125
164
|
await sendPing({
|
|
126
165
|
...current.basePayload,
|
|
127
166
|
type: "end",
|
|
167
|
+
exitReason: current.exitReason,
|
|
128
168
|
sessionDurationSec,
|
|
129
169
|
messageCount: current.messageCount,
|
|
130
170
|
timestamp: Date.now(),
|