@agentsky/sky 2.10.0 → 2.12.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/dist/chunk-7LUCHBX6.js +168 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-RF5JICNX.js +54 -0
- package/dist/{chunk-5QRKU3BR.js → chunk-XXDQKVPS.js} +2690 -2566
- package/dist/harness-connect-DUC7GIBL.js +102 -0
- package/dist/index.js +862 -328
- package/dist/products.d.ts +9 -0
- package/dist/products.js +2 -1
- package/dist/{src-RYECJN5I.js → src-S777KCS5.js} +21 -2
- package/dist/trace-UJTFSXNM.js +367 -0
- package/dist/trace-library-474QJTAZ.js +710 -0
- package/dist/worker-6Q4K4HZW.js +190 -0
- package/package.json +4 -3
package/dist/products.d.ts
CHANGED
|
@@ -85,6 +85,7 @@ declare const agentAddonsSchema: z.ZodObject<{
|
|
|
85
85
|
"happyhorse.t2v": "happyhorse.t2v";
|
|
86
86
|
"minimax.h3": "minimax.h3";
|
|
87
87
|
"fish-audio.transcribe": "fish-audio.transcribe";
|
|
88
|
+
"code-review": "code-review";
|
|
88
89
|
"exa.answer": "exa.answer";
|
|
89
90
|
"googlemap.geocode": "googlemap.geocode";
|
|
90
91
|
"googlemap.places": "googlemap.places";
|
|
@@ -94,6 +95,8 @@ declare const agentAddonsSchema: z.ZodObject<{
|
|
|
94
95
|
"meta.transcribe": "meta.transcribe";
|
|
95
96
|
"minimax.tts": "minimax.tts";
|
|
96
97
|
"elevenlabs.sound": "elevenlabs.sound";
|
|
98
|
+
"elevenlabs.tts": "elevenlabs.tts";
|
|
99
|
+
"elevenlabs.music": "elevenlabs.music";
|
|
97
100
|
"zai.transcribe": "zai.transcribe";
|
|
98
101
|
"openai.transcribe": "openai.transcribe";
|
|
99
102
|
"fish-audio.tts": "fish-audio.tts";
|
|
@@ -224,6 +227,7 @@ declare const createProductAgentSchema: z.ZodObject<{
|
|
|
224
227
|
"happyhorse.t2v": "happyhorse.t2v";
|
|
225
228
|
"minimax.h3": "minimax.h3";
|
|
226
229
|
"fish-audio.transcribe": "fish-audio.transcribe";
|
|
230
|
+
"code-review": "code-review";
|
|
227
231
|
"exa.answer": "exa.answer";
|
|
228
232
|
"googlemap.geocode": "googlemap.geocode";
|
|
229
233
|
"googlemap.places": "googlemap.places";
|
|
@@ -233,6 +237,8 @@ declare const createProductAgentSchema: z.ZodObject<{
|
|
|
233
237
|
"meta.transcribe": "meta.transcribe";
|
|
234
238
|
"minimax.tts": "minimax.tts";
|
|
235
239
|
"elevenlabs.sound": "elevenlabs.sound";
|
|
240
|
+
"elevenlabs.tts": "elevenlabs.tts";
|
|
241
|
+
"elevenlabs.music": "elevenlabs.music";
|
|
236
242
|
"zai.transcribe": "zai.transcribe";
|
|
237
243
|
"openai.transcribe": "openai.transcribe";
|
|
238
244
|
"fish-audio.tts": "fish-audio.tts";
|
|
@@ -363,6 +369,7 @@ declare const prepareProductSessionSchema: z.ZodObject<{
|
|
|
363
369
|
"happyhorse.t2v": "happyhorse.t2v";
|
|
364
370
|
"minimax.h3": "minimax.h3";
|
|
365
371
|
"fish-audio.transcribe": "fish-audio.transcribe";
|
|
372
|
+
"code-review": "code-review";
|
|
366
373
|
"exa.answer": "exa.answer";
|
|
367
374
|
"googlemap.geocode": "googlemap.geocode";
|
|
368
375
|
"googlemap.places": "googlemap.places";
|
|
@@ -372,6 +379,8 @@ declare const prepareProductSessionSchema: z.ZodObject<{
|
|
|
372
379
|
"meta.transcribe": "meta.transcribe";
|
|
373
380
|
"minimax.tts": "minimax.tts";
|
|
374
381
|
"elevenlabs.sound": "elevenlabs.sound";
|
|
382
|
+
"elevenlabs.tts": "elevenlabs.tts";
|
|
383
|
+
"elevenlabs.music": "elevenlabs.music";
|
|
375
384
|
"zai.transcribe": "zai.transcribe";
|
|
376
385
|
"openai.transcribe": "openai.transcribe";
|
|
377
386
|
"fish-audio.tts": "fish-audio.tts";
|
package/dist/products.js
CHANGED
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
skillsArraySchema,
|
|
9
9
|
systemMessageEventSchema,
|
|
10
10
|
userMessageEventSchema
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XXDQKVPS.js";
|
|
12
|
+
import "./chunk-MLKGABMK.js";
|
|
12
13
|
|
|
13
14
|
// ../../packages/spec/src/compatibility.ts
|
|
14
15
|
var capabilityInputsSchema = capabilitiesSchema;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
external_exports,
|
|
3
3
|
messagePartSchema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XXDQKVPS.js";
|
|
5
|
+
import "./chunk-MLKGABMK.js";
|
|
5
6
|
|
|
6
7
|
// ../../packages/chat/src/stream.ts
|
|
7
8
|
async function* parseSseStream(body) {
|
|
@@ -96,6 +97,16 @@ var internalEventSchema = external_exports.object({
|
|
|
96
97
|
* session.deleted) — emitted verbatim; the host already persisted it. */
|
|
97
98
|
lifecycle: external_exports.object({ id: external_exports.string().min(1), type: external_exports.string().min(1) }).passthrough().optional()
|
|
98
99
|
});
|
|
100
|
+
function personalContextForPod(body, supported) {
|
|
101
|
+
const { personal_context: grant, ...rest } = body;
|
|
102
|
+
if (!grant || supported) return body;
|
|
103
|
+
const text = `This runtime cannot receive the private personal-context credential file. Use this value instead, as X-Personal-Context on memory and context API requests and as ACTL_PERSONAL_CONTEXT for actl grep: ${grant}. Never print, persist or share it.`;
|
|
104
|
+
const parts = [...rest.parts, { type: "text", index: Math.max(-1, ...rest.parts.map((part) => part.index)) + 1, text }];
|
|
105
|
+
if (!rest.events) return { ...rest, parts };
|
|
106
|
+
const previous = rest.events.find((event) => event.type === "system.message");
|
|
107
|
+
const system = { type: "system.message", content: [{ type: "text", text: [...previous?.type === "system.message" ? previous.content.map((block) => block.text) : [], text].join("\n\n") }] };
|
|
108
|
+
return { ...rest, parts, events: [...rest.events.filter((event) => event.type !== "system.message"), system] };
|
|
109
|
+
}
|
|
99
110
|
function podMessageBody(messageId, turn, resourcesRevision = 0) {
|
|
100
111
|
if ("events" in turn) {
|
|
101
112
|
const envelope = {
|
|
@@ -143,6 +154,7 @@ function classifyPodSendStatus(status, detail) {
|
|
|
143
154
|
if (status >= 500) return { kind: "uncertain", reason: "server_error", status, detail };
|
|
144
155
|
return { kind: "rejected", status, detail };
|
|
145
156
|
}
|
|
157
|
+
var PERSONAL_CONTEXT_PODS = /* @__PURE__ */ new Set();
|
|
146
158
|
async function sendPodMessage(baseUrl, body, options = {}) {
|
|
147
159
|
const headers = {
|
|
148
160
|
"content-type": "application/json",
|
|
@@ -151,6 +163,11 @@ async function sendPodMessage(baseUrl, body, options = {}) {
|
|
|
151
163
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
152
164
|
let res;
|
|
153
165
|
try {
|
|
166
|
+
if (body.personal_context && !PERSONAL_CONTEXT_PODS.has(baseUrl)) {
|
|
167
|
+
const health = await getPodHealth(baseUrl, options);
|
|
168
|
+
if (health.personalContext) PERSONAL_CONTEXT_PODS.add(baseUrl);
|
|
169
|
+
else if (health.ok) body = personalContextForPod(body, false);
|
|
170
|
+
}
|
|
154
171
|
const user = body.events?.find((event) => event.type === "user.message");
|
|
155
172
|
const mode = user?.type === "user.message" ? user.interaction_mode : body.interaction_mode;
|
|
156
173
|
if (mode) {
|
|
@@ -196,7 +213,8 @@ async function getPodHealth(baseUrl, options = {}) {
|
|
|
196
213
|
status: res.status,
|
|
197
214
|
state: typeof body.state === "string" ? body.state : void 0,
|
|
198
215
|
openMessageId: typeof open === "string" && open ? open : null,
|
|
199
|
-
interactionModes: Array.isArray(body.interaction_modes) ? body.interaction_modes.filter((mode) => mode === "plan" || mode === "build") : void 0
|
|
216
|
+
interactionModes: Array.isArray(body.interaction_modes) ? body.interaction_modes.filter((mode) => mode === "plan" || mode === "build") : void 0,
|
|
217
|
+
personalContext: body.personal_context === true
|
|
200
218
|
};
|
|
201
219
|
} catch {
|
|
202
220
|
return { ok: false, openMessageId: null };
|
|
@@ -294,6 +312,7 @@ export {
|
|
|
294
312
|
interruptSession,
|
|
295
313
|
legacyPartsOf,
|
|
296
314
|
parseSseStream,
|
|
315
|
+
personalContextForPod,
|
|
297
316
|
podMessageBody,
|
|
298
317
|
postSessionEvents,
|
|
299
318
|
sendPodMessage,
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import {
|
|
2
|
+
USER_AGENT,
|
|
3
|
+
configPath
|
|
4
|
+
} from "./chunk-RF5JICNX.js";
|
|
5
|
+
import {
|
|
6
|
+
TRACE_CONTENT_SCOPES,
|
|
7
|
+
redactTraceText
|
|
8
|
+
} from "./chunk-XXDQKVPS.js";
|
|
9
|
+
import "./chunk-MLKGABMK.js";
|
|
10
|
+
|
|
11
|
+
// src/trace.ts
|
|
12
|
+
import { randomBytes } from "crypto";
|
|
13
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
|
|
14
|
+
import { basename, dirname, join, relative, resolve, sep } from "path";
|
|
15
|
+
var SPOOL_MAX_BYTES = 32 * 1024 * 1024;
|
|
16
|
+
var RETRY_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
17
|
+
var BATCH_MAX = 256;
|
|
18
|
+
function traceDir() {
|
|
19
|
+
return process.env.SKY_TRACE_DIR ?? join(dirname(configPath()), "trace");
|
|
20
|
+
}
|
|
21
|
+
var statePath = (dir) => join(dir, "current.json");
|
|
22
|
+
var spoolPath = (dir) => join(dir, "spool.jsonl");
|
|
23
|
+
var gapPath = (dir) => join(dir, "gaps.json");
|
|
24
|
+
var lockPath = (dir) => join(dir, "spool.lock");
|
|
25
|
+
var LOCK_WAIT_MS = 3e3;
|
|
26
|
+
var LOCK_STALE_MS = 3e4;
|
|
27
|
+
function withLock(dir, fn) {
|
|
28
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
29
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
30
|
+
for (; ; ) {
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(lockPath(dir));
|
|
33
|
+
break;
|
|
34
|
+
} catch {
|
|
35
|
+
const age = existsSync(lockPath(dir)) ? Date.now() - statSync(lockPath(dir)).mtimeMs : 0;
|
|
36
|
+
if (age > LOCK_STALE_MS) rmSync(lockPath(dir), { recursive: true, force: true });
|
|
37
|
+
else if (Date.now() > deadline) throw new Error("the trace spool is busy");
|
|
38
|
+
else Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return fn();
|
|
43
|
+
} finally {
|
|
44
|
+
rmSync(lockPath(dir), { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readState(dir = traceDir()) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(readFileSync(statePath(dir), "utf8"));
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function writeState(state, dir = traceDir()) {
|
|
55
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
56
|
+
const tmp = `${statePath(dir)}.${process.pid}.tmp`;
|
|
57
|
+
writeFileSync(tmp, JSON.stringify(state), { mode: 384 });
|
|
58
|
+
renameSync(tmp, statePath(dir));
|
|
59
|
+
}
|
|
60
|
+
function clearState(dir = traceDir()) {
|
|
61
|
+
rmSync(statePath(dir), { force: true });
|
|
62
|
+
}
|
|
63
|
+
function stamp(state, events, now = /* @__PURE__ */ new Date()) {
|
|
64
|
+
return events.map((event) => {
|
|
65
|
+
const sequence = state.next_sequence++;
|
|
66
|
+
return { event_id: `${sequence}-${randomBytes(4).toString("hex")}`, sequence, source_time: now.toISOString(), coverage: "metadata_only", ...event };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function readGaps(dir) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(readFileSync(gapPath(dir), "utf8"));
|
|
72
|
+
} catch {
|
|
73
|
+
return { dropped_full: 0, dropped_expired: 0, refused: 0 };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function addGap(dir, key, count) {
|
|
77
|
+
if (!count) return;
|
|
78
|
+
const gaps = readGaps(dir);
|
|
79
|
+
gaps[key] += count;
|
|
80
|
+
writeFileSync(gapPath(dir), JSON.stringify(gaps), { mode: 384 });
|
|
81
|
+
}
|
|
82
|
+
function append(dir, events, now, maxBytes) {
|
|
83
|
+
const size = existsSync(spoolPath(dir)) ? statSync(spoolPath(dir)).size : 0;
|
|
84
|
+
const lines = events.map((event) => `${JSON.stringify({ spooled_at: now, event })}
|
|
85
|
+
`);
|
|
86
|
+
const bytes = lines.reduce((sum, line) => sum + Buffer.byteLength(line), 0);
|
|
87
|
+
if (size + bytes > maxBytes) {
|
|
88
|
+
addGap(dir, "dropped_full", events.length);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
appendFileSync(spoolPath(dir), lines.join(""), { mode: 384 });
|
|
92
|
+
return events.length;
|
|
93
|
+
}
|
|
94
|
+
function record(build, { dir = traceDir(), now = /* @__PURE__ */ new Date(), maxBytes = SPOOL_MAX_BYTES } = {}) {
|
|
95
|
+
return withLock(dir, () => {
|
|
96
|
+
const state = readState(dir);
|
|
97
|
+
if (!state) return 0;
|
|
98
|
+
const events = stamp(state, build(state), now);
|
|
99
|
+
if (!events.length) return 0;
|
|
100
|
+
writeState(state, dir);
|
|
101
|
+
return append(dir, events, now.getTime(), maxBytes);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function readSpool(dir) {
|
|
105
|
+
if (!existsSync(spoolPath(dir))) return [];
|
|
106
|
+
return readFileSync(spoolPath(dir), "utf8").split("\n").filter(Boolean).flatMap((line) => {
|
|
107
|
+
try {
|
|
108
|
+
return [JSON.parse(line)];
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function settle(dir, done, now) {
|
|
115
|
+
withLock(dir, () => {
|
|
116
|
+
const keep = readSpool(dir).filter((entry) => !done.has(entry.event.event_id) && now - entry.spooled_at < RETRY_WINDOW_MS);
|
|
117
|
+
const tmp = `${spoolPath(dir)}.${process.pid}.tmp`;
|
|
118
|
+
writeFileSync(tmp, keep.map((entry) => `${JSON.stringify(entry)}
|
|
119
|
+
`).join(""), { mode: 384 });
|
|
120
|
+
renameSync(tmp, spoolPath(dir));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function grantSender(state, timeoutMs = 1e4) {
|
|
124
|
+
return async (events) => {
|
|
125
|
+
const res = await fetch(`${state.api_base.replace(/\/$/, "")}/traces/${state.trace_id}/events`, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { authorization: `Bearer ${state.token}`, "content-type": "application/json", "user-agent": USER_AGENT },
|
|
128
|
+
body: JSON.stringify({ events }),
|
|
129
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
130
|
+
});
|
|
131
|
+
if (res.ok) return (await res.json()).results;
|
|
132
|
+
if (res.status >= 400 && res.status < 500 && res.status !== 408 && res.status !== 429) {
|
|
133
|
+
const code = await res.json().then((body) => body.error?.code ?? `http_${res.status}`, () => `http_${res.status}`);
|
|
134
|
+
throw new FinalRefusal(code, res.status);
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`trace upload answered ${res.status}`);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
var FinalRefusal = class extends Error {
|
|
140
|
+
constructor(code, status) {
|
|
141
|
+
super(`trace upload refused: ${code}`);
|
|
142
|
+
this.code = code;
|
|
143
|
+
this.status = status;
|
|
144
|
+
}
|
|
145
|
+
code;
|
|
146
|
+
status;
|
|
147
|
+
};
|
|
148
|
+
async function flush(send, { dir = traceDir(), now = Date.now(), attempts = 1, backoffMs = 500, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
|
|
149
|
+
const all = readSpool(dir);
|
|
150
|
+
const fresh = all.filter((entry) => now - entry.spooled_at < RETRY_WINDOW_MS);
|
|
151
|
+
const expired = all.length - fresh.length;
|
|
152
|
+
addGap(dir, "dropped_expired", expired);
|
|
153
|
+
let pending = fresh;
|
|
154
|
+
let sent = 0;
|
|
155
|
+
let refused = 0;
|
|
156
|
+
let stopped;
|
|
157
|
+
const done = /* @__PURE__ */ new Set();
|
|
158
|
+
for (let attempt = 0; attempt < attempts && pending.length && !stopped; attempt++) {
|
|
159
|
+
if (attempt) await sleep(backoffMs * 2 ** (attempt - 1));
|
|
160
|
+
while (pending.length) {
|
|
161
|
+
const batch = pending.slice(0, BATCH_MAX);
|
|
162
|
+
let results;
|
|
163
|
+
try {
|
|
164
|
+
results = await send(batch.map((entry) => entry.event));
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error instanceof FinalRefusal) stopped = error.code;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
for (const result of results) done.add(result.event_id);
|
|
170
|
+
refused += results.filter((result) => result.status === "refused").length;
|
|
171
|
+
sent += results.filter((result) => result.status !== "refused").length;
|
|
172
|
+
const settledNow = new Set(results.map((result) => result.event_id));
|
|
173
|
+
pending = [...batch.filter((entry) => !settledNow.has(entry.event.event_id)), ...pending.slice(BATCH_MAX)];
|
|
174
|
+
settle(dir, done, now);
|
|
175
|
+
if (settledNow.size === 0) break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (expired && !done.size) settle(dir, done, now);
|
|
179
|
+
addGap(dir, "refused", refused);
|
|
180
|
+
return { sent, refused, expired, pending: pending.length, ...stopped ? { stopped } : {} };
|
|
181
|
+
}
|
|
182
|
+
function spoolStatus(dir = traceDir()) {
|
|
183
|
+
return { pending: readSpool(dir).length, ...readGaps(dir) };
|
|
184
|
+
}
|
|
185
|
+
var SESSION_REF = /\bsess_[A-Za-z0-9]{8,48}\b/g;
|
|
186
|
+
var startsCloudWork = (input) => Boolean(input.tool_name?.startsWith("mcp__agentsky")) || input.tool_name === "Bash" && /(^|[\s;&|(])(sky|npx\s+(-y\s+)?@agentsky\/sky)\s/.test(String(input.tool_input?.command ?? ""));
|
|
187
|
+
function eventsForClaudeHook(input, state) {
|
|
188
|
+
if (!input.session_id) return [];
|
|
189
|
+
const span = `claude-code:${input.session_id}`;
|
|
190
|
+
const tool = input.tool_name ?? null;
|
|
191
|
+
const useId = input.tool_use_id ?? null;
|
|
192
|
+
switch (input.hook_event_name) {
|
|
193
|
+
case "SessionStart":
|
|
194
|
+
return [{ span_id: span, parent_span_id: state.root_span ?? null, kind: "start", name: "claude code session", attributes: { start: input.source ?? null } }];
|
|
195
|
+
case "UserPromptSubmit":
|
|
196
|
+
return [{ span_id: span, kind: "event", name: "turn.start" }];
|
|
197
|
+
case "Stop":
|
|
198
|
+
return [{ span_id: span, kind: "event", name: "turn.end" }];
|
|
199
|
+
case "SessionEnd":
|
|
200
|
+
return [{ span_id: span, kind: "end", name: "claude code session", outcome: "completed", attributes: { reason: input.reason ?? null } }];
|
|
201
|
+
case "PreToolUse":
|
|
202
|
+
if (tool === "Task" && useId) return [{ span_id: `claude-code:${useId}`, parent_span_id: span, kind: "start", name: "subagent" }];
|
|
203
|
+
return [{ span_id: span, kind: "event", name: "tool.start", attributes: { tool, tool_use_id: useId } }];
|
|
204
|
+
case "PostToolUse": {
|
|
205
|
+
if (tool === "Task" && useId) return [{ span_id: `claude-code:${useId}`, kind: "end", name: "subagent", outcome: "completed" }];
|
|
206
|
+
const events = [{ span_id: span, kind: "event", name: "tool.end", attributes: { tool, tool_use_id: useId } }];
|
|
207
|
+
if (startsCloudWork(input)) {
|
|
208
|
+
const named = new Set(JSON.stringify(input.tool_response ?? "").match(SESSION_REF) ?? []);
|
|
209
|
+
for (const session of named) {
|
|
210
|
+
events.push({ span_id: `cloud:${session}`, parent_span_id: span, kind: "start", name: "cloud session", session_id: session, attributes: { via: tool } });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return events;
|
|
214
|
+
}
|
|
215
|
+
default:
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function claudeHooksConfig(command = "sky trace hook") {
|
|
220
|
+
const entry = [{ hooks: [{ type: "command", command, timeout: 10 }] }];
|
|
221
|
+
const matched = [{ matcher: "*", hooks: [{ type: "command", command, timeout: 10 }] }];
|
|
222
|
+
return {
|
|
223
|
+
SessionStart: entry,
|
|
224
|
+
UserPromptSubmit: entry,
|
|
225
|
+
PreToolUse: matched,
|
|
226
|
+
PostToolUse: matched,
|
|
227
|
+
Stop: entry,
|
|
228
|
+
SessionEnd: entry
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function installClaudeHooks(settingsPath, command = "sky trace hook") {
|
|
232
|
+
const settings = existsSync(settingsPath) ? JSON.parse(readFileSync(settingsPath, "utf8")) : {};
|
|
233
|
+
const hooks = settings.hooks ?? {};
|
|
234
|
+
for (const [event, entries] of Object.entries(claudeHooksConfig(command))) {
|
|
235
|
+
const existing = (hooks[event] ?? []).filter((group) => !group.hooks?.some((hook) => hook.command === command));
|
|
236
|
+
hooks[event] = [...existing, ...entries];
|
|
237
|
+
}
|
|
238
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
239
|
+
writeFileSync(settingsPath, `${JSON.stringify({ ...settings, hooks }, null, 2)}
|
|
240
|
+
`);
|
|
241
|
+
return settingsPath;
|
|
242
|
+
}
|
|
243
|
+
var FILE_READ_MAX_BYTES = 1024 * 1024;
|
|
244
|
+
var FILE_TOOLS = /* @__PURE__ */ new Set(["Read", "Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
245
|
+
var CONTENT_ESCAPED_MAX_BYTES = 56 * 1024;
|
|
246
|
+
var NEVER_READ = [
|
|
247
|
+
/(^|\/)\.env(\.|$)/,
|
|
248
|
+
/(^|\/)\.(ssh|aws|gnupg|docker|kube|gcloud|azure|password-store|git)\//,
|
|
249
|
+
/(^|\/)\.(netrc|npmrc|pypirc|pgpass|git-credentials)$/,
|
|
250
|
+
/\.(pem|key|p12|pfx|keystore|jks|kdbx)$/i,
|
|
251
|
+
/(^|\/)id_(rsa|dsa|ecdsa|ed25519)/,
|
|
252
|
+
/(^|\/)(credentials|secrets?)(\.[a-z]+)?$/i,
|
|
253
|
+
/cookies/i,
|
|
254
|
+
/(^|\/)\.(claude|codex|gemini|cursor)\//,
|
|
255
|
+
/Library\/Keychains\//
|
|
256
|
+
];
|
|
257
|
+
function parseContentScopes(value) {
|
|
258
|
+
if (!value) return [];
|
|
259
|
+
const scopes = [...new Set(value.split(",").map((part) => part.trim()).filter(Boolean))];
|
|
260
|
+
for (const scope of scopes) {
|
|
261
|
+
if (!TRACE_CONTENT_SCOPES.includes(scope)) throw new Error(`unknown capture scope ${scope} (use ${TRACE_CONTENT_SCOPES.join(", ")})`);
|
|
262
|
+
}
|
|
263
|
+
return scopes;
|
|
264
|
+
}
|
|
265
|
+
function realPath(path) {
|
|
266
|
+
try {
|
|
267
|
+
return realpathSync(path);
|
|
268
|
+
} catch {
|
|
269
|
+
return resolve(path);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function fileAllowed(path, state) {
|
|
273
|
+
const real = realPath(path);
|
|
274
|
+
if (NEVER_READ.some((pattern) => pattern.test(real.split(sep).join("/")))) return false;
|
|
275
|
+
if (state.files?.includes(real)) return true;
|
|
276
|
+
return (state.dirs ?? []).some((dir) => {
|
|
277
|
+
const rel = relative(dir, real);
|
|
278
|
+
return rel !== "" && !rel.startsWith("..") && !rel.startsWith(sep);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function contentEvent(span, name, scope, text, extra = {}) {
|
|
282
|
+
try {
|
|
283
|
+
let safe = redactTraceText(text);
|
|
284
|
+
const escaped = Buffer.byteLength(JSON.stringify(safe.text));
|
|
285
|
+
if (escaped > CONTENT_ESCAPED_MAX_BYTES) {
|
|
286
|
+
const fitted = redactTraceText(text, Math.floor(Buffer.byteLength(safe.text) * (CONTENT_ESCAPED_MAX_BYTES / escaped)));
|
|
287
|
+
safe = { ...fitted, truncated: true };
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
span_id: span,
|
|
291
|
+
kind: "event",
|
|
292
|
+
name,
|
|
293
|
+
coverage: safe.redactions ? "redacted" : safe.truncated ? "partial" : "complete",
|
|
294
|
+
content: { scope, text: safe.text, ...extra, ...safe.truncated ? { truncated: true } : {}, ...safe.redactions ? { redactions: safe.redactions } : {} }
|
|
295
|
+
};
|
|
296
|
+
} catch {
|
|
297
|
+
return { span_id: span, kind: "event", name, coverage: "metadata_only", attributes: { content_omitted: "redaction_failed" } };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function fileEvent(span, path, state) {
|
|
301
|
+
if (!state.content_scopes?.includes("files") || !fileAllowed(path, state)) return null;
|
|
302
|
+
try {
|
|
303
|
+
const real = realPath(path);
|
|
304
|
+
if (!statSync(real).isFile()) return null;
|
|
305
|
+
const bytes = readFileSync(real).subarray(0, FILE_READ_MAX_BYTES);
|
|
306
|
+
if (bytes.includes(0)) return null;
|
|
307
|
+
return contentEvent(span, "file", "files", bytes.toString("utf8"), { path: real });
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function contentForClaudeHook(input, state) {
|
|
313
|
+
const scopes = state.content_scopes ?? [];
|
|
314
|
+
if (!input.session_id || !scopes.length) return [];
|
|
315
|
+
const span = `claude-code:${input.session_id}`;
|
|
316
|
+
if (input.hook_event_name === "UserPromptSubmit") {
|
|
317
|
+
return scopes.includes("prompts") && input.prompt ? [contentEvent(span, "prompt", "prompts", input.prompt)] : [];
|
|
318
|
+
}
|
|
319
|
+
if (input.hook_event_name !== "PostToolUse") return [];
|
|
320
|
+
const events = [];
|
|
321
|
+
if (scopes.includes("tool_output") && input.tool_response !== void 0 && input.tool_name !== "Task") {
|
|
322
|
+
const text = typeof input.tool_response === "string" ? input.tool_response : JSON.stringify(input.tool_response);
|
|
323
|
+
const event = contentEvent(span, "tool.output", "tool_output", text);
|
|
324
|
+
events.push({ ...event, attributes: { ...event.attributes, tool: input.tool_name ?? null, tool_use_id: input.tool_use_id ?? null } });
|
|
325
|
+
}
|
|
326
|
+
const touched = input.tool_input;
|
|
327
|
+
const path = typeof touched?.file_path === "string" ? touched.file_path : typeof touched?.notebook_path === "string" ? touched.notebook_path : null;
|
|
328
|
+
if (path && FILE_TOOLS.has(input.tool_name ?? "")) {
|
|
329
|
+
const event = fileEvent(span, path, state);
|
|
330
|
+
if (event) events.push(event);
|
|
331
|
+
}
|
|
332
|
+
return events;
|
|
333
|
+
}
|
|
334
|
+
function chosenPaths(files, dirs) {
|
|
335
|
+
const check = (path, kind) => {
|
|
336
|
+
const real = realPath(path);
|
|
337
|
+
if (!existsSync(real)) throw new Error(`${path} does not exist`);
|
|
338
|
+
if (kind === "file" ? !statSync(real).isFile() : !statSync(real).isDirectory()) throw new Error(`${path} is not a ${kind}`);
|
|
339
|
+
if (kind === "file" && !fileAllowed(real, { files: [real] })) throw new Error(`${basename(path)} looks like a credential or agent-state file and is never captured`);
|
|
340
|
+
return real;
|
|
341
|
+
};
|
|
342
|
+
return { files: files.map((path) => check(path, "file")), dirs: dirs.map((path) => check(path, "folder")) };
|
|
343
|
+
}
|
|
344
|
+
export {
|
|
345
|
+
BATCH_MAX,
|
|
346
|
+
FinalRefusal,
|
|
347
|
+
RETRY_WINDOW_MS,
|
|
348
|
+
SPOOL_MAX_BYTES,
|
|
349
|
+
chosenPaths,
|
|
350
|
+
claudeHooksConfig,
|
|
351
|
+
clearState,
|
|
352
|
+
contentEvent,
|
|
353
|
+
contentForClaudeHook,
|
|
354
|
+
eventsForClaudeHook,
|
|
355
|
+
fileAllowed,
|
|
356
|
+
fileEvent,
|
|
357
|
+
flush,
|
|
358
|
+
grantSender,
|
|
359
|
+
installClaudeHooks,
|
|
360
|
+
parseContentScopes,
|
|
361
|
+
readState,
|
|
362
|
+
record,
|
|
363
|
+
spoolStatus,
|
|
364
|
+
stamp,
|
|
365
|
+
traceDir,
|
|
366
|
+
writeState
|
|
367
|
+
};
|