@margay/ccl-core 1.4.6-beta.0 → 1.5.0-alpha.0
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/dist/cli.js +7017 -6338
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/query.js +35 -21
- package/dist/sdk/queryInput.d.ts +1 -0
- package/dist/sdk/queryInput.js +14 -2
- package/dist/sdk/session.d.ts +6 -1
- package/dist/sdk/session.js +65 -10
- package/dist/sdk/types.d.ts +33 -0
- package/docs/context-checkpoints-and-daemon.md +37 -0
- package/docs/releases/1.5.0-alpha.0.md +44 -0
- package/examples/assistant/README.md +23 -0
- package/examples/assistant/client.mjs +227 -0
- package/examples/assistant/office/SKILL.md +18 -0
- package/examples/assistant/office/build.py +113 -0
- package/examples/assistant/office/check.py +174 -0
- package/examples/assistant/office/render.py +99 -0
- package/examples/assistant/office/workflow.md +46 -0
- package/examples/assistant/terminal.mjs +60 -0
- package/package.json +4 -2
- package/sdk/API.md +16 -0
- package/sdk/README.md +7 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# 制作与验收
|
|
2
|
+
|
|
3
|
+
本目录随 npm 包发布。命令里的 WORKFLOW 指本目录的绝对路径;INPUT/OUTPUT 是调用者提供的任务目录。原件留在 INPUT,产物写入 OUTPUT。中文字体和渲染依赖以运行环境实际可用项为准。
|
|
4
|
+
|
|
5
|
+
制作依赖:Python 3、python-docx、python-pptx、Pillow。独立 `check.py` 只使用 Python 标准库。缺依赖时使用任务自己的虚拟环境安装,或采用已授权且能满足相同合同的制品工具,不改用户全局环境。
|
|
6
|
+
|
|
7
|
+
模板 `build.py` 接受如下 `content.json`(这里是结构示意,正文与分析由助手根据本次材料写):
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"title": "报告标题",
|
|
12
|
+
"subtitle": "期间、人民币元、编制日期",
|
|
13
|
+
"sections": [{"title":"章节", "paragraphs":["完整正文段落"], "chart":"monthly", "caption":"图注及来源"}],
|
|
14
|
+
"actions": [{"action":"建议行动", "owner":"建议负责人", "date":"建议日期", "metric":"可检查指标"}],
|
|
15
|
+
"slides": [{"title":"页标题", "bullets":["观点和依据"], "chart":"channels"}],
|
|
16
|
+
"metrics": {"revenue":0,"cost":0,"gross_profit":0,"gross_margin":0,"revenue_growth":0,"top_channel":"渠道","top_channel_share":0,"highest_cost_ratio_month":"YYYY-MM","highest_cost_ratio":0,"revenue_monotonically_increasing":false},
|
|
17
|
+
"author_checks": ["作者已完成的具体检查"]
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
sections/slides 的 chart 可省略;支持 monthly(收入和成本)、channels(渠道收入),每份文档都应含二者。slides 提供 6 项;actions 至少 4 项;正文写足实质分析。比例使用 0–1 数值。输入 CSV 字段分别为 `month,revenue,cost,orders` 与 `channel,revenue,orders`,此外读取用户提供的业务简报与会议材料。别的结构应先做显式映射、保留原件与映射记录,不把未知单位默认为人民币。
|
|
22
|
+
|
|
23
|
+
写分析前,明确每项比较的起点、终点、分母和期间。首月与末月的变化只能称“末月较首月”,不能改称半年累计增长或同比;同比需要上一年同期数据。收入、成本率、毛利率各自检查完整月度序列:首尾改善不意味着逐月改善,中途反向变化要在 Word 与 PPT 中一致披露,不在另一段又称单调变化。不要把不同月份的数值合并成同一个值。
|
|
24
|
+
|
|
25
|
+
把可观察事实、业务假设、会议观察和建议分别表述。成本率下降本身不能证明固定成本摊薄、采购提效或交付改善;没有成本拆分与运营数据时,把这些作为待验证解释,不能在正文、图注或 PPT 中改写成已证实原因。收入和订单数据也不能证明获客触点、低成本渠道或未来增长潜力。只对明确推导的比率作数值结论,其他判断给出已有依据或注明需要验证。
|
|
26
|
+
|
|
27
|
+
Word 的行动表与 PPT 的行动页分别能够独立使用:至少四项行动都给出建议负责人、建议日期和可检查指标;“建议”标识可用全页说明,不能只在 Word 写完整而在 PPT 丢失日期和验收指标。内容过密时优先压缩重复叙述、调整版式,不能删除决策所需字段。
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
python3 "$WORKFLOW/build.py" "$INPUT" content.json "$OUTPUT"
|
|
31
|
+
python3 "$WORKFLOW/check.py" "$INPUT" "$OUTPUT" > independent-check.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
build 替换 OUTPUT 中 report.docx、presentation.pptx、metrics.json、validation.json、monthly.png、channels.png;非零退出需读错误并修复具体原因。模板图表取自 CSV,报告正文和 metrics 取自 content.json;不从检查器复制计算来伪造独立比较。可修改模板布局或直接编辑 Office 文件,但再次检查实际最终文件。
|
|
35
|
+
|
|
36
|
+
check 从 CSV 重新计算事实,直接解析 Office 文件;输出 JSON 含输入/产物哈希、实际正文汉字数、页数、比例、内嵌媒体数量、全文、事实表及差异。0 只表示机械检查通过;1 表示差异,2 表示输入/制品不可解析。内嵌媒体检查证明有不同图片/原生图表,不证明它们是正确数据图;独立审阅须检查。`semantic`/`overall` 不因机械检查通过自动变绿。源材料的会议观察、因果陈述、行动可行性须审阅全文,不用关键词正则裁判。
|
|
37
|
+
|
|
38
|
+
用真实 Office 渲染器导出两个制品的全部页面,再逐页检查。若调用者明确由宿主渲染,以下命令由宿主执行;生成助手完成制品和机械检查后交回,不在代理沙箱内查找、启动或排障渲染器。`render.py` 使用给定 LibreOffice 可执行文件,独立用户配置与输出目录,不调用用户正在使用的 Office 窗口。需要 PyMuPDF 将 PDF 每页转 PNG。执行:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
python3 "$WORKFLOW/render.py" --soffice /absolute/path/to/soffice "$OUTPUT" "$OUTPUT/rendered"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
返回 manifest.json 包含源文件哈希、PDF/全部页图片哈希和页数;按本次 manifest 及哈希选取审阅页面,不把目录中其他图片当作当前页面。复用输出目录时,两个制品均成功转换后,渲染器只清除上次 manifest 记录、内容未被修改且本次已不再存在的旧页;未登记文件、用户修改过的文件和符号链接保留。调用者提供渲染器/Python 路径时直接使用。未提供时只检查 PATH 和系统标准安装位置一次;确认缺失后,在任务目录准备依赖或报告具体依赖缺口,继续完成不依赖渲染的部分。不要重复全盘搜索或用像素数量、XML 抽取、自制近似 PDF 替代真实渲染。只看首页不能称为版式验收。渲染完成后交给现有独立审阅者;没有独立审阅能力时保存全部页面并报告待审,不虚构另一个审阅者。
|
|
45
|
+
|
|
46
|
+
独立审阅者应读取原简报、会议记录、CSV、independent-check.json、报告全文和 manifest 的全部页面,逐条判断:事实/趋势/极值正确;会议观察与建议分开;至少 4 条建议各有建议负责人、日期、可检查指标;图表真实且无裁切、遮挡、字号过小。记录看到的文件哈希和具体页/段问题,留存审阅来源。修复后只复核受影响内容及结构,不制造自签独立 PASS。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createInterface } from 'node:readline'
|
|
3
|
+
import { parseArgs } from 'node:util'
|
|
4
|
+
import { AssistantClient } from './client.mjs'
|
|
5
|
+
|
|
6
|
+
const { values } = parseArgs({ options: {
|
|
7
|
+
cwd: { type: 'string' }, 'state-dir': { type: 'string' }, model: { type: 'string' },
|
|
8
|
+
resume: { type: 'boolean' }, 'cli-path': { type: 'string' }, 'extra-args': { type: 'string' }, help: { type: 'boolean' },
|
|
9
|
+
} })
|
|
10
|
+
if (values.help) {
|
|
11
|
+
console.log('node terminal.mjs [--cwd PROJECT] [--state-dir DIR] [--model MODEL] [--resume]\nCommands: /status, /steer TEXT, /cancel [TURN_ID], /allow TOOL_ID, /deny TOOL_ID, /defer TOOL_ID, /artifact FILE, /office ["INPUT","OUTPUT"], /verify-office ["INPUT","OUTPUT"], /exit\nExecution, verification and file availability are separate. Restart with --resume; uncertain work is never replayed automatically.')
|
|
12
|
+
process.exit(0)
|
|
13
|
+
}
|
|
14
|
+
let textOpen = false
|
|
15
|
+
function line(message) { if (textOpen) { process.stdout.write('\n'); textOpen = false }; console.log(message) }
|
|
16
|
+
function display(event) {
|
|
17
|
+
if (event.type === 'text_delta') { process.stdout.write(event.text); textOpen = true; return }
|
|
18
|
+
if (event.type === 'connected') line(`[连接] session=${event.sessionId} pid=${event.pid}`)
|
|
19
|
+
else if (event.type === 'turn_queued') line(`[排队] ${event.turnId}`)
|
|
20
|
+
else if (event.type === 'turn_started') line(`[已发送] ${event.turnId},等待执行响应`)
|
|
21
|
+
else if (event.type === 'turn_cancel_requested') line(`[取消请求] ${event.turnId};已发生的操作不会撤销`)
|
|
22
|
+
else if (event.type === 'turn_finished') line(`[执行结束] ${event.turnId}: ${event.outcome}`)
|
|
23
|
+
else if (event.type === 'execution_result') line(`[验证] ${event.verification};执行结果不等于独立验收`)
|
|
24
|
+
else if (event.type === 'tool_use') line(`[工具] ${event.toolName}`)
|
|
25
|
+
else if (event.type === 'request_progress') line(`[请求] ${event.progress.phase}`)
|
|
26
|
+
else if (event.type === 'approval') line(`[等待权限] ${event.toolName} ${JSON.stringify(event.input)}\n/allow ${event.toolUseId} 或 /deny ${event.toolUseId};/defer ${event.toolUseId} 保存后再决定`)
|
|
27
|
+
else if (event.type === 'suspended') line(`[权限已保存] ${event.toolUseId};可退出并用 --resume 恢复后批准/拒绝`)
|
|
28
|
+
else if (event.type === 'independent_check') line(`[独立检查] 数值/结构=${event.verification.mechanical},内容审阅=${event.verification.semantic}\n${JSON.stringify(event.verification.report.failures ?? event.verification.report.error)}`)
|
|
29
|
+
else if (event.type === 'artifact_available') line(`[交付] ${event.artifact.path} 可读取,${event.artifact.bytes} 字节;内容验证另行完成`)
|
|
30
|
+
else if (['diagnostic', 'turn_error', 'error'].includes(event.type)) line(`[诊断] ${event.message}`)
|
|
31
|
+
}
|
|
32
|
+
const client = new AssistantClient({ cwd: values.cwd, stateDirectory: values['state-dir'], model: values.model,
|
|
33
|
+
cliPath: values['cli-path'], resume: values.resume, extraArgs: values['extra-args'] ? JSON.parse(values['extra-args']) : undefined,
|
|
34
|
+
onDisplay: display })
|
|
35
|
+
const input = createInterface({ input: process.stdin, terminal: !!process.stdin.isTTY })
|
|
36
|
+
let exiting = false
|
|
37
|
+
async function exit() { if (exiting) return; exiting = true; input.close(); await client.close() }
|
|
38
|
+
input.on('line', text => {
|
|
39
|
+
const [command, ...args] = text.trim().split(/\s+/)
|
|
40
|
+
const rest = args.join(' ')
|
|
41
|
+
void (async () => {
|
|
42
|
+
if (command === '/exit') await exit()
|
|
43
|
+
else if (command === '/status') line(JSON.stringify(client.status(), null, 2))
|
|
44
|
+
else if (command === '/steer') client.prompt(rest, true)
|
|
45
|
+
else if (command === '/cancel') await client.cancel(rest || undefined)
|
|
46
|
+
else if (['/allow', '/deny', '/defer'].includes(command)) await client.decide(rest, command === '/defer' ? 'pending' : command.slice(1))
|
|
47
|
+
else if (command === '/artifact') client.artifact(rest)
|
|
48
|
+
else if (['/office', '/verify-office'].includes(command)) {
|
|
49
|
+
const paths = JSON.parse(rest)
|
|
50
|
+
if (!Array.isArray(paths) || paths.length !== 2 || paths.some(path => typeof path !== 'string')) throw new Error('Use ["INPUT_DIRECTORY","OUTPUT_DIRECTORY"]')
|
|
51
|
+
if (command === '/office') client.office(...paths)
|
|
52
|
+
else await client.verifyOffice(...paths)
|
|
53
|
+
}
|
|
54
|
+
else if (text.trim()) client.prompt(text)
|
|
55
|
+
})().catch(error => line(`[错误] ${String(error)}`))
|
|
56
|
+
})
|
|
57
|
+
input.on('close', () => { if (!exiting) void client.drain().finally(exit) })
|
|
58
|
+
process.once('SIGINT', () => { void client.cancel().catch(error => line(String(error))) })
|
|
59
|
+
process.once('SIGTERM', () => { void exit() })
|
|
60
|
+
line('输入任务,或 /status 查看执行、验证与交付状态。')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@margay/ccl-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-alpha.0",
|
|
4
4
|
"description": "Core runtime extracted from Claude Code (ccl) for Margay integration",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"type": "module",
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
"docs/releases/1.4.5-rc.9.md",
|
|
43
43
|
"docs/releases/1.4.5-rc.10.md",
|
|
44
44
|
"docs/context-checkpoints-and-daemon.md",
|
|
45
|
-
"docs/releases/1.4.6-beta.0.md"
|
|
45
|
+
"docs/releases/1.4.6-beta.0.md",
|
|
46
|
+
"examples/assistant",
|
|
47
|
+
"docs/releases/1.5.0-alpha.0.md"
|
|
46
48
|
],
|
|
47
49
|
"engines": {
|
|
48
50
|
"node": ">=22.19.0"
|
package/sdk/API.md
CHANGED
|
@@ -89,6 +89,22 @@ for await (const event of resumeWithDecision(sessionId, { behavior: 'allow' }))
|
|
|
89
89
|
`onEvent(event)` 接收即时事件,Session 不保存事件历史。`controls` 提供原有
|
|
90
90
|
Query 控制方法,`pid` 用于宿主监控。
|
|
91
91
|
|
|
92
|
+
`onLifecycle(event: SessionLifecycleEvent)` 单独观察宿主状态,不增加模型调用。
|
|
93
|
+
所有通知含 `at`(毫秒)、`pendingTurns`,可用时附 `pid`/`sessionId`:
|
|
94
|
+
|
|
95
|
+
- `turn_queued`:接受入队;`turn_started`:输入已写入 CLI,尚不代表模型已响应。
|
|
96
|
+
- `turn_cancel_requested`:已请求中断;已执行操作仍可能有副作用。
|
|
97
|
+
- `turn_finished`:`succeeded`/`failed`/`cancelled`/`interrupted`/`suspended`;其中 `cancelled` 为尚未发送的队列项。
|
|
98
|
+
- `permission_waiting`/`permission_resolved`:请求 ID、工具 ID/名称或决定。
|
|
99
|
+
- `session_closed`:执行通道关闭及原因。
|
|
100
|
+
|
|
101
|
+
回调异常不终止执行;耗时工作应由宿主另行排队。执行成功不等于产物验证通过。
|
|
102
|
+
通过 `Session.interrupt()` 或 `Turn.cancel()` 请求取消,才能观察宿主取消通知;
|
|
103
|
+
`controls` 是底层控制通道,直接调用其 interrupt 不经过宿主队列状态。
|
|
104
|
+
`SDKEvent.origin` 的 `user`/`background` 与 `turnId` 用于区分用户输入和自主工作;
|
|
105
|
+
旧 CLI 没有显式来源时保持兼容归属,不能据此证明后台来源。
|
|
106
|
+
参考终端、状态恢复与权限操作见 [assistant 示例](../examples/assistant/README.md)。
|
|
107
|
+
|
|
92
108
|
`endInput()` 停止接受新输入并等待已接受轮次,但保留进程和控制通道;`close()`
|
|
93
109
|
终止进程并拒绝未完成轮次。权限回调可等待宿主决定,interrupt/close 会取消
|
|
94
110
|
等待;返回 `pending` 仍表示持久挂起,后续用 `resumeWithDecision` 恢复。
|
package/sdk/README.md
CHANGED
|
@@ -240,3 +240,10 @@ the existing durable suspend contract: it ends this process and requires
|
|
|
240
240
|
Uncooperative application iterators/callbacks cannot be forcibly stopped, but do
|
|
241
241
|
not prevent CLI cleanup. One-shot string queries and post-result `rewindFiles`
|
|
242
242
|
retain their existing behavior.
|
|
243
|
+
|
|
244
|
+
`onLifecycle` observes queue acceptance, actual input dispatch, cancellation requests,
|
|
245
|
+
permission waits, terminal turn outcomes and session closure without another model
|
|
246
|
+
call. `SessionLifecycleEvent` is exported alongside `SDKEvent`. Use `turnId` and
|
|
247
|
+
`origin` to keep background work separate from a waiting user input. Execution
|
|
248
|
+
outcomes do not certify artifacts. See the [runnable terminal host](../examples/assistant/README.md)
|
|
249
|
+
for persistence, explicit permission resumption and no-replay recovery.
|