@dv.nghiem/flowdeck 0.4.3 → 0.4.4
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/telemetry-hook.d.ts +3 -2
- package/dist/hooks/telemetry-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +352 -18
- package/dist/services/activity-reporter.d.ts +96 -0
- package/dist/services/activity-reporter.d.ts.map +1 -0
- package/dist/services/activity-reporter.test.d.ts +2 -0
- package/dist/services/activity-reporter.test.d.ts.map +1 -0
- package/dist/services/telemetry.d.ts +9 -1
- package/dist/services/telemetry.d.ts.map +1 -1
- package/dist/tools/delegate.d.ts +2 -1
- package/dist/tools/delegate.d.ts.map +1 -1
- package/dist/tools/run-pipeline.d.ts +2 -1
- package/dist/tools/run-pipeline.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActivityReporter } from "../services/activity-reporter";
|
|
1
2
|
export declare function telemetryHook(context: {
|
|
2
3
|
directory?: string;
|
|
3
4
|
}, toolInput: {
|
|
@@ -11,7 +12,7 @@ export declare function telemetryHook(context: {
|
|
|
11
12
|
runId?: string;
|
|
12
13
|
}, output: {
|
|
13
14
|
args?: Record<string, unknown>;
|
|
14
|
-
}): Promise<void>;
|
|
15
|
+
}, reporter?: ActivityReporter): Promise<void>;
|
|
15
16
|
export declare function telemetryAfterHook(context: {
|
|
16
17
|
directory?: string;
|
|
17
18
|
}, toolInput: {
|
|
@@ -28,5 +29,5 @@ export declare function telemetryAfterHook(context: {
|
|
|
28
29
|
output?: string;
|
|
29
30
|
metadata?: unknown;
|
|
30
31
|
error?: unknown;
|
|
31
|
-
}): Promise<void>;
|
|
32
|
+
}, reporter?: ActivityReporter): Promise<void>;
|
|
32
33
|
//# sourceMappingURL=telemetry-hook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-hook.d.ts","sourceRoot":"","sources":["../../src/hooks/telemetry-hook.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"telemetry-hook.d.ts","sourceRoot":"","sources":["../../src/hooks/telemetry-hook.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAsErE,wBAAsB,aAAa,CACjC,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/B,SAAS,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3J,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,EAC1C,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/B,SAAS,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3J,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAChF,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAgDf"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAkHjD,QAAA,MAAM,MAAM,EAAE,MA6Wb,CAAA;AAED,eAAe,MAAM,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -867,12 +867,215 @@ function isUiHeavyTask(input) {
|
|
|
867
867
|
return !hasOnlyNonUiSignals;
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
+
// src/services/activity-reporter.ts
|
|
871
|
+
var SUMMARY_MAX_NORMAL = 120;
|
|
872
|
+
var SUMMARY_MAX_DEBUG = 600;
|
|
873
|
+
var HEARTBEAT_INTERVAL_MS = 15000;
|
|
874
|
+
var TOAST_ON_START_TOOLS = new Set(["delegate", "run-pipeline"]);
|
|
875
|
+
function isDebugMode() {
|
|
876
|
+
return process.env.FLOWDECK_DEBUG === "true" || process.env.FLOWDECK_DEBUG === "1";
|
|
877
|
+
}
|
|
878
|
+
function summarize(text, maxLen = SUMMARY_MAX_NORMAL) {
|
|
879
|
+
if (!text)
|
|
880
|
+
return "";
|
|
881
|
+
const s = text.trim().replace(/\s+/g, " ");
|
|
882
|
+
return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + "…";
|
|
883
|
+
}
|
|
884
|
+
function fmtDuration(ms) {
|
|
885
|
+
if (ms < 1000)
|
|
886
|
+
return `${ms}ms`;
|
|
887
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
888
|
+
}
|
|
889
|
+
function normalizeCommandName(raw) {
|
|
890
|
+
return raw.replace(/^\//, "").replace(/^fd-/, "");
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
class ActivityReporter {
|
|
894
|
+
log;
|
|
895
|
+
toastFn;
|
|
896
|
+
startTimes = new Map;
|
|
897
|
+
heartbeats = new Map;
|
|
898
|
+
constructor(log, toast) {
|
|
899
|
+
this.log = log;
|
|
900
|
+
this.toastFn = toast;
|
|
901
|
+
}
|
|
902
|
+
emit(msg) {
|
|
903
|
+
try {
|
|
904
|
+
this.log(msg);
|
|
905
|
+
} catch {}
|
|
906
|
+
}
|
|
907
|
+
toastNow(msg, variant, duration) {
|
|
908
|
+
if (!this.toastFn)
|
|
909
|
+
return;
|
|
910
|
+
try {
|
|
911
|
+
this.toastFn(msg, variant, duration);
|
|
912
|
+
} catch {}
|
|
913
|
+
}
|
|
914
|
+
trackStart(key) {
|
|
915
|
+
this.startTimes.set(key, Date.now());
|
|
916
|
+
const toolName = key.split(":").pop() ?? key;
|
|
917
|
+
const interval = setInterval(() => {
|
|
918
|
+
const startMs = this.startTimes.get(key);
|
|
919
|
+
if (startMs === undefined)
|
|
920
|
+
return;
|
|
921
|
+
const elapsed = Date.now() - startMs;
|
|
922
|
+
const msg = `[⋯ ${toolName}] still running (${fmtDuration(elapsed)})`;
|
|
923
|
+
this.emit(msg);
|
|
924
|
+
this.toastNow(msg, "info", 8000);
|
|
925
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
926
|
+
if (typeof interval.unref === "function") {
|
|
927
|
+
interval.unref();
|
|
928
|
+
}
|
|
929
|
+
this.heartbeats.set(key, interval);
|
|
930
|
+
}
|
|
931
|
+
elapsedMs(key) {
|
|
932
|
+
const interval = this.heartbeats.get(key);
|
|
933
|
+
if (interval !== undefined) {
|
|
934
|
+
clearInterval(interval);
|
|
935
|
+
this.heartbeats.delete(key);
|
|
936
|
+
}
|
|
937
|
+
const t = this.startTimes.get(key);
|
|
938
|
+
if (t === undefined)
|
|
939
|
+
return;
|
|
940
|
+
this.startTimes.delete(key);
|
|
941
|
+
return Date.now() - t;
|
|
942
|
+
}
|
|
943
|
+
reportToolStarted(tool4, inputSummary, meta = {}) {
|
|
944
|
+
const maxLen = isDebugMode() ? SUMMARY_MAX_DEBUG : SUMMARY_MAX_NORMAL;
|
|
945
|
+
const parts = [`[→ ${tool4}]`];
|
|
946
|
+
if (meta.agent)
|
|
947
|
+
parts.push(`agent=${meta.agent}`);
|
|
948
|
+
if (inputSummary)
|
|
949
|
+
parts.push(summarize(inputSummary, maxLen));
|
|
950
|
+
if (isDebugMode()) {
|
|
951
|
+
if (meta.session_id)
|
|
952
|
+
parts.push(`session=${meta.session_id}`);
|
|
953
|
+
if (meta.stage)
|
|
954
|
+
parts.push(`stage=${meta.stage}`);
|
|
955
|
+
if (meta.run_id)
|
|
956
|
+
parts.push(`run=${meta.run_id}`);
|
|
957
|
+
}
|
|
958
|
+
this.emit(parts.join(" "));
|
|
959
|
+
if (TOAST_ON_START_TOOLS.has(tool4)) {
|
|
960
|
+
const agentPart = meta.agent ? ` @${meta.agent}` : "";
|
|
961
|
+
const inputPart = inputSummary ? `: ${summarize(inputSummary, 60)}` : "";
|
|
962
|
+
this.toastNow(`→ ${tool4}${agentPart}${inputPart}`, "info", 3000);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
reportToolCompleted(tool4, durationMs, resultSummary, meta = {}) {
|
|
966
|
+
const maxLen = isDebugMode() ? SUMMARY_MAX_DEBUG : SUMMARY_MAX_NORMAL;
|
|
967
|
+
const dur = durationMs !== undefined ? ` (${fmtDuration(durationMs)})` : "";
|
|
968
|
+
const parts = [`[✓ ${tool4}]${dur}`];
|
|
969
|
+
if (meta.agent)
|
|
970
|
+
parts.push(`agent=${meta.agent}`);
|
|
971
|
+
if (resultSummary)
|
|
972
|
+
parts.push(summarize(resultSummary, maxLen));
|
|
973
|
+
if (isDebugMode() && meta.retry_count && meta.retry_count > 0) {
|
|
974
|
+
parts.push(`retries=${meta.retry_count}`);
|
|
975
|
+
}
|
|
976
|
+
this.emit(parts.join(" "));
|
|
977
|
+
}
|
|
978
|
+
reportToolFailed(tool4, durationMs, error, meta = {}) {
|
|
979
|
+
const dur = durationMs !== undefined ? ` (${fmtDuration(durationMs)})` : "";
|
|
980
|
+
const parts = [`[✗ ${tool4}]${dur}`];
|
|
981
|
+
if (meta.agent)
|
|
982
|
+
parts.push(`agent=${meta.agent}`);
|
|
983
|
+
parts.push(`error=${summarize(error, isDebugMode() ? SUMMARY_MAX_DEBUG : 200)}`);
|
|
984
|
+
if (isDebugMode() && meta.retry_count && meta.retry_count > 0) {
|
|
985
|
+
parts.push(`retries=${meta.retry_count}`);
|
|
986
|
+
}
|
|
987
|
+
this.emit(parts.join(" "));
|
|
988
|
+
this.toastNow(`✗ ${tool4}${dur}: ${summarize(error, 80)}`, "error", 8000);
|
|
989
|
+
}
|
|
990
|
+
reportToolRetried(tool4, attempt, reason, meta = {}) {
|
|
991
|
+
const parts = [`[↺ ${tool4}] retry attempt=${attempt}`];
|
|
992
|
+
if (meta.agent)
|
|
993
|
+
parts.push(`agent=${meta.agent}`);
|
|
994
|
+
if (reason)
|
|
995
|
+
parts.push(`reason=${summarize(reason, 80)}`);
|
|
996
|
+
this.emit(parts.join(" "));
|
|
997
|
+
this.toastNow(`↺ ${tool4} retry #${attempt}${meta.agent ? ` @${meta.agent}` : ""}`, "warning", 5000);
|
|
998
|
+
}
|
|
999
|
+
reportToolFallback(fromTool, toTool, reason, meta = {}) {
|
|
1000
|
+
const parts = [`[⇢ fallback] ${fromTool} → ${toTool}`];
|
|
1001
|
+
if (reason)
|
|
1002
|
+
parts.push(`reason=${summarize(reason, 80)}`);
|
|
1003
|
+
if (meta.agent)
|
|
1004
|
+
parts.push(`agent=${meta.agent}`);
|
|
1005
|
+
this.emit(parts.join(" "));
|
|
1006
|
+
this.toastNow(`⇢ fallback: ${fromTool} → ${toTool}`, "info", 4000);
|
|
1007
|
+
}
|
|
1008
|
+
reportCacheHit(tool4, agent, meta = {}) {
|
|
1009
|
+
const parts = [`[≡ ${tool4}] cache hit agent=${agent}`];
|
|
1010
|
+
if (isDebugMode() && meta.session_id)
|
|
1011
|
+
parts.push(`session=${meta.session_id}`);
|
|
1012
|
+
this.emit(parts.join(" "));
|
|
1013
|
+
}
|
|
1014
|
+
reportSkipped(tool4, reason, meta = {}) {
|
|
1015
|
+
const parts = [`[⊘ ${tool4}] skipped`];
|
|
1016
|
+
if (reason)
|
|
1017
|
+
parts.push(`reason=${summarize(reason, 80)}`);
|
|
1018
|
+
if (meta.agent)
|
|
1019
|
+
parts.push(`agent=${meta.agent}`);
|
|
1020
|
+
this.emit(parts.join(" "));
|
|
1021
|
+
}
|
|
1022
|
+
reportStageProgress(stage, status, detail, meta = {}) {
|
|
1023
|
+
const icon = {
|
|
1024
|
+
started: "▶",
|
|
1025
|
+
running: "⋯",
|
|
1026
|
+
complete: "●",
|
|
1027
|
+
failed: "✗",
|
|
1028
|
+
waiting: "⌛"
|
|
1029
|
+
};
|
|
1030
|
+
const sym = icon[status] ?? "·";
|
|
1031
|
+
const parts = [`[${sym} ${stage}] ${status}`];
|
|
1032
|
+
if (detail)
|
|
1033
|
+
parts.push(summarize(detail));
|
|
1034
|
+
if (isDebugMode() && meta.workflow_id)
|
|
1035
|
+
parts.push(`workflow=${meta.workflow_id}`);
|
|
1036
|
+
this.emit(parts.join(" "));
|
|
1037
|
+
const detailPart = detail ? `: ${summarize(detail, 60)}` : "";
|
|
1038
|
+
switch (status) {
|
|
1039
|
+
case "started":
|
|
1040
|
+
this.toastNow(`▶ ${stage} started${detailPart}`, "info", 3000);
|
|
1041
|
+
break;
|
|
1042
|
+
case "complete":
|
|
1043
|
+
this.toastNow(`● ${stage} complete${detailPart}`, "success", 4000);
|
|
1044
|
+
break;
|
|
1045
|
+
case "failed":
|
|
1046
|
+
this.toastNow(`✗ ${stage} failed${detailPart}`, "error", 8000);
|
|
1047
|
+
break;
|
|
1048
|
+
case "waiting":
|
|
1049
|
+
this.toastNow(`⌛ ${stage}: waiting for input${detailPart}`, "warning", 30000);
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
reportWaitingForApproval(tool4, _meta = {}) {
|
|
1054
|
+
const msg = `⌛ Approval required: ${tool4}`;
|
|
1055
|
+
this.emit(msg);
|
|
1056
|
+
this.toastNow(msg, "warning", 30000);
|
|
1057
|
+
}
|
|
1058
|
+
reportCommandStarted(command) {
|
|
1059
|
+
const cmd = normalizeCommandName(command);
|
|
1060
|
+
const msg = `▶ /${cmd} started`;
|
|
1061
|
+
this.emit(msg);
|
|
1062
|
+
this.toastNow(msg, "info", 2500);
|
|
1063
|
+
}
|
|
1064
|
+
reportCommandCompleted(command, hasEdits) {
|
|
1065
|
+
const cmd = normalizeCommandName(command);
|
|
1066
|
+
const detail = hasEdits ? " (files modified)" : "";
|
|
1067
|
+
const msg = `● /${cmd} complete${detail}`;
|
|
1068
|
+
this.emit(msg);
|
|
1069
|
+
this.toastNow(msg, "success", 5000);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
870
1073
|
// src/tools/run-pipeline.ts
|
|
871
1074
|
function extractText(parts) {
|
|
872
1075
|
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
|
|
873
1076
|
`);
|
|
874
1077
|
}
|
|
875
|
-
function createRunPipelineTool(client) {
|
|
1078
|
+
function createRunPipelineTool(client, reporter) {
|
|
876
1079
|
return tool4({
|
|
877
1080
|
description: "Run agents in sequential pipeline. Each step's output is appended to the next step's context. One fresh child session per step. Returns full trace with session ID, input/output/duration per step.",
|
|
878
1081
|
args: {
|
|
@@ -893,6 +1096,8 @@ function createRunPipelineTool(client) {
|
|
|
893
1096
|
let aborted = false;
|
|
894
1097
|
const retryAttempts = typeof args.retry_attempts === "number" ? args.retry_attempts : 1;
|
|
895
1098
|
const maxRetries = Math.max(0, Math.floor(retryAttempts));
|
|
1099
|
+
const totalSteps = args.steps.length;
|
|
1100
|
+
reporter?.reportStageProgress("pipeline", "started", `${totalSteps} step(s)`);
|
|
896
1101
|
let inflightChildId = null;
|
|
897
1102
|
const abortHandler = () => {
|
|
898
1103
|
if (inflightChildId) {
|
|
@@ -904,7 +1109,8 @@ function createRunPipelineTool(client) {
|
|
|
904
1109
|
};
|
|
905
1110
|
context.abort.addEventListener("abort", abortHandler);
|
|
906
1111
|
try {
|
|
907
|
-
for (
|
|
1112
|
+
for (let stepIdx = 0;stepIdx < args.steps.length; stepIdx++) {
|
|
1113
|
+
const step = args.steps[stepIdx];
|
|
908
1114
|
if (context.abort.aborted) {
|
|
909
1115
|
aborted = true;
|
|
910
1116
|
break;
|
|
@@ -916,6 +1122,10 @@ function createRunPipelineTool(client) {
|
|
|
916
1122
|
---
|
|
917
1123
|
|
|
918
1124
|
${step.prompt}` : step.prompt;
|
|
1125
|
+
reporter?.reportToolStarted("run-pipeline", summarize(step.prompt, 80), {
|
|
1126
|
+
agent: step.agent,
|
|
1127
|
+
stage: `step ${stepIdx + 1}/${totalSteps}`
|
|
1128
|
+
});
|
|
919
1129
|
const createRes = await client.session.create({
|
|
920
1130
|
body: { parentID: context.sessionID, title: `${step.agent}-pipeline` },
|
|
921
1131
|
query: { directory: context.directory }
|
|
@@ -923,6 +1133,7 @@ ${step.prompt}` : step.prompt;
|
|
|
923
1133
|
if (createRes.error || !createRes.data?.id) {
|
|
924
1134
|
const errMsg = `Failed to create session: ${createRes.error?.detail ?? "unknown"}`;
|
|
925
1135
|
trace.push({ agent: step.agent, task_type: taskType, model: "", input: stepInput, output: errMsg, duration_ms: Date.now() - stepStart, success: false });
|
|
1136
|
+
reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent });
|
|
926
1137
|
aborted = true;
|
|
927
1138
|
break;
|
|
928
1139
|
}
|
|
@@ -942,6 +1153,7 @@ ${step.prompt}` : step.prompt;
|
|
|
942
1153
|
if (!shouldRetry(promptRes) || attempt === maxRetries)
|
|
943
1154
|
break;
|
|
944
1155
|
retriesUsed++;
|
|
1156
|
+
reporter?.reportToolRetried("run-pipeline", retriesUsed, "prompt response indicated retry", { agent: step.agent });
|
|
945
1157
|
}
|
|
946
1158
|
inflightChildId = null;
|
|
947
1159
|
if (context.abort.aborted) {
|
|
@@ -952,6 +1164,7 @@ ${step.prompt}` : step.prompt;
|
|
|
952
1164
|
const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
|
|
953
1165
|
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
|
|
954
1166
|
recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
|
|
1167
|
+
reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent, retry_count: retriesUsed });
|
|
955
1168
|
if (args.abort_on_failure) {
|
|
956
1169
|
aborted = true;
|
|
957
1170
|
break;
|
|
@@ -963,6 +1176,7 @@ ${step.prompt}` : step.prompt;
|
|
|
963
1176
|
const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
|
|
964
1177
|
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
|
|
965
1178
|
recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
|
|
1179
|
+
reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent, retry_count: retriesUsed });
|
|
966
1180
|
if (args.abort_on_failure) {
|
|
967
1181
|
aborted = true;
|
|
968
1182
|
break;
|
|
@@ -972,15 +1186,26 @@ ${step.prompt}` : step.prompt;
|
|
|
972
1186
|
const output = extractText(promptRes.data?.parts ?? []);
|
|
973
1187
|
trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: output || "(no text output)", duration_ms: Date.now() - stepStart, success: true, context_chars: carryContext.length });
|
|
974
1188
|
recordRun(context.directory, step.agent, "", taskType, true, Date.now() - stepStart);
|
|
1189
|
+
reporter?.reportToolCompleted("run-pipeline", Date.now() - stepStart, summarize(output, 80), {
|
|
1190
|
+
agent: step.agent,
|
|
1191
|
+
retry_count: retriesUsed,
|
|
1192
|
+
stage: `step ${stepIdx + 1}/${totalSteps}`
|
|
1193
|
+
});
|
|
975
1194
|
const rawOutput = output || "";
|
|
976
1195
|
carryContext = typeof args.max_carry_chars === "number" && rawOutput.length > args.max_carry_chars ? rawOutput.slice(rawOutput.length - args.max_carry_chars) : rawOutput;
|
|
977
1196
|
}
|
|
978
1197
|
} finally {
|
|
979
1198
|
context.abort.removeEventListener("abort", abortHandler);
|
|
980
1199
|
}
|
|
1200
|
+
const totalDuration = Date.now() - startTime;
|
|
1201
|
+
if (aborted) {
|
|
1202
|
+
reporter?.reportStageProgress("pipeline", "failed", `aborted after ${trace.length}/${totalSteps} steps`);
|
|
1203
|
+
} else {
|
|
1204
|
+
reporter?.reportStageProgress("pipeline", "complete", `${totalSteps} step(s) in ${totalDuration}ms`);
|
|
1205
|
+
}
|
|
981
1206
|
return JSON.stringify({
|
|
982
1207
|
steps: trace,
|
|
983
|
-
total_duration_ms:
|
|
1208
|
+
total_duration_ms: totalDuration,
|
|
984
1209
|
aborted
|
|
985
1210
|
});
|
|
986
1211
|
}
|
|
@@ -1386,7 +1611,7 @@ function extractText2(parts) {
|
|
|
1386
1611
|
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
|
|
1387
1612
|
`);
|
|
1388
1613
|
}
|
|
1389
|
-
function createDelegateTool(client) {
|
|
1614
|
+
function createDelegateTool(client, reporter) {
|
|
1390
1615
|
return tool5({
|
|
1391
1616
|
description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
|
|
1392
1617
|
args: {
|
|
@@ -1417,6 +1642,10 @@ function createDelegateTool(client) {
|
|
|
1417
1642
|
---
|
|
1418
1643
|
|
|
1419
1644
|
${args.prompt}` : args.prompt;
|
|
1645
|
+
reporter?.reportToolStarted("delegate", summarize(args.prompt, 100), {
|
|
1646
|
+
agent: args.agent,
|
|
1647
|
+
stage: args.stage
|
|
1648
|
+
});
|
|
1420
1649
|
const safe_to_cache = args.safe_to_cache === true && CACHEABLE_AGENTS.has(args.agent);
|
|
1421
1650
|
let stateVersion = 0;
|
|
1422
1651
|
let indexVersion = 0;
|
|
@@ -1432,6 +1661,7 @@ ${args.prompt}` : args.prompt;
|
|
|
1432
1661
|
if (metricsWorkflowId) {
|
|
1433
1662
|
recordCacheHit(context.directory, metricsWorkflowId, metricsStage, fullPrompt, args.agent, agentModel);
|
|
1434
1663
|
}
|
|
1664
|
+
reporter?.reportCacheHit("delegate", args.agent);
|
|
1435
1665
|
return JSON.stringify({
|
|
1436
1666
|
agent: args.agent,
|
|
1437
1667
|
success: true,
|
|
@@ -1489,14 +1719,20 @@ ${args.prompt}` : args.prompt;
|
|
|
1489
1719
|
recordRetryCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, "", args.agent, Date.now() - attemptStart, agentModel, retryCostUsd);
|
|
1490
1720
|
}
|
|
1491
1721
|
retriesUsed++;
|
|
1722
|
+
reporter?.reportToolRetried("delegate", retriesUsed, "prompt response indicated retry", { agent: args.agent });
|
|
1492
1723
|
}
|
|
1493
1724
|
if (!promptRes || promptRes.error) {
|
|
1725
|
+
const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
|
|
1494
1726
|
recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
|
|
1727
|
+
reporter?.reportToolFailed("delegate", Date.now() - startTime, errMsg, {
|
|
1728
|
+
agent: args.agent,
|
|
1729
|
+
retry_count: retriesUsed
|
|
1730
|
+
});
|
|
1495
1731
|
return JSON.stringify({
|
|
1496
1732
|
agent: args.agent,
|
|
1497
1733
|
session_id: childId,
|
|
1498
1734
|
success: false,
|
|
1499
|
-
error:
|
|
1735
|
+
error: errMsg,
|
|
1500
1736
|
task_type: taskType,
|
|
1501
1737
|
model: "",
|
|
1502
1738
|
retries_used: retriesUsed,
|
|
@@ -1505,12 +1741,17 @@ ${args.prompt}` : args.prompt;
|
|
|
1505
1741
|
}
|
|
1506
1742
|
const info = promptRes.data?.info;
|
|
1507
1743
|
if (info?.error) {
|
|
1744
|
+
const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
|
|
1508
1745
|
recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
|
|
1746
|
+
reporter?.reportToolFailed("delegate", Date.now() - startTime, errMsg, {
|
|
1747
|
+
agent: args.agent,
|
|
1748
|
+
retry_count: retriesUsed
|
|
1749
|
+
});
|
|
1509
1750
|
return JSON.stringify({
|
|
1510
1751
|
agent: args.agent,
|
|
1511
1752
|
session_id: childId,
|
|
1512
1753
|
success: false,
|
|
1513
|
-
error:
|
|
1754
|
+
error: errMsg,
|
|
1514
1755
|
task_type: taskType,
|
|
1515
1756
|
model: "",
|
|
1516
1757
|
retries_used: retriesUsed,
|
|
@@ -1525,6 +1766,10 @@ ${args.prompt}` : args.prompt;
|
|
|
1525
1766
|
const costUsd = agentModel ? estimateCostUSD(agentModel, inputTokens, outputTokens) : undefined;
|
|
1526
1767
|
recordModelCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, output, args.agent, Date.now() - startTime, agentModel, costUsd);
|
|
1527
1768
|
}
|
|
1769
|
+
reporter?.reportToolCompleted("delegate", Date.now() - startTime, summarize(output, 80), {
|
|
1770
|
+
agent: args.agent,
|
|
1771
|
+
retry_count: retriesUsed
|
|
1772
|
+
});
|
|
1528
1773
|
if (safe_to_cache && output) {
|
|
1529
1774
|
setCached(context.directory, args.agent, fullPromptForSession, args.context ?? "", stateVersion, indexVersion, output, true, args.cache_ttl_ms);
|
|
1530
1775
|
}
|
|
@@ -3445,7 +3690,7 @@ var COMPLETION_COMMANDS = new Set([
|
|
|
3445
3690
|
"execute",
|
|
3446
3691
|
"verify"
|
|
3447
3692
|
]);
|
|
3448
|
-
function
|
|
3693
|
+
function normalizeCommandName2(raw) {
|
|
3449
3694
|
return raw.replace(/^\//, "").replace(/^fd-/, "");
|
|
3450
3695
|
}
|
|
3451
3696
|
function notify(title, body, level = "info") {
|
|
@@ -3490,7 +3735,7 @@ class NotificationController {
|
|
|
3490
3735
|
this.log = log;
|
|
3491
3736
|
}
|
|
3492
3737
|
onCommandExecuted(rawCommand) {
|
|
3493
|
-
const name =
|
|
3738
|
+
const name = normalizeCommandName2(rawCommand);
|
|
3494
3739
|
if (!INTERACTIVE_COMMANDS.has(name) && !COMPLETION_COMMANDS.has(name)) {
|
|
3495
3740
|
this.log(`[notify] command.executed: "${name}" — not a tracked command, skipping`);
|
|
3496
3741
|
return;
|
|
@@ -3717,6 +3962,32 @@ function appendEvent2(dir, partial) {
|
|
|
3717
3962
|
}
|
|
3718
3963
|
|
|
3719
3964
|
// src/hooks/telemetry-hook.ts
|
|
3965
|
+
var toolStartTimes = new Map;
|
|
3966
|
+
var REPORTABLE_TOOLS = new Set([
|
|
3967
|
+
"delegate",
|
|
3968
|
+
"run-pipeline",
|
|
3969
|
+
"council",
|
|
3970
|
+
"bash",
|
|
3971
|
+
"write",
|
|
3972
|
+
"edit",
|
|
3973
|
+
"read",
|
|
3974
|
+
"codegraph",
|
|
3975
|
+
"codebase-state",
|
|
3976
|
+
"planning-state",
|
|
3977
|
+
"workspace-state",
|
|
3978
|
+
"repo-memory",
|
|
3979
|
+
"hash-edit",
|
|
3980
|
+
"context-generator",
|
|
3981
|
+
"volatility-map",
|
|
3982
|
+
"failure-replay",
|
|
3983
|
+
"decision-trace",
|
|
3984
|
+
"policy-engine",
|
|
3985
|
+
"reflect"
|
|
3986
|
+
]);
|
|
3987
|
+
var SELF_LOGGING_TOOLS = new Set(["delegate", "run-pipeline", "council"]);
|
|
3988
|
+
function correlationKey(sessionId, runId, tool19) {
|
|
3989
|
+
return `${sessionId}:${runId}:${tool19}`;
|
|
3990
|
+
}
|
|
3720
3991
|
function resolveIds(toolInput) {
|
|
3721
3992
|
const session_id = toolInput.sessionID ?? toolInput.sessionId ?? process.env.OPENCODE_SESSION_ID ?? "session-0";
|
|
3722
3993
|
const run_id = toolInput.messageID ?? toolInput.messageId ?? toolInput.runID ?? toolInput.runId ?? process.env.OPENCODE_RUN_ID ?? "run-0";
|
|
@@ -3739,10 +4010,24 @@ function inferStatus(output) {
|
|
|
3739
4010
|
return "ok";
|
|
3740
4011
|
}
|
|
3741
4012
|
}
|
|
3742
|
-
|
|
4013
|
+
function buildInputSummary(tool19, args) {
|
|
4014
|
+
if (tool19 === "delegate" || tool19 === "run-pipeline" || tool19 === "council") {
|
|
4015
|
+
return "";
|
|
4016
|
+
}
|
|
4017
|
+
if (tool19 === "bash" || tool19 === "write" || tool19 === "edit" || tool19 === "read") {
|
|
4018
|
+
const path = args.path ?? args.filePath ?? args.file ?? "";
|
|
4019
|
+
const cmd = args.command ?? args.cmd ?? "";
|
|
4020
|
+
return path || cmd ? summarize(String(path || cmd), 80) : "";
|
|
4021
|
+
}
|
|
4022
|
+
const firstStr = Object.values(args).find((v) => typeof v === "string");
|
|
4023
|
+
return firstStr ? summarize(firstStr, 80) : "";
|
|
4024
|
+
}
|
|
4025
|
+
async function telemetryHook(context, toolInput, output, reporter) {
|
|
3743
4026
|
const dir = context.directory ?? process.cwd();
|
|
3744
4027
|
const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
|
|
3745
4028
|
const ids = resolveIds(toolInput);
|
|
4029
|
+
const key = correlationKey(ids.session_id, ids.run_id, tool19);
|
|
4030
|
+
toolStartTimes.set(key, Date.now());
|
|
3746
4031
|
appendEvent2(dir, {
|
|
3747
4032
|
session_id: ids.session_id,
|
|
3748
4033
|
run_id: ids.run_id,
|
|
@@ -3751,19 +4036,56 @@ async function telemetryHook(context, toolInput, output) {
|
|
|
3751
4036
|
status: "ok",
|
|
3752
4037
|
meta: { parameters: output.args ?? {} }
|
|
3753
4038
|
});
|
|
4039
|
+
if (reporter && REPORTABLE_TOOLS.has(tool19) && !SELF_LOGGING_TOOLS.has(tool19)) {
|
|
4040
|
+
const inputSummary = buildInputSummary(tool19, output.args ?? {});
|
|
4041
|
+
reporter.reportToolStarted(tool19, inputSummary, {
|
|
4042
|
+
session_id: ids.session_id,
|
|
4043
|
+
run_id: ids.run_id
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
if (reporter && REPORTABLE_TOOLS.has(tool19)) {
|
|
4047
|
+
reporter.trackStart(key);
|
|
4048
|
+
}
|
|
3754
4049
|
}
|
|
3755
|
-
async function telemetryAfterHook(context, toolInput, output) {
|
|
4050
|
+
async function telemetryAfterHook(context, toolInput, output, reporter) {
|
|
3756
4051
|
const dir = context.directory ?? process.cwd();
|
|
3757
4052
|
const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
|
|
3758
4053
|
const ids = resolveIds(toolInput);
|
|
4054
|
+
const key = correlationKey(ids.session_id, ids.run_id, tool19);
|
|
3759
4055
|
const status = inferStatus(output);
|
|
4056
|
+
let duration_ms;
|
|
4057
|
+
if (reporter && REPORTABLE_TOOLS.has(tool19)) {
|
|
4058
|
+
duration_ms = reporter.elapsedMs(key);
|
|
4059
|
+
}
|
|
4060
|
+
if (duration_ms === undefined) {
|
|
4061
|
+
const startMs = toolStartTimes.get(key);
|
|
4062
|
+
if (startMs !== undefined)
|
|
4063
|
+
duration_ms = Date.now() - startMs;
|
|
4064
|
+
}
|
|
4065
|
+
toolStartTimes.delete(key);
|
|
4066
|
+
let result_summary;
|
|
4067
|
+
if (typeof output.output === "string") {
|
|
4068
|
+
result_summary = summarize(output.output, 100);
|
|
4069
|
+
} else if (output.error) {
|
|
4070
|
+
result_summary = summarize(String(output.error), 100);
|
|
4071
|
+
}
|
|
3760
4072
|
appendEvent2(dir, {
|
|
3761
4073
|
session_id: ids.session_id,
|
|
3762
4074
|
run_id: ids.run_id,
|
|
3763
|
-
event: "tool.complete",
|
|
4075
|
+
event: status === "error" ? "tool.failed" : "tool.complete",
|
|
3764
4076
|
tool: tool19,
|
|
3765
|
-
status
|
|
4077
|
+
status,
|
|
4078
|
+
duration_ms,
|
|
4079
|
+
result_summary
|
|
3766
4080
|
});
|
|
4081
|
+
if (reporter && REPORTABLE_TOOLS.has(tool19) && !SELF_LOGGING_TOOLS.has(tool19)) {
|
|
4082
|
+
if (status === "error") {
|
|
4083
|
+
const errText = output.error ? String(output.error) : typeof output.output === "string" ? output.output : "unknown error";
|
|
4084
|
+
reporter.reportToolFailed(tool19, duration_ms, errText);
|
|
4085
|
+
} else {
|
|
4086
|
+
reporter.reportToolCompleted(tool19, duration_ms, result_summary ?? "");
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
3767
4089
|
}
|
|
3768
4090
|
|
|
3769
4091
|
// src/services/approval-manager.ts
|
|
@@ -8197,8 +8519,11 @@ function loadCommands() {
|
|
|
8197
8519
|
}
|
|
8198
8520
|
var plugin = async (input, _options) => {
|
|
8199
8521
|
const { directory, client, worktree } = input;
|
|
8200
|
-
const
|
|
8201
|
-
const
|
|
8522
|
+
const appLog = (msg) => client.app.log({ body: { service: "flowdeck", level: "info", message: msg } }).catch(() => {});
|
|
8523
|
+
const toastFn = (message, variant, duration) => client.tui.showToast({ body: { message, variant, duration }, query: { directory } }).catch(() => {});
|
|
8524
|
+
const activityReporter = new ActivityReporter(appLog, toastFn);
|
|
8525
|
+
const runPipelineTool = createRunPipelineTool(client, activityReporter);
|
|
8526
|
+
const delegateTool = createDelegateTool(client, activityReporter);
|
|
8202
8527
|
const councilTool = createCouncilTool(client);
|
|
8203
8528
|
const fileTracker = new SessionFileTracker;
|
|
8204
8529
|
const { fileEdited, fileWatcherUpdated } = createFileTrackerHooks(fileTracker);
|
|
@@ -8208,11 +8533,11 @@ var plugin = async (input, _options) => {
|
|
|
8208
8533
|
const sessionIdleHook = createSessionIdleHook(client, fileTracker);
|
|
8209
8534
|
const compactionHook = createCompactionHook({ directory }, fileTracker);
|
|
8210
8535
|
const orchestratorGuard = new OrchestratorGuard;
|
|
8211
|
-
const appLog = (msg) => client.app.log({ body: { service: "flowdeck", level: "info", message: msg } }).catch(() => {});
|
|
8212
8536
|
const autoLearnHook = createAutoLearnHook(client, fileTracker, directory, appLog);
|
|
8213
8537
|
const notifCtrl = new NotificationController(undefined, appLog);
|
|
8214
8538
|
const agentConfigs = getAgentConfigs({});
|
|
8215
8539
|
const mcps = createFlowDeckMcps();
|
|
8540
|
+
let lastExecutedCommand = null;
|
|
8216
8541
|
return {
|
|
8217
8542
|
name: "@dv.nghiem/flowdeck",
|
|
8218
8543
|
agent: agentConfigs,
|
|
@@ -8315,9 +8640,13 @@ var plugin = async (input, _options) => {
|
|
|
8315
8640
|
"file.edited": fileEdited,
|
|
8316
8641
|
"file.watcher.updated": fileWatcherUpdated,
|
|
8317
8642
|
"experimental.session.compacting": compactionHook,
|
|
8318
|
-
"command.execute.before": async (
|
|
8643
|
+
"command.execute.before": async (input2, _output) => {
|
|
8644
|
+
activityReporter.reportCommandStarted(input2.command);
|
|
8645
|
+
lastExecutedCommand = input2.command;
|
|
8646
|
+
},
|
|
8319
8647
|
"permission.ask": async (input2, _output) => {
|
|
8320
8648
|
notifyPermissionNeeded(input2.title);
|
|
8649
|
+
activityReporter.reportWaitingForApproval(input2.title);
|
|
8321
8650
|
},
|
|
8322
8651
|
event: async ({ event }) => {
|
|
8323
8652
|
const type = event?.type ?? "";
|
|
@@ -8334,6 +8663,10 @@ var plugin = async (input, _options) => {
|
|
|
8334
8663
|
orchestratorGuard.onEvent(event);
|
|
8335
8664
|
if (type === "session.idle") {
|
|
8336
8665
|
const hasEdits = fileTracker.getEditedPaths().length > 0;
|
|
8666
|
+
if (lastExecutedCommand) {
|
|
8667
|
+
activityReporter.reportCommandCompleted(lastExecutedCommand, hasEdits);
|
|
8668
|
+
lastExecutedCommand = null;
|
|
8669
|
+
}
|
|
8337
8670
|
notifCtrl.onSessionIdle(hasEdits);
|
|
8338
8671
|
try {
|
|
8339
8672
|
await sessionIdleHook();
|
|
@@ -8343,6 +8676,7 @@ var plugin = async (input, _options) => {
|
|
|
8343
8676
|
}
|
|
8344
8677
|
}
|
|
8345
8678
|
if (type === "session.error") {
|
|
8679
|
+
lastExecutedCommand = null;
|
|
8346
8680
|
const err = event?.properties?.error;
|
|
8347
8681
|
const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? "An unexpected error occurred";
|
|
8348
8682
|
notifCtrl.onSessionError(errorMsg);
|
|
@@ -8387,7 +8721,7 @@ var plugin = async (input, _options) => {
|
|
|
8387
8721
|
}
|
|
8388
8722
|
}
|
|
8389
8723
|
}
|
|
8390
|
-
await telemetryHook({ directory }, toolInput, toolOutput);
|
|
8724
|
+
await telemetryHook({ directory }, toolInput, toolOutput, activityReporter);
|
|
8391
8725
|
await approvalHook({ directory }, toolInput, toolOutput);
|
|
8392
8726
|
await guardRailsHook({ directory }, toolInput, toolOutput);
|
|
8393
8727
|
await toolGuardHook({ directory }, toolInput, toolOutput);
|
|
@@ -8395,7 +8729,7 @@ var plugin = async (input, _options) => {
|
|
|
8395
8729
|
await decisionTraceHook({ directory }, toolInput, toolOutput);
|
|
8396
8730
|
},
|
|
8397
8731
|
"tool.execute.after": async (toolInput, toolOutput) => {
|
|
8398
|
-
await telemetryAfterHook({ directory }, toolInput, toolOutput);
|
|
8732
|
+
await telemetryAfterHook({ directory }, toolInput, toolOutput, activityReporter);
|
|
8399
8733
|
const afterToolName = toolInput.tool ?? toolInput.name ?? "";
|
|
8400
8734
|
if (afterToolName === "delegate" || afterToolName === "run-pipeline") {
|
|
8401
8735
|
try {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Reporter
|
|
3
|
+
*
|
|
4
|
+
* Surfaces tool lifecycle events and workflow-stage progress to the user
|
|
5
|
+
* in real-time via two channels:
|
|
6
|
+
* 1. appLog (client.app.log) — persistent server log visible in the log panel
|
|
7
|
+
* 2. toast (client.tui.showToast) — ephemeral in-TUI notifications for key events
|
|
8
|
+
*
|
|
9
|
+
* Design goals:
|
|
10
|
+
* - Every significant tool call emits a concise user-visible log line
|
|
11
|
+
* - Key events (failures, stage transitions, waiting states) also show TUI toasts
|
|
12
|
+
* - Heartbeat toast fires if a tracked tool runs > HEARTBEAT_INTERVAL_MS
|
|
13
|
+
* - Normal mode: short summaries (no raw dumps)
|
|
14
|
+
* - Debug mode (FLOWDECK_DEBUG=true): full inputs/outputs + trace metadata
|
|
15
|
+
* - Retries, fallbacks, cache hits, and skips are all individually logged
|
|
16
|
+
*/
|
|
17
|
+
/** Interval before the first "still running" heartbeat toast (ms) */
|
|
18
|
+
export declare const HEARTBEAT_INTERVAL_MS = 15000;
|
|
19
|
+
export type ToastVariant = "info" | "success" | "warning" | "error";
|
|
20
|
+
/** Injectable toast function — wraps client.tui.showToast in production. */
|
|
21
|
+
export type ToastFn = (msg: string, variant: ToastVariant, duration?: number) => void;
|
|
22
|
+
export declare function isDebugMode(): boolean;
|
|
23
|
+
/** Trim text to maxLen, appending ellipsis when truncated */
|
|
24
|
+
export declare function summarize(text: string, maxLen?: number): string;
|
|
25
|
+
/** Format milliseconds as human-readable "42ms" or "3.2s" */
|
|
26
|
+
export declare function fmtDuration(ms: number): string;
|
|
27
|
+
export interface ActivityMeta {
|
|
28
|
+
session_id?: string;
|
|
29
|
+
run_id?: string;
|
|
30
|
+
command?: string;
|
|
31
|
+
stage?: string;
|
|
32
|
+
agent?: string;
|
|
33
|
+
retry_count?: number;
|
|
34
|
+
workflow_id?: string;
|
|
35
|
+
tool?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare class ActivityReporter {
|
|
38
|
+
private readonly log;
|
|
39
|
+
private readonly toastFn?;
|
|
40
|
+
/** correlationKey → start epoch ms */
|
|
41
|
+
private readonly startTimes;
|
|
42
|
+
/** correlationKey → heartbeat interval handle */
|
|
43
|
+
private readonly heartbeats;
|
|
44
|
+
constructor(log: (msg: string) => void, toast?: ToastFn);
|
|
45
|
+
private emit;
|
|
46
|
+
/** Send an ephemeral toast to the TUI. Duration is in milliseconds. */
|
|
47
|
+
private toastNow;
|
|
48
|
+
/**
|
|
49
|
+
* Record start time against a correlation key and start a heartbeat interval.
|
|
50
|
+
* If the tracked operation hasn't finished within HEARTBEAT_INTERVAL_MS, a
|
|
51
|
+
* "still running" log line and toast are emitted to prevent the TUI from
|
|
52
|
+
* looking frozen during long-running tools.
|
|
53
|
+
*/
|
|
54
|
+
trackStart(key: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Consume the start time for key, cancel its heartbeat, and return elapsed ms.
|
|
57
|
+
* Returns undefined if key was never tracked.
|
|
58
|
+
*/
|
|
59
|
+
elapsedMs(key: string): number | undefined;
|
|
60
|
+
/** Emitted when a tool call begins. */
|
|
61
|
+
reportToolStarted(tool: string, inputSummary: string, meta?: ActivityMeta): void;
|
|
62
|
+
/** Emitted when a tool call completes successfully. */
|
|
63
|
+
reportToolCompleted(tool: string, durationMs: number | undefined, resultSummary: string, meta?: ActivityMeta): void;
|
|
64
|
+
/** Emitted when a tool call fails (after all retries are exhausted). */
|
|
65
|
+
reportToolFailed(tool: string, durationMs: number | undefined, error: string, meta?: ActivityMeta): void;
|
|
66
|
+
/** Emitted each time a tool call is retried. */
|
|
67
|
+
reportToolRetried(tool: string, attempt: number, reason: string, meta?: ActivityMeta): void;
|
|
68
|
+
/** Emitted when the system falls back from one tool/strategy to another. */
|
|
69
|
+
reportToolFallback(fromTool: string, toTool: string, reason: string, meta?: ActivityMeta): void;
|
|
70
|
+
/** Emitted when a tool call is satisfied from the prompt cache. */
|
|
71
|
+
reportCacheHit(tool: string, agent: string, meta?: ActivityMeta): void;
|
|
72
|
+
/** Emitted when a tool or step is intentionally skipped. */
|
|
73
|
+
reportSkipped(tool: string, reason: string, meta?: ActivityMeta): void;
|
|
74
|
+
/**
|
|
75
|
+
* Report high-level workflow stage transitions so users can see
|
|
76
|
+
* which phase of a long-running workflow is currently active.
|
|
77
|
+
* Key status values (started/complete/failed/waiting) also emit TUI toasts.
|
|
78
|
+
*/
|
|
79
|
+
reportStageProgress(stage: string, status: "started" | "running" | "complete" | "failed" | "waiting", detail?: string, meta?: ActivityMeta): void;
|
|
80
|
+
/**
|
|
81
|
+
* Emitted when the permission.ask hook fires — the system is blocked
|
|
82
|
+
* on user approval. Shows a prominent warning toast.
|
|
83
|
+
*/
|
|
84
|
+
reportWaitingForApproval(tool: string, _meta?: ActivityMeta): void;
|
|
85
|
+
/**
|
|
86
|
+
* Emitted when a user command begins execution.
|
|
87
|
+
* Shows a brief info toast so the user knows the system heard them.
|
|
88
|
+
*/
|
|
89
|
+
reportCommandStarted(command: string): void;
|
|
90
|
+
/**
|
|
91
|
+
* Emitted when a user command completes (session.idle fires after it).
|
|
92
|
+
* Shows a success toast with a note about file modifications.
|
|
93
|
+
*/
|
|
94
|
+
reportCommandCompleted(command: string, hasEdits: boolean): void;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=activity-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-reporter.d.ts","sourceRoot":"","sources":["../../src/services/activity-reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-reporter.test.d.ts","sourceRoot":"","sources":["../../src/services/activity-reporter.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type TelemetryEventType = "command.start" | "command.end" | "tool.call" | "tool.complete" | "agent.dispatch" | "agent.complete" | "approval.request" | "approval.resolve" | "run.complete" | "run.fail" | "policy.violation" | "patch.scored" | "contract.violation" | "agent.span.open" | "agent.span.close" | "budget.warning" | "budget.exhausted" | "deadlock.detected" | "scorecard.generated" | "supervisor.review";
|
|
1
|
+
export type TelemetryEventType = "command.start" | "command.end" | "tool.call" | "tool.complete" | "tool.failed" | "tool.retried" | "tool.fallback" | "cache.hit" | "stage.progress" | "agent.dispatch" | "agent.complete" | "approval.request" | "approval.resolve" | "run.complete" | "run.fail" | "policy.violation" | "patch.scored" | "contract.violation" | "agent.span.open" | "agent.span.close" | "budget.warning" | "budget.exhausted" | "deadlock.detected" | "scorecard.generated" | "supervisor.review";
|
|
2
2
|
export interface TelemetryEvent {
|
|
3
3
|
id: string;
|
|
4
4
|
ts: string;
|
|
@@ -15,6 +15,14 @@ export interface TelemetryEvent {
|
|
|
15
15
|
files?: string[];
|
|
16
16
|
cost_estimate?: number;
|
|
17
17
|
error_category?: string;
|
|
18
|
+
/** Short summary of the tool's input (no raw dumps) */
|
|
19
|
+
input_summary?: string;
|
|
20
|
+
/** Short summary of the tool's result */
|
|
21
|
+
result_summary?: string;
|
|
22
|
+
/** Workflow stage (e.g. "research", "plan", "execute") */
|
|
23
|
+
stage?: string;
|
|
24
|
+
/** Number of retry attempts consumed */
|
|
25
|
+
retry_count?: number;
|
|
18
26
|
/** Estimated input tokens (chars / 4) for this event. */
|
|
19
27
|
input_tokens?: number;
|
|
20
28
|
/** Estimated output tokens (chars / 4) for this event. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/services/telemetry.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,kBAAkB,GAC1B,eAAe,GACf,aAAa,GACb,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,cAAc,GACd,UAAU,GACV,kBAAkB,GAClB,cAAc,GAEd,oBAAoB,GACpB,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,kBAAkB,GAClB,mBAAmB,GACnB,qBAAqB,GAErB,mBAAmB,CAAA;AAEvB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,kBAAkB,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,cAAc,GAAG,IAAI,CAa1G;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,cAAc,EAAE,CAUrE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAE1E;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,SAAM,GAAG,cAAc,EAAE,CAuBxE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,cAAc,EAAE,CAI/E"}
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/services/telemetry.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,kBAAkB,GAC1B,eAAe,GACf,aAAa,GACb,WAAW,GACX,eAAe,GACf,aAAa,GACb,cAAc,GACd,eAAe,GACf,WAAW,GACX,gBAAgB,GAChB,gBAAgB,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,cAAc,GACd,UAAU,GACV,kBAAkB,GAClB,cAAc,GAEd,oBAAoB,GACpB,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,kBAAkB,GAClB,mBAAmB,GACnB,qBAAqB,GAErB,mBAAmB,CAAA;AAEvB,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,kBAAkB,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAA;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,cAAc,GAAG,IAAI,CAa1G;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,cAAc,EAAE,CAUrE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,CAE1E;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,SAAM,GAAG,cAAc,EAAE,CAuBxE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,cAAc,EAAE,CAI/E"}
|
package/dist/tools/delegate.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
2
|
import type { OpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
-
|
|
3
|
+
import { ActivityReporter } from "../services/activity-reporter";
|
|
4
|
+
export declare function createDelegateTool(client: OpencodeClient, reporter?: ActivityReporter | null): ToolDefinition;
|
|
4
5
|
//# sourceMappingURL=delegate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../../src/tools/delegate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../../src/tools/delegate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAWtD,OAAO,EAAE,gBAAgB,EAAa,MAAM,+BAA+B,CAAA;AAS3E,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,GAAG,cAAc,CA0Q7G"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
2
|
import type { OpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
-
|
|
3
|
+
import { ActivityReporter } from "../services/activity-reporter";
|
|
4
|
+
export declare function createRunPipelineTool(client: OpencodeClient, reporter?: ActivityReporter | null): ToolDefinition;
|
|
4
5
|
//# sourceMappingURL=run-pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-pipeline.d.ts","sourceRoot":"","sources":["../../src/tools/run-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"run-pipeline.d.ts","sourceRoot":"","sources":["../../src/tools/run-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGtD,OAAO,EAAE,gBAAgB,EAAa,MAAM,+BAA+B,CAAA;AA4B3E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,GAAG,cAAc,CA2JhH"}
|