@tea-agent/loop-agent 0.33.6 → 0.33.7-beta.1
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/CHANGELOG.md +39 -23
- package/dist/application/task-lifecycle/advance.js +4 -254
- package/dist/application/task-lifecycle/gates.js +0 -50
- package/dist/application/task-lifecycle/observe.js +2 -11
- package/dist/commands/init-upgrade.js +1 -32
- package/dist/commands/init.js +3 -94
- package/dist/executors/shell-executor.js +91 -4
- package/dist/executors/shell-write-guard.js +8 -26
- package/dist/shared/operator/capabilities.js +42 -72
- package/dist/task/source-prepare/index.js +0 -2
- package/dist/task/source-prepare/parse-intent.js +10 -58
- package/dist/task/source-prepare/prepare.js +16 -180
- package/dist/task/source-prepare/reference-integrity.js +2 -18
- package/dist/worker/console/app-data.js +0 -2
- package/dist/worker/console/chat/chat-event-store.js +25 -190
- package/dist/worker/console/chat/instruction-skills.js +217 -0
- package/dist/worker/console/chat/pi-console-config.js +32 -250
- package/dist/worker/console/chat/pi-runtime.js +71 -625
- package/dist/worker/console/chat/resource-loader.js +4 -5
- package/dist/worker/console/chat/routes.js +146 -324
- package/dist/worker/console/chat/runtime-context.js +12 -48
- package/dist/worker/console/chat/runtime-selection.js +0 -59
- package/dist/worker/console/chat/shortcuts.js +0 -1
- package/dist/worker/console/chat/tool-adapter.js +3 -9
- package/dist/worker/console/chat/tools.js +1 -5
- package/dist/worker/console/operator-actions.js +68 -559
- package/dist/worker/console/server.js +15 -8
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +1 -0
- package/dist/worker/console/static/assets/index-CteJFFL2.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +8 -45
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -16
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +184 -210
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +5 -49
- package/dist/worker/console/static-src/operator-chat/useComposer.js +0 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +74 -225
- package/dist/worker/delivery/final-verification.js +5 -13
- package/dist/worker/delivery/package.js +19 -31
- package/dist/worker/delivery/verification-bundle.js +4 -6
- package/dist/worker/observe/static/operator-chrome.css +2 -5
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/styles.css +9 -39
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +462 -33
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +25 -1
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +552 -124
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/frontend-worktree-diff.js +27 -12
- package/dist/workflows/dag/init-hybrid.js +49 -37
- package/dist/workflows/dag/types.js +7 -0
- package/dist/workflows/dag/workspace-checkpoint.js +27 -8
- package/docs/templates/backend-test-dag.json +33 -30
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -3
- package/skills/loop-agent/references/source-and-plan-practice.md +0 -13
- package/skills/loop-agent/references/task-workflow.md +0 -4
- package/dist/shared/resilient-git.js +0 -133
- package/dist/task/source-prepare/artifact-meta.js +0 -137
- package/dist/task/source-prepare/semantic-intake.js +0 -404
- package/dist/worker/console/dag-execution-receipt.js +0 -380
- package/dist/worker/console/static/assets/index-BUOLppPr.js +0 -28
- package/dist/worker/console/static/assets/index-C1KzazY5.css +0 -1
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +0 -257
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +0 -196
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { open, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { readJsonIfExists, writeSecureJson } from "../app-data.js";
|
|
5
|
+
import { OPERATOR_CHAT_ALLOWED_INSTRUCTION_SKILLS } from "./instruction-skills.js";
|
|
4
6
|
const revision = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
5
7
|
export async function readModelsConfig(agentDir) {
|
|
6
8
|
let raw = "{}";
|
|
@@ -108,219 +110,26 @@ export async function patchModelsConfig(agentDir, input) {
|
|
|
108
110
|
await rename(tmp, file);
|
|
109
111
|
return readModelsConfig(agentDir);
|
|
110
112
|
}
|
|
111
|
-
|
|
112
|
-
export
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* One npm-style range token: optional comparator/v-prefix followed by an
|
|
117
|
-
* exact/x-form version, a wildcard, or a prerelease/build suffix. Accepts
|
|
118
|
-
* >=2.0.0, <3.0.0, ^2.1.0, ~1.2.3, ~1, 1.2, 1.x, 1.2.x, 1.2.3-beta.1+build.5, *.
|
|
119
|
-
*/
|
|
120
|
-
const SPEC_RANGE_TOKEN = "(?:>=|<=|[<>=~^*v])?(?:\\d+(?:\\.(?:\\d+|x|X)){0,2}(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?|\\*|x|X)";
|
|
121
|
-
/** Whitespace-separated comparator list, e.g. ">=2.0.0 <3.0.0". */
|
|
122
|
-
const SPEC_RANGE = new RegExp(`^${SPEC_RANGE_TOKEN}(?:\\s+${SPEC_RANGE_TOKEN})*$`);
|
|
123
|
-
/**
|
|
124
|
-
* Installed-side grammar: an EXACT semver with optional prerelease and build
|
|
125
|
-
* metadata (1.2.3, 1.2.3-beta.1, 1.2.3+build.5, 1.2.3-beta.1+build.5). Ranges
|
|
126
|
-
* and partial shapes (*, 1.x, ~1, 1.2) are rejected with a field diagnostic.
|
|
127
|
-
*/
|
|
128
|
-
const INSTALLED_VERSION = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
129
|
-
/**
|
|
130
|
-
* Try to derive the explicit version/range from an npm-style spec — verbatim
|
|
131
|
-
* projection, zero resolution and zero install (AC-RF-1). Git/URL shapes
|
|
132
|
-
* (containing /, #, :// or git+) never project a configuredVersion.
|
|
133
|
-
*/
|
|
134
|
-
function versionFromSpec(spec) {
|
|
135
|
-
// npm:name@1.2.3 / name@^1.2 / @scope/name@1.2.3 — never guess on git/url specs.
|
|
136
|
-
const match = spec.match(/^(?:npm:)?(@?[^@/\s]+(?:\/[^@\s]+)?)@(.+)$/);
|
|
137
|
-
if (!match)
|
|
138
|
-
return undefined;
|
|
139
|
-
const version = match[2];
|
|
140
|
-
if (/[\/#:]|git\+/.test(version))
|
|
141
|
-
return undefined;
|
|
142
|
-
return SPEC_RANGE.test(version) ? version : undefined;
|
|
113
|
+
const preferencesFile = (appData) => path.join(appData.chats, ".instruction-skill-preferences.json");
|
|
114
|
+
export async function readSkillPreferences(appData) {
|
|
115
|
+
const value = await readJsonIfExists(preferencesFile(appData));
|
|
116
|
+
return (value?.disabledNames ?? []).filter((name) => OPERATOR_CHAT_ALLOWED_INSTRUCTION_SKILLS.includes(name));
|
|
143
117
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return { ok: false, missing: true, diagnostic: "" };
|
|
158
|
-
return {
|
|
159
|
-
ok: false,
|
|
160
|
-
diagnostic: `PACKAGE_JSON_READ_FAILED: ${file} (${error instanceof Error ? error.message : String(error)})`,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
const buffer = Buffer.alloc(MAX_PACKAGE_JSON_BYTES + 1);
|
|
165
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
166
|
-
if (bytesRead > MAX_PACKAGE_JSON_BYTES) {
|
|
167
|
-
return {
|
|
168
|
-
ok: false,
|
|
169
|
-
diagnostic: `PACKAGE_JSON_TOO_LARGE: ${file} (exceeds ${MAX_PACKAGE_JSON_BYTES} bytes)`,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
const raw = buffer.subarray(0, bytesRead).toString("utf8");
|
|
173
|
-
let parsed;
|
|
174
|
-
try {
|
|
175
|
-
parsed = JSON.parse(raw);
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
return {
|
|
179
|
-
ok: false,
|
|
180
|
-
diagnostic: `PACKAGE_JSON_INVALID: ${file} (${error instanceof Error ? error.message : String(error)})`,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
184
|
-
return {
|
|
185
|
-
ok: false,
|
|
186
|
-
diagnostic: `PACKAGE_JSON_INVALID: ${file} (root must be a JSON object)`,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
const record = parsed;
|
|
190
|
-
return { ok: true, value: { name: record.name, version: record.version } };
|
|
191
|
-
}
|
|
192
|
-
finally {
|
|
193
|
-
await handle.close().catch(() => { });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Owning npm package root for a resolved entry directory: the deepest
|
|
198
|
-
* ancestor BELOW the nearest node_modules directory (scoped:
|
|
199
|
-
* node_modules/@scope/<pkg>). Returns undefined when the entry is not inside
|
|
200
|
-
* any node_modules boundary — readOwnedPackageJson then degrades with
|
|
201
|
-
* PACKAGE_JSON_NOT_FOUND instead of walking to an ancestor manifest.
|
|
202
|
-
*/
|
|
203
|
-
function packageBoundaryRoot(startDir) {
|
|
204
|
-
let dir = startDir;
|
|
205
|
-
for (let depth = 0; depth < MAX_PACKAGE_JSON_WALK_DEPTH; depth += 1) {
|
|
206
|
-
if (path.basename(dir) === "node_modules") {
|
|
207
|
-
const segments = path
|
|
208
|
-
.relative(dir, startDir)
|
|
209
|
-
.split(path.sep)
|
|
210
|
-
.filter((segment) => segment.length > 0);
|
|
211
|
-
if (segments.length === 0)
|
|
212
|
-
return dir;
|
|
213
|
-
if (segments[0].startsWith("@") && segments.length >= 2) {
|
|
214
|
-
return path.join(dir, segments[0], segments[1]);
|
|
215
|
-
}
|
|
216
|
-
return path.join(dir, segments[0]);
|
|
217
|
-
}
|
|
218
|
-
const parent = path.dirname(dir);
|
|
219
|
-
if (parent === dir)
|
|
220
|
-
return undefined;
|
|
221
|
-
dir = parent;
|
|
222
|
-
}
|
|
223
|
-
return undefined;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Walk UP from an owned resolved resource path (bounded, separator-normalized)
|
|
227
|
-
* to the NEAREST package.json within the owning npm package boundary and
|
|
228
|
-
* project its validated metadata. The walk NEVER crosses the package root
|
|
229
|
-
* (node_modules/<pkg> or node_modules/@scope/<pkg>): a missing manifest there
|
|
230
|
-
* yields PACKAGE_JSON_NOT_FOUND instead of mis-attributing an ancestor
|
|
231
|
-
* workspace manifest (AC-RF-3). Missing name/version fields yield per-field
|
|
232
|
-
* PACKAGE_JSON_FIELD_INVALID diagnostics and suppress the whole metadata trio
|
|
233
|
-
* (AC-RF-4). Corrupt / oversized / invalid-field reads stop at that nearest
|
|
234
|
-
* package.json with a per-package diagnostic. Empty paths never probe the
|
|
235
|
-
* filesystem.
|
|
236
|
-
*/
|
|
237
|
-
async function readOwnedPackageJson(entryPath) {
|
|
238
|
-
if (!entryPath)
|
|
239
|
-
return { ok: false, diagnostics: [] };
|
|
240
|
-
const normalized = entryPath.replace(/[\\/]+/g, path.sep);
|
|
241
|
-
const startDir = path.dirname(normalized);
|
|
242
|
-
const boundaryRoot = packageBoundaryRoot(startDir);
|
|
243
|
-
if (boundaryRoot === undefined) {
|
|
244
|
-
// No legal node_modules/<pkg> or node_modules/@scope/<pkg> boundary:
|
|
245
|
-
// never walk to (or accept) an ancestor manifest (AC-B1/AC-B2).
|
|
246
|
-
return {
|
|
247
|
-
ok: false,
|
|
248
|
-
diagnostics: [
|
|
249
|
-
`PACKAGE_JSON_NOT_FOUND: ${startDir} (resolved resource is not inside a legal node_modules package boundary)`,
|
|
250
|
-
],
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
let dir = startDir;
|
|
254
|
-
for (let depth = 0; depth < MAX_PACKAGE_JSON_WALK_DEPTH; depth += 1) {
|
|
255
|
-
const file = path.join(dir, "package.json");
|
|
256
|
-
const read = await readPackageJsonBounded(file);
|
|
257
|
-
if (read.ok) {
|
|
258
|
-
const { name, version } = read.value;
|
|
259
|
-
const meta = { installedPath: dir };
|
|
260
|
-
const fieldDiagnostics = [];
|
|
261
|
-
if (name === undefined) {
|
|
262
|
-
fieldDiagnostics.push(`PACKAGE_JSON_FIELD_INVALID: ${file} (missing name)`);
|
|
263
|
-
}
|
|
264
|
-
else if (typeof name !== "string" ||
|
|
265
|
-
name.trim().length === 0 ||
|
|
266
|
-
name.length > 214) {
|
|
267
|
-
fieldDiagnostics.push(`PACKAGE_JSON_FIELD_INVALID: ${file} (name must be a non-empty string ≤ 214 chars)`);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
meta.packageName = name;
|
|
271
|
-
}
|
|
272
|
-
if (version === undefined) {
|
|
273
|
-
fieldDiagnostics.push(`PACKAGE_JSON_FIELD_INVALID: ${file} (missing version)`);
|
|
274
|
-
}
|
|
275
|
-
else if (typeof version !== "string" || !INSTALLED_VERSION.test(version)) {
|
|
276
|
-
// Type + format validation only: the browser projection (80-char limit)
|
|
277
|
-
// is the single truncation authority for versions (AC-8 reuse chain).
|
|
278
|
-
fieldDiagnostics.push(`PACKAGE_JSON_FIELD_INVALID: ${file} (version must be an exact semver string)`);
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
meta.version = version;
|
|
282
|
-
}
|
|
283
|
-
if (name === undefined || version === undefined) {
|
|
284
|
-
// A manifest missing a required field cannot prove package ownership:
|
|
285
|
-
// none of packageName/version/installedPath is projected (AC-RF-4).
|
|
286
|
-
return { ok: false, diagnostics: fieldDiagnostics };
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
ok: true,
|
|
290
|
-
meta,
|
|
291
|
-
...(fieldDiagnostics.length > 0
|
|
292
|
-
? { diagnostics: fieldDiagnostics }
|
|
293
|
-
: {}),
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
// Missing file (ENOENT): keep walking up the owned chain, but never past
|
|
297
|
-
// the owning package root. Any other read/parse failure means the nearest
|
|
298
|
-
// package.json IS the package boundary — degrade only this package and
|
|
299
|
-
// stop (AC-5).
|
|
300
|
-
if (read.missing !== true) {
|
|
301
|
-
return { ok: false, diagnostics: [read.diagnostic] };
|
|
302
|
-
}
|
|
303
|
-
if (boundaryRoot !== undefined && dir === boundaryRoot) {
|
|
304
|
-
return {
|
|
305
|
-
ok: false,
|
|
306
|
-
diagnostics: [
|
|
307
|
-
`PACKAGE_JSON_NOT_FOUND: ${dir} (no package.json within the owning npm package boundary)`,
|
|
308
|
-
],
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
const parent = path.dirname(dir);
|
|
312
|
-
if (parent === dir)
|
|
313
|
-
break;
|
|
314
|
-
dir = parent;
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
ok: false,
|
|
318
|
-
diagnostics: [
|
|
319
|
-
`PACKAGE_JSON_NOT_FOUND: ${startDir} (no package.json on the resolved resource chain)`,
|
|
320
|
-
],
|
|
321
|
-
};
|
|
118
|
+
export async function writeSkillPreferences(appData, disabledNames) {
|
|
119
|
+
const allowed = new Set(OPERATOR_CHAT_ALLOWED_INSTRUCTION_SKILLS);
|
|
120
|
+
if (disabledNames.some((name) => !allowed.has(name)))
|
|
121
|
+
throw Object.assign(new Error("preferences contain a non-allowed skill"), {
|
|
122
|
+
code: "INVALID_INPUT",
|
|
123
|
+
status: 400,
|
|
124
|
+
});
|
|
125
|
+
const normalized = [...new Set(disabledNames)].sort();
|
|
126
|
+
await writeSecureJson(preferencesFile(appData), {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
disabledNames: normalized,
|
|
129
|
+
});
|
|
130
|
+
return normalized;
|
|
322
131
|
}
|
|
323
|
-
export async function readPackageInventory(agentDir, repoRoot
|
|
132
|
+
export async function readPackageInventory(agentDir, repoRoot) {
|
|
324
133
|
const read = async (file) => {
|
|
325
134
|
try {
|
|
326
135
|
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
@@ -332,45 +141,18 @@ export async function readPackageInventory(agentDir, repoRoot, resolved) {
|
|
|
332
141
|
};
|
|
333
142
|
const global = await read(path.join(agentDir, "settings.json"));
|
|
334
143
|
const project = await read(path.join(repoRoot, ".pi", "settings.json"));
|
|
335
|
-
|
|
336
|
-
...global.map((spec) => ({
|
|
337
|
-
...project.map((spec) => ({ spec, scope: "project" })),
|
|
338
|
-
];
|
|
339
|
-
const entries = [];
|
|
340
|
-
for (const { spec, scope } of specs) {
|
|
341
|
-
// A resource belongs to this package when its source matches the spec
|
|
342
|
-
// exactly or ends with it (e.g. package subpath sources).
|
|
343
|
-
const owned = (resolved ?? []).filter((item) => item.source === spec || item.source.endsWith(`/${spec}`));
|
|
344
|
-
const diagnostics = owned
|
|
345
|
-
.map((item) => item.scope === "temporary" ? `temporary scope: ${item.path}` : "")
|
|
346
|
-
.filter(Boolean);
|
|
347
|
-
// No owned resources → no filesystem probing at all (AC-1): the optional
|
|
348
|
-
// metadata fields stay absent and only the always-projected cwd is added.
|
|
349
|
-
const meta = owned.length > 0 && owned[0]?.path
|
|
350
|
-
? await readOwnedPackageJson(owned[0].path)
|
|
351
|
-
: { ok: true, meta: undefined, diagnostics: [] };
|
|
352
|
-
if (meta.diagnostics)
|
|
353
|
-
diagnostics.push(...meta.diagnostics);
|
|
354
|
-
entries.push({
|
|
144
|
+
return [
|
|
145
|
+
...global.map((spec) => ({
|
|
355
146
|
spec,
|
|
356
|
-
scope,
|
|
147
|
+
scope: "global",
|
|
357
148
|
enabled: true,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
? { installedPath: meta.meta.installedPath }
|
|
368
|
-
: {}),
|
|
369
|
-
...(owned[0]?.path ? { path: owned[0].path } : {}),
|
|
370
|
-
resolved: owned,
|
|
371
|
-
resourceCount: owned.length,
|
|
372
|
-
diagnostics,
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
return entries;
|
|
149
|
+
resourceCount: 0,
|
|
150
|
+
})),
|
|
151
|
+
...project.map((spec) => ({
|
|
152
|
+
spec,
|
|
153
|
+
scope: "project",
|
|
154
|
+
enabled: true,
|
|
155
|
+
resourceCount: 0,
|
|
156
|
+
})),
|
|
157
|
+
];
|
|
376
158
|
}
|