Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ recordings/
.lab-agent/
.lab-agent/sessions/

# Composer paperclip uploads in a workspace checkout
ant-code-uploads/

# Local scratch files created during manual verification
test.txt
test_file.txt
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

## Unreleased

## 2.0.8 - 2026-09-06

This is a small Dashboard workflow release on the 2.0 TypeScript runtime.
The paperclip can attach PDF, docx, xlsx, pptx, and common text files.
Documents are saved under `ant-code-uploads/` with a bounded preview for
the model. The right pane can preview those files, and Open launches the
workspace copy with the system app. Permission mode ids are unchanged.

### Added

- Dashboard paperclip accepts PDF / Office Open XML / text documents in
addition to images. Documents are stored in `ant-code-uploads/` and
ingested through `document_intake`.
- PDF text-layer extraction via `unpdf`. Scanned PDFs are not OCR'd;
composer-attached PDFs with no text layer send a few page images to
the configured vision model.
- Right-pane preview for PDF, extracted Office text/tables, and
clickable attachment chips after refresh.
- Open uses the OS file association on the workspace copy.
- `ant-code-uploads/` is added to the project `.gitignore` on send.

### Upgrade

```sh
git pull
npm ci
npm run verify:install
npm link
ant-code --version
```

`ant-code --version` should print `2.0.8`. Restart a running Dashboard
and hard-refresh the browser. Gateway config and `.lab-agent` sessions
do not need to be recreated.

## 2.0.7 - 2026-09-05

This is a dependency security patch on the 2.0 TypeScript runtime.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ ant-code doctor
ant-code
```

`ant-code --version` should print `2.0.7`. If you previously linked a
`ant-code --version` should print `2.0.8`. If you previously linked a
JavaScript install, run `npm link` again so the global command points at
`src/cli/index.ts`.

Expand Down
1 change: 1 addition & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ runtime assets from the following third-party packages:
| `react` | UI runtime dependency | MIT |
| `katex` | math rendering runtime dependency; Dashboard CSS and font assets | MIT |
| `mermaid` | Dashboard diagram rendering bundle input | MIT |
| `unpdf` | PDF text-layer extraction for document_intake and Dashboard paperclip | MIT |
| `yaml` | Dashboard YAML parsing bundle input | ISC |
| `esbuild` | development/build tool for Dashboard assets | MIT |
| `postject` | development/build tool for optional executable packaging | MIT |
Expand Down
6 changes: 4 additions & 2 deletions config/skills/document-intake/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ argument_hint: 提供文档路径、需要提取的内容类型、摘要粒度
2. 优先调用 `document_intake`:
- 支持 txt/md/json/csv/xml/html。
- 支持轻量解析 docx/pptx/xlsx。
- PDF 在核心实现中只给出边界提示。
3. 如果用户允许,并且本机安装了 MarkItDown,可通过受权限控制的 shell 命令做额外转换。
- PDF 抽取内嵌文本层,默认每次最多 20 页;需要后续页时传 `pageStart`。
- Dashboard 回形针也可直接附上这些文件;发送时会写入 `ant-code-uploads/` 并抽文本。
- 扫描件没有 OCR。回形针附上的无文本层 PDF 会把前几页内嵌图送给视觉模型。
3. 只有文本层为空、且用户允许时,才考虑本机 MarkItDown 或其他 OCR 转换器。
4. 对大文件只提取目录、标题、表格预览和关键段落,不输出全文。

## 输出要求
Expand Down
41 changes: 41 additions & 0 deletions docs/releases/2.0.8-dashboard-office-documents_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Ant Code v2.0.8:Dashboard 回形针办公文档与本地预览

发布日期:2026-09-06

这是 2.0 TypeScript 运行时上的小版本。Dashboard 回形针可以附上 PDF、docx、xlsx、pptx 和常见文本,发送后写入当前项目的 `ant-code-uploads/`,并抽出有界预览给模型。右侧栏可预览这些文件,Office/PDF 的「打开」会调用本机系统应用。权限模式 id 不变。本说明处于公开发布候选阶段,不表示 CI、tag 或 GitHub Release 已经完成。

## 对用户工作流的直接变化

- Dashboard 回形针同时接受图片和 PDF / docx / xlsx / pptx / txt / Markdown / CSV / JSON / HTML。图片仍走视觉附件;文档写入工作区 `ant-code-uploads/`,提示里只放短预览和保存路径,避免把全文塞进网关请求。
- 无文本层的扫描 PDF 不做 OCR。回形针附上的这类 PDF 最多把 2 页缩小后的页面图送给已配置的视觉模型。
- 点对话里的附件芯片会在右侧栏预览。PDF 用浏览器内嵌查看器;docx / xlsx / pptx 显示抽出的文本或表格。预览栏的「打开」用系统关联应用打开工作区里那份副本,而不是再下载一遍。
- 刷新或重开任务后,新发送的附件芯片仍可点。上传目录会写入项目 `.gitignore` 的 `ant-code-uploads/`,避免大文件被提交。
- 老格式 `.doc` / `.xls` / `.ppt` 仍不支持。TUI 没有回形针。

## 配置、数据与安全边界

- 配置格式、凭据存储和网关协议没有迁移。现有配置和 `.lab-agent` 会话可继续使用。
- 回形针文档落在当前工作区的 `ant-code-uploads/`。这是项目内副本,不是你在文件选择器里选中的原路径。「打开」编辑并保存时,改的是这份副本。
- 这次没有改权限模式 id。Dashboard 仍只绑定本机回环地址。
- 自动化测试不能代表所有私有研究数据、外部模型供应商或现场网络环境都已得到验证。

## 升级与产物位置

需要 Node.js 22.18+。从源码更新:

```sh
git pull
npm ci
npm run verify:install
npm link
ant-code --version
```

`ant-code --version` 应显示 `2.0.8`。若以前 link 过旧安装,需要重新 `npm link`。正在跑的 Dashboard 需要重启并硬刷新浏览器。现有网关配置和会话不必重建。本次新增运行时依赖 `unpdf`(MIT),用于 PDF 文本层。

## 验证、限制与待完成门禁

- 开发仓 `npm test`(`node:test` 口径):tests 1283,pass 1282,fail 1。失败项是 `full access lets fork skills use the base profile toolset instead of the skill allowlist`(TLS `ECONNRESET`),与本次回形针/预览改动无关。Dashboard 文件、服务、运行时与 UI 相关单测已通过。完整 `npm run verify:release` 交给 GitHub CI。
- 扫描件 PDF 没有 OCR;Office 预览是抽出文本,不是排版还原。Windows 用系统关联应用打开本地文件。
- 未把 TaxaMask 内嵌主题、`dashboard-embed` 或源码写入守卫带入独立版。独立 Ant Code 发布不会自动更新 TaxaMask 内嵌拷贝。
- 候选阶段最终结论以正式仓同一提交的 CI 和 Release 门禁为准。
22 changes: 20 additions & 2 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 20 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ant-code/cli",
"version": "2.0.7",
"version": "2.0.8",
"description": "AGPL-licensed local coding agent with a terminal UI, local dashboard, tool permissions, skills, MCP integration, and model gateway adapters.",
"type": "module",
"private": true,
Expand Down Expand Up @@ -96,6 +96,7 @@
"mermaid": "^11.17.2",
"react": "^19.2.5",
"typescript": "6.0.3",
"unpdf": "^1.8.1",
"yaml": "^2.9.0"
},
"devDependencies": {
Expand Down
6 changes: 6 additions & 0 deletions scripts/verify-release-seal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ const REVIEWED_RUNTIME_DEPENDENCIES = new Map([
license: "Apache-2.0",
provenanceMarker: "open_source: TypeScript compiler and language service, Apache-2.0 licensed, public npm package"
}],
["unpdf", {
versionSpec: "^1.8.1",
installedVersion: "1.8.1",
license: "MIT",
provenanceMarker: "open_source: unpdf PDF text extraction library, MIT licensed, public npm package"
}],
["yaml", {
versionSpec: "^2.9.0",
installedVersion: "2.9.0",
Expand Down
2 changes: 1 addition & 1 deletion src/context/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export async function buildInitialContext(options: { cwd: string; config: import
"- /compact is a context-window operation that prefers a separate model summarization request through the configured lab gateway; if the gateway is unavailable or summarization fails, it falls back to local deterministic compaction.",
"- Compaction keeps recent messages exactly and stores a bounded redacted summary of older messages for future turns.",
"- web_fetch is the stable public tool name, but URL fetching is configured MCP-first by default: prefer the fetch MCP when available, then fall back to the built-in bounded fetcher. web_search uses built-in DuckDuckGo HTML only; do not expect Bing/Brave scrapers or search MCP. A self-hosted SearXNG is the only stable no-key search backend. Use network tools only when the task needs current or external information, and cite URLs in user-facing conclusions.",
"- document_intake extracts bounded local text from common text/HTML/Office files inside the workspace. It does not fully parse PDFs unless a skill workflow provides an external converter.",
"- document_intake extracts bounded local text from common text/HTML/Office files and the text layer of PDFs inside the workspace. PDFs default to a 20-page window; pass pageStart/maxPages to continue. Dashboard paperclip can attach those files directly: images stay vision attachments, documents are saved under ant-code-uploads/ and ingested. Scanned PDFs are not OCR'd; composer-attached PDFs with no text layer send a few embedded page images to the configured vision model.",
"- MCP is optional. Use mcp_list to inspect configured servers/tools; mcp_call works only when a local or lab-approved MCP server is configured, and missing MCP servers do not disable built-in local tools.",
"- Recommended no-key MCP servers are enabled by default when self-contained; service-bound entries such as SearXNG/SQLite may remain disabled until configured. Use /mcp doctor before relying on MCP, and /mcp doctor --live when you need tool discovery.",
"- Skills are local instruction resources discovered from project/configured skill directories. Use skill_list, skill_read, or skill_run before applying a specialized workflow.",
Expand Down
61 changes: 51 additions & 10 deletions src/core/session-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,16 +285,57 @@ export function normalizeUserTurnMessage(message: SessionMessage | string | Reco

export function persistableUserTurnMessage(prompt: string, attachments: unknown = []): SessionMessage {
const normalized = normalizeInputAttachments(attachments);
if (normalized.length === 0) {
return { role: "user", content: prompt };
const documents = Array.isArray(attachments)
? attachments.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object" && item.type === "document")
: [];
const documentBlocks = documents.map((item) => ({
type: "text",
text: `[文档附件:${String(item.name ?? "document")}]`
}));
const chips = persistableAttachmentChips(attachments);
const message: SessionMessage = normalized.length === 0 && documentBlocks.length === 0
? { role: "user", content: prompt }
: {
role: "user",
content: [
...(String(prompt ?? "").trim() ? [{ type: "text", text: String(prompt ?? "") }] : []),
...documentBlocks,
...normalized.map(imageAttachmentSummaryBlock)
]
};
if (chips.length > 0) {
message.attachments = chips;
}
return {
role: "user",
content: [
...(String(prompt ?? "").trim() ? [{ type: "text", text: String(prompt ?? "") }] : []),
...normalized.map(imageAttachmentSummaryBlock)
]
};
return message;
}

export function persistableAttachmentChips(attachments: unknown = []): NonNullable<SessionMessage["attachments"]> {
if (!Array.isArray(attachments)) {
return [];
}
const chips: NonNullable<SessionMessage["attachments"]> = [];
for (const item of attachments) {
if (!item || typeof item !== "object") {
continue;
}
const record = item as Record<string, unknown>;
const type = record.type === "document" ? "document" : record.type === "image" ? "image" : null;
if (!type) {
continue;
}
const storedPath = String(record.path ?? "").trim().replace(/\\/g, "/");
chips.push({
type,
name: String(record.name ?? type).trim().slice(0, 160) || type,
mimeType: String(record.mimeType ?? record.mime_type ?? "").trim(),
size: nonNegativeInteger(record.size ?? record.bytes ?? record.sizeBytes, 0) ?? 0,
...(storedPath ? { path: storedPath } : {})
});
if (chips.length >= 10) {
break;
}
}
return chips;
}


Expand Down Expand Up @@ -366,7 +407,7 @@ export function messagesForModelContext(messages: unknown = []): SessionMessage[
if (!message || typeof message !== "object") {
return [];
}
const { interruptedDraft: _interruptedDraft, ...rest } = message;
const { interruptedDraft: _interruptedDraft, attachments: _attachments, ...rest } = message;
return [rest];
});
}
Expand Down
10 changes: 9 additions & 1 deletion src/core/session-persist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import type {
SessionTurnMetadata
} from "./session-types.ts";
import {
imageAttachmentSummaryBlock
imageAttachmentSummaryBlock,
persistableAttachmentChips
} from "./session-messages.ts";
import {
isPlainObject
Expand Down Expand Up @@ -326,6 +327,7 @@ export function persistableTranscriptMessages(messages: unknown, session: AgentS
return persistableMessagesWithOptions(messages, {
includeThinking: true,
includeToolCalls: false,
includeAttachments: true,
stripGoalStatus: session?.goal?.enabled === true
});
}
Expand Down Expand Up @@ -700,6 +702,12 @@ export function persistableMessage(message: unknown, options: Record<string, unk
persisted.name = message.name;
}
}
if (options.includeAttachments === true && role === "user") {
const chips = persistableAttachmentChips(message.attachments);
if (chips && chips.length > 0) {
persisted.attachments = chips;
}
}
return persisted;
}

Expand Down
Loading