吸收数字员工治理动作入口
This commit is contained in:
@@ -658,6 +658,36 @@ export async function handleQimingclawDigitalApi<T>(
|
||||
if (method === 'POST' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/dispatch')) {
|
||||
return await dispatchPlan(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T;
|
||||
}
|
||||
const debugPlanActionMatch = pathname.match(/^\/api\/debug\/plan\/([^/]+)\/([^/]+)$/);
|
||||
if (method === 'POST' && debugPlanActionMatch) {
|
||||
return await handlePlanAction(
|
||||
decodeURIComponent(debugPlanActionMatch[1] || ''),
|
||||
decodeURIComponent(debugPlanActionMatch[2] || ''),
|
||||
await readQimingclawSnapshot(),
|
||||
parseOptionalJsonBody(options.body),
|
||||
) as T;
|
||||
}
|
||||
const planSubmitMatch = pathname.match(/^\/api\/plan\/([^/]+)\/submit$/);
|
||||
if (method === 'POST' && planSubmitMatch) {
|
||||
return await handlePlanAction(
|
||||
decodeURIComponent(planSubmitMatch[1] || ''),
|
||||
'submit',
|
||||
await readQimingclawSnapshot(),
|
||||
parseOptionalJsonBody(options.body),
|
||||
) as T;
|
||||
}
|
||||
if (method === 'GET' && pathname.startsWith('/api/plan/') && pathname.endsWith('/revisions')) {
|
||||
return buildPlanRevisions(decodeURIComponent(pathname.split('/')[3] || ''), await readQimingclawSnapshot()) as T;
|
||||
}
|
||||
const taskActionMatch = pathname.match(/^\/api\/task\/([^/]+)\/([^/]+)$/);
|
||||
if (method === 'POST' && taskActionMatch) {
|
||||
return await handleTaskAction(
|
||||
decodeURIComponent(taskActionMatch[1] || ''),
|
||||
decodeURIComponent(taskActionMatch[2] || ''),
|
||||
await readQimingclawSnapshot(),
|
||||
parseOptionalJsonBody(options.body),
|
||||
) as T;
|
||||
}
|
||||
if (method === 'GET' && pathname.startsWith('/api/plan/') && pathname.endsWith('/messages')) {
|
||||
return { messages: buildPlanMessages(decodeURIComponent(pathname.split('/')[3] || ''), await readQimingclawSnapshot()) } as T;
|
||||
}
|
||||
@@ -981,6 +1011,10 @@ function parseJsonBody<T>(body: BodyInit | null | undefined): T {
|
||||
return JSON.parse(body) as T;
|
||||
}
|
||||
|
||||
function parseOptionalJsonBody(body: BodyInit | null | undefined): Record<string, unknown> {
|
||||
return parseJsonBody<Record<string, unknown>>(body);
|
||||
}
|
||||
|
||||
function todayInputDate(): string {
|
||||
const date = new Date();
|
||||
return [
|
||||
@@ -2615,6 +2649,142 @@ function acpResponseForDecision(decision: string): 'once' | 'always' | 'reject'
|
||||
return 'once';
|
||||
}
|
||||
|
||||
async function handlePlanAction(
|
||||
planId: string,
|
||||
action: string,
|
||||
snapshot: QimingclawSnapshot | null,
|
||||
payload: Record<string, unknown> = {},
|
||||
): Promise<GovernanceCommandResponse> {
|
||||
const commandKind = planActionCommandKind(action);
|
||||
const correlationId = `plan-action-${slugifyIdPart(planId)}-${slugifyIdPart(action)}-${Date.now()}`;
|
||||
if (!commandKind) {
|
||||
return {
|
||||
command_id: `qimingclaw-digital-plan-action-${Date.now()}`,
|
||||
accepted: false,
|
||||
correlation_id: correlationId,
|
||||
message: `暂不支持计划动作:${action}`,
|
||||
error: 'unsupported_command',
|
||||
};
|
||||
}
|
||||
return handleGovernanceCommand({
|
||||
command_kind: commandKind,
|
||||
context_refs: { plan_id: planId },
|
||||
payload: {
|
||||
...payload,
|
||||
plan_id: planId,
|
||||
requested_action: `plan_${action}`,
|
||||
action_endpoint: `/api/debug/plan/${planId}/${action}`,
|
||||
},
|
||||
correlation_id: correlationId,
|
||||
}, snapshot);
|
||||
}
|
||||
|
||||
async function handleTaskAction(
|
||||
taskId: string,
|
||||
action: string,
|
||||
snapshot: QimingclawSnapshot | null,
|
||||
payload: Record<string, unknown> = {},
|
||||
): Promise<GovernanceCommandResponse> {
|
||||
const commandKind = taskActionCommandKind(action);
|
||||
const task = runtimeTasks(snapshot).find((candidate) => candidate.id === taskId);
|
||||
const latestRun = latestRunForTask(runtimeRuns(snapshot), taskId);
|
||||
const planId = task?.planId ?? (stringValue(payload.plan_id) || undefined);
|
||||
const runId = latestRun?.id ?? (stringValue(payload.run_id) || undefined);
|
||||
const stepId = task ? taskStepId(task) : (stringValue(payload.step_id) || undefined);
|
||||
const correlationId = `task-action-${slugifyIdPart(taskId)}-${slugifyIdPart(action)}-${Date.now()}`;
|
||||
if (!commandKind) {
|
||||
return {
|
||||
command_id: `qimingclaw-digital-task-action-${Date.now()}`,
|
||||
accepted: false,
|
||||
correlation_id: correlationId,
|
||||
message: `暂不支持任务动作:${action}`,
|
||||
error: 'unsupported_command',
|
||||
};
|
||||
}
|
||||
return handleGovernanceCommand({
|
||||
command_kind: commandKind,
|
||||
context_refs: {
|
||||
plan_id: planId,
|
||||
task_id: taskId,
|
||||
run_id: runId,
|
||||
step_id: stepId,
|
||||
},
|
||||
payload: {
|
||||
...payload,
|
||||
task_id: taskId,
|
||||
plan_id: planId ?? payload.plan_id,
|
||||
run_id: runId ?? payload.run_id,
|
||||
step_id: stepId ?? payload.step_id,
|
||||
requested_action: `task_${action}`,
|
||||
action_endpoint: `/api/task/${taskId}/${action}`,
|
||||
},
|
||||
correlation_id: correlationId,
|
||||
}, snapshot);
|
||||
}
|
||||
|
||||
function planActionCommandKind(action: string): GovernanceCommandRequest['command_kind'] | null {
|
||||
switch (action.toLowerCase()) {
|
||||
case 'submit':
|
||||
return 'submit_plan';
|
||||
case 'approve':
|
||||
return 'approve_plan';
|
||||
case 'activate':
|
||||
return 'activate_plan';
|
||||
case 'pause':
|
||||
return 'pause_plan';
|
||||
case 'resume':
|
||||
return 'resume_plan';
|
||||
case 'cancel':
|
||||
return 'cancel_plan';
|
||||
case 'amend':
|
||||
return 'amend_plan';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function taskActionCommandKind(action: string): GovernanceCommandRequest['command_kind'] | null {
|
||||
switch (action.toLowerCase()) {
|
||||
case 'retry':
|
||||
return 'retry_task';
|
||||
case 'skip':
|
||||
return 'skip_task';
|
||||
case 'pause':
|
||||
return 'pause_task';
|
||||
case 'resume':
|
||||
return 'resume_task';
|
||||
case 'cancel':
|
||||
return 'cancel_task';
|
||||
case 'start':
|
||||
return 'start_run';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildPlanRevisions(planId: string, snapshot: QimingclawSnapshot | null): { plan_id: string; revisions: Array<Record<string, unknown>> } {
|
||||
const revisions = runtimeEvents(snapshot)
|
||||
.filter((event) => event.planId === planId && event.kind.startsWith('governance_'))
|
||||
.sort((left, right) => right.occurredAt.localeCompare(left.occurredAt) || right.createdAt.localeCompare(left.createdAt))
|
||||
.map((event) => {
|
||||
const payload = asRecord(event.payload) ?? {};
|
||||
const result = asRecord(payload.result) ?? {};
|
||||
const commandKind = stringValue(payload.commandKind) || event.kind.replace(/^governance_/, '');
|
||||
return {
|
||||
revision_id: event.id,
|
||||
plan_id: planId,
|
||||
event_id: event.id,
|
||||
command_kind: commandKind,
|
||||
action: commandKind.replace(/_plan$/, ''),
|
||||
status: stringValue(result.status) || stringValue(payload.status) || 'recorded',
|
||||
message: event.message,
|
||||
created_at: event.occurredAt,
|
||||
payload,
|
||||
};
|
||||
});
|
||||
return { plan_id: planId, revisions };
|
||||
}
|
||||
|
||||
async function readRouteDecisions(limit = 80): Promise<RouteDecisionTimelineResponse> {
|
||||
const bridge = window.QimingClawBridge?.digital;
|
||||
if (!bridge?.listRouteDecisions) {
|
||||
|
||||
Reference in New Issue
Block a user