@loomhelm/multi-task-flow 0.2.8 → 0.2.10
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/README.md +14 -14
- package/package.json +2 -2
- package/src/cli/flow.ts +13 -4
- package/src/cli/service.ts +26 -0
- package/src/cli.ts +3 -1
- package/toolkit/artifact-manifest.json +69 -73
- package/toolkit/package.json +1 -1
- package/toolkit/skills/bin/check_source_policy.ts +13 -7
- package/toolkit/skills/bin/multi_task_platform_gate.ts +2 -2
- package/toolkit/skills/bin/tests/core/package_scripts.test.ts +1 -1
- package/toolkit/skills/bin/tests/plan/multi-task-flow.test.ts +3 -3
- package/toolkit/skills/catalog.json +0 -1
- package/toolkit/skills/docs/vendor/dot-codex/design/multi-task-flow/data-and-scripts.md +8 -6
- package/toolkit/skills/docs/vendor/dot-codex/design/multi-task-flow/execution.md +1 -1
- package/toolkit/skills/docs/vendor/dot-codex/design/multi-task-flow/plan-revision-and-change-audit.md +1 -1
- package/toolkit/skills/docs/vendor/dot-codex/design/multi-task-flow/web-template.md +1 -1
- package/toolkit/skills/docs/vendor/dot-codex/specs/a4ge9-stable-preview-runtime/IMPLEMENTATION.md +0 -2
- package/toolkit/skills/docs/vendor/dot-codex/specs/a4ge9-stable-preview-runtime/SPEC.md +0 -1
- package/toolkit/skills/docs/vendor/dot-codex/specs/dev-plan-standalone-typescript-finalizer/IMPLEMENTATION.md +1 -1
- package/toolkit/skills/docs/vendor/dot-codex/specs/multi-task-flow/IMPLEMENTATION.md +3 -3
- package/toolkit/skills/docs/vendor/dot-codex/specs/multi-task-flow/SCHEMA.md +4 -4
- package/toolkit/skills/docs/vendor/dot-codex/specs/stable-preview-runtime/IMPLEMENTATION.md +0 -2
- package/toolkit/skills/docs/vendor/dot-codex/specs/stable-preview-runtime/SPEC.md +0 -1
- package/toolkit/skills/multi-task-flow/SKILL.md +65 -47
- package/toolkit/skills/multi-task-flow/dist/app.css +1 -1
- package/toolkit/skills/multi-task-flow/dist/app.js +74 -74
- package/toolkit/skills/multi-task-flow/dist/index.html +3 -3
- package/toolkit/skills/multi-task-flow/lib/atomic-orchestration.ts +1 -0
- package/toolkit/skills/multi-task-flow/lib/engine.ts +65 -7
- package/toolkit/skills/multi-task-flow/lib/package-source.ts +5 -5
- package/toolkit/skills/multi-task-flow/lib/schema.ts +82 -2
- package/toolkit/skills/multi-task-flow/lib/service-pages.ts +1 -26
- package/toolkit/skills/multi-task-flow/lib/view.ts +16 -0
- package/toolkit/skills/multi-task-flow/references/shared-viewer.md +1 -1
- package/toolkit/skills/multi-task-flow/scripts/check-client-skills.ts +7 -4
- package/toolkit/skills/multi-task-flow/scripts/check-skills-installation.ts +14 -8
- package/toolkit/skills/multi-task-flow/scripts/import-client-skills.ts +1 -1
- package/toolkit/skills/multi-task-flow/scripts/package-client-skills.ts +4 -4
- package/toolkit/skills/multi-task-flow/scripts/test-viewer-source.ts +1 -1
- package/toolkit/skills/multi-task-flow/tests/atomic-orchestration-schema.test.ts +5 -0
- package/toolkit/skills/multi-task-flow/tests/browser/README.md +1 -1
- package/toolkit/skills/multi-task-flow/tests/check-skills-installation.test.ts +12 -38
- package/toolkit/skills/multi-task-flow/tests/delivery-topology.test.ts +33 -0
- package/toolkit/skills/multi-task-flow/tests/orchestrate.test.ts +3 -5
- package/toolkit/skills/multi-task-flow/tests/service-fixture.ts +10 -2
- package/toolkit/skills/multi-task-flow/tests/shared-service.test.ts +13 -0
- package/toolkit/skills/multi-task-flow/tests/ui/details-content.test.ts +9 -0
- package/toolkit/skills/multi-task-flow/tests/ui/responsive-width-contract.test.ts +25 -1
- package/toolkit/skills/multi-task-flow/tests/ui/task-context-menu.test.ts +2 -1
- package/toolkit/skills/multi-task-flow/web/app.css +64 -9
- package/toolkit/skills/multi-task-flow/web/app.ts +33 -14
- package/toolkit/skills/multi-task-flow/web/atomic-graph-view.tsx +1 -1
- package/toolkit/skills/multi-task-flow/web/details-content.ts +49 -38
- package/toolkit/skills/multi-task-flow/web/graph-view.tsx +1 -1
- package/toolkit/skills/multi-task-flow/web/index.html +3 -3
- package/toolkit/skills/multi-task-flow/web/model.ts +1 -1
- package/toolkit/skills/multi-task-flow/web/timeline.ts +11 -8
- package/toolkit/skills/package.json +2 -2
- package/toolkit/skills/source-manifest.json +693 -400
- package/toolkit/skills/support-manifest.json +1 -0
- package/toolkit/skills/multi-task-flow/scripts/serve.ts +0 -218
- package/toolkit/skills/multi-task-flow/tests/serve.test.ts +0 -194
|
@@ -32,6 +32,7 @@ function clearDetails(): HTMLElement {
|
|
|
32
32
|
const container = document.getElementById("details-content")!;
|
|
33
33
|
document.getElementById("details-summary")?.replaceChildren();
|
|
34
34
|
delete container.dataset.selection;
|
|
35
|
+
delete container.dataset.detailKind;
|
|
35
36
|
container.replaceChildren();
|
|
36
37
|
container.scrollTop = 0;
|
|
37
38
|
container.className = "details-empty";
|
|
@@ -159,6 +160,7 @@ export function createDetailContent(context: DetailContentContext) {
|
|
|
159
160
|
const selection = `${kind}:${id}`;
|
|
160
161
|
const top = container.dataset.selection === selection ? container.scrollTop : 0;
|
|
161
162
|
container.dataset.selection = selection;
|
|
163
|
+
container.dataset.detailKind = kind;
|
|
162
164
|
container.replaceChildren();
|
|
163
165
|
container.className = "";
|
|
164
166
|
const summary = document.getElementById("details-summary")!;
|
|
@@ -169,17 +171,18 @@ export function createDetailContent(context: DetailContentContext) {
|
|
|
169
171
|
const data = context.getData();
|
|
170
172
|
if (!data) return;
|
|
171
173
|
const { container, restore } = begin("task", task.id, task.title, context.labelTask(task));
|
|
174
|
+
const facts = text("div", "", "detail-group-facts");
|
|
172
175
|
const currentGroup = data.groups.find((group) => group.id === task.group_id)?.title ?? task.group_id;
|
|
173
176
|
const executionGroups = [...new Set(data.actual_intervals.filter((row) => row.task_id === task.id).map((row) => row.group_id))].map((id) => data.groups.find((group) => group.id === id)?.title ?? id);
|
|
174
|
-
field(
|
|
175
|
-
if (executionGroups.length > 1) field(
|
|
176
|
-
field(
|
|
177
|
+
field(facts, "所属交付单元", currentGroup);
|
|
178
|
+
if (executionGroups.length > 1) field(facts, "执行交付单元历史", executionGroups.join("、"));
|
|
179
|
+
field(facts, "类型", taskKindLabel(task.kind));
|
|
177
180
|
if (context.profileContext) {
|
|
178
181
|
const profileContext = context.profileContext();
|
|
179
|
-
field(
|
|
182
|
+
field(facts, "执行配置", createProfileEditor(profileContext, "task", task.id, task.execution_profile ?? null, task.effective_execution_profile ?? data.orchestration.execution_profile ?? null, data.profile_read_only ? "当前视图只读或尚未迁移" : task.profile_freeze_reason ?? (task.thread_id ? "对话已创建" : null), task.execution_profile_source === "recommendation" ? `按任务类型 ${task.kind} 推荐 · ${task.execution_profile_rule ?? "kind-v1"}` : null));
|
|
180
183
|
}
|
|
181
|
-
if (task.disposition && task.disposition !== "active") field(
|
|
182
|
-
if (task.is_held && task.hold_reason) field(
|
|
184
|
+
if (task.disposition && task.disposition !== "active") field(facts, "任务处置", task.disposition);
|
|
185
|
+
if (task.is_held && task.hold_reason) field(facts, "计划修订暂停", task.hold_reason);
|
|
183
186
|
const node = data.nodes.find((item) => item.kind === "task" && item.ref_id === task.id);
|
|
184
187
|
const projection = buildGraphProjection(data);
|
|
185
188
|
const predecessors = dependenciesFor(projection, node?.id, "incoming", data);
|
|
@@ -192,24 +195,24 @@ export function createDetailContent(context: DetailContentContext) {
|
|
|
192
195
|
return { name: source?.title ?? edge.from_atom_id, state: "", status: source?.status ?? "unknown", completedAt: source?.completed_at };
|
|
193
196
|
});
|
|
194
197
|
}, null, { kind: "task", id: task.id }));
|
|
195
|
-
|
|
196
|
-
field(
|
|
198
|
+
container.append(facts);
|
|
199
|
+
field(facts, "前驱", renderDependencyTooltip(predecessors, predecessors.length ? `查看 ${predecessors.length} 项依赖` : "无依赖"));
|
|
200
|
+
field(facts, "后继", renderDependencyTooltip(successors, successors.length ? `查看 ${successors.length} 项依赖` : "无依赖"));
|
|
197
201
|
const projectedNode = projection.nodes.find((item) => item.sourceNodeId === node?.id);
|
|
198
202
|
const plannedGroup = projectedNode?.plannedGroupId && projectedNode.plannedGroupId !== task.group_id ? data.groups.find((group) => group.id === projectedNode.plannedGroupId)?.title ?? projectedNode.plannedGroupId : null;
|
|
199
|
-
if (plannedGroup) field(
|
|
200
|
-
if (task.blocker) field(
|
|
201
|
-
field(
|
|
202
|
-
field(
|
|
203
|
-
field(
|
|
203
|
+
if (plannedGroup) field(facts, "计划归属交付单元", plannedGroup);
|
|
204
|
+
if (task.blocker) field(facts, "等待原因", task.blocker);
|
|
205
|
+
field(facts, "开始时间", renderTimestamp(task.started_at));
|
|
206
|
+
field(facts, "完成时间", renderTimestamp(task.finished_at));
|
|
207
|
+
field(facts, "持续时间", durationValue(task.started_at, task.finished_at));
|
|
204
208
|
const output = text("div", "", "detail-agent-output");
|
|
205
209
|
output.dataset.outputTaskId = task.id; output.dataset.outputFull = "true";
|
|
206
210
|
paintOutput(output, task.latest_agent_output ?? initialOutput(task.thread_id), true);
|
|
207
|
-
field(
|
|
208
|
-
if (task.state === "agent_idle") field(container, "运行时提示", "Agent 当前没有活跃 turn,可能需要人工恢复");
|
|
211
|
+
if (task.state === "agent_idle") field(facts, "运行时提示", "Agent 当前没有活跃 turn,可能需要人工恢复");
|
|
209
212
|
if (task.state === "agent_idle") {
|
|
210
213
|
const observed = task.latest_agent_output;
|
|
211
|
-
if (observed?.observed_at) field(
|
|
212
|
-
if (observed?.runtime_turn_id) field(
|
|
214
|
+
if (observed?.observed_at) field(facts, "运行时观察时间", renderTimestamp(observed.observed_at));
|
|
215
|
+
if (observed?.runtime_turn_id) field(facts, "观察 Turn", observed.runtime_turn_id);
|
|
213
216
|
}
|
|
214
217
|
const conversation = text("div", "", "detail-conversation");
|
|
215
218
|
const conversationActions = text("div", "", "detail-conversation-actions");
|
|
@@ -221,18 +224,19 @@ export function createDetailContent(context: DetailContentContext) {
|
|
|
221
224
|
const presentation = archivePresentation(task);
|
|
222
225
|
conversation.append(text("p", `${presentation.label}:${presentation.description}`, "detail-conversation-notice"));
|
|
223
226
|
}
|
|
224
|
-
field(
|
|
225
|
-
field(
|
|
226
|
-
field(
|
|
227
|
-
field(
|
|
228
|
-
field(
|
|
227
|
+
field(facts, "任务对话", conversation);
|
|
228
|
+
field(facts, "执行 Agent", task.execution_owner === "main" ? "Main Agent" : task.execution_owner === "delivery_agent" ? "交付 Agent" : task.execution_owner === "task_agent" ? "Task Agent" : "未声明");
|
|
229
|
+
field(facts, "对话策略", task.conversation_policy ?? "未声明");
|
|
230
|
+
field(facts, "交付策略", task.delivery_policy ?? "未声明");
|
|
231
|
+
field(facts, "调度状态", task.dispatch_readiness === "blocked" ? `不可调度:${task.dispatch_diagnostics?.map((item) => item.message).join(";") || "未知原因"}` : task.dispatch_readiness === "ready" ? "可调度" : "未知");
|
|
229
232
|
const pr = context.safeExternal(task.pr?.url ?? null);
|
|
230
|
-
if (pr) field(
|
|
231
|
-
field(
|
|
233
|
+
if (pr) field(facts, "子 PR", link("查看 PR", pr));
|
|
234
|
+
field(facts, "任务要求", requirementsValue(task.requirements_path, "task", task.id));
|
|
232
235
|
for (const follow of data.follow_prs.filter((item) => item.origin_task_id === task.id)) {
|
|
233
236
|
const url = context.safeExternal(follow.pr.url);
|
|
234
|
-
|
|
237
|
+
field(facts, "Follow PR", url ? link(context.labels[follow.state] ?? follow.state, url) : context.labels[follow.state] ?? follow.state);
|
|
235
238
|
}
|
|
239
|
+
field(facts, "Agent 输出", output);
|
|
236
240
|
restore();
|
|
237
241
|
};
|
|
238
242
|
|
|
@@ -255,23 +259,30 @@ export function createDetailContent(context: DetailContentContext) {
|
|
|
255
259
|
}
|
|
256
260
|
return items;
|
|
257
261
|
}, focusedAtomId, { kind: "group", id: group.id }));
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
field(
|
|
261
|
-
field(
|
|
262
|
-
field(
|
|
263
|
-
|
|
264
|
-
field(
|
|
262
|
+
const facts = text("div", "", "detail-group-facts");
|
|
263
|
+
container.append(facts);
|
|
264
|
+
if (group.coordination_execution?.state === "waiting") field(facts, "协调等待", group.coordination_execution.reason ?? "等待贡献任务");
|
|
265
|
+
if (group.coordination_execution?.state === "blocked") field(facts, "协调阻塞", group.coordination_execution.reason ?? "未知原因");
|
|
266
|
+
field(facts, "交付协调者", coordinatorValue(group));
|
|
267
|
+
field(facts, "运行就绪", readinessValue(group));
|
|
268
|
+
field(facts, "任务数", `${tasks.length} 个任务`);
|
|
269
|
+
if (historicalTaskIds.length) field(facts, "历史执行任务", historicalTaskIds.map((id) => data.tasks.find((task) => task.id === id)?.title ?? id).join("、"));
|
|
270
|
+
field(facts, "持续时间", durationValue(group.started_at ?? null, group.finished_at ?? null));
|
|
265
271
|
if (context.profileContext) {
|
|
266
272
|
const profileContext = context.profileContext();
|
|
267
|
-
field(
|
|
273
|
+
field(facts, "执行配置", createProfileEditor(profileContext, "group", group.id, group.execution_profile ?? null, group.effective_execution_profile ?? data.orchestration.execution_profile ?? null, data.profile_read_only ? "当前视图只读或尚未迁移" : group.profile_freeze_reason ?? (group.agent_thread_id ? "对话已创建" : null)));
|
|
268
274
|
}
|
|
269
|
-
field(
|
|
270
|
-
field(
|
|
271
|
-
field(
|
|
275
|
+
field(facts, "交付单元对话", group.agent_thread_id ? link("打开对话", conversationUrl(group.agent_thread_id)) : "尚未创建");
|
|
276
|
+
field(facts, "交付 Agent", group.delivery_unit_kind === "bootstrap" ? "Main Agent(Bootstrap 特例)" : group.delivery_agent_readiness === "bound" ? "已绑定" : "未绑定");
|
|
277
|
+
field(facts, "调度状态", group.dispatch_readiness === "blocked" ? `不可调度:${group.dispatch_diagnostics?.map((item) => item.message).join(";") || "未知原因"}` : group.dispatch_readiness === "ready" ? "可调度" : "未知");
|
|
272
278
|
const pr = context.safeExternal(group.parent_pr?.url ?? null);
|
|
273
|
-
field(
|
|
274
|
-
field(
|
|
279
|
+
field(facts, "父 PR", pr ? link("查看 PR", pr) : "尚未创建");
|
|
280
|
+
field(facts, "交付单元要求", requirementsValue(group.requirements_path, "group", group.id));
|
|
281
|
+
const output = text("div", "", "detail-agent-output");
|
|
282
|
+
output.dataset.outputGroupId = group.id;
|
|
283
|
+
output.dataset.outputFull = "true";
|
|
284
|
+
paintOutput(output, group.latest_agent_output ?? initialOutput(group.agent_thread_id), true);
|
|
285
|
+
field(facts, "Agent 输出", output);
|
|
275
286
|
restore();
|
|
276
287
|
};
|
|
277
288
|
|
|
@@ -65,7 +65,7 @@ export function createGraphController(context: GraphContext, calculateLayout = l
|
|
|
65
65
|
signature=key;layout=null;const serial=++generation;pending=true;error='';update?.();
|
|
66
66
|
calculateLayout(next,direction).then(result=>{
|
|
67
67
|
if(serial!==generation)return;const byId=new Map(projection!.nodes.map(n=>[n.id,n]));layout={...result,nodes:result.nodes.map(p=>({...p,node:byId.get(p.node.id)!}))};pending=false;container.style.height = `${Math.max(420,result.height+80)}px`;update?.();
|
|
68
|
-
requestAnimationFrame(()=>{if(serial!==generation)return;if(!viewportInitialized){instance?.setViewport({x:Math.max(24,(container.clientWidth-result.width)/2),y:24,zoom:1});context.setZoom(1);viewportInitialized=true;} highlightSelection();});
|
|
68
|
+
requestAnimationFrame(()=>{if(serial!==generation)return;if(!viewportInitialized){instance?.setViewport({x:Math.max(24,(container.clientWidth-result.width)/2),y:Math.max(24,(container.clientHeight-result.height)/2),zoom:1});context.setZoom(1);viewportInitialized=true;} highlightSelection();});
|
|
69
69
|
}).catch(e=>{if(serial!==generation)return;pending=false;error=`依赖图布局失败:${e instanceof Error?e.message:String(e)}`;signature='';update?.();});
|
|
70
70
|
}
|
|
71
71
|
return {render,reset:()=>{container.replaceChildren();root.render(<Surface/>);},highlightSelection,zoomIn:()=>instance?.zoomIn(),zoomOut:()=>instance?.zoomOut(),fit:()=>instance?.fitView({padding:0.08}),rankDir:()=>direction==='DOWN'?'TB':'LR',projection:()=>projection};
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
<button id="tab-tasks" type="button" role="tab" aria-selected="false" aria-controls="tasks-view" data-tab="tasks">任务</button>
|
|
51
51
|
</div>
|
|
52
52
|
<label class="search input"><i data-lucide="search" aria-hidden="true"></i><span class="sr-only">搜索任务</span><input id="search" type="search" placeholder="搜索任务"></label>
|
|
53
|
+
<div id="task-view-tools" class="task-view-tools" aria-label="任务视图工具"><span id="task-count" class="section-count"></span><div id="task-layout-switch" class="layout-switch" role="group" aria-label="对象布局"><button id="task-layout-board" type="button" class="icon-button layout-option" aria-label="看板布局" aria-describedby="task-layout-board-tooltip" aria-pressed="true"><i data-lucide="columns-3"></i><span id="task-layout-board-tooltip" class="layout-tooltip" role="tooltip">看板布局:按交付单元分列展示任务</span></button><button id="task-layout-grid" type="button" class="icon-button layout-option" aria-label="网格布局" aria-describedby="task-layout-grid-tooltip" aria-pressed="false"><i data-lucide="layout-grid"></i><span id="task-layout-grid-tooltip" class="layout-tooltip" role="tooltip">网格布局:平铺展示任务</span></button></div></div>
|
|
53
54
|
</div>
|
|
54
55
|
<div class="filters">
|
|
55
56
|
<span class="filter-caption">筛选视图</span>
|
|
@@ -70,7 +71,6 @@
|
|
|
70
71
|
<div id="connection-notice" class="notice" role="status" hidden></div>
|
|
71
72
|
<div id="phase-notice" class="notice notice-neutral" role="status" hidden><span id="phase-notice-text"></span><button type="button" id="phase-jump" class="text-button">查看实际进度</button></div>
|
|
72
73
|
<div id="operation-warning" class="notice" role="status" hidden></div>
|
|
73
|
-
<div id="plan-state" class="notice plan-state" aria-label="计划与运行状态" hidden></div>
|
|
74
74
|
<div id="plan-warning" class="notice notice-warning" role="status" hidden></div>
|
|
75
75
|
<div id="load-error" class="notice notice-error" role="alert" hidden></div>
|
|
76
76
|
<div id="graph-view" class="canvas-view" role="tabpanel" aria-labelledby="tab-graph"><div id="graph-projection-warning" class="notice notice-warning" role="status" hidden></div><div id="graph" aria-label="任务依赖图"></div><div id="atomic-graph" aria-label="原子任务依赖图" hidden></div><div id="graph-a11y" class="graph-a11y" role="list" aria-label="依赖图节点列表"></div><div id="graph-empty" class="empty" hidden>暂无任务</div></div>
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
</div>
|
|
80
80
|
<div id="tasks-view" class="workspace-tasks" role="tabpanel" aria-labelledby="tab-tasks" hidden>
|
|
81
81
|
<section id="group-section" class="task-list-section" aria-labelledby="group-heading"><div class="section-heading"><h2 id="group-heading">交付单元</h2></div><div id="group-list" class="task-list" role="list"></div></section>
|
|
82
|
-
<section id="task-section" class="task-list-section"
|
|
82
|
+
<section id="task-section" class="task-list-section"><div id="task-list" class="task-list" role="list"></div></section>
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
85
|
<div id="details-backdrop" class="details-backdrop" hidden></div>
|
|
86
|
-
<div class="details-slot" hidden><aside id="details" class="details" aria-label="详情" aria-hidden="true" tabindex="-1"><div class="details-heading"><button type="button" id="close-details" class="icon-button mobile-close" data-tooltip="关闭详情" aria-label="关闭详情"><i data-lucide="x"></i></button><div id="details-summary"></div></div><div id="details-content" class="details-empty"><span class="empty-rule" aria-hidden="true"></span><strong>选择交付单元或任务</strong><span>查看依赖、运行时间和原生会话链接</span></div></aside></div>
|
|
86
|
+
<div class="details-slot" hidden><div id="details-resize" class="details-resize" role="separator" aria-orientation="horizontal" aria-label="调整详情高度" tabindex="0"></div><aside id="details" class="details" aria-label="详情" aria-hidden="true" tabindex="-1"><div class="details-heading"><button type="button" id="close-details" class="icon-button mobile-close" data-tooltip="关闭详情" aria-label="关闭详情"><i data-lucide="x"></i></button><div id="details-summary"></div></div><div id="details-content" class="details-empty"><span class="empty-rule" aria-hidden="true"></span><strong>选择交付单元或任务</strong><span>查看依赖、运行时间和原生会话链接</span></div></aside></div>
|
|
87
87
|
</section>
|
|
88
88
|
</main>
|
|
89
89
|
</div>
|
|
@@ -115,7 +115,7 @@ export type Task = {
|
|
|
115
115
|
execution_profile_rule?: string | null;
|
|
116
116
|
execution_owner?: "main" | "delivery_agent" | "task_agent" | null;
|
|
117
117
|
conversation_policy?: "main_owned" | "delivery_owned" | "task_agent" | "none" | null;
|
|
118
|
-
delivery_policy?: "parent_pr" | "child_pr" | "evidence" | "none" | null;
|
|
118
|
+
delivery_policy?: "parent_pr" | "child_pr" | "shared_delivery_unit" | "evidence" | "none" | null;
|
|
119
119
|
dispatch_readiness?: "ready" | "blocked" | "unknown";
|
|
120
120
|
dispatch_diagnostics?: { code: string; message: string }[];
|
|
121
121
|
};
|
|
@@ -132,34 +132,37 @@ export function createTimelineController(context: TimelineContext) {
|
|
|
132
132
|
const center = target.querySelector<HTMLElement>(".vis-panel.vis-center");
|
|
133
133
|
if (!center || panBound.has(center)) return;
|
|
134
134
|
panBound.add(center);
|
|
135
|
-
let pan: { pointerId: number; originX: number; originY: number; start: number; end: number; width: number; active: boolean } | null = null;
|
|
135
|
+
let pan: { pointerId: number; originX: number; originY: number; originScrollTop: number; start: number; end: number; width: number; axis: "x" | "y" | null; active: boolean } | null = null;
|
|
136
136
|
const isCurrent = (): boolean => (mode === "planned" ? plannedTimeline : actualTimeline) === timeline && (mode === "planned" ? plannedRenderSerial : actualRenderSerial) === renderSerial;
|
|
137
137
|
const finish = (event: PointerEvent): void => {
|
|
138
138
|
if (!pan || event.pointerId !== pan.pointerId) return;
|
|
139
139
|
if (pan.active && center.hasPointerCapture(event.pointerId)) center.releasePointerCapture(event.pointerId);
|
|
140
140
|
center.classList.remove("is-panning");
|
|
141
|
-
if (pan.active && isCurrent()) syncTimelineRange(mode, timeline);
|
|
141
|
+
if (pan.active && pan.axis === "x" && isCurrent()) syncTimelineRange(mode, timeline);
|
|
142
142
|
pan = null;
|
|
143
143
|
};
|
|
144
144
|
center.addEventListener("pointerdown", (event) => {
|
|
145
145
|
if (!event.isPrimary || event.button !== 0 || (event.target instanceof HTMLElement && event.target.closest(".vis-item"))) return;
|
|
146
146
|
const range = timeline.getWindow();
|
|
147
147
|
if (!range.start || !range.end || center.clientWidth <= 0) return;
|
|
148
|
-
pan = { pointerId: event.pointerId, originX: event.clientX, originY: event.clientY, start: range.start.getTime(), end: range.end.getTime(), width: center.clientWidth, active: false };
|
|
148
|
+
pan = { pointerId: event.pointerId, originX: event.clientX, originY: event.clientY, originScrollTop: center.scrollTop, start: range.start.getTime(), end: range.end.getTime(), width: center.clientWidth, axis: null, active: false };
|
|
149
149
|
}, true);
|
|
150
150
|
center.addEventListener("pointermove", (event) => {
|
|
151
151
|
if (!pan || event.pointerId !== pan.pointerId || !isCurrent()) return;
|
|
152
152
|
const deltaX = event.clientX - pan.originX;
|
|
153
153
|
const deltaY = event.clientY - pan.originY;
|
|
154
154
|
if (!pan.active) {
|
|
155
|
-
if (Math.abs(
|
|
156
|
-
|
|
155
|
+
if (Math.max(Math.abs(deltaX), Math.abs(deltaY)) < 4) return;
|
|
156
|
+
pan.axis = Math.abs(deltaY) > Math.abs(deltaX) ? "y" : "x";
|
|
157
157
|
pan.active = true;
|
|
158
158
|
center.setPointerCapture(event.pointerId);
|
|
159
159
|
center.classList.add("is-panning");
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
if (pan.axis === "y") center.scrollTop = Math.max(0, pan.originScrollTop - deltaY);
|
|
162
|
+
else {
|
|
163
|
+
const shift = -deltaX * ((pan.end - pan.start) / pan.width);
|
|
164
|
+
applyWindow(mode, timeline, new Date(pan.start + shift), new Date(pan.end + shift), false);
|
|
165
|
+
}
|
|
163
166
|
event.preventDefault();
|
|
164
167
|
event.stopPropagation();
|
|
165
168
|
}, true);
|
|
@@ -205,7 +208,7 @@ export function createTimelineController(context: TimelineContext) {
|
|
|
205
208
|
const initialWindow = requestedWindow.start && requestedWindow.end ? { start: new Date(requestedWindow.start), end: new Date(requestedWindow.end) } : fittedWindow.start && fittedWindow.end ? { start: fittedWindow.start, end: fittedWindow.end } : null;
|
|
206
209
|
if (mode === "planned") plannedWindow = initialWindow; else actualWindow = initialWindow;
|
|
207
210
|
target.style.height = `${Math.max(284, nodes.length * 28 + 76)}px`;
|
|
208
|
-
const timeline = new Timeline(target, new DataSet(entries), groups, { editable: false, stack: false, groupOrder: "order", selectable: true, moveable: true, zoomable: true, horizontalScroll:
|
|
211
|
+
const timeline = new Timeline(target, new DataSet(entries), groups, { editable: false, stack: false, groupOrder: "order", orientation: { item: "top" }, selectable: true, moveable: true, zoomable: true, horizontalScroll: true, verticalScroll: true, zoomKey: "ctrlKey", ...(mode === "planned" ? { format: { minorLabels: { minute: "T+mm分", hour: "T+HH时" }, majorLabels: { day: "理论时间" } } } : {}), ...(requestedWindow.start && requestedWindow.end ? requestedWindow : fittedWindow) });
|
|
209
212
|
if (mode === "planned") plannedTimeline = timeline; else actualTimeline = timeline;
|
|
210
213
|
bindWheel(target, timeline, mode);
|
|
211
214
|
bindPan(target, timeline, mode, renderSerial);
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"build": "bun run --cwd multi-task-flow build",
|
|
6
|
+
"build": "bun run --cwd multi-task-flow-runtime build",
|
|
7
7
|
"test": "bun test ./bin/tests ./bin/native-tests",
|
|
8
8
|
"skills:test": "bun test ./bin/tests ./bin/native-tests",
|
|
9
|
-
"skills:build": "bun run --cwd multi-task-flow build",
|
|
9
|
+
"skills:build": "bun run --cwd multi-task-flow-runtime build",
|
|
10
10
|
"test:core": "bun test ./bin/tests/core",
|
|
11
11
|
"test:hosting": "bun test ./bin/tests/hosting",
|
|
12
12
|
"test:flow": "bun test ./bin/tests/flow",
|