fix(daemon): preserve Orbit no-artifact explanation (#1576)

This commit is contained in:
Yuhao Chen 2026-05-13 22:13:18 +08:00 committed by GitHub
parent 38a5ab69e6
commit bd0ac2c703
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 130 additions and 1 deletions

View file

@ -0,0 +1,39 @@
const NO_LIVE_ARTIFACT_SUMMARY =
'Agent succeeded but did not register a live artifact for this Orbit run.';
const MAX_FINAL_EXPLANATION_CHARS = 2_000;
interface RunEventRecord {
event?: unknown;
data?: unknown;
}
function asObject(value: unknown): Record<string, unknown> | null {
if (!value || typeof value !== 'object') return null;
return value as Record<string, unknown>;
}
function textDeltaFromEvent(record: RunEventRecord): string | null {
if (record.event !== 'agent') return null;
const data = asObject(record.data);
if (!data || data.type !== 'text_delta') return null;
return typeof data.delta === 'string' ? data.delta : null;
}
export function extractOrbitAgentFinalExplanation(events: readonly RunEventRecord[]): string | null {
const text = events
.map(textDeltaFromEvent)
.filter((delta): delta is string => delta !== null)
.join('')
.trim();
if (!text) return null;
if (text.length <= MAX_FINAL_EXPLANATION_CHARS) return text;
return `${text.slice(0, MAX_FINAL_EXPLANATION_CHARS).trimEnd()}...`;
}
export function buildOrbitNoLiveArtifactSummary(events: readonly RunEventRecord[]): string {
const explanation = extractOrbitAgentFinalExplanation(events);
return explanation
? `${NO_LIVE_ARTIFACT_SUMMARY}\n\n${explanation}`
: NO_LIVE_ARTIFACT_SUMMARY;
}

View file

@ -155,6 +155,7 @@ import {
} from './mcp-tokens.js';
import { agentCliEnvForAgent, readAppConfig, writeAppConfig } from './app-config.js';
import { OrbitService, formatLocalProjectTimestamp, renderOrbitTemplateSystemPrompt } from './orbit.js';
import { buildOrbitNoLiveArtifactSummary } from './orbit-agent-summary.js';
import {
RoutineService,
validateSchedule as validateRoutineSchedule,
@ -4657,7 +4658,9 @@ export async function startServer({
...(artifact?.id ? { artifactId: artifact.id, artifactProjectId: projectId } : {}),
summary: artifact?.id
? `Agent ${finalStatus.status} and registered live artifact ${artifact.title}.`
: `Agent ${finalStatus.status} but did not register a live artifact for this Orbit run.`,
: finalStatus.status === 'succeeded'
? buildOrbitNoLiveArtifactSummary(run.events)
: `Agent ${finalStatus.status} but did not register a live artifact for this Orbit run.`,
};
})();

View file

@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest';
import {
buildOrbitNoLiveArtifactSummary,
extractOrbitAgentFinalExplanation,
} from '../src/orbit-agent-summary.js';
describe('Orbit agent summary helpers', () => {
it('preserves the agent final explanation for no-live-artifact Orbit runs', () => {
const summary = buildOrbitNoLiveArtifactSummary([
{ event: 'agent', data: { type: 'text_delta', delta: 'Data loading failed, ' } },
{ event: 'agent', data: { type: 'text_delta', delta: 'so I did not create a daily digest artifact.' } },
]);
expect(summary).toContain(
'Agent succeeded but did not register a live artifact for this Orbit run.',
);
expect(summary).toContain(
'Data loading failed, so I did not create a daily digest artifact.',
);
});
it('extracts only user-visible text deltas from run events', () => {
expect(
extractOrbitAgentFinalExplanation([
{ event: 'stdout', data: { chunk: 'raw tool output' } },
{ event: 'stderr', data: { chunk: 'OPENAI_API_KEY=sk-raw-secret' } },
{ event: 'tool_result', data: { output: 'token=raw-tool-secret' } },
{ event: 'agent', data: { type: 'thinking_delta', delta: 'private reasoning' } },
{ event: 'agent', data: { type: 'tool_use', name: 'Read' } },
{ event: 'agent', data: { type: 'text_delta', delta: 'GitHub auth failed.' } },
]),
).toBe('GitHub auth failed.');
});
it('falls back to the implementation-level no-artifact marker without final text', () => {
expect(buildOrbitNoLiveArtifactSummary([])).toBe(
'Agent succeeded but did not register a live artifact for this Orbit run.',
);
});
it('bounds long final explanations before storing them in the Orbit receipt', () => {
const explanation = extractOrbitAgentFinalExplanation([
{ event: 'agent', data: { type: 'text_delta', delta: 'x'.repeat(2_100) } },
]);
expect(explanation).toHaveLength(2_003);
expect(explanation?.endsWith('...')).toBe(true);
});
});

View file

@ -277,6 +277,43 @@ describe('OrbitService', () => {
}
});
it('persists failed Orbit agent summaries in the last-run receipt markdown', async () => {
const dataDir = await mkdtemp(path.join(os.tmpdir(), 'orbit-test-'));
try {
const service = new OrbitService(dataDir);
service.setRunHandler(async () => ({
projectId: 'project-1',
agentRunId: 'agent-1',
completion: Promise.resolve({
agentRunId: 'agent-1',
status: 'failed',
summary:
'Agent succeeded but did not register a live artifact for this Orbit run.\n\nGitHub auth failed, so I did not create a daily digest artifact.',
}),
}));
await service.start('manual');
let status = await service.status();
for (let attempt = 0; attempt < 10 && (status.running || !status.lastRun); attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, 0));
status = await service.status();
}
expect(status.lastRun).not.toBeNull();
expect(status.running).toBe(false);
expect(status.lastRun?.connectorsSucceeded).toBe(0);
expect(status.lastRun?.connectorsFailed).toBe(1);
expect(status.lastRun?.markdown).toContain(
'Agent succeeded but did not register a live artifact for this Orbit run.',
);
expect(status.lastRun?.markdown).toContain(
'GitHub auth failed, so I did not create a daily digest artifact.',
);
} finally {
await rm(dataDir, { recursive: true, force: true });
}
});
it('tracks the most recent run per template alongside the global last run', async () => {
vi.useFakeTimers();
const dataDir = await mkdtemp(path.join(os.tmpdir(), 'orbit-test-'));