@respan/cli 0.6.9 → 0.7.1
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/hooks/claude-code.cjs +1 -1
- package/dist/hooks/codex-cli.cjs +71 -49
- package/dist/hooks/codex-cli.js +76 -57
- package/dist/hooks/gemini-cli.cjs +68 -79
- package/dist/hooks/gemini-cli.js +83 -91
- package/dist/hooks/shared.js +1 -1
- package/oclif.manifest.json +937 -937
- package/package.json +1 -1
package/dist/hooks/codex-cli.cjs
CHANGED
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
27
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
28
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
29
30
|
|
|
30
31
|
// src/hooks/shared.ts
|
|
31
32
|
var fs = __toESM(require("node:fs"), 1);
|
|
@@ -339,7 +340,7 @@ function toOtlpPayload(spans) {
|
|
|
339
340
|
})
|
|
340
341
|
},
|
|
341
342
|
scopeSpans: [{
|
|
342
|
-
scope: { name: "respan-cli-hooks", version: "0.
|
|
343
|
+
scope: { name: "respan-cli-hooks", version: "0.7.0" },
|
|
343
344
|
spans: otlpSpans
|
|
344
345
|
}]
|
|
345
346
|
}]
|
|
@@ -699,50 +700,18 @@ function findLatestSessionFile() {
|
|
|
699
700
|
return null;
|
|
700
701
|
}
|
|
701
702
|
}
|
|
702
|
-
async function
|
|
703
|
+
async function mainWorker() {
|
|
703
704
|
const scriptStart = Date.now();
|
|
704
|
-
debug("
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
}
|
|
709
|
-
let payload;
|
|
710
|
-
try {
|
|
711
|
-
payload = JSON.parse(process.argv[2]);
|
|
712
|
-
} catch (e) {
|
|
713
|
-
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
714
|
-
process.exit(0);
|
|
715
|
-
}
|
|
716
|
-
const eventType = String(payload.type ?? "");
|
|
717
|
-
if (eventType !== "agent-turn-complete") {
|
|
718
|
-
debug(`Ignoring event type: ${eventType}`);
|
|
719
|
-
process.exit(0);
|
|
720
|
-
}
|
|
721
|
-
let sessionId = String(payload["thread-id"] ?? "");
|
|
722
|
-
if (!sessionId) {
|
|
723
|
-
debug("No thread-id in notify payload");
|
|
724
|
-
process.exit(0);
|
|
725
|
-
}
|
|
726
|
-
debug(`Processing notify: type=${eventType}, session=${sessionId}`);
|
|
705
|
+
debug("Worker started");
|
|
706
|
+
const sessionId = process.env._RESPAN_CODEX_SESSION;
|
|
707
|
+
const sessionFile = process.env._RESPAN_CODEX_FILE;
|
|
708
|
+
const cwd = process.env._RESPAN_CODEX_CWD ?? "";
|
|
727
709
|
const creds = resolveCredentials();
|
|
728
710
|
if (!creds) {
|
|
729
|
-
log("ERROR", "No API key
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
let sessionFile = findSessionFile(sessionId);
|
|
733
|
-
if (!sessionFile) {
|
|
734
|
-
const latest = findLatestSessionFile();
|
|
735
|
-
if (latest) {
|
|
736
|
-
sessionId = latest.sessionId;
|
|
737
|
-
sessionFile = latest.sessionFile;
|
|
738
|
-
} else {
|
|
739
|
-
debug("No session file found");
|
|
740
|
-
process.exit(0);
|
|
741
|
-
}
|
|
711
|
+
log("ERROR", "No API key");
|
|
712
|
+
return;
|
|
742
713
|
}
|
|
743
|
-
const cwd = String(payload.cwd ?? "");
|
|
744
714
|
const config = cwd ? loadRespanConfig(path2.join(cwd, ".codex", "respan.json")) : null;
|
|
745
|
-
if (config) debug(`Loaded respan.json config from ${cwd}`);
|
|
746
715
|
const maxAttempts = 3;
|
|
747
716
|
let turns = 0;
|
|
748
717
|
try {
|
|
@@ -774,20 +743,73 @@ async function main() {
|
|
|
774
743
|
}
|
|
775
744
|
if (turns > 0) break;
|
|
776
745
|
if (attempt < maxAttempts - 1) {
|
|
777
|
-
|
|
778
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
779
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
746
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
780
747
|
}
|
|
781
748
|
}
|
|
782
749
|
const duration = (Date.now() - scriptStart) / 1e3;
|
|
783
750
|
log("INFO", `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
784
|
-
if (duration > 180) log("WARN", `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
785
751
|
} catch (e) {
|
|
786
752
|
log("ERROR", `Failed to process session: ${e}`);
|
|
787
|
-
if (DEBUG_MODE) debug(String(e.stack ?? e));
|
|
788
753
|
}
|
|
789
754
|
}
|
|
790
|
-
main()
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
755
|
+
function main() {
|
|
756
|
+
if (process.env._RESPAN_CODEX_WORKER === "1") {
|
|
757
|
+
mainWorker().catch((e) => log("ERROR", `Worker crashed: ${e}`));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
debug("Codex hook started");
|
|
761
|
+
if (process.argv.length < 3) {
|
|
762
|
+
debug("No argument provided");
|
|
763
|
+
process.exit(0);
|
|
764
|
+
}
|
|
765
|
+
let payload;
|
|
766
|
+
try {
|
|
767
|
+
payload = JSON.parse(process.argv[2]);
|
|
768
|
+
} catch (e) {
|
|
769
|
+
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
770
|
+
process.exit(0);
|
|
771
|
+
}
|
|
772
|
+
const eventType = String(payload.type ?? "");
|
|
773
|
+
if (eventType !== "agent-turn-complete") {
|
|
774
|
+
debug(`Ignoring event type: ${eventType}`);
|
|
775
|
+
process.exit(0);
|
|
776
|
+
}
|
|
777
|
+
let sessionId = String(payload["thread-id"] ?? "");
|
|
778
|
+
if (!sessionId) {
|
|
779
|
+
debug("No thread-id in notify payload");
|
|
780
|
+
process.exit(0);
|
|
781
|
+
}
|
|
782
|
+
let sessionFile = findSessionFile(sessionId);
|
|
783
|
+
if (!sessionFile) {
|
|
784
|
+
const latest = findLatestSessionFile();
|
|
785
|
+
if (latest) {
|
|
786
|
+
sessionId = latest.sessionId;
|
|
787
|
+
sessionFile = latest.sessionFile;
|
|
788
|
+
} else {
|
|
789
|
+
debug("No session file found");
|
|
790
|
+
process.exit(0);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const cwd = String(payload.cwd ?? "");
|
|
794
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
795
|
+
try {
|
|
796
|
+
const scriptPath = __filename || process.argv[1];
|
|
797
|
+
const child = (0, import_node_child_process.execFile)("node", [scriptPath], {
|
|
798
|
+
env: {
|
|
799
|
+
...process.env,
|
|
800
|
+
_RESPAN_CODEX_WORKER: "1",
|
|
801
|
+
_RESPAN_CODEX_SESSION: sessionId,
|
|
802
|
+
_RESPAN_CODEX_FILE: sessionFile,
|
|
803
|
+
_RESPAN_CODEX_CWD: cwd
|
|
804
|
+
},
|
|
805
|
+
stdio: "ignore",
|
|
806
|
+
detached: true
|
|
807
|
+
});
|
|
808
|
+
child.unref();
|
|
809
|
+
debug("Worker launched");
|
|
810
|
+
} catch (e) {
|
|
811
|
+
log("ERROR", `Failed to fork worker: ${e}`);
|
|
812
|
+
}
|
|
813
|
+
process.exit(0);
|
|
814
|
+
}
|
|
815
|
+
main();
|
package/dist/hooks/codex-cli.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import * as fs from 'node:fs';
|
|
17
17
|
import * as os from 'node:os';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
19
20
|
import { initLogging, log, debug, resolveCredentials, loadRespanConfig, loadState, saveState, acquireLock, sendSpans, addDefaultsToAll, resolveSpanFields, buildMetadata, nowISO, latencySeconds, truncate, } from './shared.js';
|
|
20
21
|
// ── Config ────────────────────────────────────────────────────────
|
|
21
22
|
const STATE_DIR = path.join(os.homedir(), '.codex', 'state');
|
|
@@ -363,57 +364,18 @@ function findLatestSessionFile() {
|
|
|
363
364
|
}
|
|
364
365
|
}
|
|
365
366
|
// ── Main ──────────────────────────────────────────────────────────
|
|
366
|
-
async function
|
|
367
|
+
async function mainWorker() {
|
|
367
368
|
const scriptStart = Date.now();
|
|
368
|
-
debug('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
process.exit(0);
|
|
373
|
-
}
|
|
374
|
-
let payload;
|
|
375
|
-
try {
|
|
376
|
-
payload = JSON.parse(process.argv[2]);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
380
|
-
process.exit(0);
|
|
381
|
-
}
|
|
382
|
-
const eventType = String(payload.type ?? '');
|
|
383
|
-
if (eventType !== 'agent-turn-complete') {
|
|
384
|
-
debug(`Ignoring event type: ${eventType}`);
|
|
385
|
-
process.exit(0);
|
|
386
|
-
}
|
|
387
|
-
let sessionId = String(payload['thread-id'] ?? '');
|
|
388
|
-
if (!sessionId) {
|
|
389
|
-
debug('No thread-id in notify payload');
|
|
390
|
-
process.exit(0);
|
|
391
|
-
}
|
|
392
|
-
debug(`Processing notify: type=${eventType}, session=${sessionId}`);
|
|
369
|
+
debug('Worker started');
|
|
370
|
+
const sessionId = process.env._RESPAN_CODEX_SESSION;
|
|
371
|
+
const sessionFile = process.env._RESPAN_CODEX_FILE;
|
|
372
|
+
const cwd = process.env._RESPAN_CODEX_CWD ?? '';
|
|
393
373
|
const creds = resolveCredentials();
|
|
394
374
|
if (!creds) {
|
|
395
|
-
log('ERROR', 'No API key
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
// Find session file
|
|
399
|
-
let sessionFile = findSessionFile(sessionId);
|
|
400
|
-
if (!sessionFile) {
|
|
401
|
-
const latest = findLatestSessionFile();
|
|
402
|
-
if (latest) {
|
|
403
|
-
sessionId = latest.sessionId;
|
|
404
|
-
sessionFile = latest.sessionFile;
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
debug('No session file found');
|
|
408
|
-
process.exit(0);
|
|
409
|
-
}
|
|
375
|
+
log('ERROR', 'No API key');
|
|
376
|
+
return;
|
|
410
377
|
}
|
|
411
|
-
// Load config
|
|
412
|
-
const cwd = String(payload.cwd ?? '');
|
|
413
378
|
const config = cwd ? loadRespanConfig(path.join(cwd, '.codex', 'respan.json')) : null;
|
|
414
|
-
if (config)
|
|
415
|
-
debug(`Loaded respan.json config from ${cwd}`);
|
|
416
|
-
// Process with retry
|
|
417
379
|
const maxAttempts = 3;
|
|
418
380
|
let turns = 0;
|
|
419
381
|
try {
|
|
@@ -447,23 +409,80 @@ async function main() {
|
|
|
447
409
|
if (turns > 0)
|
|
448
410
|
break;
|
|
449
411
|
if (attempt < maxAttempts - 1) {
|
|
450
|
-
|
|
451
|
-
debug(`No turns processed (attempt ${attempt + 1}/${maxAttempts}), retrying in ${delay}ms...`);
|
|
452
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
412
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
453
413
|
}
|
|
454
414
|
}
|
|
455
415
|
const duration = (Date.now() - scriptStart) / 1000;
|
|
456
416
|
log('INFO', `Processed ${turns} turns in ${duration.toFixed(1)}s`);
|
|
457
|
-
if (duration > 180)
|
|
458
|
-
log('WARN', `Hook took ${duration.toFixed(1)}s (>3min)`);
|
|
459
417
|
}
|
|
460
418
|
catch (e) {
|
|
461
419
|
log('ERROR', `Failed to process session: ${e}`);
|
|
462
|
-
if (DEBUG_MODE)
|
|
463
|
-
debug(String(e.stack ?? e));
|
|
464
420
|
}
|
|
465
421
|
}
|
|
466
|
-
main()
|
|
467
|
-
|
|
468
|
-
process.
|
|
469
|
-
});
|
|
422
|
+
function main() {
|
|
423
|
+
// Worker mode: re-invoked as detached subprocess
|
|
424
|
+
if (process.env._RESPAN_CODEX_WORKER === '1') {
|
|
425
|
+
mainWorker().catch((e) => log('ERROR', `Worker crashed: ${e}`));
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
debug('Codex hook started');
|
|
429
|
+
if (process.argv.length < 3) {
|
|
430
|
+
debug('No argument provided');
|
|
431
|
+
process.exit(0);
|
|
432
|
+
}
|
|
433
|
+
let payload;
|
|
434
|
+
try {
|
|
435
|
+
payload = JSON.parse(process.argv[2]);
|
|
436
|
+
}
|
|
437
|
+
catch (e) {
|
|
438
|
+
debug(`Invalid JSON in argv[2]: ${e}`);
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
const eventType = String(payload.type ?? '');
|
|
442
|
+
if (eventType !== 'agent-turn-complete') {
|
|
443
|
+
debug(`Ignoring event type: ${eventType}`);
|
|
444
|
+
process.exit(0);
|
|
445
|
+
}
|
|
446
|
+
let sessionId = String(payload['thread-id'] ?? '');
|
|
447
|
+
if (!sessionId) {
|
|
448
|
+
debug('No thread-id in notify payload');
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
// Find session file
|
|
452
|
+
let sessionFile = findSessionFile(sessionId);
|
|
453
|
+
if (!sessionFile) {
|
|
454
|
+
const latest = findLatestSessionFile();
|
|
455
|
+
if (latest) {
|
|
456
|
+
sessionId = latest.sessionId;
|
|
457
|
+
sessionFile = latest.sessionFile;
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
debug('No session file found');
|
|
461
|
+
process.exit(0);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Fork self as detached worker so Codex CLI doesn't block
|
|
465
|
+
const cwd = String(payload.cwd ?? '');
|
|
466
|
+
debug(`Forking worker for session: ${sessionId}`);
|
|
467
|
+
try {
|
|
468
|
+
const scriptPath = __filename || process.argv[1];
|
|
469
|
+
const child = execFile('node', [scriptPath], {
|
|
470
|
+
env: {
|
|
471
|
+
...process.env,
|
|
472
|
+
_RESPAN_CODEX_WORKER: '1',
|
|
473
|
+
_RESPAN_CODEX_SESSION: sessionId,
|
|
474
|
+
_RESPAN_CODEX_FILE: sessionFile,
|
|
475
|
+
_RESPAN_CODEX_CWD: cwd,
|
|
476
|
+
},
|
|
477
|
+
stdio: 'ignore',
|
|
478
|
+
detached: true,
|
|
479
|
+
});
|
|
480
|
+
child.unref();
|
|
481
|
+
debug('Worker launched');
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
log('ERROR', `Failed to fork worker: ${e}`);
|
|
485
|
+
}
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
main();
|
|
@@ -318,7 +318,7 @@ function toOtlpPayload(spans) {
|
|
|
318
318
|
})
|
|
319
319
|
},
|
|
320
320
|
scopeSpans: [{
|
|
321
|
-
scope: { name: "respan-cli-hooks", version: "0.
|
|
321
|
+
scope: { name: "respan-cli-hooks", version: "0.7.0" },
|
|
322
322
|
spans: otlpSpans
|
|
323
323
|
}]
|
|
324
324
|
}]
|
|
@@ -438,6 +438,34 @@ function detectModel(hookData) {
|
|
|
438
438
|
const llmReq = hookData.llm_request ?? {};
|
|
439
439
|
return String(llmReq.model ?? "") || "gemini-cli";
|
|
440
440
|
}
|
|
441
|
+
function buildToolSpan(detail, idx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime) {
|
|
442
|
+
const toolName = detail?.name ?? "";
|
|
443
|
+
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
444
|
+
const toolOutput = detail?.output ?? "";
|
|
445
|
+
const displayName = toolName ? toolDisplayName(toolName) : `Call ${idx + 1}`;
|
|
446
|
+
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
447
|
+
const toolMeta = {};
|
|
448
|
+
if (toolName) toolMeta.tool_name = toolName;
|
|
449
|
+
if (detail?.error) toolMeta.error = detail.error;
|
|
450
|
+
const toolStart = detail?.start_time ?? beginTime;
|
|
451
|
+
const toolEnd = detail?.end_time ?? endTime;
|
|
452
|
+
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
453
|
+
return {
|
|
454
|
+
trace_unique_id: traceUniqueId,
|
|
455
|
+
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${idx + 1}`,
|
|
456
|
+
span_parent_id: rootSpanId,
|
|
457
|
+
span_name: `Tool: ${displayName}`,
|
|
458
|
+
span_workflow_name: workflowName,
|
|
459
|
+
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
460
|
+
provider_id: "",
|
|
461
|
+
metadata: toolMeta,
|
|
462
|
+
input: toolInputStr,
|
|
463
|
+
output: truncate(toolOutput, MAX_CHARS),
|
|
464
|
+
timestamp: toolEnd,
|
|
465
|
+
start_time: toolStart,
|
|
466
|
+
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
441
469
|
function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurns, toolDetails, thoughtsTokens, textRounds, roundStartTimes) {
|
|
442
470
|
const spans = [];
|
|
443
471
|
const sessionId = String(hookData.session_id ?? "");
|
|
@@ -519,33 +547,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
|
|
|
519
547
|
}
|
|
520
548
|
if (r < rounds.length - 1) {
|
|
521
549
|
while (toolIdx < toolDetails.length) {
|
|
522
|
-
|
|
523
|
-
const toolName = detail?.name ?? "";
|
|
524
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
525
|
-
const toolOutput = detail?.output ?? "";
|
|
526
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
527
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
528
|
-
const toolMeta = {};
|
|
529
|
-
if (toolName) toolMeta.tool_name = toolName;
|
|
530
|
-
if (detail?.error) toolMeta.error = detail.error;
|
|
531
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
532
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
533
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
534
|
-
spans.push({
|
|
535
|
-
trace_unique_id: traceUniqueId,
|
|
536
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
537
|
-
span_parent_id: rootSpanId,
|
|
538
|
-
span_name: `Tool: ${displayName}`,
|
|
539
|
-
span_workflow_name: workflowName,
|
|
540
|
-
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
541
|
-
provider_id: "",
|
|
542
|
-
metadata: toolMeta,
|
|
543
|
-
input: toolInputStr,
|
|
544
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
545
|
-
timestamp: toolEnd,
|
|
546
|
-
start_time: toolStart,
|
|
547
|
-
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
548
|
-
});
|
|
550
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
549
551
|
toolIdx++;
|
|
550
552
|
const nextDetail = toolDetails[toolIdx];
|
|
551
553
|
if (nextDetail && roundStarts[r + 1] && nextDetail.start_time && nextDetail.start_time > roundStarts[r + 1]) break;
|
|
@@ -553,33 +555,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
|
|
|
553
555
|
}
|
|
554
556
|
}
|
|
555
557
|
while (toolIdx < toolDetails.length) {
|
|
556
|
-
|
|
557
|
-
const toolName = detail?.name ?? "";
|
|
558
|
-
const toolArgs = detail?.args ?? detail?.input ?? {};
|
|
559
|
-
const toolOutput = detail?.output ?? "";
|
|
560
|
-
const displayName = toolName ? toolDisplayName(toolName) : `Call ${toolIdx + 1}`;
|
|
561
|
-
const toolInputStr = toolName ? formatToolInput(toolName, toolArgs) : "";
|
|
562
|
-
const toolMeta = {};
|
|
563
|
-
if (toolName) toolMeta.tool_name = toolName;
|
|
564
|
-
if (detail?.error) toolMeta.error = detail.error;
|
|
565
|
-
const toolStart = detail?.start_time ?? beginTime;
|
|
566
|
-
const toolEnd = detail?.end_time ?? endTime;
|
|
567
|
-
const toolLat = latencySeconds(toolStart, toolEnd);
|
|
568
|
-
spans.push({
|
|
569
|
-
trace_unique_id: traceUniqueId,
|
|
570
|
-
span_unique_id: `gcli_${safeId}_${turnTs}_tool_${toolIdx + 1}`,
|
|
571
|
-
span_parent_id: rootSpanId,
|
|
572
|
-
span_name: `Tool: ${displayName}`,
|
|
573
|
-
span_workflow_name: workflowName,
|
|
574
|
-
span_path: toolName ? `tool_${toolName}` : "tool_call",
|
|
575
|
-
provider_id: "",
|
|
576
|
-
metadata: toolMeta,
|
|
577
|
-
input: toolInputStr,
|
|
578
|
-
output: truncate(toolOutput, MAX_CHARS),
|
|
579
|
-
timestamp: toolEnd,
|
|
580
|
-
start_time: toolStart,
|
|
581
|
-
...toolLat !== void 0 ? { latency: toolLat } : {}
|
|
582
|
-
});
|
|
558
|
+
spans.push(buildToolSpan(toolDetails[toolIdx], toolIdx, traceUniqueId, rootSpanId, safeId, turnTs, workflowName, beginTime, endTime));
|
|
583
559
|
toolIdx++;
|
|
584
560
|
}
|
|
585
561
|
if (thoughtsTokens > 0) {
|
|
@@ -716,7 +692,6 @@ function processBeforeTool(hookData) {
|
|
|
716
692
|
pending.push({ name: toolName, input: toolInput, start_time: nowISO() });
|
|
717
693
|
state.pending_tools = pending;
|
|
718
694
|
state.send_version = (state.send_version ?? 0) + 1;
|
|
719
|
-
state.tool_turns = (state.tool_turns ?? 0) + 1;
|
|
720
695
|
saveStreamState(sessionId, state);
|
|
721
696
|
}
|
|
722
697
|
function processAfterTool(hookData) {
|
|
@@ -873,35 +848,30 @@ function processChunk(hookData) {
|
|
|
873
848
|
debug(`Delayed send (version=${state.send_version}, delay=${SEND_DELAY}s), ${state.accumulated_text.length} chars`);
|
|
874
849
|
launchDelayedSend(sessionId, state.send_version, spans, creds.apiKey, creds.baseUrl);
|
|
875
850
|
}
|
|
876
|
-
function
|
|
851
|
+
function processChunkInWorker(dataFile) {
|
|
877
852
|
try {
|
|
853
|
+
const raw = fs2.readFileSync(dataFile, "utf-8");
|
|
854
|
+
fs2.unlinkSync(dataFile);
|
|
878
855
|
if (!raw.trim()) return;
|
|
879
856
|
const hookData = JSON.parse(raw);
|
|
880
|
-
const event = String(hookData.hook_event_name ?? "");
|
|
881
857
|
const unlock = acquireLock(LOCK_PATH);
|
|
882
858
|
try {
|
|
883
|
-
|
|
884
|
-
processBeforeTool(hookData);
|
|
885
|
-
} else if (event === "AfterTool") {
|
|
886
|
-
processAfterTool(hookData);
|
|
887
|
-
} else {
|
|
888
|
-
processChunk(hookData);
|
|
889
|
-
}
|
|
859
|
+
processChunk(hookData);
|
|
890
860
|
} finally {
|
|
891
861
|
unlock?.();
|
|
892
862
|
}
|
|
893
863
|
} catch (e) {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
864
|
+
log("ERROR", `Worker error: ${e}`);
|
|
865
|
+
try {
|
|
866
|
+
fs2.unlinkSync(dataFile);
|
|
867
|
+
} catch {
|
|
898
868
|
}
|
|
899
869
|
}
|
|
900
870
|
}
|
|
901
871
|
function main() {
|
|
902
872
|
if (process.env._RESPAN_GEM_WORKER === "1") {
|
|
903
|
-
const
|
|
904
|
-
|
|
873
|
+
const dataFile = process.env._RESPAN_GEM_FILE ?? "";
|
|
874
|
+
if (dataFile) processChunkInWorker(dataFile);
|
|
905
875
|
return;
|
|
906
876
|
}
|
|
907
877
|
let raw = "";
|
|
@@ -914,15 +884,34 @@ function main() {
|
|
|
914
884
|
process.exit(0);
|
|
915
885
|
}
|
|
916
886
|
try {
|
|
917
|
-
const
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
887
|
+
const hookData = JSON.parse(raw);
|
|
888
|
+
const event = String(hookData.hook_event_name ?? "");
|
|
889
|
+
if (event === "BeforeTool" || event === "AfterTool") {
|
|
890
|
+
const unlock = acquireLock(LOCK_PATH);
|
|
891
|
+
try {
|
|
892
|
+
if (event === "BeforeTool") processBeforeTool(hookData);
|
|
893
|
+
else processAfterTool(hookData);
|
|
894
|
+
} finally {
|
|
895
|
+
unlock?.();
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
const dataFile = path2.join(STATE_DIR, `respan_chunk_${process.pid}.json`);
|
|
899
|
+
fs2.mkdirSync(STATE_DIR, { recursive: true });
|
|
900
|
+
fs2.writeFileSync(dataFile, raw);
|
|
901
|
+
try {
|
|
902
|
+
const scriptPath = __filename || process.argv[1];
|
|
903
|
+
const child = (0, import_node_child_process.execFile)("node", [scriptPath], {
|
|
904
|
+
env: { ...process.env, _RESPAN_GEM_WORKER: "1", _RESPAN_GEM_FILE: dataFile },
|
|
905
|
+
stdio: "ignore",
|
|
906
|
+
detached: true
|
|
907
|
+
});
|
|
908
|
+
child.unref();
|
|
909
|
+
} catch (e) {
|
|
910
|
+
processChunkInWorker(dataFile);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
924
913
|
} catch (e) {
|
|
925
|
-
|
|
914
|
+
log("ERROR", `Hook error: ${e}`);
|
|
926
915
|
}
|
|
927
916
|
process.exit(0);
|
|
928
917
|
}
|