u-foo 2.5.15 → 3.0.0
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/package.json +1 -1
- package/src/code/agent.js +333 -243
- package/src/code/commands.js +16 -0
- package/src/code/context/assembler.js +18 -13
- package/src/code/context/executionSegment.js +97 -119
- package/src/code/context/index.js +11 -1
- package/src/code/context/planGraph.js +1410 -0
- package/src/code/context/planGraphService.js +857 -0
- package/src/code/context/planMode.js +398 -0
- package/src/code/context/planProjection.js +432 -0
- package/src/code/context/promptLayers.js +21 -5
- package/src/code/context/stateCommit.js +2 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/dispatch.js +17 -1
- package/src/code/index.js +2 -0
- package/src/code/nativeRunner.js +518 -37
- package/src/code/repl.js +160 -18
- package/src/code/runtime/agentWakeup.js +58 -0
- package/src/code/runtime/graphOwner.js +41 -0
- package/src/code/runtime/graphYieldRouter.js +42 -0
- package/src/code/runtime/index.js +15 -0
- package/src/code/runtime/loopMailbox.js +124 -0
- package/src/code/runtime/runtimeEvents.js +39 -0
- package/src/code/runtime/taskControl.js +565 -0
- package/src/code/runtime/taskFocus.js +165 -0
- package/src/code/runtime/taskLoop.js +383 -0
- package/src/code/runtime/taskRun.js +187 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +208 -0
- package/src/code/sessionStore.js +0 -10
- package/src/code/skills/injection.js +1 -0
- package/src/code/taskDecomposer.js +32 -8
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/ui/format/index.js +25 -1
- package/src/ui/format/markdownRenderer.js +224 -2
- package/src/ui/ink/UcodeApp.js +285 -22
- package/src/code/context/featureFlag.js +0 -13
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require("crypto");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TaskRun registry — parent Task node identity vs runnable attempt.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const TASK_RUN_STATUSES = Object.freeze([
|
|
10
|
+
"queued",
|
|
11
|
+
"running",
|
|
12
|
+
"cancelling",
|
|
13
|
+
"succeeded",
|
|
14
|
+
"failed",
|
|
15
|
+
"cancelled",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const TASK_RUN_PHASES = Object.freeze([
|
|
19
|
+
"initializing",
|
|
20
|
+
"planning",
|
|
21
|
+
"waiting_model",
|
|
22
|
+
"executing_tools",
|
|
23
|
+
"finalizing",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const TERMINAL_TASK_RUN = new Set(["succeeded", "failed", "cancelled"]);
|
|
27
|
+
|
|
28
|
+
function createTaskRunId() {
|
|
29
|
+
return `trun_${Date.now().toString(36)}_${randomUUID().slice(0, 6)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emptyTaskRunStore() {
|
|
33
|
+
return {
|
|
34
|
+
byId: {},
|
|
35
|
+
commandLog: {},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureTaskRunStore(executionState = null) {
|
|
40
|
+
const state = executionState && typeof executionState === "object" ? executionState : {};
|
|
41
|
+
if (!state.taskRuns || typeof state.taskRuns !== "object") {
|
|
42
|
+
state.taskRuns = emptyTaskRunStore();
|
|
43
|
+
}
|
|
44
|
+
if (!state.taskRuns.byId || typeof state.taskRuns.byId !== "object") {
|
|
45
|
+
state.taskRuns.byId = {};
|
|
46
|
+
}
|
|
47
|
+
if (!state.taskRuns.commandLog || typeof state.taskRuns.commandLog !== "object") {
|
|
48
|
+
state.taskRuns.commandLog = {};
|
|
49
|
+
}
|
|
50
|
+
return state.taskRuns;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createTaskRun({
|
|
54
|
+
parentGraphId = "",
|
|
55
|
+
parentNodeId = "",
|
|
56
|
+
childGraphId = "",
|
|
57
|
+
attempt = 1,
|
|
58
|
+
} = {}) {
|
|
59
|
+
const now = new Date().toISOString();
|
|
60
|
+
return {
|
|
61
|
+
id: createTaskRunId(),
|
|
62
|
+
parentGraphId: String(parentGraphId || "").trim(),
|
|
63
|
+
parentNodeId: String(parentNodeId || "").trim(),
|
|
64
|
+
childGraphId: String(childGraphId || "").trim(),
|
|
65
|
+
status: "queued",
|
|
66
|
+
phase: "initializing",
|
|
67
|
+
attempt: Number.isFinite(attempt) ? Math.max(1, Math.floor(attempt)) : 1,
|
|
68
|
+
ignoreUserPrompts: true,
|
|
69
|
+
cancelRequested: false,
|
|
70
|
+
result: null,
|
|
71
|
+
error: null,
|
|
72
|
+
changedFiles: [],
|
|
73
|
+
createdAt: now,
|
|
74
|
+
startedAt: "",
|
|
75
|
+
completedAt: "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getTaskRun(executionState = null, taskRunId = "") {
|
|
80
|
+
const store = ensureTaskRunStore(executionState);
|
|
81
|
+
const id = String(taskRunId || "").trim();
|
|
82
|
+
return id && store.byId[id] ? store.byId[id] : null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function putTaskRun(executionState = null, taskRun = null) {
|
|
86
|
+
if (!taskRun || !taskRun.id) return null;
|
|
87
|
+
const store = ensureTaskRunStore(executionState);
|
|
88
|
+
store.byId[taskRun.id] = taskRun;
|
|
89
|
+
return taskRun;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findActiveTaskRunForNode(executionState = null, parentNodeId = "") {
|
|
93
|
+
const store = ensureTaskRunStore(executionState);
|
|
94
|
+
const nodeId = String(parentNodeId || "").trim();
|
|
95
|
+
for (const run of Object.values(store.byId)) {
|
|
96
|
+
if (!run || run.parentNodeId !== nodeId) continue;
|
|
97
|
+
if (run.status === "queued" || run.status === "running" || run.status === "cancelling") {
|
|
98
|
+
return run;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function listActiveWritingTaskRuns(executionState = null) {
|
|
105
|
+
const store = ensureTaskRunStore(executionState);
|
|
106
|
+
return Object.values(store.byId).filter((run) => (
|
|
107
|
+
run
|
|
108
|
+
&& (run.status === "queued" || run.status === "running" || run.status === "cancelling")
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isTerminalTaskRun(run = null) {
|
|
113
|
+
return Boolean(run && TERMINAL_TASK_RUN.has(String(run.status || "")));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Compare-and-set status transition. Returns { ok, run }.
|
|
118
|
+
*/
|
|
119
|
+
function casTaskRunStatus(executionState = null, taskRunId = "", {
|
|
120
|
+
expectedStatus = "",
|
|
121
|
+
nextStatus = "",
|
|
122
|
+
phase = "",
|
|
123
|
+
result = null,
|
|
124
|
+
error = null,
|
|
125
|
+
changedFiles = null,
|
|
126
|
+
} = {}) {
|
|
127
|
+
const run = getTaskRun(executionState, taskRunId);
|
|
128
|
+
if (!run) return { ok: false, code: "TASK_RUN_NOT_FOUND", run: null };
|
|
129
|
+
const expected = String(expectedStatus || "").trim();
|
|
130
|
+
if (expected && run.status !== expected) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
code: "TASK_STATUS_CAS_FAILED",
|
|
134
|
+
run,
|
|
135
|
+
currentStatus: run.status,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const next = String(nextStatus || "").trim();
|
|
139
|
+
if (!TASK_RUN_STATUSES.includes(next)) {
|
|
140
|
+
return { ok: false, code: "INVALID_TASK_STATUS", run };
|
|
141
|
+
}
|
|
142
|
+
run.status = next;
|
|
143
|
+
if (phase && TASK_RUN_PHASES.includes(phase)) run.phase = phase;
|
|
144
|
+
if (result !== null) run.result = result;
|
|
145
|
+
if (error !== null) run.error = error;
|
|
146
|
+
if (Array.isArray(changedFiles)) run.changedFiles = changedFiles.map(String);
|
|
147
|
+
if (next === "running" && !run.startedAt) run.startedAt = new Date().toISOString();
|
|
148
|
+
if (TERMINAL_TASK_RUN.has(next)) {
|
|
149
|
+
run.completedAt = new Date().toISOString();
|
|
150
|
+
run.phase = "finalizing";
|
|
151
|
+
}
|
|
152
|
+
if (next === "cancelling") run.cancelRequested = true;
|
|
153
|
+
putTaskRun(executionState, run);
|
|
154
|
+
return { ok: true, run };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function cacheControlCommand(executionState = null, commandId = "", payload = {}) {
|
|
158
|
+
const id = String(commandId || "").trim();
|
|
159
|
+
if (!id) return;
|
|
160
|
+
const store = ensureTaskRunStore(executionState);
|
|
161
|
+
store.commandLog[id] = JSON.parse(JSON.stringify(payload));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getCachedControlCommand(executionState = null, commandId = "") {
|
|
165
|
+
const id = String(commandId || "").trim();
|
|
166
|
+
if (!id) return null;
|
|
167
|
+
const store = ensureTaskRunStore(executionState);
|
|
168
|
+
return store.commandLog[id] ? JSON.parse(JSON.stringify(store.commandLog[id])) : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
TASK_RUN_STATUSES,
|
|
173
|
+
TASK_RUN_PHASES,
|
|
174
|
+
TERMINAL_TASK_RUN,
|
|
175
|
+
createTaskRunId,
|
|
176
|
+
emptyTaskRunStore,
|
|
177
|
+
ensureTaskRunStore,
|
|
178
|
+
createTaskRun,
|
|
179
|
+
getTaskRun,
|
|
180
|
+
putTaskRun,
|
|
181
|
+
findActiveTaskRunForNode,
|
|
182
|
+
listActiveWritingTaskRuns,
|
|
183
|
+
isTerminalTaskRun,
|
|
184
|
+
casTaskRunStatus,
|
|
185
|
+
cacheControlCommand,
|
|
186
|
+
getCachedControlCommand,
|
|
187
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-TaskRun tool provenance for changedFiles (not git diff attribution).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function ensureProvenanceStore(executionState = null) {
|
|
8
|
+
const state = executionState && typeof executionState === "object" ? executionState : {};
|
|
9
|
+
if (!state.toolProvenance || typeof state.toolProvenance !== "object") {
|
|
10
|
+
state.toolProvenance = { byTaskRunId: {} };
|
|
11
|
+
}
|
|
12
|
+
if (!state.toolProvenance.byTaskRunId || typeof state.toolProvenance.byTaskRunId !== "object") {
|
|
13
|
+
state.toolProvenance.byTaskRunId = {};
|
|
14
|
+
}
|
|
15
|
+
return state.toolProvenance;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function touchedPathsFromTool(tool = "", args = {}) {
|
|
19
|
+
const name = String(tool || "").trim().toLowerCase();
|
|
20
|
+
const paths = [];
|
|
21
|
+
if (name === "write" || name === "edit") {
|
|
22
|
+
const path = String(args && args.path || "").trim();
|
|
23
|
+
if (path) paths.push(path);
|
|
24
|
+
}
|
|
25
|
+
return paths;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function recordToolProvenance(executionState = null, {
|
|
29
|
+
taskRunId = "",
|
|
30
|
+
tool = "",
|
|
31
|
+
args = {},
|
|
32
|
+
graphId = "",
|
|
33
|
+
nodeId = "",
|
|
34
|
+
} = {}) {
|
|
35
|
+
const id = String(taskRunId || "").trim();
|
|
36
|
+
if (!id) return [];
|
|
37
|
+
const store = ensureProvenanceStore(executionState);
|
|
38
|
+
if (!store.byTaskRunId[id]) {
|
|
39
|
+
store.byTaskRunId[id] = { paths: [], events: [] };
|
|
40
|
+
}
|
|
41
|
+
const bucket = store.byTaskRunId[id];
|
|
42
|
+
const paths = touchedPathsFromTool(tool, args);
|
|
43
|
+
for (const path of paths) {
|
|
44
|
+
if (!bucket.paths.includes(path)) bucket.paths.push(path);
|
|
45
|
+
}
|
|
46
|
+
bucket.events.push({
|
|
47
|
+
at: new Date().toISOString(),
|
|
48
|
+
tool: String(tool || ""),
|
|
49
|
+
graphId: String(graphId || ""),
|
|
50
|
+
nodeId: String(nodeId || ""),
|
|
51
|
+
paths,
|
|
52
|
+
});
|
|
53
|
+
// Cap event log
|
|
54
|
+
if (bucket.events.length > 200) bucket.events = bucket.events.slice(-200);
|
|
55
|
+
return paths;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getProvenanceChangedFiles(executionState = null, taskRunId = "") {
|
|
59
|
+
const store = ensureProvenanceStore(executionState);
|
|
60
|
+
const id = String(taskRunId || "").trim();
|
|
61
|
+
const bucket = store.byTaskRunId[id];
|
|
62
|
+
return bucket && Array.isArray(bucket.paths) ? bucket.paths.slice() : [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
ensureProvenanceStore,
|
|
67
|
+
touchedPathsFromTool,
|
|
68
|
+
recordToolProvenance,
|
|
69
|
+
getProvenanceChangedFiles,
|
|
70
|
+
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workspace write lease — up to MAX_CONCURRENT_WRITE_LEASES writing TaskRuns.
|
|
5
|
+
* Agent write/edit/side-effect bash rejected while any Task holds a write lease.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const WRITE_TOOLS = new Set(["write", "edit", "bash"]);
|
|
9
|
+
const MAX_CONCURRENT_WRITE_LEASES = 6;
|
|
10
|
+
|
|
11
|
+
function emptyWorkspaceLease() {
|
|
12
|
+
return {
|
|
13
|
+
holders: [], // [{ kind: "task_run", taskRunId, acquiredAt }]
|
|
14
|
+
mode: "write",
|
|
15
|
+
// Legacy single-holder field; migrated in ensureWorkspaceLease.
|
|
16
|
+
holder: null,
|
|
17
|
+
acquiredAt: "",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureWorkspaceLease(executionState = null) {
|
|
22
|
+
const state = executionState && typeof executionState === "object" ? executionState : {};
|
|
23
|
+
if (!state.workspaceLease || typeof state.workspaceLease !== "object") {
|
|
24
|
+
state.workspaceLease = emptyWorkspaceLease();
|
|
25
|
+
}
|
|
26
|
+
normalizeHolders(state.workspaceLease);
|
|
27
|
+
return state.workspaceLease;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeHolders(lease = null) {
|
|
31
|
+
const next = lease && typeof lease === "object" ? lease : emptyWorkspaceLease();
|
|
32
|
+
if (!Array.isArray(next.holders)) next.holders = [];
|
|
33
|
+
|
|
34
|
+
// Migrate V1 single-holder shape.
|
|
35
|
+
if (next.holder && typeof next.holder === "object" && next.holder.taskRunId) {
|
|
36
|
+
const id = String(next.holder.taskRunId || "").trim();
|
|
37
|
+
if (id && !next.holders.some((h) => h && h.taskRunId === id)) {
|
|
38
|
+
next.holders.push({
|
|
39
|
+
kind: "task_run",
|
|
40
|
+
taskRunId: id,
|
|
41
|
+
acquiredAt: String(next.acquiredAt || new Date().toISOString()),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
next.holder = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
next.holders = next.holders
|
|
48
|
+
.filter((h) => h && h.kind === "task_run" && String(h.taskRunId || "").trim())
|
|
49
|
+
.map((h) => ({
|
|
50
|
+
kind: "task_run",
|
|
51
|
+
taskRunId: String(h.taskRunId).trim(),
|
|
52
|
+
acquiredAt: String(h.acquiredAt || ""),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
// Cap defensive (should not happen if acquire gates correctly).
|
|
56
|
+
if (next.holders.length > MAX_CONCURRENT_WRITE_LEASES) {
|
|
57
|
+
next.holders = next.holders.slice(0, MAX_CONCURRENT_WRITE_LEASES);
|
|
58
|
+
}
|
|
59
|
+
return next.holders;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function listWriteLeaseHolders(executionState = null) {
|
|
63
|
+
return normalizeHolders(ensureWorkspaceLease(executionState)).slice();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function countWriteLeases(executionState = null) {
|
|
67
|
+
return listWriteLeaseHolders(executionState).length;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findWriteLeaseHolder(executionState = null, taskRunId = "") {
|
|
71
|
+
const id = String(taskRunId || "").trim();
|
|
72
|
+
if (!id) return null;
|
|
73
|
+
return listWriteLeaseHolders(executionState).find((h) => h.taskRunId === id) || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function canAcquireWriteLease(executionState = null, taskRunId = "") {
|
|
77
|
+
const id = String(taskRunId || "").trim();
|
|
78
|
+
if (!id) return { ok: false, code: "MISSING_TASK_RUN_ID" };
|
|
79
|
+
if (findWriteLeaseHolder(executionState, id)) {
|
|
80
|
+
return { ok: true, idempotent: true };
|
|
81
|
+
}
|
|
82
|
+
const count = countWriteLeases(executionState);
|
|
83
|
+
if (count >= MAX_CONCURRENT_WRITE_LEASES) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: "MAX_CONCURRENT_TASKS",
|
|
87
|
+
max: MAX_CONCURRENT_WRITE_LEASES,
|
|
88
|
+
current: count,
|
|
89
|
+
holders: listWriteLeaseHolders(executionState),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return { ok: true, current: count, max: MAX_CONCURRENT_WRITE_LEASES };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function acquireTaskWriteLease(executionState = null, taskRunId = "") {
|
|
96
|
+
const lease = ensureWorkspaceLease(executionState);
|
|
97
|
+
const holders = normalizeHolders(lease);
|
|
98
|
+
const id = String(taskRunId || "").trim();
|
|
99
|
+
if (!id) {
|
|
100
|
+
return { ok: false, code: "MISSING_TASK_RUN_ID" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const existing = holders.find((h) => h.taskRunId === id);
|
|
104
|
+
if (existing) {
|
|
105
|
+
return { ok: true, lease, holder: existing, idempotent: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (holders.length >= MAX_CONCURRENT_WRITE_LEASES) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
code: "MAX_CONCURRENT_TASKS",
|
|
112
|
+
max: MAX_CONCURRENT_WRITE_LEASES,
|
|
113
|
+
current: holders.length,
|
|
114
|
+
holders: holders.slice(),
|
|
115
|
+
message: `At most ${MAX_CONCURRENT_WRITE_LEASES} concurrent writing TaskRuns`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const holder = {
|
|
120
|
+
kind: "task_run",
|
|
121
|
+
taskRunId: id,
|
|
122
|
+
acquiredAt: new Date().toISOString(),
|
|
123
|
+
};
|
|
124
|
+
holders.push(holder);
|
|
125
|
+
lease.holders = holders;
|
|
126
|
+
lease.mode = "write";
|
|
127
|
+
return { ok: true, lease, holder };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function releaseTaskWriteLease(executionState = null, taskRunId = "") {
|
|
131
|
+
const lease = ensureWorkspaceLease(executionState);
|
|
132
|
+
const holders = normalizeHolders(lease);
|
|
133
|
+
const id = String(taskRunId || "").trim();
|
|
134
|
+
if (!id) return { ok: true, lease };
|
|
135
|
+
lease.holders = holders.filter((h) => h.taskRunId !== id);
|
|
136
|
+
if (lease.holders.length === 0) {
|
|
137
|
+
lease.acquiredAt = "";
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, lease };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function clearWorkspaceLease(executionState = null) {
|
|
143
|
+
const lease = ensureWorkspaceLease(executionState);
|
|
144
|
+
lease.holders = [];
|
|
145
|
+
lease.holder = null;
|
|
146
|
+
lease.acquiredAt = "";
|
|
147
|
+
return lease;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {string} tool
|
|
152
|
+
* @param {"agent_loop"|"task_loop"} originKind
|
|
153
|
+
* @param {string} [taskRunId]
|
|
154
|
+
*/
|
|
155
|
+
function checkWriteAllowed(executionState = null, {
|
|
156
|
+
tool = "",
|
|
157
|
+
originKind = "agent_loop",
|
|
158
|
+
taskRunId = "",
|
|
159
|
+
} = {}) {
|
|
160
|
+
const name = String(tool || "").trim().toLowerCase();
|
|
161
|
+
if (!WRITE_TOOLS.has(name)) return { ok: true };
|
|
162
|
+
|
|
163
|
+
const holders = listWriteLeaseHolders(executionState);
|
|
164
|
+
if (holders.length === 0) return { ok: true };
|
|
165
|
+
|
|
166
|
+
if (originKind === "task_loop") {
|
|
167
|
+
const id = String(taskRunId || "").trim();
|
|
168
|
+
if (holders.some((h) => h.taskRunId === id)) {
|
|
169
|
+
return { ok: true };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
code: "WORKSPACE_WRITE_LEASE_HELD",
|
|
174
|
+
holders,
|
|
175
|
+
message: "This TaskRun does not hold a workspace write lease.",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Agent loop cannot write while any task holds a lease.
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
code: "WORKSPACE_WRITE_LEASE_HELD",
|
|
183
|
+
holders,
|
|
184
|
+
owner: holders[0] || null,
|
|
185
|
+
message: `${holders.length} active TaskRun(s) hold workspace write lease(s); cancel or wait before writing.`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasActiveWriteLease(executionState = null) {
|
|
190
|
+
return countWriteLeases(executionState) > 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
WRITE_TOOLS,
|
|
195
|
+
MAX_CONCURRENT_WRITE_LEASES,
|
|
196
|
+
emptyWorkspaceLease,
|
|
197
|
+
ensureWorkspaceLease,
|
|
198
|
+
normalizeHolders,
|
|
199
|
+
listWriteLeaseHolders,
|
|
200
|
+
countWriteLeases,
|
|
201
|
+
findWriteLeaseHolder,
|
|
202
|
+
canAcquireWriteLease,
|
|
203
|
+
acquireTaskWriteLease,
|
|
204
|
+
releaseTaskWriteLease,
|
|
205
|
+
clearWorkspaceLease,
|
|
206
|
+
checkWriteAllowed,
|
|
207
|
+
hasActiveWriteLease,
|
|
208
|
+
};
|
package/src/code/sessionStore.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { randomUUID } = require("crypto");
|
|
4
|
-
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
5
4
|
const {
|
|
6
5
|
getTranscriptsDir,
|
|
7
6
|
getTranscriptFilePath,
|
|
@@ -72,7 +71,6 @@ function buildSessionSnapshot(input = {}) {
|
|
|
72
71
|
const source = input && typeof input === "object" ? input : {};
|
|
73
72
|
const sessionId = resolveSessionId(source.sessionId);
|
|
74
73
|
const createdAt = String(source.createdAt || "").trim() || toIsoNow();
|
|
75
|
-
const useV2 = isContextV2Enabled() || Number(source.version) >= 2;
|
|
76
74
|
|
|
77
75
|
const base = {
|
|
78
76
|
sessionId,
|
|
@@ -84,14 +82,6 @@ function buildSessionSnapshot(input = {}) {
|
|
|
84
82
|
updatedAt: toIsoNow(),
|
|
85
83
|
};
|
|
86
84
|
|
|
87
|
-
if (!useV2) {
|
|
88
|
-
return {
|
|
89
|
-
version: 1,
|
|
90
|
-
...base,
|
|
91
|
-
nlMessages: cloneMessages(source.nlMessages),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
85
|
return {
|
|
96
86
|
version: 2,
|
|
97
87
|
...base,
|
|
@@ -4,8 +4,34 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { runNativeAgentTask } = require("./nativeRunner");
|
|
7
|
-
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
8
7
|
const { assembleModelContext, recordToolCallInSession } = require("./context/assembler");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const {
|
|
10
|
+
buildSkillManifest,
|
|
11
|
+
renderActiveSkillBlock,
|
|
12
|
+
} = require("./skills");
|
|
13
|
+
const { sanitizeSkillContent } = require("./skills/injection");
|
|
14
|
+
|
|
15
|
+
function renderActiveSkillBodiesFromState(state = {}) {
|
|
16
|
+
const skills = Array.isArray(state.activeSkills) ? state.activeSkills : [];
|
|
17
|
+
const blocks = [];
|
|
18
|
+
for (const skill of skills) {
|
|
19
|
+
const skillPath = String(skill && skill.path || "").trim();
|
|
20
|
+
if (!skillPath) continue;
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(skillPath, "utf8");
|
|
23
|
+
const content = sanitizeSkillContent(raw);
|
|
24
|
+
const manifest = buildSkillManifest({
|
|
25
|
+
name: skill.name || "",
|
|
26
|
+
path: skillPath,
|
|
27
|
+
}, { bodyArtifactId: skill.bodyArtifactId || "" });
|
|
28
|
+
blocks.push(renderActiveSkillBlock(manifest, content));
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore missing skill bodies
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return blocks;
|
|
34
|
+
}
|
|
9
35
|
|
|
10
36
|
/**
|
|
11
37
|
* Decompose a bug fix task into manageable steps
|
|
@@ -135,13 +161,11 @@ async function runDecomposedTask({
|
|
|
135
161
|
messages = [],
|
|
136
162
|
sessionId = "",
|
|
137
163
|
state = null,
|
|
138
|
-
contextV2 = false,
|
|
139
164
|
systemBlocks = null,
|
|
140
165
|
}) {
|
|
141
166
|
const steps = decomposeBugFixTask(task);
|
|
142
167
|
const results = [];
|
|
143
168
|
let aborted = false;
|
|
144
|
-
const useV2 = Boolean(contextV2 || isContextV2Enabled());
|
|
145
169
|
|
|
146
170
|
// Check if already aborted
|
|
147
171
|
if (signal && signal.aborted) {
|
|
@@ -174,12 +198,13 @@ async function runDecomposedTask({
|
|
|
174
198
|
let stepMessages = messages;
|
|
175
199
|
let stepSystemPrompt = systemPrompt;
|
|
176
200
|
let stepSystemBlocks = systemBlocks;
|
|
177
|
-
if (
|
|
201
|
+
if (state) {
|
|
202
|
+
const skillBodies = renderActiveSkillBodiesFromState(state);
|
|
178
203
|
const assembled = assembleModelContext(state, {
|
|
179
204
|
workspaceRoot,
|
|
180
205
|
model,
|
|
181
206
|
provider,
|
|
182
|
-
turnDynamic: stepPrompt,
|
|
207
|
+
turnDynamic: [...skillBodies, stepPrompt].filter(Boolean).join("\n\n"),
|
|
183
208
|
});
|
|
184
209
|
stepMessages = assembled.messages;
|
|
185
210
|
stepSystemPrompt = assembled.systemPrompt;
|
|
@@ -197,13 +222,12 @@ async function runDecomposedTask({
|
|
|
197
222
|
timeoutMs: step.timeoutMs,
|
|
198
223
|
onToolEvent,
|
|
199
224
|
signal,
|
|
200
|
-
|
|
201
|
-
onArtifactPersisted: useV2 && state
|
|
225
|
+
onArtifactPersisted: state
|
|
202
226
|
? (persisted) => recordToolCallInSession(state, persisted, workspaceRoot)
|
|
203
227
|
: null,
|
|
204
228
|
});
|
|
205
229
|
|
|
206
|
-
if (
|
|
230
|
+
if (state && stepResult && Array.isArray(stepResult.messages)) {
|
|
207
231
|
const { syncMessagesToTranscript } = require("./context/assembler");
|
|
208
232
|
syncMessagesToTranscript(state, stepResult.messages, workspaceRoot);
|
|
209
233
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { runAskUserTool } = require("../context/userInteraction");
|
|
4
|
+
|
|
5
|
+
function runAskUserToolDispatch(args = {}, options = {}) {
|
|
6
|
+
return runAskUserTool(args, options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
runAskUserTool: runAskUserToolDispatch,
|
|
11
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
runPlanGraphCommand,
|
|
5
|
+
normalizePlanGraphCommand,
|
|
6
|
+
} = require("../context/planGraphService");
|
|
7
|
+
|
|
8
|
+
function runPlanGraphTool(args = {}, options = {}) {
|
|
9
|
+
const command = normalizePlanGraphCommand(args) || args;
|
|
10
|
+
const result = runPlanGraphCommand(command, {
|
|
11
|
+
executionState: options.executionState,
|
|
12
|
+
runTool: options.runTool,
|
|
13
|
+
autoAdvance: options.autoAdvance !== false,
|
|
14
|
+
parallel: options.parallel !== false,
|
|
15
|
+
knownTools: options.knownTools,
|
|
16
|
+
maxNodeRuns: options.maxNodeRuns,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const payload = result.modelPayload || result;
|
|
20
|
+
return {
|
|
21
|
+
ok: payload.status === "accepted",
|
|
22
|
+
...payload,
|
|
23
|
+
executionState: result.executionState,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
runPlanGraphTool,
|
|
29
|
+
};
|
package/src/ui/format/index.js
CHANGED
|
@@ -129,7 +129,16 @@ function normalizeModelLabel(model = "") {
|
|
|
129
129
|
return "default";
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
function buildUcodeBannerLines({
|
|
132
|
+
function buildUcodeBannerLines({
|
|
133
|
+
model = "",
|
|
134
|
+
engine = "ufoo-core",
|
|
135
|
+
nickname = "",
|
|
136
|
+
agentId = "",
|
|
137
|
+
workspaceRoot = "",
|
|
138
|
+
sessionId = "",
|
|
139
|
+
width = 0,
|
|
140
|
+
planMode = false,
|
|
141
|
+
} = {}) {
|
|
133
142
|
const modelLabel = normalizeModelLabel(model);
|
|
134
143
|
void width;
|
|
135
144
|
void engine;
|
|
@@ -151,6 +160,9 @@ function buildUcodeBannerLines({ model = "", engine = "ufoo-core", nickname = ""
|
|
|
151
160
|
const infoLines = [];
|
|
152
161
|
infoLines.push(`${chalk.dim("Version:")} ${chalk.cyan.bold(UCODE_VERSION)}`);
|
|
153
162
|
infoLines.push(`${chalk.dim("Model:")} ${chalk.yellow(modelLabel)}`);
|
|
163
|
+
if (planMode) {
|
|
164
|
+
infoLines.push(`${chalk.dim("Mode:")} ${chalk.magenta.bold("PLAN")}`);
|
|
165
|
+
}
|
|
154
166
|
infoLines.push(`${chalk.dim("Dictionary:")} ${chalk.gray(shortPath)}`);
|
|
155
167
|
const normalizedSessionId = String(sessionId || "").trim();
|
|
156
168
|
if (normalizedSessionId) {
|
|
@@ -254,6 +266,16 @@ function renderLogLinesWithMarkdownAnsi(text = "", state = {}) {
|
|
|
254
266
|
return renderMarkdownLinesAnsi(text, state);
|
|
255
267
|
}
|
|
256
268
|
|
|
269
|
+
function createMarkdownTableBuffer() {
|
|
270
|
+
const { createMarkdownTableBuffer: create } = require("./markdownRenderer");
|
|
271
|
+
return create();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isTableRowLine(line = "") {
|
|
275
|
+
const { isTableRowLine: check } = require("./markdownRenderer");
|
|
276
|
+
return check(line);
|
|
277
|
+
}
|
|
278
|
+
|
|
257
279
|
function messageContentText(message = {}) {
|
|
258
280
|
if (!message || typeof message !== "object") return "";
|
|
259
281
|
const content = message.content;
|
|
@@ -1233,6 +1255,8 @@ module.exports = {
|
|
|
1233
1255
|
planProjectsRail,
|
|
1234
1256
|
renderLogLinesWithMarkdown,
|
|
1235
1257
|
renderLogLinesWithMarkdownAnsi,
|
|
1258
|
+
createMarkdownTableBuffer,
|
|
1259
|
+
isTableRowLine,
|
|
1236
1260
|
resolveAgentSelectionOnDown,
|
|
1237
1261
|
resolveHistoryDownTransition,
|
|
1238
1262
|
shouldClearAgentSelectionOnUp,
|