Archived
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1d8d86b16 | ||
|
|
c1137fb09a |
@@ -2,7 +2,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseCmmn } from '@modules/loop-engine';
|
||||
import type { ItemRuntime } from '@modules/loop-engine';
|
||||
import { stepsOf } from '../activity-ops/instance-steps.ts';
|
||||
import { followRes, stepsOf } from '../activity-ops/instance-steps.ts';
|
||||
|
||||
const XML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" id="d">
|
||||
@@ -84,3 +84,18 @@ describe('stepsOf', () => {
|
||||
expect(steps[1]?.res).toBe('current');
|
||||
});
|
||||
});
|
||||
|
||||
describe('followRes:第 9 步跟第 8 步(放行那一步没有自己的收步事件)', () => {
|
||||
const step = (seq: number, res: 'done' | 'current' | 'pending' | 'fail') => ({ seq, name: `S${seq}`, by: 'agent' as const, byName: 'llm', at: null, durationMs: null, res, note: '' });
|
||||
it('已放行的单:第 8 步 done → 第 9 步也 done、时刻跟第 8 步;别的字段不动', () => {
|
||||
const steps = [...Array.from({ length: 7 }, (_, i) => step(i + 1, 'done')), { ...step(8, 'done'), at: '2026-08-27T14:30:26.080Z' }, { ...step(9, 'pending'), by: 'human' as const, byName: '柴浩' }];
|
||||
const out = followRes(steps, { seq: 9, from: 8 });
|
||||
expect(out[8]).toEqual({ ...step(9, 'done'), at: '2026-08-27T14:30:26.080Z', by: 'human', byName: '柴浩' });
|
||||
expect(out.slice(0, 8)).toEqual(steps.slice(0, 8));
|
||||
});
|
||||
it('第 8 步 fail / pending 时第 9 步照样跟;步数不够 9 的活动什么都不动', () => {
|
||||
expect(followRes([step(1, 'done'), ...Array.from({ length: 7 }, (_, i) => step(i + 2, 'pending')), step(9, 'pending')].map((s) => (s.seq === 8 ? { ...s, res: 'fail' as const } : s)), { seq: 9, from: 8 })[8]?.res).toBe('fail');
|
||||
const seven = Array.from({ length: 7 }, (_, i) => step(i + 1, 'done'));
|
||||
expect(followRes(seven, { seq: 9, from: 8 })).toEqual(seven);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,3 +99,16 @@ export function stepsOf(f: StepFacts): InstanceStep[] {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 让某一步的结果**跟随**前面那一步(2026-08-28 用户定:详情接口交给前端之前,第 9 步的 res 跟第 8 步走)。
|
||||
*
|
||||
* 为什么会需要:放行那一步(T_release,人工激活)没有自己的收步事件 —— 人签字后 `submit` 直接把整单落终态,
|
||||
* 计划项本身从没被 finish 过,于是一单已 `released` 的详情里第 9 步永远是 `pending`,而它前面拟落账那一步早就 `done`。
|
||||
* 改 `res` 与 `at`(2026-08-28 用户定,`at` 也跟);用时不编(那一步没有自己的起止痕)。两步有一步不在(别的活动步数不同)就什么都不动。
|
||||
*/
|
||||
export function followRes(steps: readonly InstanceStep[], rule: { readonly seq: number; readonly from: number }): InstanceStep[] {
|
||||
const source = steps.find((s) => s.seq === rule.from);
|
||||
if (source === undefined) return [...steps];
|
||||
return steps.map((s) => (s.seq === rule.seq ? { ...s, res: source.res, at: source.at } : s));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import { fieldSpecsOf, launchParamsOf, validateLaunchParams } from './launch-par
|
||||
import type { FieldSpec, LaunchIssue, OptionsSource } from './launch-params.ts';
|
||||
import { instanceStateOf, verdictFactOf } from './instance-state.ts';
|
||||
import type { StateVerdict } from './instance-state.ts';
|
||||
import { stepsOf } from './instance-steps.ts';
|
||||
import { followRes, stepsOf } from './instance-steps.ts';
|
||||
import { applyInstanceFilters, inputFilesOf, inputSummaryOf, outputSummaryOf, pageOf, statsOf } from './summaries.ts';
|
||||
import type { InstanceQuery } from './summaries.ts';
|
||||
import { LEDGER_TEMPLATE, injectHeightScript } from './view-html.ts';
|
||||
@@ -54,6 +54,8 @@ import type { ActivityOpsStore, TaskRow } from './store.ts';
|
||||
export const LAUNCH_CLAIM_STALE_MS = 60_000;
|
||||
/** combo 候选内联上限(文档 §4:本期一律内联;更大的表以后走检索接口) */
|
||||
export const OPTIONS_CAP = 500;
|
||||
/** 详情时间线交给前端之前:第 9 步(放行)的结果跟第 8 步(拟落账)走 —— 见 `followRes` 的注释(2026-08-28 用户定) */
|
||||
export const STEP_FOLLOW_RULE = { seq: 9, from: 8 } as const;
|
||||
|
||||
/** 输入 / 产出文件的存取 —— app 里现成的两段(`upload-store.ts` / `asset-archive.ts`),按接口注入好替身 */
|
||||
export type InstanceFileStore = {
|
||||
@@ -387,15 +389,18 @@ export function createActivityOps(deps: ActivityOpsDeps): ActivityOps {
|
||||
deps.files.listArchived(taskId),
|
||||
]);
|
||||
const llm = llmOf(manifest);
|
||||
const steps = stepsOf({
|
||||
items: loop?.parsed.items ?? [],
|
||||
runtimeItems: loop?.runtime.items ?? [],
|
||||
intents: new Map((loop?.model.steps ?? []).map((s) => [s.item, s.intent])),
|
||||
history: entries.map((e) => ({ kind: e.kind, title: e.title, at: e.at })),
|
||||
state: verdict.state,
|
||||
ownerName: summary.owner.name,
|
||||
llm,
|
||||
});
|
||||
const steps = followRes(
|
||||
stepsOf({
|
||||
items: loop?.parsed.items ?? [],
|
||||
runtimeItems: loop?.runtime.items ?? [],
|
||||
intents: new Map((loop?.model.steps ?? []).map((s) => [s.item, s.intent])),
|
||||
history: entries.map((e) => ({ kind: e.kind, title: e.title, at: e.at })),
|
||||
state: verdict.state,
|
||||
ownerName: summary.owner.name,
|
||||
llm,
|
||||
}),
|
||||
STEP_FOLLOW_RULE,
|
||||
);
|
||||
const startedMs = Date.parse(row.startedAt);
|
||||
const finishedMs = row.finishedAt === null ? undefined : Date.parse(row.finishedAt);
|
||||
const durationMs = finishedMs !== undefined && Number.isFinite(startedMs) && Number.isFinite(finishedMs) && finishedMs >= startedMs ? finishedMs - startedMs : null;
|
||||
|
||||
@@ -196,6 +196,7 @@
|
||||
| `input.files` | `InstanceFile[]` | 是 | 输入文件元数据(可为空数组) |
|
||||
| `output.files` | `InstanceFile[]` | 是 | 产出文件元数据;没产出是 `[]`,摆「暂无产出」 |
|
||||
| `steps` | `InstanceStep[]` | 是 | 时间线 |
|
||||
| | | | **第 9 步的 `res` 与 `at` 跟第 8 步走**(服务端交出前改好,2026-08-28 定):放行那一步没有自己的收步事件,不这么做已放行的单第 9 步永远 `pending`;用时不编 |
|
||||
|
||||
注意:详情里**没有** `input`/`output` 那两句摘要字符串(它们是清单行的字段),`input`/`output` 在详情里是上面的结构体。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user