openclaw-clawtown-plugin 1.1.18 → 1.1.19
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/reporter.ts +314 -27
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw-clawtown-plugin",
|
|
3
3
|
"name": "OpenClaw Clawtown Plugin",
|
|
4
4
|
"description": "Connects an OpenClaw agent to OpenClaw Forum and reports forum actions",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.19",
|
|
6
6
|
"main": "./index.ts",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
package/reporter.ts
CHANGED
|
@@ -162,6 +162,37 @@ interface PairingStatusResult {
|
|
|
162
162
|
displayName: string;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
interface AgentOutputInspection {
|
|
166
|
+
text: string;
|
|
167
|
+
jsonCandidateCount: number;
|
|
168
|
+
envelopeDetected: boolean;
|
|
169
|
+
envelopePayloadCount: number;
|
|
170
|
+
envelopePayloadTextCount: number;
|
|
171
|
+
envelopePayloadTextLength: number;
|
|
172
|
+
envelopePayloadPreview: string;
|
|
173
|
+
extractedFromEnvelope: boolean;
|
|
174
|
+
extractedJsonObject: boolean;
|
|
175
|
+
lineCount: number;
|
|
176
|
+
lastNonEmptyLinePreview: string;
|
|
177
|
+
rawOutputPreview: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
interface AgentTurnDiagnostics extends AgentOutputInspection {
|
|
181
|
+
sessionId: string;
|
|
182
|
+
transport: "powershell" | "openclaw";
|
|
183
|
+
durationMs: number;
|
|
184
|
+
stdoutLength: number;
|
|
185
|
+
stderrLength: number;
|
|
186
|
+
stderrPreview: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
interface AgentTurnResult {
|
|
190
|
+
stdout: string;
|
|
191
|
+
stderr: string;
|
|
192
|
+
extractedText: string;
|
|
193
|
+
diagnostics: AgentTurnDiagnostics;
|
|
194
|
+
}
|
|
195
|
+
|
|
165
196
|
class Reporter {
|
|
166
197
|
private userId: string;
|
|
167
198
|
private apiKey: string;
|
|
@@ -640,6 +671,19 @@ class Reporter {
|
|
|
640
671
|
let instructions = baseInstructions;
|
|
641
672
|
let rawOutput = "";
|
|
642
673
|
let executeErrorMessage = "";
|
|
674
|
+
let lastTurnDiagnostics: AgentTurnDiagnostics | null = null;
|
|
675
|
+
const runTaskTurn = async (message: string, timeoutSeconds: number, timeoutMs: number, taskKey: string) => {
|
|
676
|
+
const turn = await this.runVisibleAgentTurn(message, timeoutSeconds, timeoutMs, { taskKey });
|
|
677
|
+
lastTurnDiagnostics = turn.diagnostics;
|
|
678
|
+
return turn.extractedText;
|
|
679
|
+
};
|
|
680
|
+
const logTaskFailureDiagnostics = (stage: string, reason: TaskFailureReason, outputText: string) => {
|
|
681
|
+
if (!lastTurnDiagnostics) return;
|
|
682
|
+
if (reason.code !== "empty_model_output" && reason.code !== "invalid_json_output") return;
|
|
683
|
+
console.warn(
|
|
684
|
+
`[forum-reporter-v2] model turn diagnostics (${taskType}/${stage}): ${formatAgentTurnDiagnostics(lastTurnDiagnostics, reason, outputText)}`,
|
|
685
|
+
);
|
|
686
|
+
};
|
|
643
687
|
const reportClientFailure = async (reason: TaskFailureReason, outputText: string) => {
|
|
644
688
|
const kind = actionKindForTaskType(taskType);
|
|
645
689
|
const failureMeta = buildClientFailureMeta(taskType, outputText);
|
|
@@ -658,14 +702,31 @@ class Reporter {
|
|
|
658
702
|
modelOutputPreview: failureMeta.modelOutputPreview,
|
|
659
703
|
candidateAnswerLength: failureMeta.candidateAnswerLength,
|
|
660
704
|
candidateAnswerPreview: failureMeta.candidateAnswerPreview,
|
|
705
|
+
agentTurnSessionId: lastTurnDiagnostics?.sessionId ?? "",
|
|
706
|
+
agentTurnTransport: lastTurnDiagnostics?.transport ?? "",
|
|
707
|
+
agentTurnDurationMs: lastTurnDiagnostics?.durationMs ?? 0,
|
|
708
|
+
agentTurnStdoutLength: lastTurnDiagnostics?.stdoutLength ?? 0,
|
|
709
|
+
agentTurnStderrLength: lastTurnDiagnostics?.stderrLength ?? 0,
|
|
710
|
+
agentTurnJsonCandidateCount: lastTurnDiagnostics?.jsonCandidateCount ?? 0,
|
|
711
|
+
agentTurnEnvelopeDetected: Boolean(lastTurnDiagnostics?.envelopeDetected),
|
|
712
|
+
agentTurnEnvelopePayloadCount: lastTurnDiagnostics?.envelopePayloadCount ?? 0,
|
|
713
|
+
agentTurnEnvelopePayloadTextCount: lastTurnDiagnostics?.envelopePayloadTextCount ?? 0,
|
|
714
|
+
agentTurnEnvelopePayloadTextLength: lastTurnDiagnostics?.envelopePayloadTextLength ?? 0,
|
|
715
|
+
agentTurnOutputPreview: lastTurnDiagnostics?.rawOutputPreview ?? "",
|
|
716
|
+
agentTurnPayloadPreview: lastTurnDiagnostics?.envelopePayloadPreview ?? "",
|
|
717
|
+
agentTurnLastLinePreview: lastTurnDiagnostics?.lastNonEmptyLinePreview ?? "",
|
|
718
|
+
agentTurnStderrPreview: lastTurnDiagnostics?.stderrPreview ?? "",
|
|
661
719
|
},
|
|
662
720
|
rawOutput: outputText,
|
|
663
721
|
});
|
|
664
722
|
};
|
|
665
723
|
try {
|
|
666
|
-
rawOutput =
|
|
667
|
-
|
|
668
|
-
|
|
724
|
+
rawOutput = await runTaskTurn(
|
|
725
|
+
instructions,
|
|
726
|
+
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
727
|
+
ACTION_MODEL_TIMEOUT_MS,
|
|
728
|
+
tid || qid || taskType,
|
|
729
|
+
);
|
|
669
730
|
} catch (error: any) {
|
|
670
731
|
executeErrorMessage = String(error?.message ?? error ?? "unknown");
|
|
671
732
|
console.warn(`[forum-reporter-v2] execute task failed: ${executeErrorMessage}`);
|
|
@@ -674,6 +735,7 @@ class Reporter {
|
|
|
674
735
|
let normalized = normalizeTaskResult(taskType, rawOutput, payload);
|
|
675
736
|
if (!normalized) {
|
|
676
737
|
const firstFailureReason = diagnoseTaskResultFailure(taskType, rawOutput, payload, executeErrorMessage);
|
|
738
|
+
logTaskFailureDiagnostics("first-attempt", firstFailureReason, rawOutput);
|
|
677
739
|
if (firstFailureReason.code === "model_turn_failed") {
|
|
678
740
|
const timeoutFeedback = humanizeFailureReason(firstFailureReason, taskType);
|
|
679
741
|
console.warn(`[forum-reporter-v2] first attempt timeout for taskType=${taskType}, retry once with longer timeout`);
|
|
@@ -681,9 +743,12 @@ class Reporter {
|
|
|
681
743
|
rawOutput = "";
|
|
682
744
|
executeErrorMessage = "";
|
|
683
745
|
try {
|
|
684
|
-
rawOutput =
|
|
685
|
-
|
|
686
|
-
|
|
746
|
+
rawOutput = await runTaskTurn(
|
|
747
|
+
instructions,
|
|
748
|
+
TASK_TIMEOUT_RETRY_SECONDS,
|
|
749
|
+
ACTION_MODEL_TIMEOUT_MS,
|
|
750
|
+
`${tid || qid || taskType}-timeout-retry1`,
|
|
751
|
+
);
|
|
687
752
|
} catch (error: any) {
|
|
688
753
|
executeErrorMessage = String(error?.message ?? error ?? "unknown");
|
|
689
754
|
console.warn(`[forum-reporter-v2] timeout-retry execute failed: ${executeErrorMessage}`);
|
|
@@ -691,6 +756,7 @@ class Reporter {
|
|
|
691
756
|
normalized = normalizeTaskResult(taskType, rawOutput, payload);
|
|
692
757
|
if (!normalized) {
|
|
693
758
|
const timeoutRetryFailure = diagnoseTaskResultFailure(taskType, rawOutput, payload, executeErrorMessage);
|
|
759
|
+
logTaskFailureDiagnostics("timeout-retry", timeoutRetryFailure, rawOutput);
|
|
694
760
|
await reportClientFailure(timeoutRetryFailure, rawOutput);
|
|
695
761
|
return;
|
|
696
762
|
}
|
|
@@ -702,9 +768,12 @@ class Reporter {
|
|
|
702
768
|
rawOutput = "";
|
|
703
769
|
executeErrorMessage = "";
|
|
704
770
|
try {
|
|
705
|
-
rawOutput =
|
|
706
|
-
|
|
707
|
-
|
|
771
|
+
rawOutput = await runTaskTurn(
|
|
772
|
+
instructions,
|
|
773
|
+
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
774
|
+
ACTION_MODEL_TIMEOUT_MS,
|
|
775
|
+
`${tid || qid || taskType}-retry1`,
|
|
776
|
+
);
|
|
708
777
|
} catch (error: any) {
|
|
709
778
|
executeErrorMessage = String(error?.message ?? error ?? "unknown");
|
|
710
779
|
console.warn(`[forum-reporter-v2] retry execute failed: ${executeErrorMessage}`);
|
|
@@ -712,6 +781,7 @@ class Reporter {
|
|
|
712
781
|
normalized = normalizeTaskResult(taskType, rawOutput, payload);
|
|
713
782
|
if (!normalized) {
|
|
714
783
|
const failureReason = diagnoseTaskResultFailure(taskType, rawOutput, payload, executeErrorMessage);
|
|
784
|
+
logTaskFailureDiagnostics("retry1", failureReason, rawOutput);
|
|
715
785
|
await reportClientFailure(failureReason, rawOutput);
|
|
716
786
|
return;
|
|
717
787
|
}
|
|
@@ -732,9 +802,12 @@ class Reporter {
|
|
|
732
802
|
const retryInstructions = buildRetryInstructions(baseInstructions, serverFeedback);
|
|
733
803
|
let retryOutput = "";
|
|
734
804
|
try {
|
|
735
|
-
retryOutput =
|
|
736
|
-
|
|
737
|
-
|
|
805
|
+
retryOutput = await runTaskTurn(
|
|
806
|
+
retryInstructions,
|
|
807
|
+
TASK_FIRST_TURN_TIMEOUT_SECONDS,
|
|
808
|
+
ACTION_MODEL_TIMEOUT_MS,
|
|
809
|
+
`${tid || qid || taskType}-submit-retry1`,
|
|
810
|
+
);
|
|
738
811
|
} catch (error: any) {
|
|
739
812
|
const msg = String(error?.message ?? error ?? "unknown");
|
|
740
813
|
console.warn(`[forum-reporter-v2] submit-retry execute failed: ${msg}`);
|
|
@@ -743,6 +816,7 @@ class Reporter {
|
|
|
743
816
|
const retryNormalized = normalizeTaskResult(taskType, retryOutput, payload);
|
|
744
817
|
if (!retryNormalized) {
|
|
745
818
|
const retryFailure = diagnoseTaskResultFailure(taskType, retryOutput, payload, "");
|
|
819
|
+
logTaskFailureDiagnostics("submit-retry", retryFailure, retryOutput);
|
|
746
820
|
console.warn(`[forum-reporter-v2] submit-retry produced invalid result: ${retryFailure.code}`);
|
|
747
821
|
return;
|
|
748
822
|
}
|
|
@@ -990,10 +1064,19 @@ class Reporter {
|
|
|
990
1064
|
const prompt = buildV2ActionPrompt(context);
|
|
991
1065
|
let text = "";
|
|
992
1066
|
try {
|
|
993
|
-
const
|
|
1067
|
+
const turn = await this.runVisibleAgentTurn(prompt, 115, ACTION_CONTEXT_TIMEOUT_MS, {
|
|
994
1068
|
taskKey: `context-${context.userId}`,
|
|
995
1069
|
});
|
|
996
|
-
text =
|
|
1070
|
+
text = turn.extractedText;
|
|
1071
|
+
if (!text) {
|
|
1072
|
+
console.warn(
|
|
1073
|
+
`[forum-reporter-v2] action-context model turn diagnostics: ${formatAgentTurnDiagnostics(
|
|
1074
|
+
turn.diagnostics,
|
|
1075
|
+
{ code: "empty_model_output", detail: "model returned empty content" },
|
|
1076
|
+
text,
|
|
1077
|
+
)}`,
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
997
1080
|
} catch (error: any) {
|
|
998
1081
|
const modelTurnError = String(error?.message ?? error ?? "unknown");
|
|
999
1082
|
console.warn(`[forum-reporter-v2] model turn failed, fallback to deterministic action: ${modelTurnError}`);
|
|
@@ -1039,7 +1122,7 @@ class Reporter {
|
|
|
1039
1122
|
timeoutSeconds: number,
|
|
1040
1123
|
timeoutMs: number,
|
|
1041
1124
|
options?: { taskKey?: string },
|
|
1042
|
-
): Promise<
|
|
1125
|
+
): Promise<AgentTurnResult> {
|
|
1043
1126
|
// 每次任务使用一次性 session,避免历史累积污染后续任务。
|
|
1044
1127
|
const baseSessionId = buildOneShotSessionId(this.userId, options?.taskKey);
|
|
1045
1128
|
try {
|
|
@@ -1066,8 +1149,9 @@ class Reporter {
|
|
|
1066
1149
|
message: string,
|
|
1067
1150
|
timeoutSeconds: number,
|
|
1068
1151
|
timeoutMs: number,
|
|
1069
|
-
): Promise<
|
|
1152
|
+
): Promise<AgentTurnResult> {
|
|
1070
1153
|
const isWindows = process.platform === "win32";
|
|
1154
|
+
const startedAt = Date.now();
|
|
1071
1155
|
if (isWindows) {
|
|
1072
1156
|
const script = buildWindowsAgentCommandScript({
|
|
1073
1157
|
agentId: this.openclawAgentId,
|
|
@@ -1096,7 +1180,13 @@ class Reporter {
|
|
|
1096
1180
|
}),
|
|
1097
1181
|
timeoutMs,
|
|
1098
1182
|
);
|
|
1099
|
-
return
|
|
1183
|
+
return buildAgentTurnResult({
|
|
1184
|
+
stdout: String(result.stdout ?? ""),
|
|
1185
|
+
stderr: String(result.stderr ?? ""),
|
|
1186
|
+
sessionId,
|
|
1187
|
+
transport: "powershell",
|
|
1188
|
+
durationMs: Date.now() - startedAt,
|
|
1189
|
+
});
|
|
1100
1190
|
}
|
|
1101
1191
|
|
|
1102
1192
|
const args = [
|
|
@@ -1122,7 +1212,13 @@ class Reporter {
|
|
|
1122
1212
|
}),
|
|
1123
1213
|
timeoutMs,
|
|
1124
1214
|
);
|
|
1125
|
-
return
|
|
1215
|
+
return buildAgentTurnResult({
|
|
1216
|
+
stdout: String(result.stdout ?? ""),
|
|
1217
|
+
stderr: String(result.stderr ?? ""),
|
|
1218
|
+
sessionId,
|
|
1219
|
+
transport: "openclaw",
|
|
1220
|
+
durationMs: Date.now() - startedAt,
|
|
1221
|
+
});
|
|
1126
1222
|
}
|
|
1127
1223
|
|
|
1128
1224
|
private resolveInstanceLockPath(userId: string) {
|
|
@@ -1832,25 +1928,210 @@ function quoteForPowerShell(value: string) {
|
|
|
1832
1928
|
}
|
|
1833
1929
|
|
|
1834
1930
|
function extractReplyText(output: string): string {
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1931
|
+
return inspectAgentOutput(output).text;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function inspectAgentOutput(output: string): AgentOutputInspection {
|
|
1935
|
+
const rawOutput = String(output ?? "");
|
|
1936
|
+
const text = rawOutput.trim();
|
|
1937
|
+
const rawOutputPreview = compactPreview(rawOutput, 220);
|
|
1938
|
+
const jsonCandidates = text ? parseJsonObjectCandidates(text) : [];
|
|
1939
|
+
let envelopeDetected = false;
|
|
1940
|
+
let envelopePayloadCount = 0;
|
|
1941
|
+
let envelopePayloadTextCount = 0;
|
|
1942
|
+
let envelopePayloadTextLength = 0;
|
|
1943
|
+
let envelopePayloadPreview = "";
|
|
1944
|
+
let extractedFromEnvelope = false;
|
|
1945
|
+
let extractedJsonObject = false;
|
|
1946
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1947
|
+
const lastNonEmptyLinePreview = lines.length ? compactPreview(lines[lines.length - 1], 160) : "";
|
|
1948
|
+
|
|
1949
|
+
if (!text) {
|
|
1950
|
+
return {
|
|
1951
|
+
text: "",
|
|
1952
|
+
jsonCandidateCount: 0,
|
|
1953
|
+
envelopeDetected: false,
|
|
1954
|
+
envelopePayloadCount: 0,
|
|
1955
|
+
envelopePayloadTextCount: 0,
|
|
1956
|
+
envelopePayloadTextLength: 0,
|
|
1957
|
+
envelopePayloadPreview: "",
|
|
1958
|
+
extractedFromEnvelope: false,
|
|
1959
|
+
extractedJsonObject: false,
|
|
1960
|
+
lineCount: 0,
|
|
1961
|
+
lastNonEmptyLinePreview: "",
|
|
1962
|
+
rawOutputPreview,
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1838
1966
|
for (let i = jsonCandidates.length - 1; i >= 0; i -= 1) {
|
|
1839
1967
|
try {
|
|
1840
1968
|
const parsed = JSON.parse(jsonCandidates[i]);
|
|
1969
|
+
const envelopeMeta = inspectAgentEnvelope(parsed);
|
|
1970
|
+
if (envelopeMeta.detected) {
|
|
1971
|
+
envelopeDetected = true;
|
|
1972
|
+
envelopePayloadCount = envelopeMeta.payloadCount;
|
|
1973
|
+
envelopePayloadTextCount = envelopeMeta.payloadTextCount;
|
|
1974
|
+
envelopePayloadTextLength = envelopeMeta.payloadTextLength;
|
|
1975
|
+
envelopePayloadPreview = envelopeMeta.payloadPreview;
|
|
1976
|
+
}
|
|
1841
1977
|
const payloadText = extractPayloadTextFromAgentEnvelope(parsed);
|
|
1842
|
-
if (payloadText)
|
|
1843
|
-
|
|
1978
|
+
if (payloadText) {
|
|
1979
|
+
extractedFromEnvelope = true;
|
|
1980
|
+
return {
|
|
1981
|
+
text: payloadText,
|
|
1982
|
+
jsonCandidateCount: jsonCandidates.length,
|
|
1983
|
+
envelopeDetected,
|
|
1984
|
+
envelopePayloadCount,
|
|
1985
|
+
envelopePayloadTextCount,
|
|
1986
|
+
envelopePayloadTextLength,
|
|
1987
|
+
envelopePayloadPreview,
|
|
1988
|
+
extractedFromEnvelope,
|
|
1989
|
+
extractedJsonObject,
|
|
1990
|
+
lineCount: lines.length,
|
|
1991
|
+
lastNonEmptyLinePreview,
|
|
1992
|
+
rawOutputPreview,
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
if (parsed && typeof parsed === "object" && (parsed.kind || parsed.action || parsed.content || parsed.answerId)) {
|
|
1996
|
+
extractedJsonObject = true;
|
|
1997
|
+
return {
|
|
1998
|
+
text: jsonCandidates[i],
|
|
1999
|
+
jsonCandidateCount: jsonCandidates.length,
|
|
2000
|
+
envelopeDetected,
|
|
2001
|
+
envelopePayloadCount,
|
|
2002
|
+
envelopePayloadTextCount,
|
|
2003
|
+
envelopePayloadTextLength,
|
|
2004
|
+
envelopePayloadPreview,
|
|
2005
|
+
extractedFromEnvelope,
|
|
2006
|
+
extractedJsonObject,
|
|
2007
|
+
lineCount: lines.length,
|
|
2008
|
+
lastNonEmptyLinePreview,
|
|
2009
|
+
rawOutputPreview,
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
1844
2012
|
} catch {}
|
|
1845
2013
|
}
|
|
1846
|
-
|
|
1847
|
-
if (!lines.length)
|
|
2014
|
+
|
|
2015
|
+
if (!lines.length) {
|
|
2016
|
+
return {
|
|
2017
|
+
text: "",
|
|
2018
|
+
jsonCandidateCount: jsonCandidates.length,
|
|
2019
|
+
envelopeDetected,
|
|
2020
|
+
envelopePayloadCount,
|
|
2021
|
+
envelopePayloadTextCount,
|
|
2022
|
+
envelopePayloadTextLength,
|
|
2023
|
+
envelopePayloadPreview,
|
|
2024
|
+
extractedFromEnvelope,
|
|
2025
|
+
extractedJsonObject,
|
|
2026
|
+
lineCount: 0,
|
|
2027
|
+
lastNonEmptyLinePreview: "",
|
|
2028
|
+
rawOutputPreview,
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1848
2032
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
1849
2033
|
const line = lines[i];
|
|
1850
2034
|
if (!line) continue;
|
|
1851
|
-
if (line.startsWith("{") && line.endsWith("}"))
|
|
2035
|
+
if (line.startsWith("{") && line.endsWith("}")) {
|
|
2036
|
+
return {
|
|
2037
|
+
text: line,
|
|
2038
|
+
jsonCandidateCount: jsonCandidates.length,
|
|
2039
|
+
envelopeDetected,
|
|
2040
|
+
envelopePayloadCount,
|
|
2041
|
+
envelopePayloadTextCount,
|
|
2042
|
+
envelopePayloadTextLength,
|
|
2043
|
+
envelopePayloadPreview,
|
|
2044
|
+
extractedFromEnvelope,
|
|
2045
|
+
extractedJsonObject,
|
|
2046
|
+
lineCount: lines.length,
|
|
2047
|
+
lastNonEmptyLinePreview,
|
|
2048
|
+
rawOutputPreview,
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
1852
2051
|
}
|
|
1853
|
-
|
|
2052
|
+
|
|
2053
|
+
return {
|
|
2054
|
+
text: lines.join("\n"),
|
|
2055
|
+
jsonCandidateCount: jsonCandidates.length,
|
|
2056
|
+
envelopeDetected,
|
|
2057
|
+
envelopePayloadCount,
|
|
2058
|
+
envelopePayloadTextCount,
|
|
2059
|
+
envelopePayloadTextLength,
|
|
2060
|
+
envelopePayloadPreview,
|
|
2061
|
+
extractedFromEnvelope,
|
|
2062
|
+
extractedJsonObject,
|
|
2063
|
+
lineCount: lines.length,
|
|
2064
|
+
lastNonEmptyLinePreview,
|
|
2065
|
+
rawOutputPreview,
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
function buildAgentTurnResult(input: {
|
|
2070
|
+
stdout: string;
|
|
2071
|
+
stderr: string;
|
|
2072
|
+
sessionId: string;
|
|
2073
|
+
transport: "powershell" | "openclaw";
|
|
2074
|
+
durationMs: number;
|
|
2075
|
+
}): AgentTurnResult {
|
|
2076
|
+
const inspected = inspectAgentOutput(input.stdout);
|
|
2077
|
+
return {
|
|
2078
|
+
stdout: input.stdout,
|
|
2079
|
+
stderr: input.stderr,
|
|
2080
|
+
extractedText: inspected.text,
|
|
2081
|
+
diagnostics: {
|
|
2082
|
+
...inspected,
|
|
2083
|
+
sessionId: input.sessionId,
|
|
2084
|
+
transport: input.transport,
|
|
2085
|
+
durationMs: Math.max(0, Math.round(Number(input.durationMs) || 0)),
|
|
2086
|
+
stdoutLength: input.stdout.length,
|
|
2087
|
+
stderrLength: input.stderr.length,
|
|
2088
|
+
stderrPreview: compactPreview(input.stderr, 180),
|
|
2089
|
+
},
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
function inspectAgentEnvelope(parsed: any) {
|
|
2094
|
+
const hasEnvelope = Array.isArray(parsed?.result?.payloads) || Array.isArray(parsed?.payloads);
|
|
2095
|
+
const payloads = Array.isArray(parsed?.result?.payloads)
|
|
2096
|
+
? parsed.result.payloads
|
|
2097
|
+
: Array.isArray(parsed?.payloads)
|
|
2098
|
+
? parsed.payloads
|
|
2099
|
+
: [];
|
|
2100
|
+
const payloadTexts = payloads
|
|
2101
|
+
.map((item: any) => (typeof item?.text === "string" ? item.text.trim() : ""))
|
|
2102
|
+
.filter(Boolean);
|
|
2103
|
+
return {
|
|
2104
|
+
detected: hasEnvelope,
|
|
2105
|
+
payloadCount: payloads.length,
|
|
2106
|
+
payloadTextCount: payloadTexts.length,
|
|
2107
|
+
payloadTextLength: payloadTexts.reduce((sum: number, item: string) => sum + item.length, 0),
|
|
2108
|
+
payloadPreview: compactPreview(payloadTexts.join("\n"), 180),
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
function formatAgentTurnDiagnostics(diag: AgentTurnDiagnostics, reason: TaskFailureReason, outputText = "") {
|
|
2113
|
+
const parts = [
|
|
2114
|
+
`reason=${String(reason.code ?? "").trim() || "unknown"}`,
|
|
2115
|
+
`session=${diag.sessionId || "-"}`,
|
|
2116
|
+
`transport=${diag.transport}`,
|
|
2117
|
+
`durationMs=${diag.durationMs}`,
|
|
2118
|
+
`stdoutLen=${diag.stdoutLength}`,
|
|
2119
|
+
`stderrLen=${diag.stderrLength}`,
|
|
2120
|
+
`jsonCandidates=${diag.jsonCandidateCount}`,
|
|
2121
|
+
`envelope=${diag.envelopeDetected ? "yes" : "no"}`,
|
|
2122
|
+
`payloads=${diag.envelopePayloadCount}`,
|
|
2123
|
+
`payloadTexts=${diag.envelopePayloadTextCount}`,
|
|
2124
|
+
`payloadTextLen=${diag.envelopePayloadTextLength}`,
|
|
2125
|
+
`lineCount=${diag.lineCount}`,
|
|
2126
|
+
];
|
|
2127
|
+
if (diag.extractedFromEnvelope) parts.push("source=envelope");
|
|
2128
|
+
else if (diag.extractedJsonObject) parts.push("source=json-object");
|
|
2129
|
+
if (diag.rawOutputPreview) parts.push(`stdoutPreview="${diag.rawOutputPreview}"`);
|
|
2130
|
+
if (diag.envelopePayloadPreview) parts.push(`payloadPreview="${diag.envelopePayloadPreview}"`);
|
|
2131
|
+
if (diag.lastNonEmptyLinePreview) parts.push(`lastLine="${diag.lastNonEmptyLinePreview}"`);
|
|
2132
|
+
if (diag.stderrPreview) parts.push(`stderrPreview="${diag.stderrPreview}"`);
|
|
2133
|
+
if (outputText) parts.push(`extractedPreview="${compactPreview(outputText, 140)}"`);
|
|
2134
|
+
return parts.join(", ");
|
|
1854
2135
|
}
|
|
1855
2136
|
|
|
1856
2137
|
function extractPayloadTextFromAgentEnvelope(parsed: any): string {
|
|
@@ -2592,6 +2873,12 @@ function truncate(value: string, max: number) {
|
|
|
2592
2873
|
return `${value.slice(0, Math.max(0, max - 1))}…`;
|
|
2593
2874
|
}
|
|
2594
2875
|
|
|
2876
|
+
function compactPreview(value: string, max = 180) {
|
|
2877
|
+
const cleaned = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
2878
|
+
if (!cleaned) return "";
|
|
2879
|
+
return truncate(cleaned, max);
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2595
2882
|
function coerceToString(value: unknown): string {
|
|
2596
2883
|
if (typeof value === "string") return value;
|
|
2597
2884
|
if (value === null || value === undefined) return "";
|