Harness spec 3.2.0 in the CLI: effort levels, reasoning round-trip, no absolute paths
ci / check (push) Canceled after 0s
ci / check (push) Canceled after 0s
Levels (models/schema.json levels): provider/effort.ts levelsOf/levelFields/ readLevel — `off` only with thinking_switch and sends enable_thinking false and no effort, `default` for a model that takes no efforts and sends nothing, a level not offered is read as the start level. The engine's effort is a level; /effort shows nothing for one level, a Thinking switch for [off, X], a list otherwise; the status bar follows. /v1/models' thinking_switch, efforts [] and capacity.parallel are read (lembas/webui.ts); session/capacity.ts counts parallel slots. Reasoning (context.md §8): an assistant message of the current turn carries reasoning_content (provider/common.ts turnReasoning, openai-chat.ts); step-boundary deliveries, nudges and the wrap-up are marked turn: false and do not start a turn. Paths (context.md §11): the <env> block names the project and, started below the root, that directory relative to it (assemble.ts envLines); instruction files and @ attachments are named from the root or as ~/…; file tools and bash resolve relative paths against the project root. The team rules (team/team.ts) and conformance runners for levels, reasoning, paths and team; the team runtime follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
36 files changed
+1168
-153
No files matched your search
@@ -3,6 +3,7 @@
|
||||
// them, and counted as read so the model may edit them at once. The prompt text is unchanged.
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs"
|
||||
import { join, resolve } from "node:path"
|
||||
import { promptPath } from "../prompt/assemble.ts"
|
||||
import type { ImagePart } from "../provider/types.ts"
|
||||
import type { ToolContext } from "../tool/tool.ts"
|
||||
import { isImage, loadImage } from "./image.ts"
|
||||
@@ -34,6 +35,8 @@ export function attachmentsFor(text: string, ctx: Pick<ToolContext, "root" | "cw
|
||||
const rel = m[1]!.replace(/\\([ #])/g, "$1").replace(/[.,;:!?)]+$/, "")
|
||||
const abs = resolve(ctx.cwd, rel)
|
||||
if (allow && !allow(abs)) continue
|
||||
// Named to the model as its tools read paths: from the project root, ~/… under home (context.md §11).
|
||||
const named = promptPath(ctx.root, abs) + (rel.endsWith("/") && abs !== "/" ? "/" : "")
|
||||
const key = `${abs}#${m[2] ?? ""}-${m[3] ?? ""}`
|
||||
if (seen.has(key)) continue
|
||||
seen.add(key)
|
||||
@@ -47,25 +50,25 @@ export function attachmentsFor(text: string, ctx: Pick<ToolContext, "root" | "cw
|
||||
const entries = readdirSync(abs, { withFileTypes: true })
|
||||
.map((e) => (e.isDirectory() ? `${e.name}/` : e.name))
|
||||
.sort()
|
||||
out.push({ path: rel, text: `<directory path="${rel}">\n${entries.join("\n")}\n</directory>` })
|
||||
out.push({ path: rel, text: `<directory path="${named}">\n${entries.join("\n")}\n</directory>` })
|
||||
continue
|
||||
}
|
||||
if (isImage(abs)) {
|
||||
if (!vision) {
|
||||
out.push({ path: rel, text: `<image path="${rel}">(not attached: this model has no vision)</image>` })
|
||||
out.push({ path: rel, text: `<image path="${named}">(not attached: this model has no vision)</image>` })
|
||||
continue
|
||||
}
|
||||
const img = loadImage(abs)
|
||||
out.push(typeof img === "string" ? { path: rel, text: `<image path="${rel}">(not attached: ${img})</image>` } : { path: rel, text: `<image path="${rel}"/>`, image: img })
|
||||
out.push(typeof img === "string" ? { path: rel, text: `<image path="${named}">(not attached: ${img})</image>` } : { path: rel, text: `<image path="${named}"/>`, image: img })
|
||||
continue
|
||||
}
|
||||
if (st.size > MAX_BYTES && !m[2]) {
|
||||
out.push({ path: rel, text: `<file path="${rel}">\n(${st.size} bytes — too large to attach whole; read it in parts, or reference a range like @${rel}#1-200)\n</file>` })
|
||||
out.push({ path: rel, text: `<file path="${named}">\n(${st.size} bytes — too large to attach whole; read it in parts, or reference a range like @${named}#1-200)\n</file>` })
|
||||
continue
|
||||
}
|
||||
const raw = readFileSync(abs)
|
||||
if (raw.subarray(0, 8192).includes(0)) {
|
||||
out.push({ path: rel, text: `<file path="${rel}">\n(binary file, ${st.size} bytes)\n</file>` })
|
||||
out.push({ path: rel, text: `<file path="${named}">\n(binary file, ${st.size} bytes)\n</file>` })
|
||||
continue
|
||||
}
|
||||
const lines = raw.toString("utf8").split("\n")
|
||||
@@ -75,7 +78,7 @@ export function attachmentsFor(text: string, ctx: Pick<ToolContext, "root" | "cw
|
||||
const body = lines.slice(from - 1, to).map((l, i) => `${from + i}: ${l}`).join("\n")
|
||||
const range = m[2] ? ` lines="${from}-${to}"` : ""
|
||||
const more = !m[2] && lines.length > to ? `\n(${lines.length - to} more lines — read with offset ${to + 1})` : ""
|
||||
out.push({ path: rel, text: `<file path="${rel}"${range}>\n${body}${more}\n</file>` })
|
||||
out.push({ path: rel, text: `<file path="${named}"${range}>\n${body}${more}\n</file>` })
|
||||
if (!m[2]) {
|
||||
ctx.readFiles.add(abs)
|
||||
ctx.fileStamps.set(abs, st.mtimeMs)
|
||||
|
||||
Reference in new issue
Block a user