fix: normalize empty device job blockers
This commit is contained in:
@@ -458,9 +458,16 @@ test("cloud api dispatches authorized device jobs to the internal device-pod exe
|
||||
const output = await getJson(port, `/v1/device-pods/device-pod-71-freq/jobs/${runningJob.body.job.id}/output`, aliceLogin.cookie);
|
||||
assert.equal(output.status, 200);
|
||||
assert.equal(output.body.status, "completed");
|
||||
assert.equal(output.body.blocker, null);
|
||||
assert.equal(output.body.freshness.stale, false);
|
||||
assert.equal(output.body.output.text, "executor output");
|
||||
assert.equal(output.body.truncation.truncated, false);
|
||||
assert.ok(executorRequests.some((item) => item.method === "GET" && item.url.endsWith(`/jobs/${runningJob.body.job.id}/output`)));
|
||||
|
||||
const events = await getJson(port, "/v1/device-pods/device-pod-71-freq/events", aliceLogin.cookie);
|
||||
const completedEvent = events.body.events.find((event) => event.refs.jobId === runningJob.body.job.id);
|
||||
assert.equal(completedEvent.status, "completed");
|
||||
assert.equal(completedEvent.blocker, null);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
||||
await new Promise((resolve, reject) => executor.close((error) => (error ? reject(error) : resolve())));
|
||||
|
||||
@@ -675,8 +675,8 @@ class AccessController {
|
||||
scope: "job",
|
||||
intent: job.intent,
|
||||
status: job.status,
|
||||
summary: job.blocker?.summary ?? `job ${job.id} ${job.status}`,
|
||||
blocker: job.blocker,
|
||||
summary: normalizeBlocker(job.blocker)?.summary ?? `job ${job.id} ${job.status}`,
|
||||
blocker: normalizeBlocker(job.blocker),
|
||||
refs: jobRefs(job)
|
||||
}));
|
||||
return {
|
||||
@@ -952,6 +952,7 @@ class AccessController {
|
||||
}
|
||||
|
||||
jobPayload(job, pod, { lease = null } = {}) {
|
||||
const blocker = normalizeBlocker(job.blocker);
|
||||
return {
|
||||
serviceId: CLOUD_API_SERVICE_ID,
|
||||
contractVersion: DEVICE_JOB_CONTRACT_VERSION,
|
||||
@@ -964,8 +965,8 @@ class AccessController {
|
||||
operationId: job.operationId,
|
||||
job: publicJob(job),
|
||||
lease: lease ? publicDeviceLease(lease) : null,
|
||||
blocker: job.blocker,
|
||||
freshness: freshness(job.updatedAt, job.blocker),
|
||||
blocker,
|
||||
freshness: freshness(job.updatedAt, blocker),
|
||||
outputUrl: `/v1/device-pods/${encodeURIComponent(pod.id)}/jobs/${encodeURIComponent(job.id)}/output`,
|
||||
cancelUrl: `/v1/device-pods/${encodeURIComponent(pod.id)}/jobs/${encodeURIComponent(job.id)}/cancel`
|
||||
};
|
||||
@@ -1273,10 +1274,11 @@ function pgUser(row) { return row ? { id: row.id, username: row.username, displa
|
||||
function pgSession(row) { return { id: row.id, userId: row.user_id, tokenHash: row.session_token_hash, createdAt: row.created_at, lastSeenAt: row.last_seen_at, expiresAt: row.expires_at, revokedAt: row.revoked_at, user: { id: row.user_id, username: row.username, displayName: row.display_name, role: row.role, status: row.status, passwordHash: row.password_hash, createdAt: row.user_created_at, updatedAt: row.user_updated_at } }; }
|
||||
function pgDevicePod(row) { return { id: row.id, name: row.name, status: row.status, profile: parseJson(row.profile_json, {}), profileHash: row.profile_hash, createdAt: row.created_at, updatedAt: row.updated_at }; }
|
||||
function pgLease(row) { return row ? { devicePodId: row.device_pod_id, holderSessionId: row.holder_session_id, holderUserId: row.holder_user_id, leaseTokenHash: row.lease_token_hash, createdAt: row.created_at, expiresAt: row.expires_at, releasedAt: row.released_at } : null; }
|
||||
function pgJob(row) { return row ? { id: row.id, devicePodId: row.device_pod_id, ownerUserId: row.owner_user_id, status: row.status, intent: row.intent, args: parseJson(row.args_json, {}), reason: row.reason, traceId: row.trace_id, operationId: row.operation_id, output: parseJson(row.output_json, {}), blocker: parseJson(row.blocker_json, null), createdAt: row.created_at, updatedAt: row.updated_at, completedAt: row.completed_at } : null; }
|
||||
function pgJob(row) { return row ? { id: row.id, devicePodId: row.device_pod_id, ownerUserId: row.owner_user_id, status: row.status, intent: row.intent, args: parseJson(row.args_json, {}), reason: row.reason, traceId: row.trace_id, operationId: row.operation_id, output: parseJson(row.output_json, {}), blocker: normalizeBlocker(parseJson(row.blocker_json, null)), createdAt: row.created_at, updatedAt: row.updated_at, completedAt: row.completed_at } : null; }
|
||||
function pgAgentSession(row) { return row ? { id: row.id, projectId: row.project_id, agentId: row.agent_id, status: row.status, startedAt: row.started_at, endedAt: row.ended_at, ownerUserId: row.owner_user_id, conversationId: row.conversation_id, threadId: row.thread_id, lastTraceId: row.last_trace_id, session: parseJson(row.session_json, {}), updatedAt: row.updated_at } : null; }
|
||||
function jobParams(job) { return [job.id, job.devicePodId, job.ownerUserId, job.status, job.intent, stableJson(job.args), job.reason, job.traceId, job.operationId, stableJson(job.output), stableJson(job.blocker ?? {}), job.createdAt, job.updatedAt, job.completedAt]; }
|
||||
function parseJson(value, fallback) { if (!value) return fallback; if (typeof value === "object") return value; try { return JSON.parse(String(value)); } catch { return fallback; } }
|
||||
function normalizeBlocker(value) { return value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0 ? value : null; }
|
||||
|
||||
async function fetchJsonWithTimeout(fetchImpl, url, options, timeoutMs) {
|
||||
const controller = new AbortController();
|
||||
|
||||
Reference in New Issue
Block a user