@lazyingart/agintiflow 0.20.300 → 0.20.301
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/docs/supervision-campaign-ledger.md +16 -0
- package/package.json +1 -1
- package/scripts/smoke-model-roles.js +198 -0
- package/src/agent-runner.js +95 -23
- package/src/scs-evidence.js +159 -4
|
@@ -56,6 +56,22 @@ in `evidence_json` and `events`.
|
|
|
56
56
|
|
|
57
57
|
## Current Campaign Findings
|
|
58
58
|
|
|
59
|
+
### Established media-chain reuse and permission boundaries
|
|
60
|
+
|
|
61
|
+
`media-chain-readiness-020` passed after reconciling a stale machine-ledger
|
|
62
|
+
`running` row with the retained session's terminal `session.finished` event.
|
|
63
|
+
AgInTi inspected the existing Musia, LabCanvas, LALACHAN, Xiaoyunque, and
|
|
64
|
+
LazyEdit entry points and produced `media-routine-readiness.md` without
|
|
65
|
+
generating media, starting or focusing a browser, uploading, copying to
|
|
66
|
+
Nutstore, creating a remote job, or publishing.
|
|
67
|
+
|
|
68
|
+
The independent acceptance contract verified every required source-grounded
|
|
69
|
+
entry, 16 read-only command results, zero forbidden external-action tools, and
|
|
70
|
+
zero long jobs. The resulting contract keeps four permissions independent:
|
|
71
|
+
reviewed-song generation, music-video generation, LazyEdit processing, and
|
|
72
|
+
public platform publication. An earlier stage or old context cannot authorize a
|
|
73
|
+
later irreversible stage.
|
|
74
|
+
|
|
59
75
|
### Retained dependencies, linked-worktree commits, and clean visual evidence
|
|
60
76
|
|
|
61
77
|
`data-analysis-retained-dependencies-060` resumed the original DeepSeek session
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.301",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,14 +31,23 @@ import {
|
|
|
31
31
|
} from "../src/scs-controller.js";
|
|
32
32
|
import {
|
|
33
33
|
buildScsEvidenceLedger,
|
|
34
|
+
buildTmuxGitIntent,
|
|
34
35
|
deriveScsTaskContract,
|
|
35
36
|
evaluateScsEvidence,
|
|
36
37
|
gitActionsSatisfyContract,
|
|
37
38
|
inferSuccessfulGitActionsFromCommandResult,
|
|
39
|
+
inferSuccessfulGitActionsFromTmuxCapture,
|
|
40
|
+
reconcileTmuxGitEvidenceEvents,
|
|
38
41
|
} from "../src/scs-evidence.js";
|
|
39
42
|
import { resolveRuntimeConfig } from "../src/config.js";
|
|
40
43
|
import { classifyGoalIntent, isDirectAnswerIntent } from "../src/goal-intent.js";
|
|
41
44
|
import { languageWriterDefaults } from "../src/writing-specialist.js";
|
|
45
|
+
import {
|
|
46
|
+
attachVerifiedTmuxGitEvidence,
|
|
47
|
+
recordDurableEvidenceCategories,
|
|
48
|
+
rememberTmuxGitIntent,
|
|
49
|
+
sanitizeToolResult,
|
|
50
|
+
} from "../src/agent-runner.js";
|
|
42
51
|
|
|
43
52
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
44
53
|
for (const key of [
|
|
@@ -1117,6 +1126,195 @@ assert(
|
|
|
1117
1126
|
!committedEvaluation.missing.some((item) => item.category === "git"),
|
|
1118
1127
|
"a successful git commit did not satisfy the explicit git requirement"
|
|
1119
1128
|
);
|
|
1129
|
+
const tmuxCommitCommand = [
|
|
1130
|
+
'echo "===AGINTI_REVIEW_COMMIT_START==="',
|
|
1131
|
+
'git add README.md && git commit -m "finish review"',
|
|
1132
|
+
'echo "===COMMIT_EXIT:$?==="',
|
|
1133
|
+
].join(" ; ");
|
|
1134
|
+
const tmuxCommitIntent = buildTmuxGitIntent(tmuxCommitCommand);
|
|
1135
|
+
assert(
|
|
1136
|
+
tmuxCommitIntent?.actions.includes("commit") &&
|
|
1137
|
+
tmuxCommitIntent.markers.includes("===AGINTI_REVIEW_COMMIT_START==="),
|
|
1138
|
+
"a marked workspace tmux commit command did not retain bounded Git intent"
|
|
1139
|
+
);
|
|
1140
|
+
const tmuxCommitCapture = {
|
|
1141
|
+
ok: true,
|
|
1142
|
+
toolName: "tmux_capture_pane",
|
|
1143
|
+
target: "review:0.0",
|
|
1144
|
+
content: [
|
|
1145
|
+
"===AGINTI_REVIEW_COMMIT_START===",
|
|
1146
|
+
"[review abc1234] finish review",
|
|
1147
|
+
" 1 file changed, 2 insertions(+)",
|
|
1148
|
+
"===COMMIT_EXIT:0===",
|
|
1149
|
+
].join("\n"),
|
|
1150
|
+
};
|
|
1151
|
+
const verifiedTmuxCommit = inferSuccessfulGitActionsFromTmuxCapture(
|
|
1152
|
+
tmuxCommitIntent,
|
|
1153
|
+
tmuxCommitCapture
|
|
1154
|
+
);
|
|
1155
|
+
assert(
|
|
1156
|
+
verifiedTmuxCommit.length === 1 && verifiedTmuxCommit[0] === "commit",
|
|
1157
|
+
"a fresh marked tmux capture did not prove its successful Git commit"
|
|
1158
|
+
);
|
|
1159
|
+
assert(
|
|
1160
|
+
inferSuccessfulGitActionsFromTmuxCapture(tmuxCommitIntent, {
|
|
1161
|
+
...tmuxCommitCapture,
|
|
1162
|
+
content: tmuxCommitCapture.content.replace("COMMIT_EXIT:0", "COMMIT_EXIT:1"),
|
|
1163
|
+
}).length === 0,
|
|
1164
|
+
"a nonzero tmux commit exit marker was accepted as Git evidence"
|
|
1165
|
+
);
|
|
1166
|
+
assert(
|
|
1167
|
+
inferSuccessfulGitActionsFromTmuxCapture(tmuxCommitIntent, {
|
|
1168
|
+
...tmuxCommitCapture,
|
|
1169
|
+
content: tmuxCommitCapture.content.replace("AGINTI_REVIEW_COMMIT_START", "STALE_COMMIT_START"),
|
|
1170
|
+
}).length === 0,
|
|
1171
|
+
"a tmux capture without the current command marker was accepted as fresh Git evidence"
|
|
1172
|
+
);
|
|
1173
|
+
const tmuxEvidenceState = {
|
|
1174
|
+
meta: {
|
|
1175
|
+
goalContract: { revision: 2 },
|
|
1176
|
+
projectVerification: { mutationRevision: 3 },
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
const tmuxSendResult = {
|
|
1180
|
+
ok: true,
|
|
1181
|
+
toolName: "tmux_send_keys",
|
|
1182
|
+
target: "review:0.0",
|
|
1183
|
+
sentTextSha256: "a".repeat(64),
|
|
1184
|
+
};
|
|
1185
|
+
rememberTmuxGitIntent(
|
|
1186
|
+
tmuxEvidenceState,
|
|
1187
|
+
{ text: tmuxCommitCommand },
|
|
1188
|
+
tmuxSendResult
|
|
1189
|
+
);
|
|
1190
|
+
const trackedTmuxCapture = { ...tmuxCommitCapture };
|
|
1191
|
+
assert(
|
|
1192
|
+
attachVerifiedTmuxGitEvidence(tmuxEvidenceState, trackedTmuxCapture).includes("commit"),
|
|
1193
|
+
"the same-pane tmux capture did not close the pending Git intent"
|
|
1194
|
+
);
|
|
1195
|
+
recordDurableEvidenceCategories(tmuxEvidenceState, trackedTmuxCapture);
|
|
1196
|
+
assert(
|
|
1197
|
+
tmuxEvidenceState.meta.durableGitEvidence.some(
|
|
1198
|
+
(item) =>
|
|
1199
|
+
item.action === "commit" &&
|
|
1200
|
+
item.goalRevision === 2 &&
|
|
1201
|
+
item.mutationRevision === 3
|
|
1202
|
+
),
|
|
1203
|
+
"verified tmux commit evidence was not retained at the active goal and mutation revision"
|
|
1204
|
+
);
|
|
1205
|
+
assert(
|
|
1206
|
+
!tmuxEvidenceState.meta.pendingTmuxGitEvidence["review:0.0"],
|
|
1207
|
+
"verified tmux Git intent remained pending after same-pane capture"
|
|
1208
|
+
);
|
|
1209
|
+
const tmuxCommitLedger = buildScsEvidenceLedger({
|
|
1210
|
+
context: {
|
|
1211
|
+
events: [
|
|
1212
|
+
{
|
|
1213
|
+
type: "tool.completed",
|
|
1214
|
+
data: {
|
|
1215
|
+
...tmuxCommitCapture,
|
|
1216
|
+
verifiedGitActions: verifiedTmuxCommit,
|
|
1217
|
+
verifiedGitGoalRevision: 1,
|
|
1218
|
+
verifiedGitMutationRevision: 1,
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
],
|
|
1222
|
+
},
|
|
1223
|
+
});
|
|
1224
|
+
assert(
|
|
1225
|
+
evaluateScsEvidence(commitContract, tmuxCommitLedger).missingGitActions.length === 0,
|
|
1226
|
+
"verified tmux commit evidence did not satisfy the completion contract"
|
|
1227
|
+
);
|
|
1228
|
+
const historicalTmuxEvents = [
|
|
1229
|
+
{ type: "goal.updated", data: { revision: 2 } },
|
|
1230
|
+
{
|
|
1231
|
+
type: "tool.completed",
|
|
1232
|
+
data: {
|
|
1233
|
+
ok: true,
|
|
1234
|
+
toolName: "run_command",
|
|
1235
|
+
goalRevision: 2,
|
|
1236
|
+
projectMutationRevision: 3,
|
|
1237
|
+
args: { command: "git status --short" },
|
|
1238
|
+
exitCode: 0,
|
|
1239
|
+
stdout: " M README.md",
|
|
1240
|
+
},
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
type: "tool.started",
|
|
1244
|
+
data: {
|
|
1245
|
+
toolName: "tmux_send_keys",
|
|
1246
|
+
args: { target: "review:0.0", text: tmuxCommitCommand },
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
type: "tool.completed",
|
|
1251
|
+
data: {
|
|
1252
|
+
...tmuxSendResult,
|
|
1253
|
+
target: "review:0.0",
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
type: "tool.completed",
|
|
1258
|
+
data: {
|
|
1259
|
+
...tmuxCommitCapture,
|
|
1260
|
+
content: undefined,
|
|
1261
|
+
contentPreview: tmuxCommitCapture.content,
|
|
1262
|
+
},
|
|
1263
|
+
},
|
|
1264
|
+
];
|
|
1265
|
+
const reconciledHistoricalEvents = reconcileTmuxGitEvidenceEvents(
|
|
1266
|
+
historicalTmuxEvents
|
|
1267
|
+
);
|
|
1268
|
+
assert(
|
|
1269
|
+
reconciledHistoricalEvents.at(-1)?.data?.verifiedGitActions?.includes("commit") &&
|
|
1270
|
+
reconciledHistoricalEvents.at(-1)?.data?.verifiedGitGoalRevision === 2 &&
|
|
1271
|
+
reconciledHistoricalEvents.at(-1)?.data?.verifiedGitMutationRevision === 3,
|
|
1272
|
+
"restart reconciliation did not recover revision-scoped tmux commit evidence"
|
|
1273
|
+
);
|
|
1274
|
+
assert(
|
|
1275
|
+
evaluateScsEvidence(
|
|
1276
|
+
commitContract,
|
|
1277
|
+
buildScsEvidenceLedger({ context: { events: historicalTmuxEvents } })
|
|
1278
|
+
).missingGitActions.length === 0,
|
|
1279
|
+
"historical marked tmux commit events did not satisfy the completion contract"
|
|
1280
|
+
);
|
|
1281
|
+
for (const invalidEvents of [
|
|
1282
|
+
historicalTmuxEvents.map((event, index) =>
|
|
1283
|
+
index === 3 ? { ...event, type: "tool.failed", data: { ...event.data, ok: false } } : event
|
|
1284
|
+
),
|
|
1285
|
+
historicalTmuxEvents.map((event, index) =>
|
|
1286
|
+
index === 4 ? { ...event, data: { ...event.data, target: "other:0.0" } } : event
|
|
1287
|
+
),
|
|
1288
|
+
historicalTmuxEvents.map((event, index) =>
|
|
1289
|
+
index === 4
|
|
1290
|
+
? {
|
|
1291
|
+
...event,
|
|
1292
|
+
data: {
|
|
1293
|
+
...event.data,
|
|
1294
|
+
contentPreview: event.data.contentPreview.replace("COMMIT_EXIT:0", "COMMIT_EXIT:1"),
|
|
1295
|
+
},
|
|
1296
|
+
}
|
|
1297
|
+
: event
|
|
1298
|
+
),
|
|
1299
|
+
]) {
|
|
1300
|
+
assert(
|
|
1301
|
+
!reconcileTmuxGitEvidenceEvents(invalidEvents).at(-1)?.data?.verifiedGitActions,
|
|
1302
|
+
"failed, cross-pane, or nonzero historical tmux evidence was accepted"
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
const sanitizedLongTmuxCapture = sanitizeToolResult({
|
|
1306
|
+
...tmuxCommitCapture,
|
|
1307
|
+
content: `${"old scrollback\n".repeat(200)}${tmuxCommitCapture.content}`,
|
|
1308
|
+
});
|
|
1309
|
+
assert(
|
|
1310
|
+
sanitizedLongTmuxCapture.contentTruncated === true &&
|
|
1311
|
+
sanitizedLongTmuxCapture.contentTail.includes("===COMMIT_EXIT:0===") &&
|
|
1312
|
+
inferSuccessfulGitActionsFromTmuxCapture(
|
|
1313
|
+
tmuxCommitIntent,
|
|
1314
|
+
sanitizedLongTmuxCapture
|
|
1315
|
+
).includes("commit"),
|
|
1316
|
+
"truncated tmux evidence did not preserve and verify its bounded content tail"
|
|
1317
|
+
);
|
|
1120
1318
|
const recoveredCommitActions = inferSuccessfulGitActionsFromCommandResult({
|
|
1121
1319
|
ok: true,
|
|
1122
1320
|
exitCode: 0,
|
package/src/agent-runner.js
CHANGED
|
@@ -147,9 +147,11 @@ import {
|
|
|
147
147
|
finishResultClaimsBlocker,
|
|
148
148
|
finishResultClaimsIncompleteWork,
|
|
149
149
|
hasScsBlockerEvidence,
|
|
150
|
+
buildTmuxGitIntent,
|
|
150
151
|
inferAuthoritativeReadOnlyRoutine,
|
|
151
152
|
inferGitActionsFromCommand,
|
|
152
153
|
inferSuccessfulGitActionsFromCommandResult,
|
|
154
|
+
inferSuccessfulGitActionsFromTmuxCapture,
|
|
153
155
|
isResponseOnlyEvidenceScope,
|
|
154
156
|
successfulGitCommitProvesFileMutation,
|
|
155
157
|
isObservationalGitAction,
|
|
@@ -9788,6 +9790,9 @@ export function sanitizeToolResult(result) {
|
|
|
9788
9790
|
safeResult.contentTruncated = false;
|
|
9789
9791
|
} else {
|
|
9790
9792
|
safeResult.contentPreview = safeResult.content.slice(0, TOOL_RESULT_CONTENT_PREVIEW_CHARS);
|
|
9793
|
+
if (safeResult.toolName === "tmux_capture_pane") {
|
|
9794
|
+
safeResult.contentTail = safeResult.content.slice(-TOOL_RESULT_CONTENT_PREVIEW_CHARS);
|
|
9795
|
+
}
|
|
9791
9796
|
safeResult.contentTruncated = true;
|
|
9792
9797
|
delete safeResult.content;
|
|
9793
9798
|
}
|
|
@@ -13778,7 +13783,55 @@ export function mergeDurableGitEvidence(
|
|
|
13778
13783
|
return [...merged.values()].slice(-Math.max(1, Number(limit || 40)));
|
|
13779
13784
|
}
|
|
13780
13785
|
|
|
13781
|
-
function
|
|
13786
|
+
export function rememberTmuxGitIntent(state = {}, args = {}, toolResult = {}) {
|
|
13787
|
+
const gitIntent = toolResult?.ok ? buildTmuxGitIntent(args.text || "") : null;
|
|
13788
|
+
if (!gitIntent || !toolResult?.target) return null;
|
|
13789
|
+
state.meta = state.meta || {};
|
|
13790
|
+
const pendingByTarget = state.meta.pendingTmuxGitEvidence || {};
|
|
13791
|
+
const pending = {
|
|
13792
|
+
...gitIntent,
|
|
13793
|
+
target: toolResult.target,
|
|
13794
|
+
sentTextSha256: toolResult.sentTextSha256,
|
|
13795
|
+
goalRevision: Math.max(0, Number(state.meta?.goalContract?.revision || 0)),
|
|
13796
|
+
mutationRevision: Math.max(
|
|
13797
|
+
0,
|
|
13798
|
+
Number(state.meta?.projectVerification?.mutationRevision || 0)
|
|
13799
|
+
),
|
|
13800
|
+
};
|
|
13801
|
+
pendingByTarget[toolResult.target] = pending;
|
|
13802
|
+
state.meta.pendingTmuxGitEvidence = Object.fromEntries(
|
|
13803
|
+
Object.entries(pendingByTarget).slice(-8)
|
|
13804
|
+
);
|
|
13805
|
+
toolResult.tmuxGitIntentActions = gitIntent.actions;
|
|
13806
|
+
toolResult.tmuxGitIntentMarkers = gitIntent.markers;
|
|
13807
|
+
return pending;
|
|
13808
|
+
}
|
|
13809
|
+
|
|
13810
|
+
export function attachVerifiedTmuxGitEvidence(state = {}, toolResult = {}) {
|
|
13811
|
+
const pendingByTarget = state.meta?.pendingTmuxGitEvidence || {};
|
|
13812
|
+
const pending = pendingByTarget[toolResult?.target];
|
|
13813
|
+
const verifiedGitActions = inferSuccessfulGitActionsFromTmuxCapture(
|
|
13814
|
+
pending,
|
|
13815
|
+
toolResult
|
|
13816
|
+
);
|
|
13817
|
+
if (!verifiedGitActions.length) return [];
|
|
13818
|
+
toolResult.verifiedGitActions = verifiedGitActions;
|
|
13819
|
+
toolResult.verifiedGitSource = "tmux_capture_pane";
|
|
13820
|
+
toolResult.verifiedGitGoalRevision = Math.max(
|
|
13821
|
+
0,
|
|
13822
|
+
Number(pending.goalRevision || 0)
|
|
13823
|
+
);
|
|
13824
|
+
toolResult.verifiedGitMutationRevision = Math.max(
|
|
13825
|
+
0,
|
|
13826
|
+
Number(pending.mutationRevision || 0)
|
|
13827
|
+
);
|
|
13828
|
+
toolResult.verifiedGitCommandSha256 = String(pending.sentTextSha256 || "");
|
|
13829
|
+
delete pendingByTarget[toolResult.target];
|
|
13830
|
+
state.meta.pendingTmuxGitEvidence = pendingByTarget;
|
|
13831
|
+
return verifiedGitActions;
|
|
13832
|
+
}
|
|
13833
|
+
|
|
13834
|
+
export function recordDurableEvidenceCategories(state = {}, toolResult = {}) {
|
|
13782
13835
|
if (!toolResult || toolResult.ok === false || toolResult.blocked || toolResult.skipped) return;
|
|
13783
13836
|
state.meta = state.meta || {};
|
|
13784
13837
|
const categories = new Set(
|
|
@@ -13798,33 +13851,50 @@ function recordDurableEvidenceCategories(state = {}, toolResult = {}) {
|
|
|
13798
13851
|
categories.add("artifact");
|
|
13799
13852
|
}
|
|
13800
13853
|
if (toolName === "read_image") categories.add("visual");
|
|
13854
|
+
const gitActions = toolName === "run_command" && Number(toolResult.exitCode ?? 0) === 0
|
|
13855
|
+
? inferSuccessfulGitActionsFromCommandResult(toolResult)
|
|
13856
|
+
: Array.isArray(toolResult.verifiedGitActions)
|
|
13857
|
+
? toolResult.verifiedGitActions
|
|
13858
|
+
: [];
|
|
13801
13859
|
if (toolName === "run_command" && Number(toolResult.exitCode ?? 0) === 0) {
|
|
13802
13860
|
categories.add("command");
|
|
13803
13861
|
const command = String(toolResult.args?.command || "").trim();
|
|
13804
|
-
const gitActions = inferSuccessfulGitActionsFromCommandResult(toolResult);
|
|
13805
|
-
if (gitActions.length) {
|
|
13806
|
-
categories.add("git");
|
|
13807
|
-
if (successfulGitCommitProvesFileMutation(toolResult)) categories.add("file");
|
|
13808
|
-
state.meta.durableGitActions = [
|
|
13809
|
-
...new Set([
|
|
13810
|
-
...(Array.isArray(state.meta.durableGitActions) ? state.meta.durableGitActions : []),
|
|
13811
|
-
...gitActions,
|
|
13812
|
-
]),
|
|
13813
|
-
];
|
|
13814
|
-
const goalRevision = Math.max(0, Number(state.meta?.goalContract?.revision || 0));
|
|
13815
|
-
const existingGitEvidence = Array.isArray(state.meta.durableGitEvidence)
|
|
13816
|
-
? state.meta.durableGitEvidence
|
|
13817
|
-
: [];
|
|
13818
|
-
state.meta.durableGitEvidence = mergeDurableGitEvidence(existingGitEvidence, gitActions, {
|
|
13819
|
-
goalRevision,
|
|
13820
|
-
mutationRevision: Math.max(
|
|
13821
|
-
0,
|
|
13822
|
-
Number(state.meta?.projectVerification?.mutationRevision || 0)
|
|
13823
|
-
),
|
|
13824
|
-
});
|
|
13825
|
-
}
|
|
13826
13862
|
if (/\b(?:pytest|unittest|npm\s+test|pnpm\s+test|yarn\s+test)\b/i.test(command)) categories.add("test");
|
|
13827
13863
|
}
|
|
13864
|
+
if (gitActions.length) {
|
|
13865
|
+
categories.add("git");
|
|
13866
|
+
if (
|
|
13867
|
+
successfulGitCommitProvesFileMutation(toolResult) ||
|
|
13868
|
+
(gitActions.includes("commit") && toolResult.verifiedGitSource === "tmux_capture_pane")
|
|
13869
|
+
) {
|
|
13870
|
+
categories.add("file");
|
|
13871
|
+
}
|
|
13872
|
+
state.meta.durableGitActions = [
|
|
13873
|
+
...new Set([
|
|
13874
|
+
...(Array.isArray(state.meta.durableGitActions) ? state.meta.durableGitActions : []),
|
|
13875
|
+
...gitActions,
|
|
13876
|
+
]),
|
|
13877
|
+
];
|
|
13878
|
+
const goalRevision = Math.max(
|
|
13879
|
+
0,
|
|
13880
|
+
Number(toolResult.verifiedGitGoalRevision ?? state.meta?.goalContract?.revision ?? 0)
|
|
13881
|
+
);
|
|
13882
|
+
const mutationRevision = Math.max(
|
|
13883
|
+
0,
|
|
13884
|
+
Number(
|
|
13885
|
+
toolResult.verifiedGitMutationRevision ??
|
|
13886
|
+
state.meta?.projectVerification?.mutationRevision ??
|
|
13887
|
+
0
|
|
13888
|
+
)
|
|
13889
|
+
);
|
|
13890
|
+
const existingGitEvidence = Array.isArray(state.meta.durableGitEvidence)
|
|
13891
|
+
? state.meta.durableGitEvidence
|
|
13892
|
+
: [];
|
|
13893
|
+
state.meta.durableGitEvidence = mergeDurableGitEvidence(existingGitEvidence, gitActions, {
|
|
13894
|
+
goalRevision,
|
|
13895
|
+
mutationRevision,
|
|
13896
|
+
});
|
|
13897
|
+
}
|
|
13828
13898
|
state.meta.durableEvidenceCategories = [...categories];
|
|
13829
13899
|
}
|
|
13830
13900
|
|
|
@@ -18759,6 +18829,7 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
18759
18829
|
}
|
|
18760
18830
|
case "tmux_capture_pane": {
|
|
18761
18831
|
const result = await captureTmuxPane(args, config);
|
|
18832
|
+
attachVerifiedTmuxGitEvidence(state, result);
|
|
18762
18833
|
const eventResult = sanitizeToolResult(result);
|
|
18763
18834
|
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
18764
18835
|
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
@@ -18766,6 +18837,7 @@ async function executeTool(browserState, toolCall, snapshot, config, store, obse
|
|
|
18766
18837
|
}
|
|
18767
18838
|
case "tmux_send_keys": {
|
|
18768
18839
|
const result = await sendTmuxKeys(args, config);
|
|
18840
|
+
rememberTmuxGitIntent(state, args, result);
|
|
18769
18841
|
const eventResult = sanitizeToolResult(result);
|
|
18770
18842
|
await store.appendEvent(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
|
18771
18843
|
observers.event(result.ok ? "tool.completed" : "tool.failed", eventResult);
|
package/src/scs-evidence.js
CHANGED
|
@@ -2120,6 +2120,147 @@ export function inferSuccessfulGitActionsFromCommandResult(payload = {}) {
|
|
|
2120
2120
|
return [];
|
|
2121
2121
|
}
|
|
2122
2122
|
|
|
2123
|
+
export function buildTmuxGitIntent(command = "") {
|
|
2124
|
+
const text = String(command || "");
|
|
2125
|
+
// `$?` is expected in the explicit tmux exit marker. Remove only that
|
|
2126
|
+
// non-command expansion before parsing; all other shell expansion remains
|
|
2127
|
+
// subject to the normal ambiguity guard.
|
|
2128
|
+
const parseable = text.replace(/\$\?/g, "0");
|
|
2129
|
+
const actions = inferGitActionsFromCommand(parseable, {
|
|
2130
|
+
requireFailurePropagation: false,
|
|
2131
|
+
}).filter((action) => !isObservationalGitAction(action));
|
|
2132
|
+
const markers = [
|
|
2133
|
+
...text.matchAll(/={3,}([A-Z0-9][A-Z0-9_-]{3,96}(?:START|BEGIN))={3,}/g),
|
|
2134
|
+
].map((match) => String(match[0] || ""));
|
|
2135
|
+
if (!actions.length || !markers.length) return null;
|
|
2136
|
+
return {
|
|
2137
|
+
actions: unique(actions),
|
|
2138
|
+
markers: unique(markers).slice(0, 4),
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
export function inferSuccessfulGitActionsFromTmuxCapture(intent = {}, payload = {}) {
|
|
2143
|
+
if (
|
|
2144
|
+
!intent ||
|
|
2145
|
+
!payload ||
|
|
2146
|
+
payload.ok === false ||
|
|
2147
|
+
payload.blocked ||
|
|
2148
|
+
String(payload.toolName || "") !== "tmux_capture_pane"
|
|
2149
|
+
) {
|
|
2150
|
+
return [];
|
|
2151
|
+
}
|
|
2152
|
+
const content = [payload.content, payload.contentPreview, payload.contentTail]
|
|
2153
|
+
.map((item) => String(item || ""))
|
|
2154
|
+
.filter(Boolean)
|
|
2155
|
+
.join("\n");
|
|
2156
|
+
const markers = Array.isArray(intent.markers)
|
|
2157
|
+
? intent.markers.map(String).filter(Boolean)
|
|
2158
|
+
: [];
|
|
2159
|
+
if (!content || !markers.length || !markers.some((marker) => content.includes(marker))) {
|
|
2160
|
+
return [];
|
|
2161
|
+
}
|
|
2162
|
+
const requested = Array.isArray(intent.actions)
|
|
2163
|
+
? intent.actions.map((item) => String(item || "").toLowerCase()).filter(Boolean)
|
|
2164
|
+
: [];
|
|
2165
|
+
const verified = [];
|
|
2166
|
+
if (
|
|
2167
|
+
requested.includes("commit") &&
|
|
2168
|
+
/={3,}(?:GIT_)?COMMIT_EXIT\s*[:=]\s*0={3,}/i.test(content) &&
|
|
2169
|
+
/^\[[^\]\n]+\s+[0-9a-f]{7,}\]\s+\S+/mi.test(content)
|
|
2170
|
+
) {
|
|
2171
|
+
verified.push("commit");
|
|
2172
|
+
}
|
|
2173
|
+
return verified;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
export function reconcileTmuxGitEvidenceEvents(events = []) {
|
|
2177
|
+
const source = Array.isArray(events) ? events : [];
|
|
2178
|
+
const pendingStarts = new Map();
|
|
2179
|
+
const activeIntents = new Map();
|
|
2180
|
+
let goalRevision = 0;
|
|
2181
|
+
let mutationRevision = 0;
|
|
2182
|
+
|
|
2183
|
+
return source.map((event) => {
|
|
2184
|
+
const type = String(event?.type || "");
|
|
2185
|
+
const data = event?.data && typeof event.data === "object" ? event.data : {};
|
|
2186
|
+
goalRevision = Math.max(
|
|
2187
|
+
goalRevision,
|
|
2188
|
+
Number(
|
|
2189
|
+
data.goalRevision ??
|
|
2190
|
+
(type === "goal.updated" ? data.revision : 0) ??
|
|
2191
|
+
0
|
|
2192
|
+
) || 0
|
|
2193
|
+
);
|
|
2194
|
+
mutationRevision = Math.max(
|
|
2195
|
+
mutationRevision,
|
|
2196
|
+
Number(data.projectMutationRevision ?? data.mutationRevision ?? 0) || 0
|
|
2197
|
+
);
|
|
2198
|
+
|
|
2199
|
+
const toolName = String(data.toolName || "");
|
|
2200
|
+
const target = String(data.target || data.args?.target || "");
|
|
2201
|
+
if (type === "tool.started" && toolName === "tmux_send_keys" && target) {
|
|
2202
|
+
const intent = buildTmuxGitIntent(data.args?.text || "");
|
|
2203
|
+
if (intent) {
|
|
2204
|
+
pendingStarts.set(target, {
|
|
2205
|
+
...intent,
|
|
2206
|
+
target,
|
|
2207
|
+
goalRevision,
|
|
2208
|
+
mutationRevision,
|
|
2209
|
+
});
|
|
2210
|
+
} else {
|
|
2211
|
+
pendingStarts.delete(target);
|
|
2212
|
+
activeIntents.delete(target);
|
|
2213
|
+
}
|
|
2214
|
+
return event;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
if (["tool.completed", "tool.failed"].includes(type) && toolName === "tmux_send_keys" && target) {
|
|
2218
|
+
const pending = pendingStarts.get(target);
|
|
2219
|
+
pendingStarts.delete(target);
|
|
2220
|
+
if (type === "tool.completed" && data.ok !== false && pending) {
|
|
2221
|
+
activeIntents.set(target, {
|
|
2222
|
+
...pending,
|
|
2223
|
+
sentTextSha256: String(data.sentTextSha256 || ""),
|
|
2224
|
+
});
|
|
2225
|
+
} else {
|
|
2226
|
+
activeIntents.delete(target);
|
|
2227
|
+
}
|
|
2228
|
+
return event;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
if (type === "tool.completed" && toolName === "tmux_capture_pane" && target) {
|
|
2232
|
+
const pending = activeIntents.get(target);
|
|
2233
|
+
const verifiedGitActions = inferSuccessfulGitActionsFromTmuxCapture(
|
|
2234
|
+
pending,
|
|
2235
|
+
data
|
|
2236
|
+
);
|
|
2237
|
+
if (verifiedGitActions.length) {
|
|
2238
|
+
activeIntents.delete(target);
|
|
2239
|
+
return {
|
|
2240
|
+
...event,
|
|
2241
|
+
data: {
|
|
2242
|
+
...data,
|
|
2243
|
+
verifiedGitActions,
|
|
2244
|
+
verifiedGitSource: "tmux_capture_pane",
|
|
2245
|
+
verifiedGitGoalRevision: Math.max(
|
|
2246
|
+
0,
|
|
2247
|
+
Number(pending.goalRevision || 0)
|
|
2248
|
+
),
|
|
2249
|
+
verifiedGitMutationRevision: Math.max(
|
|
2250
|
+
0,
|
|
2251
|
+
Number(pending.mutationRevision || 0)
|
|
2252
|
+
),
|
|
2253
|
+
verifiedGitCommandSha256: String(
|
|
2254
|
+
pending.sentTextSha256 || ""
|
|
2255
|
+
),
|
|
2256
|
+
},
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return event;
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2123
2264
|
export function successfulGitCommitProvesFileMutation(payload = {}) {
|
|
2124
2265
|
if (!inferSuccessfulGitActionsFromCommandResult(payload).includes("commit")) return false;
|
|
2125
2266
|
const output = String(payload.stdout || payload.result || "");
|
|
@@ -3955,7 +4096,9 @@ function toolPayloadToEvidence(payload = {}, source = "tool") {
|
|
|
3955
4096
|
if (["read_image", "generate_image"].includes(toolName) || /\b(screenshot|visible|thumbnail|preview|image)\b/.test(text)) {
|
|
3956
4097
|
push("visual", `${toolName || "tool"} supplied visual evidence`, payload.path || payload.outputPath || args.path || "");
|
|
3957
4098
|
}
|
|
3958
|
-
const gitActions =
|
|
4099
|
+
const gitActions = Array.isArray(payload.verifiedGitActions)
|
|
4100
|
+
? payload.verifiedGitActions
|
|
4101
|
+
: inferSuccessfulGitActionsFromCommandResult(payload);
|
|
3959
4102
|
if (gitActions.length) {
|
|
3960
4103
|
push(
|
|
3961
4104
|
"git",
|
|
@@ -3964,8 +4107,18 @@ function toolPayloadToEvidence(payload = {}, source = "tool") {
|
|
|
3964
4107
|
{
|
|
3965
4108
|
gitAction: gitActions.at(-1),
|
|
3966
4109
|
gitActions,
|
|
3967
|
-
goalRevision: Math.max(
|
|
3968
|
-
|
|
4110
|
+
goalRevision: Math.max(
|
|
4111
|
+
0,
|
|
4112
|
+
Number(payload.verifiedGitGoalRevision ?? payload.goalRevision ?? 0)
|
|
4113
|
+
),
|
|
4114
|
+
mutationRevision: Math.max(
|
|
4115
|
+
0,
|
|
4116
|
+
Number(
|
|
4117
|
+
payload.verifiedGitMutationRevision ??
|
|
4118
|
+
payload.projectMutationRevision ??
|
|
4119
|
+
0
|
|
4120
|
+
)
|
|
4121
|
+
),
|
|
3969
4122
|
}
|
|
3970
4123
|
);
|
|
3971
4124
|
}
|
|
@@ -4038,7 +4191,9 @@ function messageToEvidence(message = {}) {
|
|
|
4038
4191
|
}
|
|
4039
4192
|
|
|
4040
4193
|
export function buildScsEvidenceLedger({ state = {}, context = {} } = {}) {
|
|
4041
|
-
const events =
|
|
4194
|
+
const events = reconcileTmuxGitEvidenceEvents(
|
|
4195
|
+
Array.isArray(context.events) ? context.events : []
|
|
4196
|
+
);
|
|
4042
4197
|
const messages = Array.isArray(state.messages) ? state.messages : [];
|
|
4043
4198
|
const eventEvidence = events.flatMap(eventToEvidence);
|
|
4044
4199
|
// Completed tools are mirrored into both the append-only event stream and
|