278 lines
11 KiB
TypeScript
278 lines
11 KiB
TypeScript
type TargetDispatchDecision = "dispatched" | "idempotent-replay" | "concurrent-replay";
|
|
|
|
export function compactTargetTaskMutationPayload(raw: Record<string, unknown>): Record<string, unknown> | null {
|
|
const data = asRecord(innerData(raw));
|
|
const targetTask = firstRecord(data.targetTask, raw.targetTask);
|
|
const aipodBinding = firstRecord(data.aipodBinding, raw.aipodBinding);
|
|
const autoDispatch = firstRecord(data.autoDispatch, raw.autoDispatch);
|
|
const decision = boundedText(autoDispatch.decision, 80);
|
|
if (Object.keys(targetTask).length === 0 || !isTargetDispatchDecision(decision)) return null;
|
|
|
|
const identity = compactTargetTaskIdentity(data, autoDispatch);
|
|
const targetSummary = compactTargetTaskSummary(targetTask);
|
|
const aipodSummary = compactTargetAipodBinding(aipodBinding);
|
|
const autoDispatchSummary = {
|
|
enabled: autoDispatch.enabled === true,
|
|
decision,
|
|
mutation: booleanOrNull(autoDispatch.mutation),
|
|
duplicateDispatchPrevented: booleanOrNull(autoDispatch.duplicateDispatchPrevented),
|
|
identity,
|
|
valuesPrinted: false,
|
|
};
|
|
const next = targetTaskIdentityNext(identity, targetSummary, aipodSummary);
|
|
|
|
return {
|
|
ok: raw.ok !== false,
|
|
action: boundedText(raw.action, 120) ?? "queue-submit-auto-dispatch",
|
|
data: {
|
|
...identity,
|
|
state: identity.taskState,
|
|
mutation: booleanOrNull(data.mutation) ?? autoDispatchSummary.mutation,
|
|
autoDispatch: autoDispatchSummary,
|
|
valuesPrinted: false,
|
|
},
|
|
targetTask: targetSummary,
|
|
aipodBinding: aipodSummary,
|
|
autoDispatch: autoDispatchSummary,
|
|
next,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function compactTargetTaskIdentity(
|
|
data: Record<string, unknown>,
|
|
autoDispatch: Record<string, unknown>,
|
|
): Record<string, unknown> {
|
|
const declared = asRecord(autoDispatch.identity);
|
|
const task = asRecord(data.task);
|
|
const attempt = firstRecord(data.latestAttempt, task.latestAttempt);
|
|
const run = asRecord(data.run);
|
|
const command = asRecord(data.command);
|
|
const runner = firstRecord(data.runner, data.runnerJob);
|
|
const session = firstRecord(data.session, run.sessionRef, task.sessionRef);
|
|
return {
|
|
taskId: firstText(200, declared.taskId, data.taskId, task.id),
|
|
taskState: firstText(80, declared.taskState, data.taskState, task.state, task.status),
|
|
attemptId: firstText(200, declared.attemptId, data.attemptId, attempt.attemptId, attempt.id),
|
|
attemptState: firstText(80, data.attemptState, attempt.state, attempt.status),
|
|
runId: firstText(200, declared.runId, data.runId, run.id),
|
|
runState: firstText(80, data.runState, run.state, run.status),
|
|
commandId: firstText(200, declared.commandId, data.commandId, command.id),
|
|
commandState: firstText(80, data.commandState, command.state, command.status),
|
|
runnerJobId: firstText(200, declared.runnerJobId, data.runnerJobId, attempt.runnerJobId, runner.id),
|
|
runnerState: firstText(80, data.runnerState, attempt.runnerState, runner.state, runner.status),
|
|
sessionId: firstText(240, declared.sessionId, data.sessionId, attempt.sessionId, session.sessionId, session.id),
|
|
sessionState: firstText(80, data.sessionState, session.state, session.status),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function compactTargetTaskSummary(targetTask: Record<string, unknown>): Record<string, unknown> {
|
|
const workspace = asRecord(targetTask.workspace);
|
|
return {
|
|
title: boundedText(targetTask.title, 80),
|
|
mdtodoId: boundedText(targetTask.mdtodoId, 80),
|
|
target: boundedText(targetTask.target, 120),
|
|
targetWorkspace: boundedText(targetTask.targetWorkspace, 360),
|
|
targetRoute: boundedText(targetTask.targetRoute, 480),
|
|
projectId: boundedText(targetTask.projectId, 240),
|
|
repository: boundedText(targetTask.repository, 240),
|
|
ref: boundedText(targetTask.ref, 240),
|
|
workspace: {
|
|
root: boundedText(workspace.root, 360),
|
|
gitStateInspected: booleanOrNull(workspace.gitStateInspected),
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function compactTargetAipodBinding(binding: Record<string, unknown>): Record<string, unknown> {
|
|
const workspaceRef = asRecord(binding.workspaceRef);
|
|
const resourceBundleRef = asRecord(binding.resourceBundleRef);
|
|
const session = asRecord(binding.session);
|
|
const provider = compactCredentialPresence(binding.providerCredentials, "provider", 4);
|
|
const tool = compactCredentialPresence(binding.toolCredentials, "tool", 6);
|
|
return {
|
|
aipod: boundedText(binding.aipod, 160),
|
|
node: boundedText(binding.node, 120),
|
|
lane: boundedText(binding.lane, 120),
|
|
namespace: boundedText(binding.namespace, 160),
|
|
projectId: boundedText(binding.projectId, 240),
|
|
providerId: boundedText(binding.providerId, 160),
|
|
backendProfile: boundedText(binding.backendProfile, 160),
|
|
workspaceRef: {
|
|
kind: boundedText(workspaceRef.kind, 80),
|
|
path: boundedText(workspaceRef.path, 360),
|
|
valid: booleanOrNull(workspaceRef.valid),
|
|
valuesPrinted: false,
|
|
},
|
|
resourceBundleRef: {
|
|
kind: boundedText(resourceBundleRef.kind, 80),
|
|
repoUrl: boundedRepositoryUrl(resourceBundleRef.repoUrl),
|
|
ref: boundedText(resourceBundleRef.ref, 240),
|
|
inheritedFromAipod: booleanOrNull(resourceBundleRef.inheritedFromAipod),
|
|
credentialRefPresent: booleanOrNull(resourceBundleRef.credentialRefPresent),
|
|
valuesPrinted: false,
|
|
},
|
|
session: {
|
|
sessionId: boundedText(session.sessionId, 240),
|
|
identitySource: boundedText(session.identitySource, 120),
|
|
valuesPrinted: false,
|
|
},
|
|
credentialRefs: {
|
|
totalCount: provider.count + tool.count,
|
|
allPresent: provider.missingCount + tool.missingCount === 0,
|
|
provider,
|
|
tool,
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function compactCredentialPresence(value: unknown, kind: "provider" | "tool", limit: number): {
|
|
count: number;
|
|
presentCount: number;
|
|
missingCount: number;
|
|
omittedCount: number;
|
|
items: Record<string, unknown>[];
|
|
valuesPrinted: false;
|
|
} {
|
|
const source = asRecords(value);
|
|
const presentCount = source.filter((item) => item.bindingPresent === true).length;
|
|
const items = source.slice(0, limit).map((item) => {
|
|
const secretRef = asRecord(item.secretRef);
|
|
return {
|
|
...(kind === "provider"
|
|
? { profile: boundedText(item.profile, 160) }
|
|
: {
|
|
tool: boundedText(item.tool, 160),
|
|
purpose: boundedText(item.purpose, 200),
|
|
}),
|
|
secretRef: {
|
|
namespace: boundedText(secretRef.namespace, 160),
|
|
name: boundedText(secretRef.name, 200),
|
|
keys: boundedStrings(secretRef.keys, 6, 80),
|
|
valuesPrinted: false,
|
|
},
|
|
bindingPresent: item.bindingPresent === true,
|
|
valuesPrinted: false,
|
|
};
|
|
});
|
|
return {
|
|
count: source.length,
|
|
presentCount,
|
|
missingCount: source.length - presentCount,
|
|
omittedCount: source.length - items.length,
|
|
items,
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function targetTaskIdentityNext(
|
|
identity: Record<string, unknown>,
|
|
targetTask: Record<string, unknown>,
|
|
aipodBinding: Record<string, unknown>,
|
|
): Record<string, unknown> {
|
|
const taskId = asString(identity.taskId);
|
|
const runId = asString(identity.runId);
|
|
const commandId = asString(identity.commandId);
|
|
const runnerJobId = asString(identity.runnerJobId);
|
|
const sessionId = asString(identity.sessionId);
|
|
const title = asString(targetTask.title);
|
|
const selector = title === null ? null : shellQuote(title);
|
|
const target = asString(targetTask.target) ?? asString(aipodBinding.node);
|
|
const lane = asString(aipodBinding.lane);
|
|
const suffix = [
|
|
target === null ? null : `--target ${target}`,
|
|
lane === null ? null : `--lane ${lane}`,
|
|
].filter((value): value is string => value !== null).join(" ");
|
|
const scoped = (command: string): string => suffix.length === 0 ? command : `${command} ${suffix}`;
|
|
return {
|
|
...(taskId === null ? {} : {
|
|
task: scoped(`bun scripts/cli.ts agentrun describe ${selector ?? `task/${taskId}`}`),
|
|
attempts: scoped(`bun scripts/cli.ts agentrun get attempts --task ${taskId}`),
|
|
}),
|
|
...(runId === null ? {} : {
|
|
events: scoped(`bun scripts/cli.ts agentrun events ${selector ?? `run/${runId}`} --after-seq 0 --limit 20`),
|
|
}),
|
|
...(runId === null || commandId === null ? {} : {
|
|
result: scoped(selector === null
|
|
? `bun scripts/cli.ts agentrun result run/${runId} --command ${commandId}`
|
|
: `bun scripts/cli.ts agentrun result ${selector}`),
|
|
}),
|
|
...(runnerJobId === null || runId === null ? {} : {
|
|
runner: scoped(`bun scripts/cli.ts agentrun describe runnerjob/${runnerJobId} --run ${runId}`),
|
|
}),
|
|
...(sessionId === null ? {} : {
|
|
logs: scoped(`bun scripts/cli.ts agentrun logs ${selector ?? `session/${sessionId}`} --tail 120`),
|
|
}),
|
|
};
|
|
}
|
|
|
|
function shellQuote(value: string): string {
|
|
return `'${value.replace(/'/gu, `'"'"'`)}'`;
|
|
}
|
|
|
|
function innerData(value: Record<string, unknown>): unknown {
|
|
return value.data ?? value;
|
|
}
|
|
|
|
function firstRecord(...values: unknown[]): Record<string, unknown> {
|
|
for (const value of values) {
|
|
const item = asRecord(value);
|
|
if (Object.keys(item).length > 0) return item;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
? value as Record<string, unknown>
|
|
: {};
|
|
}
|
|
|
|
function asRecords(value: unknown): Record<string, unknown>[] {
|
|
return Array.isArray(value) ? value.map(asRecord).filter((item) => Object.keys(item).length > 0) : [];
|
|
}
|
|
|
|
function asString(value: unknown): string | null {
|
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
}
|
|
|
|
function firstText(maxChars: number, ...values: unknown[]): string | null {
|
|
for (const value of values) {
|
|
const text = boundedText(value, maxChars);
|
|
if (text !== null) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function boundedText(value: unknown, maxChars: number): string | null {
|
|
const text = asString(value);
|
|
if (text === null) return null;
|
|
const oneLine = text.replace(/\s+/gu, " ").trim();
|
|
return oneLine.length <= maxChars ? oneLine : `${oneLine.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
}
|
|
|
|
function boundedStrings(value: unknown, limit: number, maxChars: number): string[] {
|
|
return Array.isArray(value)
|
|
? value.slice(0, limit).map((item) => boundedText(item, maxChars)).filter((item): item is string => item !== null)
|
|
: [];
|
|
}
|
|
|
|
function boundedRepositoryUrl(value: unknown): string | null {
|
|
const text = asString(value);
|
|
if (text === null) return null;
|
|
return boundedText(text.replace(/^(https?:\/\/)[^@/]+@/u, "$1[redacted]@"), 320);
|
|
}
|
|
|
|
function booleanOrNull(value: unknown): boolean | null {
|
|
return typeof value === "boolean" ? value : null;
|
|
}
|
|
|
|
function isTargetDispatchDecision(value: string | null): value is TargetDispatchDecision {
|
|
return value === "dispatched" || value === "idempotent-replay" || value === "concurrent-replay";
|
|
}
|