@tea-agent/loop-agent 0.44.0-next.13 → 0.44.0-next.14
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 +12 -0
- package/README.md +12 -12
- package/dist/adapters/loop-agent.js +34 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +5 -5
- package/dist/cli/program.js +1 -4
- package/dist/commands/init-legacy-audit.js +330 -0
- package/dist/commands/init-upgrade.js +644 -104
- package/dist/commands/init.js +685 -330
- package/dist/commands/operator-guide.js +99 -0
- package/dist/commands/operator.js +16 -3
- package/dist/executors/pi-sdk-executor.js +3 -1
- package/dist/governance/harness.js +2 -7
- package/dist/governance/manifest-types.js +28 -9
- package/dist/governance/project-capability-scanner.js +263 -0
- package/dist/governance/project-config.js +96 -0
- package/dist/governance/resource-catalog.js +151 -0
- package/dist/infrastructure/console/app-data.js +6 -2
- package/dist/shared/operator/capabilities.js +2 -2
- package/dist/shared/package-metadata.js +23 -7
- package/dist/shared/pi-context-pressure/extension.js +23 -0
- package/dist/shared/pi-context-pressure/index.js +1 -0
- package/dist/shared/pi-context-pressure/telemetry.js +4 -0
- package/dist/shared/pi-context-pressure/tool-result-pruner.js +98 -0
- package/dist/shared/update/npm-client.js +49 -1
- package/dist/worker/cli.js +3 -5
- package/dist/worker/console/chat/browser-automation.js +114 -5
- package/dist/worker/console/chat/browser-routes.js +21 -0
- package/dist/worker/console/chat/chat-event-store.js +7 -6
- package/dist/worker/console/chat/context-insights.js +84 -19
- package/dist/worker/console/chat/pet/pet-affinity.js +169 -0
- package/dist/worker/console/chat/pet/pet-persist.js +109 -0
- package/dist/worker/console/chat/pet/pet-registry.js +142 -0
- package/dist/worker/console/chat/pet/pet-remarks.js +48 -0
- package/dist/worker/console/chat/pet/pet-routes.js +125 -0
- package/dist/worker/console/chat/pet/pet-service.js +262 -0
- package/dist/worker/console/chat/pet/pet-state.js +99 -0
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +17 -2
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +7 -2
- package/dist/worker/console/chat/routes.js +43 -7
- package/dist/worker/console/chat/scheduled-goal-request.js +14 -6
- package/dist/worker/console/chat/session-store.js +20 -3
- package/dist/worker/console/console-shutdown.js +34 -0
- package/dist/worker/console/server.js +2 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BA7qCL_r.js → abnfDiagram-N423BO3Z-DASc69AM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Df09ajwL.js → arc-h09p-LAc.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CpF9sw5R.js → architectureDiagram-T3A2C74G-CZ3dtLkh.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BtPys8ja.js → blockDiagram-VBNYF7ZC-DMrRhMXG.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DZPnTWqT.js → c4Diagram-5PPSVZJV-DQxisHV6.js} +1 -1
- package/dist/worker/console/static/assets/channel-D1zgKZfg.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BD9D4h1p.js → chunk-2GRJ4B5K-CefVN9zh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-Svn8yBmn.js → chunk-2Q5K7J3B-DmR1Koq2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-B8EbQj-S.js → chunk-5RXB4S5H-NlzrSW7L.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-tENZpcpF.js → chunk-5VM5RSS4-DfelJv-0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DBx71Lco.js → chunk-6Q2QTUOP-BK6RFy58.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-1v_QamMz.js → chunk-GF5L2VYU-BAeh9lz4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BAMCNNBG.js → chunk-JWPE2WC7-ChPS5WzG.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BvuEury-.js → chunk-KBJHAD2P-BoVe5IM6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-YqFuHp8s.js → chunk-RYQCIY6F-C4q_0Lw9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-ouI5T7Mr.js → chunk-XXDRQBXY-Bb9TvjeY.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-Bri0_Izk.js → cose-bilkent-JH36ORCC-DmNnL8Ff.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-e8Vhhi4X.js → cynefin-VYW2F7L2-Contvx58.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-tSIXGQiJ.js → cynefinDiagram-MW4NZA55-Cigq7cjk.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Rl-sF5pt.js → dagre-VZM6K2ZE-dEbC0nxb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Db6ddPz8.js → diagram-7IWD3JNH-CzIztESV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Jwd_Xw5S.js → diagram-B4RE2ZJO-Bo14zbaY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CeC_8InC.js → diagram-LBJQPF4R-C4xUyil6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LKPRaNq8.js → diagram-Q27KOJAE-CaBY_uYa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-DlVQytMW.js → diagram-UB23O5K3-ClUXUeiu.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DnfJZwO-.js → ebnfDiagram-BXEA7PRR-BbGCgjC6.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-3axVSXca.js → erDiagram-JOGREHBK-Dr-AcwSl.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-xe04-_L-.js → flowDiagram-UKHOOZJN-BG8Gcfyc.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-hpmRINiD.js → ganttDiagram-PKOTCBZU-CFVaPs5r.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-6HSNz9Ko.js → gitGraphDiagram-DS77QQ5N-ChJ8pm-O.js} +1 -1
- package/dist/worker/console/static/assets/index-B_5GhVIO.js +488 -0
- package/dist/worker/console/static/assets/index-C7YMBluX.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BF6ksWTX.js → infoDiagram-6WML65LV-C5J9-uCZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DXisdeD7.js → ishikawaDiagram-WSZJBQD7-BcfjqzDv.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BWu65wmu.js → journeyDiagram-NVQOT4AX-BzzCO-rD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-hfNhHqAI.js → kanban-definition-27J2QSJJ-NCHUZ46h.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D6HTfQcp.js → linear-BKZlWCUU.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Iv91R1WP.js → mermaid.core-DbTaQjFE.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CfPChwjR.js → mindmap-definition-FAOFIHXS-CroYl5xQ.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B7TcoqHz.js → pegDiagram-VL7TDLO6-DgMBx-9k.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-ouyPKMtQ.js → pieDiagram-7S7Q4E2Y-CyWDmbq1.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B9tuyLbh.js → quadrantDiagram-CIZ2JOQS-BgGEOqSP.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Ck4q9nJs.js → railroadDiagram-AXF67PYL-BMZ7oeeV.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-wXLX5Dnp.js → requirementDiagram-LRYGKXZP-DxqyGhcc.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-D2XWw6_m.js → sankeyDiagram-W5VNT64P-DnSvNCG8.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DzxKiug4.js → sequenceDiagram-SI44F4Z6-BiZh6f-R.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWUqK5Us.js → sizeCapture-X5ZJPWSS-BKXSI8WB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-D5Pa4O5g.js → stateDiagram-OKZ733FA-CfSgoYFm.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DgHi3vjb.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-VH7cRnsJ.js → swimlanes-SLNWSIFB-CyGADcHe.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bgvdt1Y0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-C4HM5NoZ.js → timeline-definition-Z64GVDOM-HmPaSPOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-Bu-4hOlA.js → vennDiagram-T6HMQDX7-VA2VBbez.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CasFEvvl.js → wardleyDiagram-T6FBY63Y-BxjiBPZa.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D-gpwPSA.js → xychartDiagram-ELKLHX3M--hUY-qFj.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static/pets/ember/pet.json +167 -0
- package/dist/worker/console/static/pets/ember/spritesheet.png +0 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +4 -3
- package/dist/worker/console/static-src/operator-chat/use-stat-dialog.js +95 -0
- package/dist/worker/console/workspace-context.js +14 -0
- package/dist/worker/console/workspace-initialization.js +25 -16
- package/dist/worker/delivery/package.js +4 -1
- package/dist/worker/observe/static/operator-chrome.js +1 -42
- package/dist/workflows/dag/backend-test-pytest-collection.js +4 -1
- package/dist/workflows/dag/final-verification.js +45 -10
- package/dist/workflows/dag/hybrid/sources.js +14 -14
- package/dist/workflows/dag/hybrid/templates/backend-test.js +5 -5
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +4 -4
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +6 -6
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +5 -5
- package/dist/workflows/dag/skill-instructions.js +102 -32
- package/dist/workflows/dag/skill-snapshot.js +3 -0
- package/dist/workflows/dag/skills.js +7 -7
- package/dist/workflows/dag/validate.js +2 -2
- package/dist/workflows/dag/workspace-checkpoint.js +13 -1
- package/docs/architecture/runtime-boundaries.md +28 -22
- package/docs/init-surface.manifest.json +43 -295
- package/docs/templates/agent-dag.base.json +9 -9
- package/docs/templates/agent-dag.final-verification.json +6 -6
- package/docs/templates/agent-dag.supervised-implementation.json +12 -12
- package/docs/templates/frontend-implementation-dag.json +2 -2
- package/docs/templates/frontend-test-dag.json +2 -2
- package/docs/templates/hybrid-dag.json +9 -9
- package/docs/templates/knowledge-sync-dag.json +8 -8
- package/package.json +6 -2
- package/resource-catalog.v1.json +1973 -0
- package/resources/agent-bridge/loop-agent/SKILL.md +40 -0
- package/resources/init-history/legacy-surface.v1.json +33 -0
- package/skills/init-update/SKILL.md +34 -0
- package/skills/init-update/agents/openai.yaml +6 -0
- package/dist/worker/console/static/assets/channel-DiTNLZUm.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DgL8aiyZ.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DgL8aiyZ.js +0 -1
- package/dist/worker/console/static/assets/index-C_8_gWal.css +0 -1
- package/dist/worker/console/static/assets/index-DybcVXCu.js +0 -488
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-C0QIbqO4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BB9CzRHB.js +0 -8
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { findPackageRoot } from "../shared/package-metadata.js";
|
|
7
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
8
|
+
function isSafePackagePath(value) {
|
|
9
|
+
if (!value || path.isAbsolute(value) || value.includes("\\"))
|
|
10
|
+
return false;
|
|
11
|
+
const parts = value.split("/");
|
|
12
|
+
return parts.every((part) => part !== "" && part !== "." && part !== "..");
|
|
13
|
+
}
|
|
14
|
+
export const resourceCatalogEntrySchema = z
|
|
15
|
+
.object({
|
|
16
|
+
id: z.string().trim().min(1).regex(/^[A-Za-z0-9][A-Za-z0-9:._/-]*$/),
|
|
17
|
+
path: z.string().refine(isSafePackagePath, "path must stay within the package root"),
|
|
18
|
+
kind: z.enum(["document", "skill", "template", "schema", "prompt", "script", "other"]),
|
|
19
|
+
bytes: z.number().int().nonnegative(),
|
|
20
|
+
sha256: z.string().regex(SHA256_RE),
|
|
21
|
+
schemaVersion: z.number().int().positive(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export const resourceCatalogSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
schemaVersion: z.literal(1),
|
|
27
|
+
entries: z.array(resourceCatalogEntrySchema),
|
|
28
|
+
})
|
|
29
|
+
.strict()
|
|
30
|
+
.superRefine((catalog, ctx) => {
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
const paths = new Set();
|
|
33
|
+
for (const [index, entry] of catalog.entries.entries()) {
|
|
34
|
+
if (ids.has(entry.id))
|
|
35
|
+
ctx.addIssue({ code: "custom", path: ["entries", index, "id"], message: "duplicate resource id" });
|
|
36
|
+
if (paths.has(entry.path))
|
|
37
|
+
ctx.addIssue({ code: "custom", path: ["entries", index, "path"], message: "duplicate resource path" });
|
|
38
|
+
ids.add(entry.id);
|
|
39
|
+
paths.add(entry.path);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
export const CONTROLLER_RESOURCE_IDS = {
|
|
43
|
+
loopAgentBridgeSkill: "agent-bridge:loop-agent",
|
|
44
|
+
};
|
|
45
|
+
export function builtinSkillResourceId(name) {
|
|
46
|
+
return `builtin-skill:${name}`;
|
|
47
|
+
}
|
|
48
|
+
export function builtinSkillReferenceResourceId(name, relativePath) {
|
|
49
|
+
return `builtin-skill-resource:${name}/${relativePath}`;
|
|
50
|
+
}
|
|
51
|
+
export class ResourceCatalogError extends Error {
|
|
52
|
+
code;
|
|
53
|
+
constructor(code, message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.code = code;
|
|
56
|
+
this.name = "ResourceCatalogError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function sha256(bytes) {
|
|
60
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
61
|
+
}
|
|
62
|
+
async function assertContainedRegularFile(root, candidate) {
|
|
63
|
+
const [rootReal, candidateReal, linkInfo] = await Promise.all([
|
|
64
|
+
realpath(root),
|
|
65
|
+
realpath(candidate),
|
|
66
|
+
lstat(candidate),
|
|
67
|
+
]);
|
|
68
|
+
const relative = path.relative(rootReal, candidateReal);
|
|
69
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
70
|
+
throw new ResourceCatalogError("RESOURCE_PATH_ESCAPE", `resource resolves outside package root: ${candidate}`);
|
|
71
|
+
}
|
|
72
|
+
if (!linkInfo.isFile() || linkInfo.isSymbolicLink() || linkInfo.nlink !== 1) {
|
|
73
|
+
throw new ResourceCatalogError("RESOURCE_NOT_REGULAR", `resource is not a regular file: ${candidate}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Lazy package resource provider: load the small catalog once, hash bytes only when used. */
|
|
77
|
+
export class ControllerContextProvider {
|
|
78
|
+
packageRoot;
|
|
79
|
+
identity;
|
|
80
|
+
entries;
|
|
81
|
+
constructor(packageRoot, catalog, catalogBytes) {
|
|
82
|
+
this.packageRoot = packageRoot;
|
|
83
|
+
this.entries = new Map(catalog.entries.map((entry) => [entry.id, entry]));
|
|
84
|
+
this.identity = { schemaVersion: 1, catalogDigest: sha256(catalogBytes) };
|
|
85
|
+
}
|
|
86
|
+
static async load(packageRoot, catalogName = "resource-catalog.v1.json") {
|
|
87
|
+
const catalogPath = path.join(packageRoot, catalogName);
|
|
88
|
+
await assertContainedRegularFile(packageRoot, catalogPath);
|
|
89
|
+
const bytes = await readFile(catalogPath);
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new ResourceCatalogError("RESOURCE_CATALOG_INVALID", `invalid resource catalog JSON: ${String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
const catalog = resourceCatalogSchema.safeParse(parsed);
|
|
98
|
+
if (!catalog.success) {
|
|
99
|
+
throw new ResourceCatalogError("RESOURCE_CATALOG_INVALID", `invalid resource catalog: ${catalog.error.message}`);
|
|
100
|
+
}
|
|
101
|
+
return new ControllerContextProvider(packageRoot, catalog.data, bytes);
|
|
102
|
+
}
|
|
103
|
+
list() {
|
|
104
|
+
return [...this.entries.values()].map((entry) => ({ ...entry }));
|
|
105
|
+
}
|
|
106
|
+
entry(id) {
|
|
107
|
+
const entry = this.entries.get(id);
|
|
108
|
+
return entry ? { ...entry } : undefined;
|
|
109
|
+
}
|
|
110
|
+
async read(id) {
|
|
111
|
+
const entry = this.entries.get(id);
|
|
112
|
+
if (!entry)
|
|
113
|
+
throw new ResourceCatalogError("RESOURCE_NOT_FOUND", `resource not found: ${id}`);
|
|
114
|
+
const file = path.join(this.packageRoot, ...entry.path.split("/"));
|
|
115
|
+
await assertContainedRegularFile(this.packageRoot, file);
|
|
116
|
+
const before = await stat(file);
|
|
117
|
+
const bytes = await readFile(file);
|
|
118
|
+
const after = await stat(file);
|
|
119
|
+
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
|
|
120
|
+
throw new ResourceCatalogError("RESOURCE_CHANGED_DURING_READ", `resource changed while reading: ${id}`);
|
|
121
|
+
}
|
|
122
|
+
if (bytes.byteLength !== entry.bytes || sha256(bytes) !== entry.sha256) {
|
|
123
|
+
throw new ResourceCatalogError("RESOURCE_HASH_MISMATCH", `resource does not match catalog: ${id}`);
|
|
124
|
+
}
|
|
125
|
+
return bytes;
|
|
126
|
+
}
|
|
127
|
+
async readText(id) {
|
|
128
|
+
const entry = this.entries.get(id);
|
|
129
|
+
if (!entry)
|
|
130
|
+
throw new ResourceCatalogError("RESOURCE_NOT_FOUND", `resource not found: ${id}`);
|
|
131
|
+
const bytes = await this.read(id);
|
|
132
|
+
return {
|
|
133
|
+
id,
|
|
134
|
+
relativePath: entry.path,
|
|
135
|
+
absolutePath: path.join(this.packageRoot, ...entry.path.split("/")),
|
|
136
|
+
kind: entry.kind,
|
|
137
|
+
schemaVersion: entry.schemaVersion,
|
|
138
|
+
bytes: entry.bytes,
|
|
139
|
+
sha256: entry.sha256,
|
|
140
|
+
text: bytes.toString("utf8"),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export async function createControllerContextProvider(options = {}) {
|
|
145
|
+
const packageRoot = options.packageRoot
|
|
146
|
+
?? findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
147
|
+
if (!packageRoot) {
|
|
148
|
+
throw new ResourceCatalogError("PACKAGE_ROOT_NOT_FOUND", "cannot locate the loop-agent package root for controller resources");
|
|
149
|
+
}
|
|
150
|
+
return ControllerContextProvider.load(packageRoot, options.catalogName ?? "resource-catalog.v1.json");
|
|
151
|
+
}
|
|
@@ -290,13 +290,17 @@ export async function removeStaged(filePath) {
|
|
|
290
290
|
await rm(filePath, { force: true });
|
|
291
291
|
}
|
|
292
292
|
/** Synchronous helpers used by boot-time path checks. */
|
|
293
|
-
export function writeSecureJsonSync(filePath, value) {
|
|
293
|
+
export function writeSecureJsonSync(filePath, value, options) {
|
|
294
294
|
const dir = path.dirname(filePath);
|
|
295
295
|
ensureSecureDir(dir);
|
|
296
296
|
const resolved = assertSafeAppDataPath(dir, filePath);
|
|
297
297
|
const tmp = `${resolved}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
298
|
+
const pretty = options?.pretty !== false;
|
|
299
|
+
const body = pretty
|
|
300
|
+
? `${JSON.stringify(value, null, 2)}\n`
|
|
301
|
+
: `${JSON.stringify(value)}\n`;
|
|
298
302
|
try {
|
|
299
|
-
writeFileSync(tmp,
|
|
303
|
+
writeFileSync(tmp, body, {
|
|
300
304
|
encoding: "utf8",
|
|
301
305
|
mode: 0o600,
|
|
302
306
|
});
|
|
@@ -395,7 +395,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
395
395
|
{
|
|
396
396
|
action: "prepareScheduledTask", cli: "console scheduled task preparation", kind: "mutation",
|
|
397
397
|
inputSchemaVersion: 1, resultSchemaVersion: 1, envelopeSchemaVersion: 1, requiredErrorCodes: ["INVALID_INPUT"],
|
|
398
|
-
description: "Preferred entry for natural-language one-time scheduled tasks, including read-only inspection reports. Call this directly after understanding the goal, output scope, executable acceptance and time; the server uses the CURRENT project directory and creates the immutable PRD, managed Task and strict writeSet gate, binds THIS Chat automatically and returns a REAL planned goal/confirmation card. No pre-existing task, worktree or session ID is needed: never ask the user for these internal fields and do not manually create/edit them with bash/write/newTask/importPrd. Keep preparation concise: preserve the user goal without inventing extra requirements. Omit budget for ordinary tasks. Use minimal independent acceptance commands (AC-1, AC-2), preferably existing test/build scripts or test -s scheduled-report.md for a report; no compound shell/pipelines/substitution, language-specific grep checks or redundant scope assertions (the host enforces scope). Infer routine defaults and verification commands from the repository; ask only about genuinely ambiguous goal/scope/time. For read-only requests allow just an ordinary report file in the current project directory, e.g. scheduled-report.md, not .harness/. Use schedule.delayMinutes for relative time (counted after preparation finishes), or localAt + IANA timezone for an exact date/time. The current workspace must match committed HEAD; never auto-commit/stash/reset existing changes. The card exposes the exact time, bounded defaults and committed base before one human confirmation. Same request in this Chat is idempotent. Only a returned goalId with state=planned is a prepared reservation; textual drafts, Tasks or failed operations are NOT bookings. Never replace a requested schedule with immediate execution or sleep, never call runDag or self-confirm. On failure explain the returned stage, correct business inputs and retry this tool; do not mutate imported PRD/runtime artifacts. For a new distinct appointment, make its goal/time distinct.",
|
|
398
|
+
description: "Preferred entry for natural-language one-time scheduled tasks, including read-only inspection reports. Call this directly after understanding the goal, output scope, executable acceptance and time; the server uses the CURRENT project directory and creates the immutable PRD, managed Task and strict writeSet gate, binds THIS Chat automatically and returns a REAL planned goal/confirmation card. No pre-existing task, worktree or session ID is needed: never ask the user for these internal fields and do not manually create/edit them with bash/write/newTask/importPrd. Keep preparation concise: preserve the user goal without inventing extra requirements. Omit budget for ordinary tasks: the host freezes 5 goal attempts, 5 DAG runs and 4 supervisor calls, with a 15-minute latest-start grace and a 240-minute execution window, all shown before confirmation. maxTurns counts goal attempts, not model tool calls. Explicit budget or maxDurationMinutes is never raised. Use minimal independent acceptance commands (AC-1, AC-2), preferably existing test/build scripts or test -s scheduled-report.md for a report; no compound shell/pipelines/substitution, language-specific grep checks or redundant scope assertions (the host enforces scope). Infer routine defaults and verification commands from the repository; ask only about genuinely ambiguous goal/scope/time. For read-only requests allow just an ordinary report file in the current project directory, e.g. scheduled-report.md, not .harness/. Use schedule.delayMinutes for relative time (counted after preparation finishes), or localAt + IANA timezone for an exact date/time. The current workspace must match committed HEAD; never auto-commit/stash/reset existing changes. The card exposes the exact time, bounded defaults and committed base before one human confirmation. Same request in this Chat is idempotent. Only a returned goalId with state=planned is a prepared reservation; textual drafts, Tasks or failed operations are NOT bookings. Never replace a requested schedule with immediate execution or sleep, never call runDag or self-confirm. On failure explain the returned stage, correct business inputs and retry this tool; do not mutate imported PRD/runtime artifacts. For a new distinct appointment, make its goal/time distinct.",
|
|
399
399
|
inputParams: [{ name: "request", type: "object", required: true, description: "Business goal, workflow, allowedPaths, acceptance and schedule; optional forbiddenPaths/budget. Runtime identity is server-owned." }],
|
|
400
400
|
modelCallable: "always", humanConfirmation: "none",
|
|
401
401
|
},
|
|
@@ -409,7 +409,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
409
409
|
"requiredErrorCodes": [
|
|
410
410
|
"INVALID_INPUT"
|
|
411
411
|
],
|
|
412
|
-
"description": "Advanced entry ONLY for an already prepared Task. For a new natural-language reservation use prepareScheduledTask instead. Requires the current repository or its linked worktree at frozenBase and a managed Task already strict-prepared at its writeSet gate. booking must include managedTaskId, originalGoal, chatSessionRef, workflow, worktreeRef, frozenBase, allowedPaths, forbiddenPaths, acceptance {policy, criteria [{acId,description,command}]}, budget {maxTurns,maxDagRuns,maxSupervisorCalls}, time {executeAtUtc,latestStartUtc,stopAtUtc,expiresAt,timezone,allowedHours,misfirePolicy}. Resolve natural-language dates with the user timezone; ask about ambiguous time/AC/scope. No execution or authorization occurs. Show the complete scope/time/budget for one browser confirmation. Never call runDag for this reservation.",
|
|
412
|
+
"description": "Advanced entry ONLY for an already prepared Task. For a new natural-language reservation use prepareScheduledTask instead. Requires the current repository or its linked worktree at frozenBase and a managed Task already strict-prepared at its writeSet gate. booking must include managedTaskId, originalGoal, chatSessionRef, workflow, worktreeRef, frozenBase, allowedPaths, forbiddenPaths, acceptance {policy, criteria [{acId,description,command}]}, budget {maxTurns,maxDagRuns,maxSupervisorCalls}, time {executeAtUtc,latestStartUtc,stopAtUtc,expiresAt,timezone,allowedHours,misfirePolicy}. This entry never fills omitted budget; for implementation work supply at least maxTurns=5, maxDagRuns=5, maxSupervisorCalls=4. Resolve natural-language dates with the user timezone; ask about ambiguous time/AC/scope. No execution or authorization occurs. Show the complete scope/time/budget for one browser confirmation. Never call runDag for this reservation.",
|
|
413
413
|
"inputParams": [
|
|
414
414
|
{
|
|
415
415
|
"name": "booking",
|
|
@@ -106,8 +106,9 @@ export function computeBinarySha256(filePath) {
|
|
|
106
106
|
* Compute a portable, deterministic content fingerprint for the installed
|
|
107
107
|
* package rooted at `packageRoot`.
|
|
108
108
|
*
|
|
109
|
-
* The fingerprint covers
|
|
110
|
-
*
|
|
109
|
+
* The fingerprint covers executable code plus package-owned Skills, templates,
|
|
110
|
+
* bridge resources, and their generated catalog. It is independent of the
|
|
111
|
+
* absolute installation path.
|
|
111
112
|
*
|
|
112
113
|
* Sorting uses code-point order (UTF-8 byte comparison), NOT localeCompare,
|
|
113
114
|
* to ensure deterministic behaviour across locales.
|
|
@@ -118,17 +119,23 @@ export function computePackageFingerprint(packageRoot) {
|
|
|
118
119
|
"bin/**",
|
|
119
120
|
"dist/**",
|
|
120
121
|
"skills/**",
|
|
122
|
+
"docs/templates/**",
|
|
123
|
+
"resources/**",
|
|
124
|
+
"resource-catalog.v1.json",
|
|
121
125
|
];
|
|
122
126
|
// Collecting entries ---------------------------------------------------
|
|
123
127
|
const entries = [];
|
|
124
128
|
// 1) package.json (required single file)
|
|
125
129
|
addRequiredFile(entries, path.join(packageRoot, "package.json"), "");
|
|
126
|
-
// 2)
|
|
127
|
-
for (const dir of ["bin", "dist", "skills"]) {
|
|
130
|
+
// 2) Package-owned executable and controller-resource trees.
|
|
131
|
+
for (const dir of ["bin", "dist", "skills", "docs/templates", "resources"]) {
|
|
128
132
|
const absoluteDir = path.join(packageRoot, dir);
|
|
129
133
|
collectTree(entries, absoluteDir, dir);
|
|
130
134
|
}
|
|
131
|
-
// 3)
|
|
135
|
+
// 3) The generated catalog binds logical resource ids to those bytes. It is
|
|
136
|
+
// optional only for source/test fixtures; published packages always ship it.
|
|
137
|
+
addOptionalFile(entries, path.join(packageRoot, "resource-catalog.v1.json"), "");
|
|
138
|
+
// 4) Deterministic sort by relative path (code-point / byte order)
|
|
132
139
|
entries.sort((a, b) => {
|
|
133
140
|
const aBuf = Buffer.from(a.relativePath, "utf-8");
|
|
134
141
|
const bBuf = Buffer.from(b.relativePath, "utf-8");
|
|
@@ -141,14 +148,14 @@ export function computePackageFingerprint(packageRoot) {
|
|
|
141
148
|
}
|
|
142
149
|
return aBuf.length - bBuf.length;
|
|
143
150
|
});
|
|
144
|
-
//
|
|
151
|
+
// 5) Cumulative hash: hash of (<relativePath> + "\t" + <contentHash>) lines
|
|
145
152
|
const cumulative = createHash("sha256");
|
|
146
153
|
for (const entry of entries) {
|
|
147
154
|
cumulative.update(`${entry.relativePath}\t${entry.contentHash}\n`);
|
|
148
155
|
}
|
|
149
156
|
return {
|
|
150
157
|
algorithm: "sha256",
|
|
151
|
-
scopeVersion:
|
|
158
|
+
scopeVersion: 2,
|
|
152
159
|
scope,
|
|
153
160
|
value: `sha256:${cumulative.digest("hex")}`,
|
|
154
161
|
fileCount: entries.length,
|
|
@@ -316,6 +323,15 @@ function addRequiredFile(entries, absolutePath, relativeDir) {
|
|
|
316
323
|
: path.basename(absolutePath);
|
|
317
324
|
entries.push({ relativePath, contentHash });
|
|
318
325
|
}
|
|
326
|
+
function addOptionalFile(entries, absolutePath, relativeDir) {
|
|
327
|
+
try {
|
|
328
|
+
addRequiredFile(entries, absolutePath, relativeDir);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
if (!isNotFound(error))
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
319
335
|
function collectTree(entries, absoluteDir, relativeDir) {
|
|
320
336
|
let stats;
|
|
321
337
|
try {
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { shouldRewriteAssistantTransientProviderError } from "../pi-provider-recovery.js";
|
|
2
2
|
import { buildOverflowCheckpoint } from "./checkpoint.js";
|
|
3
|
+
import { prunePiToolResultMessages, } from "./tool-result-pruner.js";
|
|
3
4
|
import { shouldRewriteAssistantOverflow } from "./overflow.js";
|
|
4
5
|
import { clearRetrieveBypass, compressProviderPayload, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, } from "./sift-bridge.js";
|
|
5
6
|
import { emptyContextPressureCounters, incrementCounter, } from "./telemetry.js";
|
|
6
7
|
export function createCompactionController() {
|
|
7
8
|
return {
|
|
8
9
|
proactiveCompaction: false,
|
|
10
|
+
pruneArmed: false,
|
|
9
11
|
counters: emptyContextPressureCounters(),
|
|
10
12
|
};
|
|
11
13
|
}
|
|
12
14
|
export function createContextPressureExtension(options) {
|
|
13
15
|
const l2 = options.policy.mode !== "off";
|
|
14
16
|
const l1 = Boolean(options.sift);
|
|
17
|
+
const pruner = l2 && options.prune !== undefined;
|
|
15
18
|
const transientRecovery = options.normalizeTransientProviderErrors === true;
|
|
16
19
|
if (!l2 && !l1 && !transientRecovery)
|
|
17
20
|
return undefined;
|
|
@@ -54,6 +57,8 @@ export function createContextPressureExtension(options) {
|
|
|
54
57
|
const reason = event.reason;
|
|
55
58
|
if (reason === "overflow") {
|
|
56
59
|
incrementCounter(options.controller.counters, "overflow");
|
|
60
|
+
if (pruner)
|
|
61
|
+
options.controller.pruneArmed = true;
|
|
57
62
|
}
|
|
58
63
|
if (reason === "manual") {
|
|
59
64
|
incrementCounter(options.controller.counters, "manual");
|
|
@@ -79,6 +84,24 @@ export function createContextPressureExtension(options) {
|
|
|
79
84
|
};
|
|
80
85
|
});
|
|
81
86
|
}
|
|
87
|
+
if (pruner) {
|
|
88
|
+
api.on("context", (event) => {
|
|
89
|
+
if (!options.controller.pruneArmed)
|
|
90
|
+
return undefined;
|
|
91
|
+
const messages = Array.isArray(event.messages) ? event.messages : [];
|
|
92
|
+
try {
|
|
93
|
+
const result = prunePiToolResultMessages(messages, options.prune);
|
|
94
|
+
if (!result.changed)
|
|
95
|
+
return undefined;
|
|
96
|
+
options.controller.counters.toolResultPruned += result.messagesPruned;
|
|
97
|
+
options.controller.counters.charsPrunedEstimate += result.charsRemoved;
|
|
98
|
+
return { messages: result.messages };
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
82
105
|
if (l1 && options.sift) {
|
|
83
106
|
const sift = options.sift;
|
|
84
107
|
api.on("before_provider_request", (event) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { isContextOverflow, CONTEXT_OVERFLOW_PHRASES, OVERFLOW_FIXTURES, NORMALIZED_OVERFLOW_PREFIX, shouldRewriteAssistantOverflow, normalizeOverflowErrorMessage, buildGeneratedOverflowClassifierSource, } from "./overflow.js";
|
|
2
2
|
export { parseCompactionPolicy, deriveEffectiveWindow, conservativeTokenEstimate, projectPromptPressure, applyProcessCompactionOverride, inMemoryCompactionSettings, PI_DEFAULT_RESERVE_TOKENS, DEFAULT_KEEP_RECENT_TOKENS, } from "./compaction-policy.js";
|
|
3
3
|
export { buildOverflowCheckpoint } from "./checkpoint.js";
|
|
4
|
+
export { PI_TOOL_RESULT_PRUNE_MARKER, DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG, resolvePiToolResultPruneConfig, parsePiToolResultPruneConfig, piCodePointLength, prunePiToolResultMessages, } from "./tool-result-pruner.js";
|
|
4
5
|
export { createContextPressureExtension, createCompactionController, } from "./extension.js";
|
|
5
6
|
export { createSiftBridgeState, parseSiftBridgeConfig, compressProviderPayload, executeSiftRetrieve, housekeepSiftStash, createSiftRetrieveToolDefinition, SIFT_RETRIEVE_TOOL_NAME, SIFT_POLICY_VERSION, } from "./sift-bridge.js";
|
|
6
7
|
export { isConsoleSiftEnabled, isDagSiftEnabled, readContextPressureMode, parseEnabledFlag, parseContextPressureMode, } from "./env.js";
|
|
@@ -9,6 +9,8 @@ export function emptyContextPressureCounters() {
|
|
|
9
9
|
l0CompactFailed: 0,
|
|
10
10
|
tokensSavedEstimate: 0,
|
|
11
11
|
blocksCompressed: 0,
|
|
12
|
+
toolResultPruned: 0,
|
|
13
|
+
charsPrunedEstimate: 0,
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
export function incrementCounter(counters, reason, amount = 1) {
|
|
@@ -47,5 +49,7 @@ export function projectSafeTelemetry(counters) {
|
|
|
47
49
|
failOpen: counters.failOpen,
|
|
48
50
|
tokensSavedEstimate: counters.tokensSavedEstimate,
|
|
49
51
|
blocksCompressed: counters.blocksCompressed,
|
|
52
|
+
toolResultPruned: counters.toolResultPruned,
|
|
53
|
+
charsPrunedEstimate: counters.charsPrunedEstimate,
|
|
50
54
|
};
|
|
51
55
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** Deterministic, model-free pruning of Pi tool-result messages. */
|
|
2
|
+
import { parseBoundedNumber, parseEnabledFlag } from "./env.js";
|
|
3
|
+
export const PI_TOOL_RESULT_PRUNE_MARKER = "\n\n[... tool result middle pruned ...]\n\n";
|
|
4
|
+
export const DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG = Object.freeze({ thresholdChars: 8192, headChars: 4096, tailChars: 1024 });
|
|
5
|
+
export function parsePiToolResultPruneConfig(env = process.env) {
|
|
6
|
+
const enabled = parseEnabledFlag(env.HARNESS_TOOL_RESULT_PRUNE, true);
|
|
7
|
+
const diagnostics = enabled.diagnostic ? [enabled.diagnostic] : [];
|
|
8
|
+
const threshold = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_THRESHOLD_CHARS, 8192, 1, 1_000_000);
|
|
9
|
+
const head = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_HEAD_CHARS, 4096, 0, 1_000_000);
|
|
10
|
+
const tail = parseBoundedNumber(env.HARNESS_TOOL_RESULT_PRUNE_TAIL_CHARS, 1024, 0, 1_000_000);
|
|
11
|
+
for (const item of [threshold, head, tail])
|
|
12
|
+
if (item.diagnostic)
|
|
13
|
+
diagnostics.push(item.diagnostic);
|
|
14
|
+
try {
|
|
15
|
+
return { enabled: enabled.enabled, config: resolvePiToolResultPruneConfig({ thresholdChars: Math.floor(threshold.value), headChars: Math.floor(head.value), tailChars: Math.floor(tail.value) }), diagnostics };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
diagnostics.push(error instanceof Error ? error.message : String(error));
|
|
19
|
+
return { enabled: false, config: DEFAULT_PI_TOOL_RESULT_PRUNE_CONFIG, diagnostics };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function resolvePiToolResultPruneConfig(config = {}) {
|
|
23
|
+
const keys = new Set(["thresholdChars", "headChars", "tailChars"]);
|
|
24
|
+
for (const key of Object.keys(config))
|
|
25
|
+
if (!keys.has(key))
|
|
26
|
+
throw new Error(`PiToolResultPruneConfig: unknown key "${key}"`);
|
|
27
|
+
const out = { thresholdChars: config.thresholdChars ?? 8192, headChars: config.headChars ?? 4096, tailChars: config.tailChars ?? 1024 };
|
|
28
|
+
if (!Number.isInteger(out.thresholdChars) || out.thresholdChars <= 0)
|
|
29
|
+
throw new Error("thresholdChars must be positive integer");
|
|
30
|
+
if (!Number.isInteger(out.headChars) || out.headChars < 0)
|
|
31
|
+
throw new Error("headChars must be non-negative integer");
|
|
32
|
+
if (!Number.isInteger(out.tailChars) || out.tailChars < 0)
|
|
33
|
+
throw new Error("tailChars must be non-negative integer");
|
|
34
|
+
if (out.headChars + Array.from(PI_TOOL_RESULT_PRUNE_MARKER).length + out.tailChars > out.thresholdChars)
|
|
35
|
+
throw new Error("prune budgets exceed threshold");
|
|
36
|
+
return Object.freeze(out);
|
|
37
|
+
}
|
|
38
|
+
export function piCodePointLength(text) { return Array.from(text).length; }
|
|
39
|
+
function textBlock(value) { return value && typeof value === "object" && value.type === "text" && typeof value.text === "string"; }
|
|
40
|
+
function isToolResult(message) { return message.role === "tool" || message.role === "toolResult" || message.type === "tool_result" || message.role === "tool_result"; }
|
|
41
|
+
function pruneBlocks(input, cfg) {
|
|
42
|
+
const before = input.reduce((n, b) => n + (textBlock(b) ? piCodePointLength(b.text) : 0), 0);
|
|
43
|
+
if (before <= cfg.thresholdChars)
|
|
44
|
+
return { blocks: null, before, after: before };
|
|
45
|
+
const cutStart = cfg.headChars, cutEnd = before - cfg.tailChars;
|
|
46
|
+
let consumed = 0, inserted = false;
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const block of input) {
|
|
49
|
+
if (!textBlock(block)) {
|
|
50
|
+
out.push(block);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const points = Array.from(block.text);
|
|
54
|
+
const bs = consumed, be = bs + points.length;
|
|
55
|
+
const headEnd = Math.min(points.length, Math.max(0, cutStart - bs));
|
|
56
|
+
const tailStart = Math.min(points.length, Math.max(0, cutEnd - bs));
|
|
57
|
+
const marker = !inserted && bs < cutEnd && be > cutStart ? PI_TOOL_RESULT_PRUNE_MARKER : "";
|
|
58
|
+
if (marker)
|
|
59
|
+
inserted = true;
|
|
60
|
+
const text = points.slice(0, headEnd).join("") + marker + points.slice(tailStart).join("");
|
|
61
|
+
if (text)
|
|
62
|
+
out.push({ ...block, text });
|
|
63
|
+
consumed = be;
|
|
64
|
+
}
|
|
65
|
+
const after = out.reduce((n, b) => n + (textBlock(b) ? piCodePointLength(b.text) : 0), 0);
|
|
66
|
+
if (!inserted || after > cfg.thresholdChars || after >= before)
|
|
67
|
+
throw new Error("Pi tool-result prune invariant failed");
|
|
68
|
+
return { blocks: out, before, after };
|
|
69
|
+
}
|
|
70
|
+
/** Returns a new transcript; input/full originals are never mutated or persisted. */
|
|
71
|
+
export function prunePiToolResultMessages(messages, config) {
|
|
72
|
+
const cfg = resolvePiToolResultPruneConfig(config);
|
|
73
|
+
const output = messages.slice();
|
|
74
|
+
let charsRemoved = 0, messagesPruned = 0;
|
|
75
|
+
for (let i = 0; i < output.length; i++) {
|
|
76
|
+
const message = output[i];
|
|
77
|
+
if (!message || !isToolResult(message))
|
|
78
|
+
continue;
|
|
79
|
+
if (typeof message.content === "string") {
|
|
80
|
+
const r = pruneBlocks([{ type: "text", text: message.content }], cfg);
|
|
81
|
+
if (r.blocks) {
|
|
82
|
+
output[i] = { ...message, content: r.blocks[0].text };
|
|
83
|
+
charsRemoved += r.before - r.after;
|
|
84
|
+
messagesPruned++;
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!Array.isArray(message.content))
|
|
89
|
+
continue;
|
|
90
|
+
const r = pruneBlocks(message.content, cfg);
|
|
91
|
+
if (r.blocks) {
|
|
92
|
+
output[i] = { ...message, content: r.blocks };
|
|
93
|
+
charsRemoved += r.before - r.after;
|
|
94
|
+
messagesPruned++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { messages: output, changed: messagesPruned > 0, charsRemoved, messagesPruned };
|
|
98
|
+
}
|
|
@@ -4,6 +4,39 @@ import path from "node:path";
|
|
|
4
4
|
import semver from "semver";
|
|
5
5
|
const PACKAGE_NAME = "@tea-agent/loop-agent";
|
|
6
6
|
export const DEFAULT_NPM_QUERY_TIMEOUT_MS = 15_000;
|
|
7
|
+
function quoteWindowsCommandArgument(value) {
|
|
8
|
+
if (/[%!^&|<>]/.test(value)) {
|
|
9
|
+
throw new Error("unsafe Windows update command argument");
|
|
10
|
+
}
|
|
11
|
+
return /^[A-Za-z0-9_@./:\\=,+-]+$/.test(value)
|
|
12
|
+
? value
|
|
13
|
+
: `"${value.replaceAll('"', '""')}"`;
|
|
14
|
+
}
|
|
15
|
+
export function resolveUpdateCommandLaunch(command, args, platform = process.platform, comspec = process.env.ComSpec ?? "cmd.exe") {
|
|
16
|
+
if (platform !== "win32") {
|
|
17
|
+
return { command, args: [...args], windowsVerbatimArguments: false };
|
|
18
|
+
}
|
|
19
|
+
const isExplicitPath = command.includes("/") || command.includes("\\");
|
|
20
|
+
const isKnownBareShim = !isExplicitPath &&
|
|
21
|
+
path.win32.extname(command) === "" &&
|
|
22
|
+
new Set(["npm", "npx", "loop-agent"]).has(command.toLowerCase());
|
|
23
|
+
const isCommandShim = isKnownBareShim || /\.(?:cmd|bat)$/i.test(command);
|
|
24
|
+
if (!isCommandShim) {
|
|
25
|
+
return { command, args: [...args], windowsVerbatimArguments: false };
|
|
26
|
+
}
|
|
27
|
+
const windowsCommand = isKnownBareShim ? `${command}.cmd` : command;
|
|
28
|
+
const commandLine = [windowsCommand, ...args]
|
|
29
|
+
.map(quoteWindowsCommandArgument)
|
|
30
|
+
.join(" ");
|
|
31
|
+
const commandPayload = commandLine.startsWith('"')
|
|
32
|
+
? `"${commandLine}"`
|
|
33
|
+
: commandLine;
|
|
34
|
+
return {
|
|
35
|
+
command: comspec,
|
|
36
|
+
args: [`/d /s /c ${commandPayload}`],
|
|
37
|
+
windowsVerbatimArguments: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
7
40
|
export function createNpmUpdateClient(options) {
|
|
8
41
|
const npmCommand = options.npmCommand ?? "npm";
|
|
9
42
|
const loopAgentCommand = options.loopAgentCommand ?? "loop-agent";
|
|
@@ -166,11 +199,26 @@ function isAllowedChannelVersion(version, channel) {
|
|
|
166
199
|
}
|
|
167
200
|
async function runCommand(command, args, options) {
|
|
168
201
|
return new Promise((resolve) => {
|
|
169
|
-
|
|
202
|
+
let launch;
|
|
203
|
+
try {
|
|
204
|
+
launch = resolveUpdateCommandLaunch(command, args);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
resolve({
|
|
208
|
+
exitCode: null,
|
|
209
|
+
stdout: "",
|
|
210
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
211
|
+
timedOut: false,
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const child = spawn(launch.command, launch.args, {
|
|
170
216
|
cwd: options.cwd,
|
|
171
217
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
172
218
|
stdio: ["ignore", "pipe", "pipe"],
|
|
173
219
|
shell: false,
|
|
220
|
+
windowsHide: true,
|
|
221
|
+
windowsVerbatimArguments: launch.windowsVerbatimArguments,
|
|
174
222
|
});
|
|
175
223
|
let stdout = "";
|
|
176
224
|
let stderr = "";
|
package/dist/worker/cli.js
CHANGED
|
@@ -873,12 +873,10 @@ export async function runConsoleServe(options) {
|
|
|
873
873
|
await server.close();
|
|
874
874
|
return;
|
|
875
875
|
}
|
|
876
|
-
await
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
process.once("SIGTERM", shutdown);
|
|
876
|
+
const { holdConsoleUntilSignal } = await import("./console/console-shutdown.js");
|
|
877
|
+
await holdConsoleUntilSignal({
|
|
878
|
+
close: () => server.close(),
|
|
880
879
|
});
|
|
881
|
-
await server.close();
|
|
882
880
|
}
|
|
883
881
|
catch (error) {
|
|
884
882
|
const detail = error instanceof Error ? error.message : String(error);
|