@slock-ai/daemon 0.54.1 → 0.54.2
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-X366KJGT.js → chunk-7ZOPGUXT.js} +631 -340
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1950,6 +1950,7 @@ function unregisterAgentCredentialProxyForLaunch(input) {
|
|
|
1950
1950
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1951
1951
|
var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
1952
1952
|
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1953
|
+
var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
1953
1954
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1954
1955
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
1955
1956
|
"SLOCK_AGENT_CREDENTIAL_KEY"
|
|
@@ -1996,6 +1997,34 @@ function windowsUtf8Env() {
|
|
|
1996
1997
|
LC_ALL: "C.UTF-8"
|
|
1997
1998
|
};
|
|
1998
1999
|
}
|
|
2000
|
+
function posixLoopbackNoProxyPrelude() {
|
|
2001
|
+
return [
|
|
2002
|
+
`SLOCK_LOOPBACK_NO_PROXY=${shellSingleQuote(LOOPBACK_NO_PROXY)}`,
|
|
2003
|
+
`SLOCK_EXISTING_NO_PROXY="\${NO_PROXY:-}"`,
|
|
2004
|
+
`if [ -n "\${no_proxy:-}" ]; then SLOCK_EXISTING_NO_PROXY="\${SLOCK_EXISTING_NO_PROXY:+$SLOCK_EXISTING_NO_PROXY,}$no_proxy"; fi`,
|
|
2005
|
+
`NO_PROXY="\${SLOCK_LOOPBACK_NO_PROXY}\${SLOCK_EXISTING_NO_PROXY:+,$SLOCK_EXISTING_NO_PROXY}"`,
|
|
2006
|
+
`no_proxy="$NO_PROXY"`,
|
|
2007
|
+
"export NO_PROXY no_proxy"
|
|
2008
|
+
].join("\n");
|
|
2009
|
+
}
|
|
2010
|
+
function cmdLoopbackNoProxyLines() {
|
|
2011
|
+
return [
|
|
2012
|
+
`set "SLOCK_LOOPBACK_NO_PROXY=${LOOPBACK_NO_PROXY}"`,
|
|
2013
|
+
`set "SLOCK_EXISTING_NO_PROXY=%NO_PROXY%"`,
|
|
2014
|
+
`if defined no_proxy (if defined SLOCK_EXISTING_NO_PROXY (set "SLOCK_EXISTING_NO_PROXY=%SLOCK_EXISTING_NO_PROXY%,%no_proxy%") else set "SLOCK_EXISTING_NO_PROXY=%no_proxy%")`,
|
|
2015
|
+
`if defined SLOCK_EXISTING_NO_PROXY (set "NO_PROXY=%SLOCK_LOOPBACK_NO_PROXY%,%SLOCK_EXISTING_NO_PROXY%") else set "NO_PROXY=%SLOCK_LOOPBACK_NO_PROXY%"`,
|
|
2016
|
+
`set "no_proxy=%NO_PROXY%"`
|
|
2017
|
+
];
|
|
2018
|
+
}
|
|
2019
|
+
function powershellLoopbackNoProxyLines() {
|
|
2020
|
+
return [
|
|
2021
|
+
`$loopbackNoProxy = ${powershellSingleQuote(LOOPBACK_NO_PROXY)}`,
|
|
2022
|
+
"$existingNoProxy = @($env:NO_PROXY, $env:no_proxy) | Where-Object { $_ }",
|
|
2023
|
+
`if ($existingNoProxy.Count -gt 0) { $mergedNoProxy = "$loopbackNoProxy,$($existingNoProxy -join ',')" } else { $mergedNoProxy = $loopbackNoProxy }`,
|
|
2024
|
+
"$env:NO_PROXY = $mergedNoProxy",
|
|
2025
|
+
"$env:no_proxy = $mergedNoProxy"
|
|
2026
|
+
];
|
|
2027
|
+
}
|
|
1999
2028
|
function runtimeContextEnv(config) {
|
|
2000
2029
|
const ctx = config.runtimeContext;
|
|
2001
2030
|
if (!ctx) return {};
|
|
@@ -2045,6 +2074,7 @@ async function prepareCliTransport(ctx, extraEnv = {}, platform = process.platfo
|
|
|
2045
2074
|
const posixWrapper = path2.join(slockDir, "slock");
|
|
2046
2075
|
const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
|
|
2047
2076
|
const posixBody = `#!/usr/bin/env bash
|
|
2077
|
+
${posixLoopbackNoProxyPrelude()}
|
|
2048
2078
|
${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
|
|
2049
2079
|
`;
|
|
2050
2080
|
writeFileSync(posixWrapper, posixBody, { mode: 493 });
|
|
@@ -2061,6 +2091,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
|
2061
2091
|
"set LANG=C.UTF-8",
|
|
2062
2092
|
"set LC_ALL=C.UTF-8",
|
|
2063
2093
|
"chcp 65001 >NUL 2>NUL",
|
|
2094
|
+
...cmdLoopbackNoProxyLines(),
|
|
2064
2095
|
cmdCredentialLine.trimEnd(),
|
|
2065
2096
|
`"${process.execPath}" "${ctx.slockCliPath}" %*`,
|
|
2066
2097
|
""
|
|
@@ -2081,6 +2112,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
|
2081
2112
|
"$env:PYTHONUTF8 = '1'",
|
|
2082
2113
|
"$env:LANG = 'C.UTF-8'",
|
|
2083
2114
|
"$env:LC_ALL = 'C.UTF-8'",
|
|
2115
|
+
...powershellLoopbackNoProxyLines(),
|
|
2084
2116
|
...psCredentialLines,
|
|
2085
2117
|
`$node = ${powershellSingleQuote(process.execPath)}`,
|
|
2086
2118
|
`$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
|
|
@@ -2715,6 +2747,114 @@ import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_
|
|
|
2715
2747
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2716
2748
|
import os3 from "os";
|
|
2717
2749
|
import path5 from "path";
|
|
2750
|
+
|
|
2751
|
+
// src/runtimeTurnState.ts
|
|
2752
|
+
var RuntimeTurnState = class {
|
|
2753
|
+
currentTurnId = null;
|
|
2754
|
+
/**
|
|
2755
|
+
* Post-tool window where the app-server may not yet accept stdin steering.
|
|
2756
|
+
* Gate busy-mode delivery until turn/completed or next progress.
|
|
2757
|
+
*/
|
|
2758
|
+
steeringGateActive = false;
|
|
2759
|
+
reset() {
|
|
2760
|
+
this.currentTurnId = null;
|
|
2761
|
+
this.steeringGateActive = false;
|
|
2762
|
+
}
|
|
2763
|
+
get activeTurnId() {
|
|
2764
|
+
return this.currentTurnId;
|
|
2765
|
+
}
|
|
2766
|
+
get canSteerBusy() {
|
|
2767
|
+
return Boolean(this.currentTurnId && !this.steeringGateActive);
|
|
2768
|
+
}
|
|
2769
|
+
markTurnStarted(turnId) {
|
|
2770
|
+
if (turnId !== void 0 && turnId !== null) {
|
|
2771
|
+
this.currentTurnId = turnId;
|
|
2772
|
+
}
|
|
2773
|
+
this.steeringGateActive = false;
|
|
2774
|
+
}
|
|
2775
|
+
adoptTurnId(turnId) {
|
|
2776
|
+
this.currentTurnId = turnId;
|
|
2777
|
+
}
|
|
2778
|
+
markProgress() {
|
|
2779
|
+
this.steeringGateActive = false;
|
|
2780
|
+
}
|
|
2781
|
+
markToolBoundary() {
|
|
2782
|
+
this.steeringGateActive = true;
|
|
2783
|
+
}
|
|
2784
|
+
markTurnCompleted() {
|
|
2785
|
+
this.currentTurnId = null;
|
|
2786
|
+
this.steeringGateActive = false;
|
|
2787
|
+
}
|
|
2788
|
+
};
|
|
2789
|
+
|
|
2790
|
+
// src/drivers/codexTelemetrySidecar.ts
|
|
2791
|
+
function finiteNumber(value) {
|
|
2792
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2793
|
+
}
|
|
2794
|
+
function finiteString(value) {
|
|
2795
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2796
|
+
}
|
|
2797
|
+
function ratio(numerator, denominator) {
|
|
2798
|
+
if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
|
|
2799
|
+
return Number((numerator / denominator).toFixed(6));
|
|
2800
|
+
}
|
|
2801
|
+
function withDefined(attrs) {
|
|
2802
|
+
return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
|
|
2803
|
+
}
|
|
2804
|
+
function parseTokenUsageTelemetry(message) {
|
|
2805
|
+
const usage = message.params?.tokenUsage;
|
|
2806
|
+
const total = usage?.total;
|
|
2807
|
+
if (!total || typeof total !== "object") return null;
|
|
2808
|
+
const inputTokens = finiteNumber(total.inputTokens);
|
|
2809
|
+
const cachedInputTokens = finiteNumber(total.cachedInputTokens);
|
|
2810
|
+
const totalTokens = finiteNumber(total.totalTokens);
|
|
2811
|
+
const modelContextWindow = finiteNumber(usage.modelContextWindow);
|
|
2812
|
+
const attrs = withDefined({
|
|
2813
|
+
totalTokens,
|
|
2814
|
+
inputTokens,
|
|
2815
|
+
cachedInputTokens,
|
|
2816
|
+
outputTokens: finiteNumber(total.outputTokens),
|
|
2817
|
+
reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
|
|
2818
|
+
modelContextWindow,
|
|
2819
|
+
cachedInputRatio: ratio(cachedInputTokens, inputTokens),
|
|
2820
|
+
contextUtilization: ratio(totalTokens, modelContextWindow)
|
|
2821
|
+
});
|
|
2822
|
+
if (Object.keys(attrs).length === 0) return null;
|
|
2823
|
+
return { kind: "telemetry", name: "token_usage", attrs };
|
|
2824
|
+
}
|
|
2825
|
+
function parseRateLimitTelemetry(message) {
|
|
2826
|
+
const rateLimits = message.params?.rateLimits;
|
|
2827
|
+
const primary = rateLimits?.primary;
|
|
2828
|
+
if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
|
|
2829
|
+
const attrs = withDefined({
|
|
2830
|
+
limitId: finiteString(rateLimits.limitId),
|
|
2831
|
+
planType: finiteString(rateLimits.planType),
|
|
2832
|
+
usedPercent: finiteNumber(primary.usedPercent),
|
|
2833
|
+
windowDurationMins: finiteNumber(primary.windowDurationMins),
|
|
2834
|
+
resetsAt: finiteNumber(primary.resetsAt)
|
|
2835
|
+
});
|
|
2836
|
+
if (Object.keys(attrs).length === 0) return null;
|
|
2837
|
+
return { kind: "telemetry", name: "rate_limits", attrs };
|
|
2838
|
+
}
|
|
2839
|
+
function parseCodexTelemetryEvent(message) {
|
|
2840
|
+
switch (message.method) {
|
|
2841
|
+
case "thread/tokenUsage/updated":
|
|
2842
|
+
return parseTokenUsageTelemetry(message);
|
|
2843
|
+
case "account/rateLimits/updated":
|
|
2844
|
+
return parseRateLimitTelemetry(message);
|
|
2845
|
+
default:
|
|
2846
|
+
return null;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/drivers/codexEventNormalizer.ts
|
|
2851
|
+
function parseCodexJsonRpcLine(line) {
|
|
2852
|
+
try {
|
|
2853
|
+
return JSON.parse(line);
|
|
2854
|
+
} catch {
|
|
2855
|
+
return null;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2718
2858
|
function getCodexNotificationErrorMessage(params) {
|
|
2719
2859
|
const topLevelMessage = params?.message;
|
|
2720
2860
|
if (typeof topLevelMessage === "string" && topLevelMessage.trim()) {
|
|
@@ -2726,6 +2866,246 @@ function getCodexNotificationErrorMessage(params) {
|
|
|
2726
2866
|
}
|
|
2727
2867
|
return null;
|
|
2728
2868
|
}
|
|
2869
|
+
function joinReasoningText(item) {
|
|
2870
|
+
const summary = Array.isArray(item.summary) ? item.summary.filter((entry) => typeof entry === "string") : [];
|
|
2871
|
+
const content = Array.isArray(item.content) ? item.content.filter((entry) => typeof entry === "string") : [];
|
|
2872
|
+
return [...summary, ...content].join("\n").trim();
|
|
2873
|
+
}
|
|
2874
|
+
function rawResponseItemProgressEvent(message) {
|
|
2875
|
+
if (message.method !== "rawResponseItem/completed") return null;
|
|
2876
|
+
const item = message.params?.item ?? message.params?.responseItem ?? message.params?.rawItem ?? message.params;
|
|
2877
|
+
if (!item || typeof item !== "object") return null;
|
|
2878
|
+
const itemType = typeof item.type === "string" ? item.type : void 0;
|
|
2879
|
+
let payloadBytes;
|
|
2880
|
+
try {
|
|
2881
|
+
payloadBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
|
|
2882
|
+
} catch {
|
|
2883
|
+
payloadBytes = void 0;
|
|
2884
|
+
}
|
|
2885
|
+
return {
|
|
2886
|
+
kind: "internal_progress",
|
|
2887
|
+
source: "codex_raw_response_item",
|
|
2888
|
+
itemType,
|
|
2889
|
+
payloadBytes
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
var CodexEventNormalizer = class {
|
|
2893
|
+
mcpToolPrefix;
|
|
2894
|
+
currentThreadId = null;
|
|
2895
|
+
sessionAnnounced = false;
|
|
2896
|
+
streamedAgentMessageIds = /* @__PURE__ */ new Set();
|
|
2897
|
+
streamedReasoningIds = /* @__PURE__ */ new Set();
|
|
2898
|
+
turnState = new RuntimeTurnState();
|
|
2899
|
+
constructor(opts) {
|
|
2900
|
+
this.mcpToolPrefix = opts.mcpToolPrefix;
|
|
2901
|
+
}
|
|
2902
|
+
reset(opts = {}) {
|
|
2903
|
+
this.currentThreadId = opts.threadId ?? null;
|
|
2904
|
+
this.turnState.reset();
|
|
2905
|
+
this.sessionAnnounced = false;
|
|
2906
|
+
this.streamedAgentMessageIds.clear();
|
|
2907
|
+
this.streamedReasoningIds.clear();
|
|
2908
|
+
}
|
|
2909
|
+
get threadId() {
|
|
2910
|
+
return this.currentThreadId;
|
|
2911
|
+
}
|
|
2912
|
+
get activeTurnId() {
|
|
2913
|
+
return this.turnState.activeTurnId;
|
|
2914
|
+
}
|
|
2915
|
+
get canSteerBusy() {
|
|
2916
|
+
return this.turnState.canSteerBusy;
|
|
2917
|
+
}
|
|
2918
|
+
adoptThreadId(threadId) {
|
|
2919
|
+
this.currentThreadId = threadId;
|
|
2920
|
+
}
|
|
2921
|
+
normalizeMessage(message) {
|
|
2922
|
+
const events = [];
|
|
2923
|
+
if (message.result) {
|
|
2924
|
+
const thread = message.result.thread;
|
|
2925
|
+
if (thread && typeof thread.id === "string") {
|
|
2926
|
+
return this.handleThreadReady(thread.id, events);
|
|
2927
|
+
}
|
|
2928
|
+
const turn = message.result.turn;
|
|
2929
|
+
if (turn && typeof turn.id === "string") {
|
|
2930
|
+
this.turnState.adoptTurnId(turn.id);
|
|
2931
|
+
return { events };
|
|
2932
|
+
}
|
|
2933
|
+
if (typeof message.result.turnId === "string") {
|
|
2934
|
+
this.turnState.adoptTurnId(message.result.turnId);
|
|
2935
|
+
return { events };
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
if (message.error) {
|
|
2939
|
+
events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
|
|
2940
|
+
return { events };
|
|
2941
|
+
}
|
|
2942
|
+
const telemetry = parseCodexTelemetryEvent(message);
|
|
2943
|
+
if (telemetry) {
|
|
2944
|
+
events.push(telemetry);
|
|
2945
|
+
return { events };
|
|
2946
|
+
}
|
|
2947
|
+
const rawProgress = rawResponseItemProgressEvent(message);
|
|
2948
|
+
if (rawProgress) {
|
|
2949
|
+
events.push(rawProgress);
|
|
2950
|
+
return { events };
|
|
2951
|
+
}
|
|
2952
|
+
switch (message.method) {
|
|
2953
|
+
case "thread/started": {
|
|
2954
|
+
const threadId = message.params?.thread?.id;
|
|
2955
|
+
if (typeof threadId === "string") {
|
|
2956
|
+
return this.handleThreadReady(threadId, events);
|
|
2957
|
+
}
|
|
2958
|
+
break;
|
|
2959
|
+
}
|
|
2960
|
+
case "turn/started": {
|
|
2961
|
+
const turnId = message.params?.turn?.id;
|
|
2962
|
+
this.turnState.markTurnStarted(typeof turnId === "string" ? turnId : null);
|
|
2963
|
+
events.push({ kind: "thinking", text: "" });
|
|
2964
|
+
break;
|
|
2965
|
+
}
|
|
2966
|
+
case "item/agentMessage/delta": {
|
|
2967
|
+
const delta = message.params?.delta;
|
|
2968
|
+
const itemId = message.params?.itemId;
|
|
2969
|
+
if (typeof itemId === "string") {
|
|
2970
|
+
this.streamedAgentMessageIds.add(itemId);
|
|
2971
|
+
}
|
|
2972
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
2973
|
+
this.turnState.markProgress();
|
|
2974
|
+
events.push({ kind: "text", text: delta });
|
|
2975
|
+
}
|
|
2976
|
+
break;
|
|
2977
|
+
}
|
|
2978
|
+
case "item/reasoning/summaryTextDelta":
|
|
2979
|
+
case "item/reasoning/textDelta": {
|
|
2980
|
+
const delta = message.params?.delta;
|
|
2981
|
+
const itemId = message.params?.itemId;
|
|
2982
|
+
if (typeof itemId === "string") {
|
|
2983
|
+
this.streamedReasoningIds.add(itemId);
|
|
2984
|
+
}
|
|
2985
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
2986
|
+
this.turnState.markProgress();
|
|
2987
|
+
events.push({ kind: "thinking", text: delta });
|
|
2988
|
+
}
|
|
2989
|
+
break;
|
|
2990
|
+
}
|
|
2991
|
+
case "item/started":
|
|
2992
|
+
case "item/completed": {
|
|
2993
|
+
const item = message.params?.item;
|
|
2994
|
+
if (!item || typeof item !== "object" || typeof item.type !== "string") break;
|
|
2995
|
+
const itemType = item.type;
|
|
2996
|
+
const isStarted = message.method === "item/started";
|
|
2997
|
+
const isCompleted = message.method === "item/completed";
|
|
2998
|
+
switch (itemType) {
|
|
2999
|
+
case "reasoning":
|
|
3000
|
+
if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
|
|
3001
|
+
const text = joinReasoningText(item);
|
|
3002
|
+
if (text) {
|
|
3003
|
+
this.turnState.markProgress();
|
|
3004
|
+
events.push({ kind: "thinking", text });
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (isCompleted && typeof item.id === "string") {
|
|
3008
|
+
this.streamedReasoningIds.delete(item.id);
|
|
3009
|
+
}
|
|
3010
|
+
break;
|
|
3011
|
+
case "agentMessage":
|
|
3012
|
+
if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
|
|
3013
|
+
this.turnState.markProgress();
|
|
3014
|
+
events.push({ kind: "text", text: item.text });
|
|
3015
|
+
}
|
|
3016
|
+
if (isCompleted && typeof item.id === "string") {
|
|
3017
|
+
this.streamedAgentMessageIds.delete(item.id);
|
|
3018
|
+
}
|
|
3019
|
+
break;
|
|
3020
|
+
case "commandExecution":
|
|
3021
|
+
if (isStarted && typeof item.command === "string") {
|
|
3022
|
+
events.push({ kind: "tool_call", name: "shell", input: { command: item.command } });
|
|
3023
|
+
}
|
|
3024
|
+
if (isCompleted) {
|
|
3025
|
+
events.push({ kind: "tool_output", name: "shell" });
|
|
3026
|
+
this.turnState.markToolBoundary();
|
|
3027
|
+
}
|
|
3028
|
+
break;
|
|
3029
|
+
case "contextCompaction":
|
|
3030
|
+
if (isStarted) {
|
|
3031
|
+
events.push({ kind: "compaction_started" });
|
|
3032
|
+
}
|
|
3033
|
+
if (isCompleted) {
|
|
3034
|
+
events.push({ kind: "compaction_finished" });
|
|
3035
|
+
}
|
|
3036
|
+
break;
|
|
3037
|
+
case "fileChange":
|
|
3038
|
+
if (isStarted && Array.isArray(item.changes)) {
|
|
3039
|
+
for (const change of item.changes) {
|
|
3040
|
+
events.push({
|
|
3041
|
+
kind: "tool_call",
|
|
3042
|
+
name: "file_change",
|
|
3043
|
+
input: { path: change?.path, kind: change?.kind }
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
break;
|
|
3048
|
+
case "mcpToolCall":
|
|
3049
|
+
if (isStarted) {
|
|
3050
|
+
const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
|
|
3051
|
+
events.push({ kind: "tool_call", name: toolName, input: item.arguments });
|
|
3052
|
+
}
|
|
3053
|
+
if (isCompleted) {
|
|
3054
|
+
const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
|
|
3055
|
+
events.push({ kind: "tool_output", name: toolName });
|
|
3056
|
+
this.turnState.markToolBoundary();
|
|
3057
|
+
}
|
|
3058
|
+
break;
|
|
3059
|
+
case "collabAgentToolCall":
|
|
3060
|
+
if (isStarted) {
|
|
3061
|
+
events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
|
|
3062
|
+
}
|
|
3063
|
+
if (isCompleted) {
|
|
3064
|
+
this.turnState.markToolBoundary();
|
|
3065
|
+
}
|
|
3066
|
+
break;
|
|
3067
|
+
case "webSearch":
|
|
3068
|
+
if (isStarted) {
|
|
3069
|
+
events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
|
|
3070
|
+
}
|
|
3071
|
+
if (isCompleted) {
|
|
3072
|
+
this.turnState.markToolBoundary();
|
|
3073
|
+
}
|
|
3074
|
+
break;
|
|
3075
|
+
}
|
|
3076
|
+
break;
|
|
3077
|
+
}
|
|
3078
|
+
case "turn/completed": {
|
|
3079
|
+
const turn = message.params?.turn;
|
|
3080
|
+
if (turn?.status === "failed" && turn?.error?.message) {
|
|
3081
|
+
events.push({ kind: "error", message: turn.error.message });
|
|
3082
|
+
}
|
|
3083
|
+
this.turnState.markTurnCompleted();
|
|
3084
|
+
this.streamedAgentMessageIds.clear();
|
|
3085
|
+
this.streamedReasoningIds.clear();
|
|
3086
|
+
events.push({ kind: "turn_end", sessionId: this.currentThreadId || void 0 });
|
|
3087
|
+
break;
|
|
3088
|
+
}
|
|
3089
|
+
case "error":
|
|
3090
|
+
events.push({
|
|
3091
|
+
kind: "error",
|
|
3092
|
+
message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
|
|
3093
|
+
});
|
|
3094
|
+
break;
|
|
3095
|
+
}
|
|
3096
|
+
return { events };
|
|
3097
|
+
}
|
|
3098
|
+
handleThreadReady(threadId, events) {
|
|
3099
|
+
this.currentThreadId = threadId;
|
|
3100
|
+
if (!this.sessionAnnounced) {
|
|
3101
|
+
events.push({ kind: "session_init", sessionId: threadId });
|
|
3102
|
+
this.sessionAnnounced = true;
|
|
3103
|
+
}
|
|
3104
|
+
return { events, threadReady: threadId };
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
|
|
3108
|
+
// src/drivers/codex.ts
|
|
2729
3109
|
function ensureGitRepoForCodex(workingDirectory, deps = {}) {
|
|
2730
3110
|
const existsSyncFn = deps.existsSyncFn ?? existsSync4;
|
|
2731
3111
|
const execSyncFn = deps.execSyncFn ?? execSync;
|
|
@@ -2839,11 +3219,6 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
2839
3219
|
"Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
|
|
2840
3220
|
);
|
|
2841
3221
|
}
|
|
2842
|
-
function joinReasoningText(item) {
|
|
2843
|
-
const summary = Array.isArray(item.summary) ? item.summary.filter((entry) => typeof entry === "string") : [];
|
|
2844
|
-
const content = Array.isArray(item.content) ? item.content.filter((entry) => typeof entry === "string") : [];
|
|
2845
|
-
return [...summary, ...content].join("\n").trim();
|
|
2846
|
-
}
|
|
2847
3222
|
var CodexDriver = class {
|
|
2848
3223
|
id = "codex";
|
|
2849
3224
|
lifecycle = {
|
|
@@ -2919,7 +3294,10 @@ var CodexDriver = class {
|
|
|
2919
3294
|
cwd: ctx.workingDirectory,
|
|
2920
3295
|
approvalPolicy: "never",
|
|
2921
3296
|
sandbox: "danger-full-access",
|
|
2922
|
-
developerInstructions: ctx.standingPrompt
|
|
3297
|
+
developerInstructions: ctx.standingPrompt,
|
|
3298
|
+
// Raw response items are used only as payload-free liveness signals in
|
|
3299
|
+
// the daemon. They replace the previous transcript-mtime heuristic.
|
|
3300
|
+
experimentalRawEvents: true
|
|
2923
3301
|
};
|
|
2924
3302
|
if (ctx.config.model) {
|
|
2925
3303
|
threadParams.model = ctx.config.model;
|
|
@@ -2943,35 +3321,21 @@ var CodexDriver = class {
|
|
|
2943
3321
|
}
|
|
2944
3322
|
process = null;
|
|
2945
3323
|
requestId = 0;
|
|
2946
|
-
threadId = null;
|
|
2947
|
-
activeTurnId = null;
|
|
2948
3324
|
pendingInitialPrompt = null;
|
|
2949
3325
|
initializeRequestId = null;
|
|
2950
3326
|
pendingThreadRequest = null;
|
|
2951
3327
|
initialTurnStarted = false;
|
|
2952
|
-
|
|
2953
|
-
streamedAgentMessageIds = /* @__PURE__ */ new Set();
|
|
2954
|
-
streamedReasoningIds = /* @__PURE__ */ new Set();
|
|
2955
|
-
/**
|
|
2956
|
-
* Post-tool window where the app-server may not yet accept stdin steering.
|
|
2957
|
-
* Gate busy-mode delivery until turn/completed or next progress.
|
|
2958
|
-
*/
|
|
2959
|
-
steeringGateActive = false;
|
|
3328
|
+
normalizer = new CodexEventNormalizer({ mcpToolPrefix: this.mcpToolPrefix });
|
|
2960
3329
|
async spawn(ctx) {
|
|
2961
3330
|
ensureGitRepoForCodex(ctx.workingDirectory);
|
|
2962
3331
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
2963
3332
|
this.process = null;
|
|
2964
3333
|
this.requestId = 0;
|
|
2965
|
-
this.threadId = ctx.config.sessionId || null;
|
|
2966
|
-
this.activeTurnId = null;
|
|
2967
3334
|
this.pendingInitialPrompt = ctx.prompt;
|
|
2968
3335
|
this.initializeRequestId = null;
|
|
2969
3336
|
this.pendingThreadRequest = null;
|
|
2970
3337
|
this.initialTurnStarted = false;
|
|
2971
|
-
this.
|
|
2972
|
-
this.streamedAgentMessageIds.clear();
|
|
2973
|
-
this.streamedReasoningIds.clear();
|
|
2974
|
-
this.steeringGateActive = false;
|
|
3338
|
+
this.normalizer.reset({ threadId: ctx.config.sessionId || null });
|
|
2975
3339
|
const args = ["app-server", "--listen", "stdio://"];
|
|
2976
3340
|
args.push(...this.buildRuntimeActionsConfigArgs(ctx));
|
|
2977
3341
|
const { command, args: spawnArgs } = resolveCodexSpawn(args);
|
|
@@ -2992,10 +3356,8 @@ var CodexDriver = class {
|
|
|
2992
3356
|
return { process: proc };
|
|
2993
3357
|
}
|
|
2994
3358
|
parseLine(line) {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
message = JSON.parse(line);
|
|
2998
|
-
} catch {
|
|
3359
|
+
const message = parseCodexJsonRpcLine(line);
|
|
3360
|
+
if (!message) {
|
|
2999
3361
|
return [];
|
|
3000
3362
|
}
|
|
3001
3363
|
const events = [];
|
|
@@ -3009,194 +3371,34 @@ var CodexDriver = class {
|
|
|
3009
3371
|
}
|
|
3010
3372
|
return events;
|
|
3011
3373
|
}
|
|
3012
|
-
const thread = message.result.thread;
|
|
3013
|
-
if (thread && typeof thread.id === "string") {
|
|
3014
|
-
this.handleThreadReady(thread.id, events);
|
|
3015
|
-
return events;
|
|
3016
|
-
}
|
|
3017
|
-
const turn = message.result.turn;
|
|
3018
|
-
if (turn && typeof turn.id === "string") {
|
|
3019
|
-
this.activeTurnId = turn.id;
|
|
3020
|
-
return events;
|
|
3021
|
-
}
|
|
3022
|
-
if (typeof message.result.turnId === "string") {
|
|
3023
|
-
this.activeTurnId = message.result.turnId;
|
|
3024
|
-
return events;
|
|
3025
|
-
}
|
|
3026
3374
|
}
|
|
3027
|
-
if (message.error) {
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
this.pendingThreadRequest = null;
|
|
3031
|
-
}
|
|
3375
|
+
if (message.error && message.id === this.initializeRequestId) {
|
|
3376
|
+
this.initializeRequestId = null;
|
|
3377
|
+
this.pendingThreadRequest = null;
|
|
3032
3378
|
events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
|
|
3033
3379
|
return events;
|
|
3034
3380
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
if (typeof threadId === "string") {
|
|
3039
|
-
this.handleThreadReady(threadId, events);
|
|
3040
|
-
}
|
|
3041
|
-
break;
|
|
3042
|
-
}
|
|
3043
|
-
case "turn/started": {
|
|
3044
|
-
const turnId = message.params?.turn?.id;
|
|
3045
|
-
if (typeof turnId === "string") {
|
|
3046
|
-
this.activeTurnId = turnId;
|
|
3047
|
-
}
|
|
3048
|
-
this.steeringGateActive = false;
|
|
3049
|
-
events.push({ kind: "thinking", text: "" });
|
|
3050
|
-
break;
|
|
3051
|
-
}
|
|
3052
|
-
case "item/agentMessage/delta": {
|
|
3053
|
-
const delta = message.params?.delta;
|
|
3054
|
-
const itemId = message.params?.itemId;
|
|
3055
|
-
if (typeof itemId === "string") {
|
|
3056
|
-
this.streamedAgentMessageIds.add(itemId);
|
|
3057
|
-
}
|
|
3058
|
-
if (typeof delta === "string" && delta.length > 0) {
|
|
3059
|
-
this.steeringGateActive = false;
|
|
3060
|
-
events.push({ kind: "text", text: delta });
|
|
3061
|
-
}
|
|
3062
|
-
break;
|
|
3063
|
-
}
|
|
3064
|
-
case "item/reasoning/summaryTextDelta":
|
|
3065
|
-
case "item/reasoning/textDelta": {
|
|
3066
|
-
const delta = message.params?.delta;
|
|
3067
|
-
const itemId = message.params?.itemId;
|
|
3068
|
-
if (typeof itemId === "string") {
|
|
3069
|
-
this.streamedReasoningIds.add(itemId);
|
|
3070
|
-
}
|
|
3071
|
-
if (typeof delta === "string" && delta.length > 0) {
|
|
3072
|
-
this.steeringGateActive = false;
|
|
3073
|
-
events.push({ kind: "thinking", text: delta });
|
|
3074
|
-
}
|
|
3075
|
-
break;
|
|
3076
|
-
}
|
|
3077
|
-
case "item/started":
|
|
3078
|
-
case "item/completed": {
|
|
3079
|
-
const item = message.params?.item;
|
|
3080
|
-
if (!item || typeof item !== "object" || typeof item.type !== "string") break;
|
|
3081
|
-
const itemType = item.type;
|
|
3082
|
-
const isStarted = message.method === "item/started";
|
|
3083
|
-
const isCompleted = message.method === "item/completed";
|
|
3084
|
-
switch (itemType) {
|
|
3085
|
-
case "reasoning":
|
|
3086
|
-
if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
|
|
3087
|
-
const text = joinReasoningText(item);
|
|
3088
|
-
if (text) {
|
|
3089
|
-
this.steeringGateActive = false;
|
|
3090
|
-
events.push({ kind: "thinking", text });
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
if (isCompleted && typeof item.id === "string") {
|
|
3094
|
-
this.streamedReasoningIds.delete(item.id);
|
|
3095
|
-
}
|
|
3096
|
-
break;
|
|
3097
|
-
case "agentMessage":
|
|
3098
|
-
if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
|
|
3099
|
-
this.steeringGateActive = false;
|
|
3100
|
-
events.push({ kind: "text", text: item.text });
|
|
3101
|
-
}
|
|
3102
|
-
if (isCompleted && typeof item.id === "string") {
|
|
3103
|
-
this.streamedAgentMessageIds.delete(item.id);
|
|
3104
|
-
}
|
|
3105
|
-
break;
|
|
3106
|
-
case "commandExecution":
|
|
3107
|
-
if (isStarted && typeof item.command === "string") {
|
|
3108
|
-
events.push({ kind: "tool_call", name: "shell", input: { command: item.command } });
|
|
3109
|
-
}
|
|
3110
|
-
if (isCompleted) {
|
|
3111
|
-
events.push({ kind: "tool_output", name: "shell" });
|
|
3112
|
-
this.steeringGateActive = true;
|
|
3113
|
-
}
|
|
3114
|
-
break;
|
|
3115
|
-
case "contextCompaction":
|
|
3116
|
-
if (isStarted) {
|
|
3117
|
-
events.push({ kind: "compaction_started" });
|
|
3118
|
-
}
|
|
3119
|
-
if (isCompleted) {
|
|
3120
|
-
events.push({ kind: "compaction_finished" });
|
|
3121
|
-
}
|
|
3122
|
-
break;
|
|
3123
|
-
case "fileChange":
|
|
3124
|
-
if (isStarted && Array.isArray(item.changes)) {
|
|
3125
|
-
for (const change of item.changes) {
|
|
3126
|
-
events.push({
|
|
3127
|
-
kind: "tool_call",
|
|
3128
|
-
name: "file_change",
|
|
3129
|
-
input: { path: change?.path, kind: change?.kind }
|
|
3130
|
-
});
|
|
3131
|
-
}
|
|
3132
|
-
}
|
|
3133
|
-
break;
|
|
3134
|
-
case "mcpToolCall":
|
|
3135
|
-
if (isStarted) {
|
|
3136
|
-
const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
|
|
3137
|
-
events.push({ kind: "tool_call", name: toolName, input: item.arguments });
|
|
3138
|
-
}
|
|
3139
|
-
if (isCompleted) {
|
|
3140
|
-
const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
|
|
3141
|
-
events.push({ kind: "tool_output", name: toolName });
|
|
3142
|
-
this.steeringGateActive = true;
|
|
3143
|
-
}
|
|
3144
|
-
break;
|
|
3145
|
-
case "collabAgentToolCall":
|
|
3146
|
-
if (isStarted) {
|
|
3147
|
-
events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
|
|
3148
|
-
}
|
|
3149
|
-
if (isCompleted) {
|
|
3150
|
-
this.steeringGateActive = true;
|
|
3151
|
-
}
|
|
3152
|
-
break;
|
|
3153
|
-
case "webSearch":
|
|
3154
|
-
if (isStarted) {
|
|
3155
|
-
events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
|
|
3156
|
-
}
|
|
3157
|
-
if (isCompleted) {
|
|
3158
|
-
this.steeringGateActive = true;
|
|
3159
|
-
}
|
|
3160
|
-
break;
|
|
3161
|
-
}
|
|
3162
|
-
break;
|
|
3163
|
-
}
|
|
3164
|
-
case "turn/completed": {
|
|
3165
|
-
const turn = message.params?.turn;
|
|
3166
|
-
if (turn?.status === "failed" && turn?.error?.message) {
|
|
3167
|
-
events.push({ kind: "error", message: turn.error.message });
|
|
3168
|
-
}
|
|
3169
|
-
this.activeTurnId = null;
|
|
3170
|
-
this.streamedAgentMessageIds.clear();
|
|
3171
|
-
this.streamedReasoningIds.clear();
|
|
3172
|
-
this.steeringGateActive = false;
|
|
3173
|
-
events.push({ kind: "turn_end", sessionId: this.threadId || void 0 });
|
|
3174
|
-
break;
|
|
3175
|
-
}
|
|
3176
|
-
case "error":
|
|
3177
|
-
events.push({
|
|
3178
|
-
kind: "error",
|
|
3179
|
-
message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
|
|
3180
|
-
});
|
|
3181
|
-
break;
|
|
3381
|
+
const result = this.normalizer.normalizeMessage(message);
|
|
3382
|
+
if (result.threadReady) {
|
|
3383
|
+
this.startInitialTurn();
|
|
3182
3384
|
}
|
|
3183
|
-
return events;
|
|
3385
|
+
return result.events;
|
|
3184
3386
|
}
|
|
3185
3387
|
encodeStdinMessage(text, sessionId, opts) {
|
|
3186
|
-
if (!this.threadId && sessionId) {
|
|
3187
|
-
this.
|
|
3388
|
+
if (!this.normalizer.threadId && sessionId) {
|
|
3389
|
+
this.normalizer.adoptThreadId(sessionId);
|
|
3188
3390
|
}
|
|
3189
|
-
if (!this.threadId) return null;
|
|
3391
|
+
if (!this.normalizer.threadId) return null;
|
|
3190
3392
|
const mode = opts?.mode || "busy";
|
|
3191
3393
|
if (mode === "busy") {
|
|
3192
|
-
if (!this.
|
|
3394
|
+
if (!this.normalizer.canSteerBusy) return null;
|
|
3193
3395
|
return JSON.stringify({
|
|
3194
3396
|
jsonrpc: "2.0",
|
|
3195
3397
|
id: this.nextRequestId(),
|
|
3196
3398
|
method: "turn/steer",
|
|
3197
3399
|
params: {
|
|
3198
|
-
threadId: this.threadId,
|
|
3199
|
-
expectedTurnId: this.activeTurnId,
|
|
3400
|
+
threadId: this.normalizer.threadId,
|
|
3401
|
+
expectedTurnId: this.normalizer.activeTurnId,
|
|
3200
3402
|
input: [{ type: "text", text }]
|
|
3201
3403
|
}
|
|
3202
3404
|
});
|
|
@@ -3206,7 +3408,7 @@ var CodexDriver = class {
|
|
|
3206
3408
|
id: this.nextRequestId(),
|
|
3207
3409
|
method: "turn/start",
|
|
3208
3410
|
params: {
|
|
3209
|
-
threadId: this.threadId,
|
|
3411
|
+
threadId: this.normalizer.threadId,
|
|
3210
3412
|
input: [{ type: "text", text }]
|
|
3211
3413
|
}
|
|
3212
3414
|
});
|
|
@@ -3226,21 +3428,13 @@ var CodexDriver = class {
|
|
|
3226
3428
|
this.requestId += 1;
|
|
3227
3429
|
return this.requestId;
|
|
3228
3430
|
}
|
|
3229
|
-
handleThreadReady(threadId, events) {
|
|
3230
|
-
this.threadId = threadId;
|
|
3231
|
-
if (!this.sessionAnnounced) {
|
|
3232
|
-
events.push({ kind: "session_init", sessionId: threadId });
|
|
3233
|
-
this.sessionAnnounced = true;
|
|
3234
|
-
}
|
|
3235
|
-
this.startInitialTurn();
|
|
3236
|
-
}
|
|
3237
3431
|
startInitialTurn() {
|
|
3238
|
-
if (this.initialTurnStarted || !this.pendingInitialPrompt || !this.threadId) return;
|
|
3432
|
+
if (this.initialTurnStarted || !this.pendingInitialPrompt || !this.normalizer.threadId) return;
|
|
3239
3433
|
this.initialTurnStarted = true;
|
|
3240
3434
|
const prompt = this.pendingInitialPrompt;
|
|
3241
3435
|
this.pendingInitialPrompt = null;
|
|
3242
3436
|
this.sendRequest("turn/start", {
|
|
3243
|
-
threadId: this.threadId,
|
|
3437
|
+
threadId: this.normalizer.threadId,
|
|
3244
3438
|
input: [{ type: "text", text: prompt }]
|
|
3245
3439
|
});
|
|
3246
3440
|
}
|
|
@@ -5079,6 +5273,87 @@ function redactUrlQuery(value) {
|
|
|
5079
5273
|
}
|
|
5080
5274
|
}
|
|
5081
5275
|
|
|
5276
|
+
// src/runtimeProgressState.ts
|
|
5277
|
+
var RuntimeProgressState = class {
|
|
5278
|
+
lastEventAtMs;
|
|
5279
|
+
lastEventKindValue = null;
|
|
5280
|
+
staleSinceMs = null;
|
|
5281
|
+
constructor(nowMs = Date.now()) {
|
|
5282
|
+
this.lastEventAtMs = nowMs;
|
|
5283
|
+
}
|
|
5284
|
+
get lastEventAt() {
|
|
5285
|
+
return this.lastEventAtMs;
|
|
5286
|
+
}
|
|
5287
|
+
get lastEventKind() {
|
|
5288
|
+
return this.lastEventKindValue;
|
|
5289
|
+
}
|
|
5290
|
+
get staleSince() {
|
|
5291
|
+
return this.staleSinceMs;
|
|
5292
|
+
}
|
|
5293
|
+
get isStale() {
|
|
5294
|
+
return this.staleSinceMs !== null;
|
|
5295
|
+
}
|
|
5296
|
+
ageMs(nowMs = Date.now()) {
|
|
5297
|
+
return nowMs - this.lastEventAtMs;
|
|
5298
|
+
}
|
|
5299
|
+
noteRuntimeEvent(eventKind, nowMs = Date.now()) {
|
|
5300
|
+
this.lastEventAtMs = nowMs;
|
|
5301
|
+
this.lastEventKindValue = eventKind ?? null;
|
|
5302
|
+
this.staleSinceMs = null;
|
|
5303
|
+
}
|
|
5304
|
+
noteInternalProgress(observedAtMs = Date.now()) {
|
|
5305
|
+
this.lastEventAtMs = observedAtMs;
|
|
5306
|
+
this.staleSinceMs = null;
|
|
5307
|
+
}
|
|
5308
|
+
markStale(nowMs = Date.now()) {
|
|
5309
|
+
this.staleSinceMs ??= nowMs;
|
|
5310
|
+
return this.staleSinceMs;
|
|
5311
|
+
}
|
|
5312
|
+
};
|
|
5313
|
+
|
|
5314
|
+
// src/runtimeNotificationState.ts
|
|
5315
|
+
var RuntimeNotificationState = class {
|
|
5316
|
+
timerValue = null;
|
|
5317
|
+
pendingCountValue = 0;
|
|
5318
|
+
get pendingCount() {
|
|
5319
|
+
return this.pendingCountValue;
|
|
5320
|
+
}
|
|
5321
|
+
get timer() {
|
|
5322
|
+
return this.timerValue;
|
|
5323
|
+
}
|
|
5324
|
+
get hasTimer() {
|
|
5325
|
+
return this.timerValue !== null;
|
|
5326
|
+
}
|
|
5327
|
+
add(count = 1) {
|
|
5328
|
+
this.pendingCountValue += count;
|
|
5329
|
+
return this.pendingCountValue;
|
|
5330
|
+
}
|
|
5331
|
+
clearPending() {
|
|
5332
|
+
this.pendingCountValue = 0;
|
|
5333
|
+
}
|
|
5334
|
+
clearTimer() {
|
|
5335
|
+
if (this.timerValue) {
|
|
5336
|
+
clearTimeout(this.timerValue);
|
|
5337
|
+
this.timerValue = null;
|
|
5338
|
+
}
|
|
5339
|
+
}
|
|
5340
|
+
clear() {
|
|
5341
|
+
this.clearPending();
|
|
5342
|
+
this.clearTimer();
|
|
5343
|
+
}
|
|
5344
|
+
schedule(callback, delayMs) {
|
|
5345
|
+
if (this.timerValue) return false;
|
|
5346
|
+
this.timerValue = setTimeout(callback, delayMs);
|
|
5347
|
+
return true;
|
|
5348
|
+
}
|
|
5349
|
+
takePendingAndClearTimer() {
|
|
5350
|
+
const count = this.pendingCountValue;
|
|
5351
|
+
this.pendingCountValue = 0;
|
|
5352
|
+
this.clearTimer();
|
|
5353
|
+
return count;
|
|
5354
|
+
}
|
|
5355
|
+
};
|
|
5356
|
+
|
|
5082
5357
|
// src/agentProcessManager.ts
|
|
5083
5358
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
5084
5359
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
@@ -5297,6 +5572,36 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
5297
5572
|
}
|
|
5298
5573
|
return ref;
|
|
5299
5574
|
}
|
|
5575
|
+
function classifySpawnFailure(error) {
|
|
5576
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
5577
|
+
const lower = detail.toLowerCase();
|
|
5578
|
+
if (lower.includes("agent credential proxy") && lower.includes("failed to bind")) {
|
|
5579
|
+
return {
|
|
5580
|
+
reason: "agent_proxy_bind_failed",
|
|
5581
|
+
detail,
|
|
5582
|
+
userMessage: "Local agent proxy could not start. Check if another daemon or service is using the required local port."
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5585
|
+
if (lower.includes("runner_credential_mint") || error instanceof RunnerCredentialMintError) {
|
|
5586
|
+
return {
|
|
5587
|
+
reason: "runner_credential_mint_failed",
|
|
5588
|
+
detail,
|
|
5589
|
+
userMessage: "Runner credential mint failed. Ensure the server is deployed and the daemon binary is compatible."
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
if (lower.includes("enoent") || lower.includes("cannot resolve") || lower.includes("not found")) {
|
|
5593
|
+
return {
|
|
5594
|
+
reason: "runtime_not_found",
|
|
5595
|
+
detail,
|
|
5596
|
+
userMessage: "Runtime executable not found. Ensure the required CLI is installed and available on PATH."
|
|
5597
|
+
};
|
|
5598
|
+
}
|
|
5599
|
+
return {
|
|
5600
|
+
reason: "runtime_spawn_failed",
|
|
5601
|
+
detail,
|
|
5602
|
+
userMessage: `Start failed: ${detail}`
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5300
5605
|
function formatSenderHandle(message) {
|
|
5301
5606
|
return message.sender_description ? `@${message.sender_name} \u2014 ${message.sender_description}` : `@${message.sender_name}`;
|
|
5302
5607
|
}
|
|
@@ -5945,26 +6250,29 @@ function hasDirectStdinRecoveryEvidence(ap) {
|
|
|
5945
6250
|
(text) => /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text)
|
|
5946
6251
|
);
|
|
5947
6252
|
}
|
|
5948
|
-
function
|
|
5949
|
-
if (!ap.sessionId) return
|
|
6253
|
+
function resumeSessionRecoveryReason(ap) {
|
|
6254
|
+
if (!ap.sessionId) return null;
|
|
5950
6255
|
const candidates = [
|
|
5951
6256
|
ap.lastRuntimeError,
|
|
5952
6257
|
...ap.recentStderr
|
|
5953
6258
|
].filter((value) => !!value);
|
|
5954
6259
|
if (ap.driver.id === "claude") {
|
|
5955
|
-
return candidates.some((text) => /No conversation found with session ID/i.test(text));
|
|
6260
|
+
return candidates.some((text) => /No conversation found with session ID/i.test(text)) ? "missing" : null;
|
|
5956
6261
|
}
|
|
5957
6262
|
if (ap.driver.id === "opencode") {
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
6263
|
+
if (candidates.some((text) => /Session not found/i.test(text) && text.includes(ap.sessionId))) return "missing";
|
|
6264
|
+
if (candidates.some(isOpenCodeReplayRejectedByProvider)) return "provider_replay_rejected";
|
|
6265
|
+
return null;
|
|
5961
6266
|
}
|
|
5962
6267
|
if (ap.driver.id === "gemini") {
|
|
5963
6268
|
return candidates.some(
|
|
5964
6269
|
(text) => /Error resuming session:\s*Invalid session identifier/i.test(text) && text.includes(ap.sessionId)
|
|
5965
|
-
);
|
|
6270
|
+
) ? "missing" : null;
|
|
5966
6271
|
}
|
|
5967
|
-
return
|
|
6272
|
+
return null;
|
|
6273
|
+
}
|
|
6274
|
+
function isOpenCodeReplayRejectedByProvider(text) {
|
|
6275
|
+
return /Invalid request:\s*the message at position \d+ with role ['"]?assistant['"]? must not be empty/i.test(text);
|
|
5968
6276
|
}
|
|
5969
6277
|
function resumeSessionRuntimeLabel(runtimeId) {
|
|
5970
6278
|
if (runtimeId === "opencode") return "OpenCode";
|
|
@@ -6021,7 +6329,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
|
6021
6329
|
launchId: ap.launchId || void 0,
|
|
6022
6330
|
sessionIdPresent: Boolean(ap.sessionId),
|
|
6023
6331
|
inboxCount: ap.inbox.length,
|
|
6024
|
-
pendingNotificationCount: ap.
|
|
6332
|
+
pendingNotificationCount: ap.notifications.pendingCount,
|
|
6025
6333
|
processPidPresent: typeof ap.process.pid === "number",
|
|
6026
6334
|
busyDeliveryMode: ap.driver.busyDeliveryMode,
|
|
6027
6335
|
supportsStdinNotification: ap.driver.supportsStdinNotification,
|
|
@@ -6035,7 +6343,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
|
6035
6343
|
};
|
|
6036
6344
|
}
|
|
6037
6345
|
function classifyRuntimeStallReason(ap) {
|
|
6038
|
-
if (ap.
|
|
6346
|
+
if (ap.runtimeProgress.lastEventKind === "tool_output" && ap.gatedSteering.outstandingToolUses === 0) {
|
|
6039
6347
|
return "harness_post_tool_silent_wedge";
|
|
6040
6348
|
}
|
|
6041
6349
|
return "no_runtime_events";
|
|
@@ -6766,16 +7074,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6766
7074
|
startupUnreadSummary: unreadSummary,
|
|
6767
7075
|
startupResumePrompt: resumePrompt,
|
|
6768
7076
|
isIdle: false,
|
|
6769
|
-
|
|
6770
|
-
pendingNotificationCount: 0,
|
|
7077
|
+
notifications: new RuntimeNotificationState(),
|
|
6771
7078
|
activityHeartbeat: null,
|
|
6772
7079
|
startupTimeoutTimer: null,
|
|
6773
7080
|
startupTimedOut: false,
|
|
6774
7081
|
compactionWatchdog: null,
|
|
6775
7082
|
compactionStartedAt: null,
|
|
6776
|
-
|
|
6777
|
-
lastRuntimeEventKind: null,
|
|
6778
|
-
runtimeProgressStaleSince: null,
|
|
7083
|
+
runtimeProgress: new RuntimeProgressState(Date.now()),
|
|
6779
7084
|
runtimeTraceSpan: null,
|
|
6780
7085
|
runtimeTraceCounters: createRuntimeTraceCounters(),
|
|
6781
7086
|
lastActivity: "",
|
|
@@ -6884,7 +7189,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6884
7189
|
clean_exit: code === 0,
|
|
6885
7190
|
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
6886
7191
|
inbox_count: current?.inbox.length ?? 0,
|
|
6887
|
-
pending_notification_count: current?.
|
|
7192
|
+
pending_notification_count: current?.notifications.pendingCount ?? 0,
|
|
6888
7193
|
...this.processExitTraceAttrs.get(proc)
|
|
6889
7194
|
});
|
|
6890
7195
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
@@ -6893,9 +7198,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6893
7198
|
if (this.agents.has(agentId)) {
|
|
6894
7199
|
const ap = this.agents.get(agentId);
|
|
6895
7200
|
if (ap.process !== proc) return;
|
|
6896
|
-
|
|
6897
|
-
clearTimeout(ap.notificationTimer);
|
|
6898
|
-
}
|
|
7201
|
+
ap.notifications.clearTimer();
|
|
6899
7202
|
if (ap.pendingTrajectory?.timer) {
|
|
6900
7203
|
clearTimeout(ap.pendingTrajectory.timer);
|
|
6901
7204
|
}
|
|
@@ -6909,7 +7212,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6909
7212
|
const expectedTermination = Boolean(ap.expectedTerminationReason) || ap.startupTimedOut;
|
|
6910
7213
|
const processEndedCleanly = finalCode === 0 || expectedTermination && !ap.lastRuntimeError;
|
|
6911
7214
|
const terminalFailureDetail = processEndedCleanly ? null : classifyTerminalFailure(ap);
|
|
6912
|
-
const
|
|
7215
|
+
const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
|
|
7216
|
+
const shouldColdStartResumeSession = resumeRecoveryReason !== null;
|
|
6913
7217
|
const summary = summarizeCrash(finalCode, finalSignal);
|
|
6914
7218
|
this.endRuntimeTrace(ap, processEndedCleanly ? "ok" : "error", {
|
|
6915
7219
|
outcome: processEndedCleanly ? "process-exit" : "process-crash",
|
|
@@ -6923,20 +7227,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6923
7227
|
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
6924
7228
|
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
6925
7229
|
this.agents.delete(agentId);
|
|
6926
|
-
if (
|
|
7230
|
+
if (shouldColdStartResumeSession) {
|
|
6927
7231
|
const staleSessionId = ap.sessionId;
|
|
6928
7232
|
const runtimeLabel = resumeSessionRuntimeLabel(ap.driver.id);
|
|
6929
7233
|
const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
|
|
7234
|
+
const reasonText = resumeRecoveryReason === "provider_replay_rejected" ? "was rejected by the provider during replay" : "is unavailable locally";
|
|
7235
|
+
const activityText = resumeRecoveryReason === "provider_replay_rejected" ? `Stored ${runtimeLabel} session replay rejected; cold-starting a new session\u2026` : `Stored ${runtimeLabel} session missing; cold-starting a new session\u2026`;
|
|
6930
7236
|
logger.warn(
|
|
6931
|
-
`[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId}
|
|
7237
|
+
`[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} ${reasonText}; falling back to cold start`
|
|
6932
7238
|
);
|
|
6933
7239
|
this.broadcastActivity(
|
|
6934
7240
|
agentId,
|
|
6935
7241
|
"working",
|
|
6936
|
-
|
|
7242
|
+
activityText,
|
|
6937
7243
|
[{
|
|
6938
7244
|
kind: "text",
|
|
6939
|
-
text: `Stored ${runtimeLabel} session ${staleSessionId}
|
|
7245
|
+
text: `Stored ${runtimeLabel} session ${staleSessionId} ${reasonText}. Falling back to a cold start; earlier runtime context may not be restored.`
|
|
6940
7246
|
}]
|
|
6941
7247
|
);
|
|
6942
7248
|
this.startAgent(
|
|
@@ -7205,15 +7511,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7205
7511
|
enqueueRuntimeProfileNotification(agentId, ap, message, kind, key) {
|
|
7206
7512
|
ap.inbox.push(message);
|
|
7207
7513
|
if (ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
7208
|
-
ap.
|
|
7514
|
+
ap.notifications.add();
|
|
7209
7515
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7210
7516
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
7211
7517
|
reason: "runtime_profile",
|
|
7212
7518
|
kind,
|
|
7213
7519
|
pendingMessages: ap.inbox.length
|
|
7214
7520
|
});
|
|
7215
|
-
} else if (!ap.
|
|
7216
|
-
ap.
|
|
7521
|
+
} else if (!ap.notifications.hasTimer) {
|
|
7522
|
+
ap.notifications.schedule(() => {
|
|
7217
7523
|
this.sendStdinNotification(agentId);
|
|
7218
7524
|
}, 3e3);
|
|
7219
7525
|
}
|
|
@@ -7228,7 +7534,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7228
7534
|
session_id_present: Boolean(ap.sessionId),
|
|
7229
7535
|
launchId: ap.launchId || void 0,
|
|
7230
7536
|
inbox_count: ap.inbox.length,
|
|
7231
|
-
pending_notification_count: ap.
|
|
7537
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
7232
7538
|
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
7233
7539
|
supports_stdin_notification: ap.driver.supportsStdinNotification
|
|
7234
7540
|
});
|
|
@@ -7266,9 +7572,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7266
7572
|
}
|
|
7267
7573
|
return;
|
|
7268
7574
|
}
|
|
7269
|
-
|
|
7270
|
-
clearTimeout(ap.notificationTimer);
|
|
7271
|
-
}
|
|
7575
|
+
ap.notifications.clearTimer();
|
|
7272
7576
|
if (ap.activityHeartbeat) {
|
|
7273
7577
|
clearInterval(ap.activityHeartbeat);
|
|
7274
7578
|
}
|
|
@@ -7449,11 +7753,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7449
7753
|
return true;
|
|
7450
7754
|
}
|
|
7451
7755
|
if (ap.gatedSteering.compacting) {
|
|
7452
|
-
ap.
|
|
7453
|
-
|
|
7454
|
-
clearTimeout(ap.notificationTimer);
|
|
7455
|
-
ap.notificationTimer = null;
|
|
7456
|
-
}
|
|
7756
|
+
ap.notifications.add();
|
|
7757
|
+
ap.notifications.clearTimer();
|
|
7457
7758
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7458
7759
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
7459
7760
|
reason: "compaction_boundary",
|
|
@@ -7461,7 +7762,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7461
7762
|
});
|
|
7462
7763
|
}
|
|
7463
7764
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_buffered", {
|
|
7464
|
-
pendingNotificationCount: ap.
|
|
7765
|
+
pendingNotificationCount: ap.notifications.pendingCount,
|
|
7465
7766
|
pendingMessages: ap.inbox.length,
|
|
7466
7767
|
busyDeliveryMode: ap.driver.busyDeliveryMode
|
|
7467
7768
|
});
|
|
@@ -7473,16 +7774,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7473
7774
|
session_id_present: true,
|
|
7474
7775
|
launchId: ap.launchId || void 0,
|
|
7475
7776
|
inbox_count: ap.inbox.length,
|
|
7476
|
-
pending_notification_count: ap.
|
|
7777
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
7477
7778
|
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
7478
7779
|
notification_timer_present: false
|
|
7479
7780
|
}));
|
|
7480
7781
|
return true;
|
|
7481
7782
|
}
|
|
7482
7783
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7483
|
-
ap.
|
|
7484
|
-
if (!ap.
|
|
7485
|
-
ap.
|
|
7784
|
+
ap.notifications.add();
|
|
7785
|
+
if (!ap.notifications.hasTimer) {
|
|
7786
|
+
ap.notifications.schedule(() => {
|
|
7486
7787
|
this.sendStdinNotification(agentId);
|
|
7487
7788
|
}, 3e3);
|
|
7488
7789
|
}
|
|
@@ -7498,14 +7799,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7498
7799
|
session_id_present: true,
|
|
7499
7800
|
launchId: ap.launchId || void 0,
|
|
7500
7801
|
inbox_count: ap.inbox.length,
|
|
7501
|
-
pending_notification_count: ap.
|
|
7502
|
-
notification_timer_present:
|
|
7802
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
7803
|
+
notification_timer_present: ap.notifications.hasTimer
|
|
7503
7804
|
}));
|
|
7504
7805
|
return true;
|
|
7505
7806
|
}
|
|
7506
|
-
ap.
|
|
7507
|
-
if (!ap.
|
|
7508
|
-
ap.
|
|
7807
|
+
ap.notifications.add();
|
|
7808
|
+
if (!ap.notifications.hasTimer) {
|
|
7809
|
+
ap.notifications.schedule(() => {
|
|
7509
7810
|
this.sendStdinNotification(agentId);
|
|
7510
7811
|
}, 3e3);
|
|
7511
7812
|
}
|
|
@@ -7516,8 +7817,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7516
7817
|
runtime: ap.config.runtime,
|
|
7517
7818
|
session_id_present: true,
|
|
7518
7819
|
inbox_count: ap.inbox.length,
|
|
7519
|
-
pending_notification_count: ap.
|
|
7520
|
-
notification_timer_present:
|
|
7820
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
7821
|
+
notification_timer_present: ap.notifications.hasTimer
|
|
7521
7822
|
}));
|
|
7522
7823
|
return true;
|
|
7523
7824
|
}
|
|
@@ -8312,40 +8613,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8312
8613
|
this.startRuntimeTrace(agentId, ap, "runtime-progress").addEvent(name, attrs);
|
|
8313
8614
|
}
|
|
8314
8615
|
noteRuntimeProgress(ap, eventKind) {
|
|
8315
|
-
ap.
|
|
8316
|
-
ap.lastRuntimeEventKind = eventKind ?? null;
|
|
8317
|
-
ap.runtimeProgressStaleSince = null;
|
|
8318
|
-
}
|
|
8319
|
-
observeRuntimeTranscriptProgress(agentId, ap, staleForMs, source) {
|
|
8320
|
-
if (ap.config.runtime !== "codex" || !ap.sessionId) return false;
|
|
8321
|
-
const ref = resolveRuntimeSessionRef(ap.config.runtime, ap.sessionId, this.runtimeSessionHomeDir);
|
|
8322
|
-
if (!ref.reachable || !ref.path) return false;
|
|
8323
|
-
let mtimeMs;
|
|
8324
|
-
try {
|
|
8325
|
-
const stats = statSync2(ref.path);
|
|
8326
|
-
if (!stats.isFile()) return false;
|
|
8327
|
-
mtimeMs = stats.mtimeMs;
|
|
8328
|
-
} catch {
|
|
8329
|
-
return false;
|
|
8330
|
-
}
|
|
8331
|
-
if (mtimeMs <= ap.lastRuntimeEventAt) return false;
|
|
8332
|
-
const now = Date.now();
|
|
8333
|
-
const transcriptAgeMs = Math.max(0, now - mtimeMs);
|
|
8334
|
-
if (transcriptAgeMs >= RUNTIME_PROGRESS_STALE_MS) return false;
|
|
8335
|
-
ap.lastRuntimeEventAt = mtimeMs;
|
|
8336
|
-
ap.runtimeProgressStaleSince = null;
|
|
8337
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
|
|
8338
|
-
turn_outcome: "held",
|
|
8339
|
-
turn_subtype: "runtime_progress",
|
|
8340
|
-
turn_reason: "internal_activity_observed",
|
|
8341
|
-
signal: "runtime_transcript_mtime",
|
|
8342
|
-
source,
|
|
8343
|
-
runtime: ap.config.runtime,
|
|
8344
|
-
sessionRefReachable: true,
|
|
8345
|
-
transcriptAgeMs,
|
|
8346
|
-
previousStaleForMs: staleForMs
|
|
8347
|
-
});
|
|
8348
|
-
return true;
|
|
8616
|
+
ap.runtimeProgress.noteRuntimeEvent(eventKind);
|
|
8349
8617
|
}
|
|
8350
8618
|
recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
|
|
8351
8619
|
if (ap.driver.busyDeliveryMode !== "gated") return;
|
|
@@ -8376,13 +8644,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8376
8644
|
return this.sendStdinNotification(agentId);
|
|
8377
8645
|
}
|
|
8378
8646
|
const pendingMessages = ap.inbox.length;
|
|
8379
|
-
const pendingNotificationCount = ap.
|
|
8647
|
+
const pendingNotificationCount = ap.notifications.pendingCount;
|
|
8380
8648
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
8381
|
-
ap.
|
|
8382
|
-
if (ap.notificationTimer) {
|
|
8383
|
-
clearTimeout(ap.notificationTimer);
|
|
8384
|
-
ap.notificationTimer = null;
|
|
8385
|
-
}
|
|
8649
|
+
ap.notifications.clear();
|
|
8386
8650
|
ap.gatedSteering.lastFlushReason = reason;
|
|
8387
8651
|
if (reason !== "turn_end") {
|
|
8388
8652
|
ap.gatedSteering.inFlightBatch = {
|
|
@@ -8405,16 +8669,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8405
8669
|
if (reason !== "turn_end") {
|
|
8406
8670
|
ap.gatedSteering.inFlightBatch = null;
|
|
8407
8671
|
}
|
|
8408
|
-
ap.pendingNotificationCount
|
|
8672
|
+
ap.notifications.add(pendingNotificationCount || pendingMessages);
|
|
8409
8673
|
return false;
|
|
8410
8674
|
}
|
|
8411
8675
|
flushCompactionBoundaryMessages(agentId, ap) {
|
|
8412
8676
|
if (!ap.sessionId || !ap.driver.supportsStdinNotification || ap.inbox.length === 0) return false;
|
|
8413
|
-
if (ap.
|
|
8414
|
-
|
|
8415
|
-
clearTimeout(ap.notificationTimer);
|
|
8416
|
-
ap.notificationTimer = null;
|
|
8417
|
-
}
|
|
8677
|
+
if (ap.notifications.pendingCount > 0) {
|
|
8678
|
+
ap.notifications.clearTimer();
|
|
8418
8679
|
this.sendStdinNotification(agentId);
|
|
8419
8680
|
return true;
|
|
8420
8681
|
}
|
|
@@ -8442,7 +8703,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8442
8703
|
handleRuntimeStartupTimeout(agentId, ap, timeoutMs) {
|
|
8443
8704
|
const current = this.agents.get(agentId);
|
|
8444
8705
|
if (current !== ap) return;
|
|
8445
|
-
if (ap.
|
|
8706
|
+
if (ap.runtimeProgress.lastEventKind) {
|
|
8446
8707
|
this.clearRuntimeStartupTimeout(ap);
|
|
8447
8708
|
return;
|
|
8448
8709
|
}
|
|
@@ -8451,8 +8712,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8451
8712
|
const detail = terminalFailureDetail?.detail ?? formatRuntimeStartTimeoutMessage(ap.driver.id);
|
|
8452
8713
|
ap.startupTimedOut = true;
|
|
8453
8714
|
ap.lastRuntimeError = detail;
|
|
8454
|
-
ap.
|
|
8455
|
-
const staleForMs = Math.max(timeoutMs,
|
|
8715
|
+
ap.runtimeProgress.markStale();
|
|
8716
|
+
const staleForMs = Math.max(timeoutMs, ap.runtimeProgress.ageMs());
|
|
8456
8717
|
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, Math.max(1, Math.floor(staleForMs / 6e4)));
|
|
8457
8718
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.start.timeout", {
|
|
8458
8719
|
turn_outcome: "failed",
|
|
@@ -8491,7 +8752,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8491
8752
|
const batch = ap.gatedSteering.inFlightBatch;
|
|
8492
8753
|
ap.gatedSteering.inFlightBatch = null;
|
|
8493
8754
|
ap.inbox.unshift(...batch.messages);
|
|
8494
|
-
ap.
|
|
8755
|
+
ap.notifications.add(batch.messages.length);
|
|
8495
8756
|
this.recordGatedSteeringEvent(agentId, ap, "requeue", {
|
|
8496
8757
|
reason,
|
|
8497
8758
|
originalFlushReason: batch.reason,
|
|
@@ -8503,11 +8764,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8503
8764
|
}
|
|
8504
8765
|
markRuntimeProgressStaleIfNeeded(agentId, ap) {
|
|
8505
8766
|
if (ap.lastActivity !== "working" && ap.lastActivity !== "thinking") return false;
|
|
8506
|
-
if (ap.
|
|
8507
|
-
const staleForMs =
|
|
8767
|
+
if (ap.runtimeProgress.isStale) return true;
|
|
8768
|
+
const staleForMs = ap.runtimeProgress.ageMs();
|
|
8508
8769
|
if (staleForMs < RUNTIME_PROGRESS_STALE_MS) return false;
|
|
8509
|
-
|
|
8510
|
-
ap.runtimeProgressStaleSince = Date.now();
|
|
8770
|
+
ap.runtimeProgress.markStale();
|
|
8511
8771
|
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
8512
8772
|
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
8513
8773
|
const turnReason = classifyRuntimeStallReason(ap);
|
|
@@ -8540,11 +8800,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8540
8800
|
const canRestartDirectStdinProcess = directStdinRuntime && Boolean(ap.sessionId) && (ap.gatedSteering.outstandingToolUses === 0 || hasDirectStdinRecoveryEvidence(ap));
|
|
8541
8801
|
const canRestartStalledProcess = !ap.driver.supportsStdinNotification || canRestartDirectStdinProcess;
|
|
8542
8802
|
if (!canRestartStalledProcess) return false;
|
|
8543
|
-
const staleForMs =
|
|
8544
|
-
if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.
|
|
8545
|
-
if (this.observeRuntimeTranscriptProgress(agentId, ap, staleForMs, "queued_recovery")) return false;
|
|
8803
|
+
const staleForMs = ap.runtimeProgress.ageMs();
|
|
8804
|
+
if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgress.isStale) return false;
|
|
8546
8805
|
const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
|
|
8547
|
-
ap.
|
|
8806
|
+
ap.runtimeProgress.markStale();
|
|
8548
8807
|
const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
|
|
8549
8808
|
const turnReason = classifyRuntimeStallReason(ap);
|
|
8550
8809
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
|
|
@@ -8593,14 +8852,38 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8593
8852
|
/** Handle a single ParsedEvent from any runtime driver */
|
|
8594
8853
|
handleParsedEvent(agentId, event, driver) {
|
|
8595
8854
|
const ap = this.agents.get(agentId);
|
|
8855
|
+
if (event.kind === "telemetry") {
|
|
8856
|
+
if (ap) this.recordRuntimeTelemetry(agentId, ap, event);
|
|
8857
|
+
return;
|
|
8858
|
+
}
|
|
8596
8859
|
if (ap) {
|
|
8597
|
-
const wasStalled =
|
|
8860
|
+
const wasStalled = ap.runtimeProgress.isStale;
|
|
8598
8861
|
this.clearRuntimeStartupTimeout(ap);
|
|
8599
8862
|
this.noteRuntimeTraceCounter(ap, event);
|
|
8600
|
-
|
|
8863
|
+
const eventAttrs = event.kind === "internal_progress" ? {
|
|
8864
|
+
kind: event.kind,
|
|
8865
|
+
source: event.source,
|
|
8866
|
+
itemType: event.itemType,
|
|
8867
|
+
payloadBytes: event.payloadBytes
|
|
8868
|
+
} : { kind: event.kind };
|
|
8869
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", eventAttrs);
|
|
8601
8870
|
if (wasStalled) {
|
|
8602
8871
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
|
|
8603
8872
|
}
|
|
8873
|
+
if (event.kind === "internal_progress") {
|
|
8874
|
+
ap.runtimeProgress.noteInternalProgress();
|
|
8875
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
|
|
8876
|
+
turn_outcome: "held",
|
|
8877
|
+
turn_subtype: "runtime_progress",
|
|
8878
|
+
turn_reason: "internal_activity_observed",
|
|
8879
|
+
signal: event.source,
|
|
8880
|
+
source: "runtime_event",
|
|
8881
|
+
runtime: ap.config.runtime,
|
|
8882
|
+
itemType: event.itemType,
|
|
8883
|
+
payloadBytes: event.payloadBytes
|
|
8884
|
+
});
|
|
8885
|
+
return;
|
|
8886
|
+
}
|
|
8604
8887
|
this.noteRuntimeProgress(ap, event.kind);
|
|
8605
8888
|
}
|
|
8606
8889
|
switch (event.kind) {
|
|
@@ -8704,11 +8987,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8704
8987
|
this.setGatedSteeringPhase(agentId, ap, "idle", { event: "turn_end" });
|
|
8705
8988
|
if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
8706
8989
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
8707
|
-
ap.
|
|
8708
|
-
if (ap.notificationTimer) {
|
|
8709
|
-
clearTimeout(ap.notificationTimer);
|
|
8710
|
-
ap.notificationTimer = null;
|
|
8711
|
-
}
|
|
8990
|
+
ap.notifications.clear();
|
|
8712
8991
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
8713
8992
|
ap.gatedSteering.lastFlushReason = "turn_end";
|
|
8714
8993
|
this.recordGatedSteeringEvent(agentId, ap, "flush", {
|
|
@@ -8802,11 +9081,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8802
9081
|
}
|
|
8803
9082
|
} else {
|
|
8804
9083
|
ap.isIdle = true;
|
|
8805
|
-
ap.
|
|
8806
|
-
if (ap.notificationTimer) {
|
|
8807
|
-
clearTimeout(ap.notificationTimer);
|
|
8808
|
-
ap.notificationTimer = null;
|
|
8809
|
-
}
|
|
9084
|
+
ap.notifications.clear();
|
|
8810
9085
|
logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
|
|
8811
9086
|
}
|
|
8812
9087
|
}
|
|
@@ -8818,6 +9093,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8818
9093
|
}
|
|
8819
9094
|
}
|
|
8820
9095
|
}
|
|
9096
|
+
recordRuntimeTelemetry(agentId, ap, event) {
|
|
9097
|
+
const attrs = {
|
|
9098
|
+
agentId,
|
|
9099
|
+
launchId: ap.launchId || void 0,
|
|
9100
|
+
runtime: ap.config.runtime,
|
|
9101
|
+
model: ap.config.model,
|
|
9102
|
+
telemetry_name: event.name,
|
|
9103
|
+
...event.attrs
|
|
9104
|
+
};
|
|
9105
|
+
ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
|
|
9106
|
+
this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
|
|
9107
|
+
}
|
|
8821
9108
|
sendAgentStatus(agentId, status, launchId) {
|
|
8822
9109
|
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
8823
9110
|
}
|
|
@@ -8871,12 +9158,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8871
9158
|
sendStdinNotification(agentId) {
|
|
8872
9159
|
const ap = this.agents.get(agentId);
|
|
8873
9160
|
if (!ap) return false;
|
|
8874
|
-
const count = ap.
|
|
8875
|
-
ap.pendingNotificationCount = 0;
|
|
8876
|
-
if (ap.notificationTimer) {
|
|
8877
|
-
clearTimeout(ap.notificationTimer);
|
|
8878
|
-
ap.notificationTimer = null;
|
|
8879
|
-
}
|
|
9161
|
+
const count = ap.notifications.takePendingAndClearTimer();
|
|
8880
9162
|
if (count === 0) return false;
|
|
8881
9163
|
if (ap.isIdle) return false;
|
|
8882
9164
|
if (!ap.sessionId) return false;
|
|
@@ -8886,7 +9168,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8886
9168
|
pendingMessages: ap.inbox.length,
|
|
8887
9169
|
busyDeliveryMode: ap.driver.busyDeliveryMode
|
|
8888
9170
|
});
|
|
8889
|
-
ap.
|
|
9171
|
+
ap.notifications.add(count);
|
|
8890
9172
|
logger.info(
|
|
8891
9173
|
`[Agent ${agentId}] Suppressing stdin delivery until context compaction finishes; pending=${ap.inbox.length}`
|
|
8892
9174
|
);
|
|
@@ -8912,7 +9194,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8912
9194
|
});
|
|
8913
9195
|
return true;
|
|
8914
9196
|
} else {
|
|
8915
|
-
ap.
|
|
9197
|
+
ap.notifications.add(count);
|
|
8916
9198
|
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
8917
9199
|
agentId,
|
|
8918
9200
|
runtime: ap.config.runtime,
|
|
@@ -8963,7 +9245,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8963
9245
|
messages_count: messages.length,
|
|
8964
9246
|
session_id_present: Boolean(ap.sessionId),
|
|
8965
9247
|
inbox_count: ap.inbox.length,
|
|
8966
|
-
pending_notification_count: ap.
|
|
9248
|
+
pending_notification_count: ap.notifications.pendingCount,
|
|
8967
9249
|
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
8968
9250
|
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
8969
9251
|
...this.messagesTraceAttrs(messages)
|
|
@@ -10443,10 +10725,19 @@ var DaemonCore = class {
|
|
|
10443
10725
|
case "agent:start":
|
|
10444
10726
|
logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
|
|
10445
10727
|
this.startAgentFromMessage(msg).catch((err) => {
|
|
10446
|
-
const
|
|
10447
|
-
logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
|
|
10728
|
+
const classification = classifySpawnFailure(err);
|
|
10729
|
+
logger.error(`[Agent ${msg.agentId}] Start failed (${classification.reason}): ${classification.detail}`);
|
|
10730
|
+
this.recordDaemonTrace("daemon.agent.spawn.failed", {
|
|
10731
|
+
agentId: msg.agentId,
|
|
10732
|
+
launchId: msg.launchId,
|
|
10733
|
+
runtime: msg.config.runtime,
|
|
10734
|
+
model: msg.config.model,
|
|
10735
|
+
failure_reason: classification.reason,
|
|
10736
|
+
failure_detail: classification.detail,
|
|
10737
|
+
session_id_present: Boolean(msg.config.sessionId)
|
|
10738
|
+
}, "error");
|
|
10448
10739
|
this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
|
|
10449
|
-
this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail:
|
|
10740
|
+
this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: classification.userMessage, launchId: msg.launchId });
|
|
10450
10741
|
});
|
|
10451
10742
|
break;
|
|
10452
10743
|
case "agent:stop":
|