@evandu/super-pi 0.1.161 → 0.1.162
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/agents/super-pi-task/core.ts +1 -1
- package/agents/super-pi-task/index.ts +1 -1
- package/dist/client/.super-pi-client-sources.json +4 -4
- package/dist/client/assets/{CodeMirrorEditor-CDaL0eZg.js → CodeMirrorEditor-Ch1Ipz3r.js} +2 -2
- package/dist/client/assets/{dist-Cm3rvfSQ.js → dist-B0NwzG2K.js} +1 -1
- package/dist/client/assets/{dist-gCJkYldU.js → dist-BAxkLl0w.js} +1 -1
- package/dist/client/assets/{dist-CiyMaqx4.js → dist-BGN6sRpP.js} +1 -1
- package/dist/client/assets/{dist-CSdmsB0d.js → dist-B_k2ubHz.js} +1 -1
- package/dist/client/assets/{dist-BDNs_xhA.js → dist-BelPaaJY.js} +1 -1
- package/dist/client/assets/{dist-DhPkHpbv.js → dist-BofmoyAA.js} +1 -1
- package/dist/client/assets/{dist-BJG0l-sn.js → dist-BucgltXJ.js} +1 -1
- package/dist/client/assets/{dist-BA58NP6M.js → dist-COh20l57.js} +1 -1
- package/dist/client/assets/{dist-jwSzBAO8.js → dist-CZfh2TpC.js} +1 -1
- package/dist/client/assets/{dist-DnaLB6eU.js → dist-C_rEORol.js} +1 -1
- package/dist/client/assets/{dist-DL66Z_jn.js → dist-Chs1uuQX.js} +1 -1
- package/dist/client/assets/{dist-BHWAql8E.js → dist-CmFGh26v.js} +1 -1
- package/dist/client/assets/{dist-CZlI9li3.js → dist-Cp0He9AW.js} +1 -1
- package/dist/client/assets/{dist-D1cVuzfF.js → dist-D5kM2io7.js} +1 -1
- package/dist/client/assets/{dist-S5Gkqadu.js → dist-DsvKYPCw.js} +1 -1
- package/dist/client/assets/{dist-DVCVhi5y.js → dist-EWYaYkiN.js} +1 -1
- package/dist/client/assets/{dist-DPwOtaMP.js → dist-L0PobKDk.js} +1 -1
- package/dist/client/assets/{dist-CTelFWCj.js → dist-MIa-fDZ9.js} +1 -1
- package/dist/client/assets/{dist-BZgXABCU.js → dist-PaqaOBBw.js} +1 -1
- package/dist/client/assets/{dist-DO3oDtGc.js → dist-opu-Ndfn.js} +1 -1
- package/dist/client/assets/{dist-DOdfw0L-.js → dist-phbezeQa.js} +1 -1
- package/dist/client/assets/{dist-CWgIVqDh.js → dist-sV52WV4g.js} +1 -1
- package/dist/client/assets/{index-B9tN8KD3.js → index-PN2dNRnQ.js} +10 -10
- package/dist/client/assets/{index-BK3ZIqJj.css → index-unAm6EaY.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/application/project-service.d.ts +14 -0
- package/dist/server/application/project-service.js +133 -0
- package/dist/server/http-server.js +9 -0
- package/package.json +3 -2
package/dist/client/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="alternate icon" href="/favicon.ico" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/favicon.svg" />
|
|
10
10
|
<title>Super Pi</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-PN2dNRnQ.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-unAm6EaY.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
|
@@ -96,6 +96,20 @@ export declare class ProjectService {
|
|
|
96
96
|
private resources;
|
|
97
97
|
list(workspaceId?: string): Project[];
|
|
98
98
|
get(id: string, workspaceId?: string): Project;
|
|
99
|
+
/**
|
|
100
|
+
* THE PROJECT AS A REPORT A HUMAN CAN READ.
|
|
101
|
+
*
|
|
102
|
+
* Everything the panel shows in pieces - the objective, the tasks and who did them, the
|
|
103
|
+
* deliveries and their evidence, the reviews that accepted them, the exceptions and how they
|
|
104
|
+
* were decided - in one piece of Markdown, in reading order.
|
|
105
|
+
*
|
|
106
|
+
* TWO THINGS THIS REPORT REFUSES TO DO. It prints no session handle: a conversation id is a
|
|
107
|
+
* machine address, and a report full of addresses is not something a person can read or paste
|
|
108
|
+
* into a weekly note - the session's own title is used instead. And it prints no credential of
|
|
109
|
+
* any kind: the CONTENT of the tasks, events, reports and exceptions is the report, and a
|
|
110
|
+
* capability or a token that happens to live in a session profile is not content.
|
|
111
|
+
*/
|
|
112
|
+
exportReport(id: string, workspaceId?: string): Promise<string>;
|
|
99
113
|
projectForSession(conversationId: string): Project | undefined;
|
|
100
114
|
create(input: CreateProjectInput, initiatorConversationId?: string): Promise<Project>;
|
|
101
115
|
private admission;
|
|
@@ -122,6 +122,35 @@ const CACHE_WRITE_WEIGHT = 1.25;
|
|
|
122
122
|
const tokens = (usage) => usage ? Math.round(usage.input + usage.output + usage.cacheWrite * CACHE_WRITE_WEIGHT + usage.cacheRead * CACHE_READ_WEIGHT) : 0;
|
|
123
123
|
/** The budget a project starts with, and the floor the one-click raise falls back to. */
|
|
124
124
|
const DEFAULT_MAX_TOKENS = 100_000;
|
|
125
|
+
// THE WORDS THE REPORT SPEAKS IN. A reader of the report is a person, not a console, so every
|
|
126
|
+
// state and every role is written the way a person would say it out loud.
|
|
127
|
+
const PROJECT_STATUS_TEXT = {
|
|
128
|
+
planning: "草稿", active: "进行中", blocked: "已阻塞", awaiting_approval: "等待审批",
|
|
129
|
+
awaiting_review: "等待复核", completed: "已完成", cancelled: "已取消", failed: "失败",
|
|
130
|
+
};
|
|
131
|
+
const TASK_STATUS_TEXT = {
|
|
132
|
+
pending: "等待依赖", ready: "待派发", in_progress: "执行中", blocked: "已阻塞",
|
|
133
|
+
awaiting_review: "待复核", completed: "已完成", failed: "失败", cancelled: "已取消",
|
|
134
|
+
};
|
|
135
|
+
const EVENT_TEXT = {
|
|
136
|
+
created: "创建项目", started: "启动执行", task_created: "登记任务", dispatched: "派发执行",
|
|
137
|
+
submitted: "提交交付", reviewed: "复核", review_requested: "启动复核", task_updated: "任务更新", status: "状态变更",
|
|
138
|
+
budget: "预算变更", report: "进展汇报", escalated: "例外升级", decision: "决定",
|
|
139
|
+
completed: "项目完成", ready: "依赖就绪",
|
|
140
|
+
};
|
|
141
|
+
const ESCALATION_STATUS_TEXT = { open: "未决", resolved: "已解决", dismissed: "已忽略" };
|
|
142
|
+
const SEVERITY_TEXT = { low: "低", medium: "中", high: "高" };
|
|
143
|
+
const REPORT_KIND_TEXT = {
|
|
144
|
+
progress: "进展", blocked: "阻塞", delivery: "交付", decision_request: "决策请求", review: "复核",
|
|
145
|
+
};
|
|
146
|
+
/** ONE LINE, ONE MEANING: a value that carries newlines must not break the layout of the report. */
|
|
147
|
+
const oneLine = (value) => value.replace(/\r?\n+/g, " ").trim();
|
|
148
|
+
/** A table cell must not carry a pipe of its own, or the row it sits in falls apart. */
|
|
149
|
+
const cell = (value) => oneLine(value).replace(/\|/g, "\\|");
|
|
150
|
+
/** A long delivery is trimmed, and the trim says so - a silent cut would read as the whole truth. */
|
|
151
|
+
const truncate = (value, limit) => value.length > limit ? `${value.slice(0, limit)}…(已截断)` : value;
|
|
152
|
+
/** A timestamp a person can read at a glance, and a missing one that says so. */
|
|
153
|
+
const moment = (value) => value ? value.replace("T", " ").replace(/\.\d+Z$/, "") : "无";
|
|
125
154
|
/**
|
|
126
155
|
* THE TWO SHAPES A PROJECT CAN HAVE.
|
|
127
156
|
*
|
|
@@ -328,6 +357,109 @@ export class ProjectService {
|
|
|
328
357
|
get(id, workspaceId) {
|
|
329
358
|
return this.projectView(this.raw(id, workspaceId));
|
|
330
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* THE PROJECT AS A REPORT A HUMAN CAN READ.
|
|
362
|
+
*
|
|
363
|
+
* Everything the panel shows in pieces - the objective, the tasks and who did them, the
|
|
364
|
+
* deliveries and their evidence, the reviews that accepted them, the exceptions and how they
|
|
365
|
+
* were decided - in one piece of Markdown, in reading order.
|
|
366
|
+
*
|
|
367
|
+
* TWO THINGS THIS REPORT REFUSES TO DO. It prints no session handle: a conversation id is a
|
|
368
|
+
* machine address, and a report full of addresses is not something a person can read or paste
|
|
369
|
+
* into a weekly note - the session's own title is used instead. And it prints no credential of
|
|
370
|
+
* any kind: the CONTENT of the tasks, events, reports and exceptions is the report, and a
|
|
371
|
+
* capability or a token that happens to live in a session profile is not content.
|
|
372
|
+
*/
|
|
373
|
+
async exportReport(id, workspaceId) {
|
|
374
|
+
const p = workspaceId === undefined ? this.store.project(id) : this.raw(id, workspaceId);
|
|
375
|
+
if (!p)
|
|
376
|
+
throw new Error("项目不存在或不属于当前工作区");
|
|
377
|
+
const store = this.store;
|
|
378
|
+
/** THE TITLE A PERSON CALLS A SESSION BY. The handle stays out of the report. */
|
|
379
|
+
const session = (conversationId) => {
|
|
380
|
+
if (!conversationId)
|
|
381
|
+
return "系统";
|
|
382
|
+
return store.conversation(conversationId)?.title ?? "未知会话";
|
|
383
|
+
};
|
|
384
|
+
/** THE ROLE A TASK PLAYS, in the words the report uses. */
|
|
385
|
+
const role = (t) => t.role === "leader" ? "负责人" : t.parentTaskId ? "执行者" : "负责人直属";
|
|
386
|
+
/** THE ACCEPTANCE OF A TASK, told the truth about what is behind it. */
|
|
387
|
+
const acceptance = (t) => {
|
|
388
|
+
if (t.review?.lightweight === true)
|
|
389
|
+
return "自评交付(轻量项目,无独立复核)";
|
|
390
|
+
if (!t.review)
|
|
391
|
+
return "未验收";
|
|
392
|
+
return oneLine(t.review.summary);
|
|
393
|
+
};
|
|
394
|
+
/** THE TIMES A DELIVERY WAS SENT BACK. The current review only holds the last word, so the
|
|
395
|
+
* earlier rejections are read back out of the timeline - the event summary carries the exact
|
|
396
|
+
* text the reviewer wrote that day. */
|
|
397
|
+
const reworkNotes = (t) => {
|
|
398
|
+
const prefix = `退回返工:${t.title};`;
|
|
399
|
+
return (p.events ?? []).filter((e) => e.kind === "reviewed" && e.taskId === t.id && e.summary.startsWith(prefix))
|
|
400
|
+
.map((e) => e.summary.slice(prefix.length).trim());
|
|
401
|
+
};
|
|
402
|
+
const lines = [];
|
|
403
|
+
lines.push(`# ${oneLine(p.title)}`, "", `> ${oneLine(p.objective)}`);
|
|
404
|
+
if (p.deliverables.length)
|
|
405
|
+
lines.push(`> 交付物:${p.deliverables.map(oneLine).join(";")}`);
|
|
406
|
+
if (p.acceptanceCriteria.length)
|
|
407
|
+
lines.push(`> 验收标准:${p.acceptanceCriteria.map(oneLine).join(";")}`);
|
|
408
|
+
lines.push("", "## 概览", "");
|
|
409
|
+
lines.push(`- 状态:${PROJECT_STATUS_TEXT[p.status] ?? p.status}`);
|
|
410
|
+
lines.push(`- 模式:${lightweight(p) ? "轻量" : "标准"}`);
|
|
411
|
+
lines.push(`- 风险:${SEVERITY_TEXT[p.risk] ?? p.risk}`);
|
|
412
|
+
lines.push(`- 创建时间:${moment(p.createdAt)}`);
|
|
413
|
+
lines.push(`- 最后更新时间:${moment(p.updatedAt)}`);
|
|
414
|
+
if (p.closedAt)
|
|
415
|
+
lines.push(`- 完成时间:${moment(p.closedAt)}`);
|
|
416
|
+
lines.push(`- 预算上限:${p.budget?.maxTokens ?? DEFAULT_MAX_TOKENS} Token`);
|
|
417
|
+
// THE USAGE IS REPORTED IN THE SAME BILLING-EQUIVALENT UNITS THE BUDGET IS SPENT IN, and the
|
|
418
|
+
// formula is printed next to the number: a budget nobody can predict is a budget nobody trusts.
|
|
419
|
+
lines.push(`- 已用等效 Token:${this.resources(p).tokens}(口径:输入 + 输出 + 缓存写入×${CACHE_WRITE_WEIGHT} + 缓存读取×${CACHE_READ_WEIGHT},取整)`);
|
|
420
|
+
lines.push(`- 任务:共 ${p.tasks.length} 个,已完成 ${p.tasks.filter((t) => t.status === "completed").length} 个`);
|
|
421
|
+
lines.push("", "## 交付与验收", "");
|
|
422
|
+
if (!p.tasks.length)
|
|
423
|
+
lines.push("无");
|
|
424
|
+
p.tasks.forEach((t, index) => {
|
|
425
|
+
lines.push(`### ${index + 1}. ${oneLine(t.title)}`, "");
|
|
426
|
+
lines.push(`- 角色:${role(t)}`);
|
|
427
|
+
lines.push(`- 状态:${TASK_STATUS_TEXT[t.status] ?? t.status}`);
|
|
428
|
+
lines.push(`- 负责人:${t.ownerConversationId ? session(t.ownerConversationId) : "未派发"}`);
|
|
429
|
+
lines.push(`- 复核者:${t.reviewerConversationId ? session(t.reviewerConversationId) : "无"}`);
|
|
430
|
+
lines.push(`- 交付内容:${t.output ? truncate(oneLine(t.output), 200) : "无"}`);
|
|
431
|
+
lines.push(`- 证据:${t.evidenceRefs?.length ? t.evidenceRefs.map(oneLine).join("、") : "无"}`);
|
|
432
|
+
lines.push(`- 验收结论:${acceptance(t)}`);
|
|
433
|
+
if (t.review?.evidenceRefs?.length)
|
|
434
|
+
lines.push(`- 复核证据:${t.review.evidenceRefs.map(oneLine).join("、")}`);
|
|
435
|
+
const notes = reworkNotes(t);
|
|
436
|
+
if (notes.length)
|
|
437
|
+
lines.push(`- 退回意见:${notes.join(";")}`);
|
|
438
|
+
lines.push("");
|
|
439
|
+
});
|
|
440
|
+
lines.push("## 例外与决定", "");
|
|
441
|
+
if (!p.escalations.length)
|
|
442
|
+
lines.push("无");
|
|
443
|
+
else {
|
|
444
|
+
lines.push("| 例外 | 严重程度 | 状态 | 决定 | 解决时间 |", "| --- | --- | --- | --- | --- |");
|
|
445
|
+
for (const e of p.escalations) {
|
|
446
|
+
lines.push(`| ${cell(e.reason)} | ${SEVERITY_TEXT[e.severity] ?? e.severity} | ${ESCALATION_STATUS_TEXT[e.status] ?? e.status} | ${cell(e.decision ?? "无")} | ${e.resolvedAt ? moment(e.resolvedAt) : "无"} |`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
lines.push("", "## 时间线", "");
|
|
450
|
+
const events = [...(p.events ?? [])].sort((a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt));
|
|
451
|
+
if (!events.length)
|
|
452
|
+
lines.push("无");
|
|
453
|
+
for (const e of events)
|
|
454
|
+
lines.push(`- ${moment(e.createdAt)} · ${EVENT_TEXT[e.kind] ?? e.kind} · ${oneLine(e.summary)} · ${session(e.actorConversationId)}`);
|
|
455
|
+
lines.push("", "## 最近汇报", "");
|
|
456
|
+
if (!p.reports.length)
|
|
457
|
+
lines.push("无");
|
|
458
|
+
for (const r of p.reports) {
|
|
459
|
+
lines.push(`- ${REPORT_KIND_TEXT[r.kind] ?? r.kind}:${oneLine(r.progress)} | 偏差:${r.variance ? oneLine(r.variance) : "无"} | 下一步:${r.nextStep ? oneLine(r.nextStep) : "无"} | 风险:${r.risks?.length ? r.risks.map(oneLine).join("、") : "无"}`);
|
|
460
|
+
}
|
|
461
|
+
return `${lines.join("\n")}\n`;
|
|
462
|
+
}
|
|
331
463
|
projectForSession(conversationId) {
|
|
332
464
|
let c = this.store.conversation(conversationId);
|
|
333
465
|
const seen = new Set();
|
|
@@ -1092,6 +1224,7 @@ export class ProjectService {
|
|
|
1092
1224
|
this.member(p, actor);
|
|
1093
1225
|
switch (body.action) {
|
|
1094
1226
|
case "get": return p;
|
|
1227
|
+
case "export": return this.exportReport(id, workspaceId);
|
|
1095
1228
|
case "start": return this.start(id, workspaceId, actor);
|
|
1096
1229
|
case "add_task": return this.addTask(id, body, workspaceId, actor);
|
|
1097
1230
|
case "dispatch": return this.dispatchTask(id, text(body.taskId, "taskId"), workspaceId, actor);
|
|
@@ -1551,6 +1551,15 @@ export function createHttpServer(options) {
|
|
|
1551
1551
|
json(200, { escalations: project.escalations });
|
|
1552
1552
|
return;
|
|
1553
1553
|
}
|
|
1554
|
+
// THE REPORT IS MARKDOWN, NOT JSON. It exists so a person can open the URL in a browser
|
|
1555
|
+
// and read the project, or paste it into a weekly note - a JSON body would need a reader
|
|
1556
|
+
// to have one, and the whole point is that the reader does not.
|
|
1557
|
+
if (subresource === "report") {
|
|
1558
|
+
const markdown = await service.exportReport(projectId, requestedWorkspaceId);
|
|
1559
|
+
response.writeHead(200, { "content-type": "text/markdown; charset=utf-8", "cache-control": "no-store" });
|
|
1560
|
+
response.end(markdown);
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1554
1563
|
json(404, { error: "未知的项目资源" });
|
|
1555
1564
|
return;
|
|
1556
1565
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.162",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -223,7 +223,8 @@
|
|
|
223
223
|
"scripts/session-report-watchdog.test.mjs",
|
|
224
224
|
"scripts/session-report-tool.test.mjs",
|
|
225
225
|
"scripts/session-stop-subtree.test.mjs",
|
|
226
|
-
"scripts/awaiting-background-status.test.mjs"
|
|
226
|
+
"scripts/awaiting-background-status.test.mjs",
|
|
227
|
+
"scripts/project-lifecycle-e2e.test.mjs"
|
|
227
228
|
],
|
|
228
229
|
"check:browser": [
|
|
229
230
|
"scripts/interrupt-browser.test.mjs",
|