@tea-agent/loop-agent 0.23.1 → 0.24.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 +61 -1
- package/README.md +1 -1
- package/bin/agent-worker.js +0 -0
- package/dist/commands/init.js +2 -2
- package/dist/executors/shell-executor.js +20 -7
- package/dist/shared/operator/capabilities.js +475 -2
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/frontend-project-capability.js +316 -0
- package/dist/task/task-demand-routing.js +432 -0
- package/dist/worker/console/app-data.js +2 -0
- package/dist/worker/console/chat/artifact-card.js +23 -0
- package/dist/worker/console/chat/chat-event-store.js +495 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +37 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/operation-card.js +23 -0
- package/dist/worker/console/chat/pi-console-config.js +158 -0
- package/dist/worker/console/chat/pi-runtime.js +581 -43
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +116 -0
- package/dist/worker/console/chat/resource-loader.js +18 -17
- package/dist/worker/console/chat/routes.js +1359 -65
- package/dist/worker/console/chat/runtime-context.js +24 -0
- package/dist/worker/console/chat/runtime-selection.js +37 -0
- package/dist/worker/console/chat/session-store.js +210 -11
- package/dist/worker/console/chat/shortcuts.js +15 -0
- package/dist/worker/console/chat/tool-adapter.js +81 -194
- package/dist/worker/console/chat/tools.js +72 -48
- package/dist/worker/console/chat/usage.js +37 -0
- package/dist/worker/console/chat/workspace-landing.js +56 -0
- package/dist/worker/console/dag-confirmation.js +42 -8
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/interview/grill-me.js +28 -5
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/operation-runner.js +6 -2
- package/dist/worker/console/operation-sse.js +26 -0
- package/dist/worker/console/operator-actions.js +420 -7
- package/dist/worker/console/server.js +14 -2
- package/dist/worker/console/static/assets/index-Cv68Ge5n.js +27 -0
- package/dist/worker/console/static/assets/index-Dp_2sKGk.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observe/routes.js +8 -4
- package/dist/workflows/dag/backend-test-intake-context.js +10 -12
- package/dist/workflows/dag/backend-test-markdown-workflow.js +232 -47
- package/dist/workflows/dag/backend-test-result-contract.js +233 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +23 -17
- package/dist/workflows/dag/frontend-lint-baseline.js +4 -4
- package/dist/workflows/dag/frontend-project-capability.js +1 -316
- package/dist/workflows/dag/frontend-worktree-diff.js +4 -1
- package/dist/workflows/dag/init-hybrid.js +46 -43
- package/dist/workflows/dag/rerun-plan.js +6 -10
- package/dist/workflows/dag/task-demand-routing.js +1 -383
- package/docs/README.md +1 -1
- package/docs/architecture/README.md +5 -5
- package/docs/architecture/evolution.md +4 -4
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/backend-test-dag.json +2 -2
- package/docs/templates/frontend-implementation-contract.schema.json +1 -1
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-implementation/SKILL.md +7 -0
- package/skills/frontend-implementation/references/node-contracts.md +1 -1
- package/skills/frontend-verification/SKILL.md +4 -3
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/index-DVl7Jxt5.js +0 -25
- package/dist/worker/console/static/assets/index-lVcIr9Ju.css +0 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { open, readFile, rename, writeFile } from "node:fs/promises";
|
|
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";
|
|
6
|
+
const revision = (raw) => createHash("sha256").update(raw).digest("hex");
|
|
7
|
+
export async function readModelsConfig(agentDir) {
|
|
8
|
+
let raw = "{}";
|
|
9
|
+
try {
|
|
10
|
+
raw = await readFile(path.join(agentDir, "models.json"), "utf8");
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
/* empty config */
|
|
14
|
+
}
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
return {
|
|
17
|
+
revision: revision(raw),
|
|
18
|
+
providers: Object.entries(parsed.providers ?? {}).map(([name, provider]) => ({
|
|
19
|
+
name,
|
|
20
|
+
api: typeof provider.api === "string" ? provider.api : undefined,
|
|
21
|
+
baseUrl: typeof provider.baseUrl === "string" ? provider.baseUrl : undefined,
|
|
22
|
+
credential: {
|
|
23
|
+
configured: typeof provider.apiKey === "string" && provider.apiKey.length > 0,
|
|
24
|
+
},
|
|
25
|
+
models: Array.isArray(provider.models)
|
|
26
|
+
? provider.models
|
|
27
|
+
.map((model) => {
|
|
28
|
+
const item = model && typeof model === "object"
|
|
29
|
+
? model
|
|
30
|
+
: {};
|
|
31
|
+
return {
|
|
32
|
+
id: String(item.id ?? ""),
|
|
33
|
+
name: typeof item.name === "string" ? item.name : undefined,
|
|
34
|
+
};
|
|
35
|
+
})
|
|
36
|
+
.filter((model) => model.id)
|
|
37
|
+
: [],
|
|
38
|
+
})),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function patchModelsConfig(agentDir, input) {
|
|
42
|
+
if (!["upsert-model", "remove-model", "remove-provider"].includes(input.action))
|
|
43
|
+
throw Object.assign(new Error("invalid models config action"), {
|
|
44
|
+
code: "INVALID_INPUT",
|
|
45
|
+
status: 400,
|
|
46
|
+
});
|
|
47
|
+
const file = path.join(agentDir, "models.json");
|
|
48
|
+
let raw = "{}";
|
|
49
|
+
try {
|
|
50
|
+
raw = await readFile(file, "utf8");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* create below */
|
|
54
|
+
}
|
|
55
|
+
if (revision(raw) !== input.expectedRevision)
|
|
56
|
+
throw Object.assign(new Error("models config changed"), {
|
|
57
|
+
code: "CONFIG_CONFLICT",
|
|
58
|
+
status: 409,
|
|
59
|
+
});
|
|
60
|
+
if (!/^[A-Za-z0-9._-]+$/.test(input.provider))
|
|
61
|
+
throw Object.assign(new Error("invalid provider"), {
|
|
62
|
+
code: "INVALID_INPUT",
|
|
63
|
+
status: 400,
|
|
64
|
+
});
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
parsed.providers ??= {};
|
|
67
|
+
const provider = parsed.providers[input.provider];
|
|
68
|
+
if (input.action === "remove-provider")
|
|
69
|
+
delete parsed.providers[input.provider];
|
|
70
|
+
else {
|
|
71
|
+
if (!provider)
|
|
72
|
+
throw Object.assign(new Error("provider not found; configure credentials with Pi CLI"), { code: "NOT_FOUND", status: 404 });
|
|
73
|
+
const models = Array.isArray(provider.models)
|
|
74
|
+
? provider.models.filter((model) => model && typeof model === "object")
|
|
75
|
+
: [];
|
|
76
|
+
if (!input.model?.id || input.model.id.length > 160)
|
|
77
|
+
throw Object.assign(new Error("model id is required"), {
|
|
78
|
+
code: "INVALID_INPUT",
|
|
79
|
+
status: 400,
|
|
80
|
+
});
|
|
81
|
+
if (input.model.name && input.model.name.length > 160)
|
|
82
|
+
throw Object.assign(new Error("model name is too long"), {
|
|
83
|
+
code: "INVALID_INPUT",
|
|
84
|
+
status: 400,
|
|
85
|
+
});
|
|
86
|
+
provider.models =
|
|
87
|
+
input.action === "remove-model"
|
|
88
|
+
? models.filter((model) => model.id !== input.model.id)
|
|
89
|
+
: [
|
|
90
|
+
...models.filter((model) => model.id !== input.model.id),
|
|
91
|
+
{
|
|
92
|
+
...(models.find((model) => model.id === input.model.id) ?? {}),
|
|
93
|
+
id: input.model.id,
|
|
94
|
+
...(input.model.name !== undefined
|
|
95
|
+
? { name: input.model.name }
|
|
96
|
+
: {}),
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
const body = `${JSON.stringify(parsed, null, 2)}\n`;
|
|
101
|
+
const tmp = path.join(agentDir, `.models.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
|
|
102
|
+
await writeFile(tmp, body, { encoding: "utf8", mode: 0o600 });
|
|
103
|
+
const handle = await open(tmp, "r+");
|
|
104
|
+
try {
|
|
105
|
+
await handle.sync();
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await handle.close();
|
|
109
|
+
}
|
|
110
|
+
await rename(tmp, file);
|
|
111
|
+
return readModelsConfig(agentDir);
|
|
112
|
+
}
|
|
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));
|
|
117
|
+
}
|
|
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;
|
|
131
|
+
}
|
|
132
|
+
export async function readPackageInventory(agentDir, repoRoot) {
|
|
133
|
+
const read = async (file) => {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
136
|
+
return (parsed.packages ?? []).filter((item) => typeof item === "string");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const global = await read(path.join(agentDir, "settings.json"));
|
|
143
|
+
const project = await read(path.join(repoRoot, ".pi", "settings.json"));
|
|
144
|
+
return [
|
|
145
|
+
...global.map((spec) => ({
|
|
146
|
+
spec,
|
|
147
|
+
scope: "global",
|
|
148
|
+
enabled: true,
|
|
149
|
+
resourceCount: 0,
|
|
150
|
+
})),
|
|
151
|
+
...project.map((spec) => ({
|
|
152
|
+
spec,
|
|
153
|
+
scope: "project",
|
|
154
|
+
enabled: true,
|
|
155
|
+
resourceCount: 0,
|
|
156
|
+
})),
|
|
157
|
+
];
|
|
158
|
+
}
|