@inflexa-ai/harness 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/agents/conversation-agent.d.ts +3 -0
- package/dist/agents/conversation-agent.js +2 -2
- package/dist/agents/sandbox/shared.d.ts +3 -0
- package/dist/agents/sandbox/shared.js +2 -2
- package/dist/app/chat-turn.d.ts +3 -0
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +3 -1
- package/dist/execution/artifact-metadata.d.ts +3 -0
- package/dist/execution/artifact-metadata.js +8 -2
- package/dist/execution/artifact-registration.d.ts +2 -1
- package/dist/execution/artifact-registration.js +8 -2
- package/dist/execution/ephemeral-runner.d.ts +3 -0
- package/dist/execution/ephemeral-runner.js +8 -4
- package/dist/execution/post-step-pipeline.d.ts +3 -0
- package/dist/execution/post-step-pipeline.js +24 -4
- package/dist/execution/reconcile-manifest.d.ts +7 -0
- package/dist/execution/reconcile-manifest.js +19 -5
- package/dist/execution/report-runner.d.ts +3 -0
- package/dist/execution/report-runner.js +4 -4
- package/dist/execution/step-summary.d.ts +3 -0
- package/dist/execution/step-summary.js +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/lib/chrome.d.ts +4 -1
- package/dist/lib/chrome.js +10 -7
- package/dist/lib/console-logger.d.ts +19 -0
- package/dist/lib/console-logger.js +62 -0
- package/dist/lib/logger.d.ts +88 -0
- package/dist/lib/logger.js +37 -0
- package/dist/lib/otel.d.ts +2 -1
- package/dist/lib/otel.js +8 -3
- package/dist/runtime/boot.d.ts +2 -2
- package/dist/runtime/boot.js +1 -1
- package/dist/runtime/connection-budget.d.ts +2 -2
- package/dist/runtime/connection-budget.js +5 -3
- package/dist/runtime/dbos.d.ts +7 -7
- package/dist/runtime/dbos.js +14 -11
- package/dist/runtime/shutdown.d.ts +2 -2
- package/dist/runtime/shutdown.js +4 -3
- package/dist/sandbox/create-sandbox.d.ts +2 -2
- package/dist/sandbox/docker-client.d.ts +2 -2
- package/dist/sandbox/docker-client.js +4 -1
- package/dist/sandbox/k8s-client.d.ts +3 -0
- package/dist/sandbox/k8s-client.js +5 -4
- package/dist/sandbox/notification-sweep.d.ts +3 -3
- package/dist/sandbox/notification-sweep.js +4 -2
- package/dist/sandbox/reaper.d.ts +3 -3
- package/dist/sandbox/reaper.js +7 -4
- package/dist/sandbox/watchdog.d.ts +3 -3
- package/dist/sandbox/watchdog.js +8 -2
- package/dist/state/init.d.ts +2 -1
- package/dist/state/init.js +5 -6
- package/dist/tasks/data-profile.d.ts +5 -0
- package/dist/tasks/data-profile.js +24 -17
- package/dist/tools/bio/search-dgidb.d.ts +11 -1
- package/dist/tools/bio/search-dgidb.js +92 -78
- package/dist/tools/iterate-report.d.ts +3 -0
- package/dist/tools/iterate-report.js +3 -1
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +2 -2
- package/dist/tools/workspace/execute-command.d.ts +3 -0
- package/dist/tools/workspace/execute-command.js +3 -1
- package/dist/workflows/execute-analysis.d.ts +3 -0
- package/dist/workflows/execute-analysis.js +11 -7
- package/dist/workflows/execute-target-assessment.d.ts +3 -0
- package/dist/workflows/execute-target-assessment.js +19 -15
- package/dist/workflows/sandbox-step.d.ts +9 -0
- package/dist/workflows/sandbox-step.js +39 -17
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
- package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
- package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
- package/dist/workflows/target-assessment/phase5-persist.js +8 -3
- package/dist/workflows/target-assessment/progress.d.ts +2 -1
- package/dist/workflows/target-assessment/progress.js +4 -3
- package/dist/workspace/filesystem.js +163 -74
- package/package.json +95 -96
package/README.md
CHANGED
|
@@ -37,6 +37,9 @@ import {
|
|
|
37
37
|
createNoopArtifactRegistry, // ArtifactRegistry seam
|
|
38
38
|
UnavailablePreviewPublisher, // PreviewPublisher seam
|
|
39
39
|
createDbosRunLauncher, // shared RunLauncher
|
|
40
|
+
createConsoleLogger, // Logger — writes to console
|
|
41
|
+
createNoopLogger, // Logger — discards (the fallback when none is wired)
|
|
42
|
+
defaultErrorFields, // the shipped `Logger.errorFields` mapping
|
|
40
43
|
defineTool,
|
|
41
44
|
runAgent,
|
|
42
45
|
makeLocalAuth,
|
|
@@ -45,6 +48,51 @@ import {
|
|
|
45
48
|
|
|
46
49
|
`assembleCoreRuntime` is the single composition point: it registers the durable workflows and builds the conversation agent over them. The harness declares **five capability seams** an embedder wires — `RunAuthorizer`, `ResolveBilling`, `ArtifactRegistry`, `RunCharge`, `PreviewPublisher` — plus the **shared `RunLauncher`** (one host-neutral realization, `createDbosRunLauncher`). Local, dependency-free realizations of all five ship from the barrel; an embedder constructs them (or its own) and passes them in. The harness only ever sees the interface.
|
|
47
50
|
|
|
51
|
+
## Logging
|
|
52
|
+
|
|
53
|
+
The harness names no logging library. It logs through a `Logger` you supply, so the destination, format, and verbosity stay yours:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
export interface Logger {
|
|
57
|
+
debug(msg: string, fields?: LogFields): void;
|
|
58
|
+
info(msg: string, fields?: LogFields): void;
|
|
59
|
+
warn(msg: string, fields?: LogFields): void;
|
|
60
|
+
error(msg: string, fields?: LogFields): void;
|
|
61
|
+
with(fields: LogFields): Logger; // bind context onto every record (slog's `With`)
|
|
62
|
+
named(name: string): Logger; // bind a namespace, rendered as a `[a.b]` message prefix
|
|
63
|
+
errorFields(err: unknown): LogFields; // normalize a thrown value; `defaultErrorFields` is the shipped mapping
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pass `createConsoleLogger()` if you have no logging infrastructure. Wire nothing and the harness falls back to `createNoopLogger()` — it never falls back to `console`, because a host whose UI owns stdout (an alternate-screen TUI, say) discards console output, and a diagnostic written to a discarded stream is worse than no diagnostic at all.
|
|
68
|
+
|
|
69
|
+
`errorFields` is on the interface, not a helper we own, so your realization can defer to your sink's native error handling rather than a shape we imposed.
|
|
70
|
+
|
|
71
|
+
**Migrating from a version that took a `pino.Logger`** — `bootHarness` and the deps bags now take this interface. pino is object-first where this is message-first, so pass an adapter:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import type { LogFields, Logger } from "@inflexa-ai/harness";
|
|
75
|
+
import type pino from "pino";
|
|
76
|
+
|
|
77
|
+
function pinoAsHarnessLogger(p: pino.Logger, names: readonly string[] = []): Logger {
|
|
78
|
+
const prefixed = (msg: string): string => (names.length > 0 ? `[${names.join(".")}] ${msg}` : msg);
|
|
79
|
+
const emit =
|
|
80
|
+
(level: "debug" | "info" | "warn" | "error") =>
|
|
81
|
+
(msg: string, fields?: LogFields): void => {
|
|
82
|
+
p[level](fields ?? {}, prefixed(msg));
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
debug: emit("debug"),
|
|
86
|
+
info: emit("info"),
|
|
87
|
+
warn: emit("warn"),
|
|
88
|
+
error: emit("error"),
|
|
89
|
+
with: (fields) => pinoAsHarnessLogger(p.child(fields), names),
|
|
90
|
+
named: (name) => pinoAsHarnessLogger(p, [...names, name]),
|
|
91
|
+
errorFields: (err) => ({ err }), // let pino's own `err` serializer render it
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
48
96
|
## How it executes
|
|
49
97
|
|
|
50
98
|
Chat is a plain in-process turn (`prepareChatTurn` → `runAgent` → `appendTurn`); the harness ships no HTTP layer, so the host owns the route. Compute-heavy work runs as **durable workflows** — `executeAnalysis` starts a child workflow per plan step once its dependencies have completed, and each step drives a sandbox agent inside a container. The same `runAgent` primitive runs in both modes; durability and the event sink are injected, so the loop body is identical.
|
|
@@ -41,10 +41,13 @@ import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
|
41
41
|
import type { RunLauncher } from "../execution/run-launcher.js";
|
|
42
42
|
import { type SubmitReportDeps } from "../tools/iterate-report.js";
|
|
43
43
|
import type { EphemeralWorkflowInput, EphemeralResult } from "../execution/ephemeral-runner.js";
|
|
44
|
+
import type { Logger } from "../lib/logger.js";
|
|
44
45
|
/** Canonical agent id — the single source of truth. */
|
|
45
46
|
export declare const CONVERSATION_AGENT_ID: "conversation-agent";
|
|
46
47
|
/** The shared dependencies the composition root explodes apart. */
|
|
47
48
|
export interface ConversationAgentDeps {
|
|
49
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
50
|
+
readonly logger?: Logger;
|
|
48
51
|
/** The LLM seam every loop-driving tool runs its sub-agent on. */
|
|
49
52
|
readonly provider: ChatProvider;
|
|
50
53
|
/** Postgres pool — plan persistence, run inspection, workspace index, working memory. */
|
|
@@ -32,7 +32,7 @@ import { createWorkingMemory } from "../memory/working-memory.js";
|
|
|
32
32
|
import { conversationPrompt } from "../prompts/conversation.js";
|
|
33
33
|
import { composeSystemPrompt } from "./system-prompt.js";
|
|
34
34
|
// Bio-lookup leaf tools (pure — no dependencies).
|
|
35
|
-
import { searchGeneTool, searchPathwayTool, lookupGoTermTool, searchInteractionsTool, chemblTool, pubchemTool, openTargetsTool, searchPharmgkbTool, searchFaersTool, searchClinicalTrialsTool, searchGeoDatasetsTool,
|
|
35
|
+
import { searchGeneTool, searchPathwayTool, lookupGoTermTool, searchInteractionsTool, chemblTool, pubchemTool, openTargetsTool, searchPharmgkbTool, searchFaersTool, searchClinicalTrialsTool, searchGeoDatasetsTool, createSearchDgidbTool, searchBgeeExpressionTool, getImpcKoProfileTool, checkSafetyPanelTool, } from "../tools/bio/index.js";
|
|
36
36
|
import { createNcbiTools, createChemDbTools } from "../tools/bio/keys.js";
|
|
37
37
|
// Dependency-bearing tool factories.
|
|
38
38
|
import { createGeneratePlanTool, createLiteratureReviewerTool, createInspectRunTool, createInspectDataProfileTool, createGenerateAnalogyReportTool, } from "../tools/research/index.js";
|
|
@@ -70,7 +70,7 @@ export function createConversationAgent(deps) {
|
|
|
70
70
|
searchFaersTool,
|
|
71
71
|
searchClinicalTrialsTool,
|
|
72
72
|
searchGeoDatasetsTool,
|
|
73
|
-
|
|
73
|
+
createSearchDgidbTool({ ...(deps.logger ? { logger: deps.logger } : {}) }),
|
|
74
74
|
// Preclinical.
|
|
75
75
|
searchBgeeExpressionTool,
|
|
76
76
|
getImpcKoProfileTool,
|
|
@@ -25,6 +25,7 @@ import type { ProvenanceCollector as LineageCollector } from "../../provenance/c
|
|
|
25
25
|
import { type BioToolKeys } from "../../tools/bio/keys.js";
|
|
26
26
|
import { type BlockerHolder } from "../../tools/sandbox/report-blocker.js";
|
|
27
27
|
import type { AgentMeta, SandboxToolName } from "./types.js";
|
|
28
|
+
import type { Logger } from "../../lib/logger.js";
|
|
28
29
|
/**
|
|
29
30
|
* Tools every sandbox agent receives — sandbox-environment introspection,
|
|
30
31
|
* library docs, and run inspection. Spread into each agent's
|
|
@@ -50,6 +51,8 @@ export interface SandboxStepCoords {
|
|
|
50
51
|
}
|
|
51
52
|
/** The shared dependency graph every sandbox agent draws from. */
|
|
52
53
|
export interface SandboxAgentDeps {
|
|
54
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
55
|
+
readonly logger?: Logger;
|
|
53
56
|
readonly provider: ChatProvider;
|
|
54
57
|
readonly pool: Pool;
|
|
55
58
|
readonly sandboxClient: SandboxClient;
|
|
@@ -26,7 +26,7 @@ import { createInspectRunTool } from "../../tools/research/inspect-run.js";
|
|
|
26
26
|
// Data-profile retrieval (dependency-bearing) — always-on, see `createSandboxAgent`.
|
|
27
27
|
import { createInspectDataProfileTool } from "../../tools/research/inspect-data-profile.js";
|
|
28
28
|
// Bio leaf tools.
|
|
29
|
-
import { checkSafetyPanelTool, chemblTool, getImpcKoProfileTool, lookupGoTermTool, openTargetsTool, pubchemTool, searchBgeeExpressionTool, searchClinicalTrialsTool,
|
|
29
|
+
import { checkSafetyPanelTool, chemblTool, getImpcKoProfileTool, lookupGoTermTool, openTargetsTool, pubchemTool, searchBgeeExpressionTool, searchClinicalTrialsTool, createSearchDgidbTool, searchFaersTool, searchGeneTool, searchGeoDatasetsTool, searchGwasCatalogTool, searchInteractionsTool, searchPathwayTool, searchPharmgkbTool, } from "../../tools/bio/index.js";
|
|
30
30
|
import { createNcbiTools, createChemDbTools } from "../../tools/bio/keys.js";
|
|
31
31
|
// Workspace read surface.
|
|
32
32
|
import { createFileStatTool, createGrepTool, createListFilesTool, createReadFileTool, createWorkspaceSearchTool } from "../../tools/workspace/index.js";
|
|
@@ -80,7 +80,7 @@ function resolveSandboxTools(deps, tools) {
|
|
|
80
80
|
searchClinicalTrials: searchClinicalTrialsTool,
|
|
81
81
|
searchGeoDatasets: searchGeoDatasetsTool,
|
|
82
82
|
searchClinvar: ncbi.searchClinvar,
|
|
83
|
-
searchDgidb:
|
|
83
|
+
searchDgidb: createSearchDgidbTool({ ...(deps.logger ? { logger: deps.logger } : {}) }),
|
|
84
84
|
searchGwasCatalog: searchGwasCatalogTool,
|
|
85
85
|
searchDisgenet: chemDb.searchDisgenet,
|
|
86
86
|
searchDrugbank: chemDb.searchDrugbank,
|
package/dist/app/chat-turn.d.ts
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { Pool } from "pg";
|
|
16
16
|
import { type AssembledMessages } from "./message-assembly.js";
|
|
17
|
+
import type { Logger } from "../lib/logger.js";
|
|
17
18
|
export interface PrepareChatTurnDeps {
|
|
19
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
20
|
+
readonly logger?: Logger;
|
|
18
21
|
readonly pool: Pool;
|
|
19
22
|
}
|
|
20
23
|
export interface PrepareChatTurnParams {
|
package/dist/app/chat-turn.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createThreadStore } from "../memory/thread-store.js";
|
|
|
19
19
|
import { createWorkingMemory } from "../memory/working-memory.js";
|
|
20
20
|
import { loadAnalysisStatus } from "../state/index.js";
|
|
21
21
|
import { assembleMessages } from "./message-assembly.js";
|
|
22
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
22
23
|
/**
|
|
23
24
|
* Prepare one chat turn: resolve thread ownership, seed the title, load
|
|
24
25
|
* analysis status, and assemble the message array. Input sanitization stays
|
|
@@ -48,7 +49,8 @@ export async function prepareChatTurn(deps, params) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
catch (err) {
|
|
51
|
-
|
|
52
|
+
const logger = (deps.logger ?? createNoopLogger()).named("harness.chat");
|
|
53
|
+
logger.warn("title-seed failed (non-fatal)", logger.errorFields(err));
|
|
52
54
|
}
|
|
53
55
|
const analysisState = await loadAnalysisStatus(pool, analysisId).unwrapOr(null);
|
|
54
56
|
const history = createThreadHistory(pool);
|
|
@@ -22,7 +22,10 @@ import type { ChatProvider, EmbeddingProvider } from "../providers/types.js";
|
|
|
22
22
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
23
23
|
import type { RunFinding } from "../workflows/execute-analysis.js";
|
|
24
24
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
25
|
+
import type { Logger } from "../lib/logger.js";
|
|
25
26
|
export interface SynthesizeRunDeps {
|
|
27
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
28
|
+
readonly logger?: Logger;
|
|
26
29
|
readonly pool: Pool;
|
|
27
30
|
readonly provider: ChatProvider;
|
|
28
31
|
/** Write-side embedder — its `dimensions` sizes the per-analysis index. */
|
|
@@ -20,6 +20,7 @@ import { ensureSearchIndex, searchIndexName } from "../workspace/search-config.j
|
|
|
20
20
|
import { createVectorStore } from "../state/vector-store.js";
|
|
21
21
|
import { loadPlan, queryRun } from "../state/index.js";
|
|
22
22
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
23
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
23
24
|
export async function synthesizeRun(deps, params) {
|
|
24
25
|
const { pool, provider, embedding, resolveWorkspaceRoot, synthesisModel, bioKeys } = deps;
|
|
25
26
|
const { analysisId, runId, completedSteps, session, emit, onProgress } = params;
|
|
@@ -71,7 +72,8 @@ export async function synthesizeRun(deps, params) {
|
|
|
71
72
|
}));
|
|
72
73
|
}
|
|
73
74
|
catch (indexErr) {
|
|
74
|
-
|
|
75
|
+
const logger = (deps.logger ?? createNoopLogger()).named("synthesize-run");
|
|
76
|
+
logger.warn("vector indexing failed", { runId, ...logger.errorFields(indexErr) });
|
|
75
77
|
}
|
|
76
78
|
await onProgress("persisting", "Persisting synthesis");
|
|
77
79
|
await persistSynthesis({ workspaceRoot, runId, synthesis });
|
|
@@ -24,6 +24,7 @@ import type { AgentSession } from "../auth/types.js";
|
|
|
24
24
|
import type { LoopMessage } from "../loop/types.js";
|
|
25
25
|
import type { AgentChat } from "../providers/types.js";
|
|
26
26
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
27
|
+
import type { Logger } from "../lib/logger.js";
|
|
27
28
|
export interface ArtifactForMetadata {
|
|
28
29
|
/** Path used to update `cortex_artifacts` after metadata generation. */
|
|
29
30
|
readonly dbPath: string;
|
|
@@ -33,6 +34,8 @@ export interface ArtifactForMetadata {
|
|
|
33
34
|
readonly sizeBytes?: number;
|
|
34
35
|
}
|
|
35
36
|
export interface GenerateFileMetadataOptions {
|
|
37
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
38
|
+
readonly logger?: Logger;
|
|
36
39
|
readonly provider: AgentChat;
|
|
37
40
|
readonly session: AgentSession;
|
|
38
41
|
readonly artifacts: readonly ArtifactForMetadata[];
|
|
@@ -29,6 +29,7 @@ import { createReadFileTool } from "../tools/workspace/read-file.js";
|
|
|
29
29
|
import { composeSystemPrompt } from "../agents/system-prompt.js";
|
|
30
30
|
import { inferArtifactType } from "../schemas/artifact-manifest.js";
|
|
31
31
|
import { SubmitFileMetadataInputSchema } from "../schemas/file-metadata.js";
|
|
32
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
32
33
|
/** Sub-agent identity for the describer loop — provenance only. */
|
|
33
34
|
const DESCRIBER_AGENT_ID = "file-metadata-describer";
|
|
34
35
|
/**
|
|
@@ -156,6 +157,7 @@ function fallbackEntry(artifact, extra) {
|
|
|
156
157
|
* result always carries exactly one entry per input artifact.
|
|
157
158
|
*/
|
|
158
159
|
export async function generateFileMetadata(opts) {
|
|
160
|
+
const logger = (opts.logger ?? createNoopLogger()).named("artifact-metadata").with({ resourceId: opts.resourceId });
|
|
159
161
|
if (opts.artifacts.length === 0) {
|
|
160
162
|
return { indexed: 0, entries: [] };
|
|
161
163
|
}
|
|
@@ -183,7 +185,7 @@ export async function generateFileMetadata(opts) {
|
|
|
183
185
|
});
|
|
184
186
|
}
|
|
185
187
|
catch (err) {
|
|
186
|
-
|
|
188
|
+
logger.warn("describer loop failed; using fallbacks", logger.errorFields(err));
|
|
187
189
|
}
|
|
188
190
|
const entries = [];
|
|
189
191
|
const fallbackPaths = [];
|
|
@@ -198,7 +200,11 @@ export async function generateFileMetadata(opts) {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
if (fallbackPaths.length > 0) {
|
|
201
|
-
|
|
203
|
+
logger.warn("file(s) used a deterministic fallback description", {
|
|
204
|
+
fallbackCount: fallbackPaths.length,
|
|
205
|
+
artifactCount: opts.artifacts.length,
|
|
206
|
+
fallbackPaths,
|
|
207
|
+
});
|
|
202
208
|
}
|
|
203
209
|
return { indexed: holder.size, entries };
|
|
204
210
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { Pool, PoolClient } from "pg";
|
|
13
13
|
import type { AgentSession } from "../auth/types.js";
|
|
14
|
+
import type { Logger } from "../lib/logger.js";
|
|
14
15
|
import type { ArtifactRegistry, ArtifactRegistrationInput } from "./artifact-registry.js";
|
|
15
16
|
export type { ArtifactRegistrationInput };
|
|
16
17
|
export interface ArtifactRegistrationResult {
|
|
@@ -33,4 +34,4 @@ export interface ArtifactRegistrationResult {
|
|
|
33
34
|
* delegates external provenance registration to the injected `ArtifactRegistry`
|
|
34
35
|
* and applies any returned external ids back onto the local rows.
|
|
35
36
|
*/
|
|
36
|
-
export declare function registerStepArtifacts(db: Pool | PoolClient, registry: ArtifactRegistry, input: ArtifactRegistrationInput, session: AgentSession): Promise<ArtifactRegistrationResult>;
|
|
37
|
+
export declare function registerStepArtifacts(db: Pool | PoolClient, registry: ArtifactRegistry, input: ArtifactRegistrationInput, session: AgentSession, logger?: Logger): Promise<ArtifactRegistrationResult>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* (an external provenance ledger, or a filesystem index) is a composition-root
|
|
10
10
|
* decision — this module never names one.
|
|
11
11
|
*/
|
|
12
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
12
13
|
import { upsertArtifacts, updateArtifactId } from "../state/index.js";
|
|
13
14
|
// ── Registration ─────────────────────────────────────────────────────
|
|
14
15
|
/**
|
|
@@ -18,8 +19,13 @@ import { upsertArtifacts, updateArtifactId } from "../state/index.js";
|
|
|
18
19
|
* delegates external provenance registration to the injected `ArtifactRegistry`
|
|
19
20
|
* and applies any returned external ids back onto the local rows.
|
|
20
21
|
*/
|
|
21
|
-
export async function registerStepArtifacts(db, registry, input, session
|
|
22
|
+
export async function registerStepArtifacts(db, registry, input, session,
|
|
23
|
+
// A parameter rather than a field on `ArtifactRegistrationInput`: that type is
|
|
24
|
+
// the seam's payload, handed verbatim to `registry.register`, and an
|
|
25
|
+
// embedder's registry has no business receiving the host's logger.
|
|
26
|
+
logger = createNoopLogger()) {
|
|
22
27
|
const { resourceId, runId, stepId, artifacts } = input;
|
|
28
|
+
const log = logger.named("artifact-registration").with({ runId, stepId });
|
|
23
29
|
if (artifacts.length === 0) {
|
|
24
30
|
return { localCount: 0, externalRegistered: 0, externalFailed: 0, failureDetails: [] };
|
|
25
31
|
}
|
|
@@ -27,7 +33,7 @@ export async function registerStepArtifacts(db, registry, input, session) {
|
|
|
27
33
|
const emptyHash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
28
34
|
const localEntries = artifacts.map((a) => {
|
|
29
35
|
if (a.size === 0 || a.hash === emptyHash) {
|
|
30
|
-
|
|
36
|
+
log.warn("empty artifact", { path: `${dbPathPrefix}${a.path}`, hash: a.hash?.slice(0, 16), size: a.size });
|
|
31
37
|
}
|
|
32
38
|
return {
|
|
33
39
|
resourceId,
|
|
@@ -29,12 +29,15 @@ import type { SandboxClient } from "../sandbox/client.js";
|
|
|
29
29
|
import type { BioToolKeys } from "../tools/bio/keys.js";
|
|
30
30
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
31
31
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
32
|
+
import type { Logger } from "../lib/logger.js";
|
|
32
33
|
/** Sub-agent identity — appended to `callPath`, set as `agentId`. */
|
|
33
34
|
export declare const EPHEMERAL_AGENT_ID = "ephemeral-executor";
|
|
34
35
|
/** Workflow-id prefix — the boot-time never-recover sweep matches on it. */
|
|
35
36
|
export declare const EPHEMERAL_WORKFLOW_PREFIX: "ephemeral:";
|
|
36
37
|
/** The body's construction-time deps — closed over at registration. */
|
|
37
38
|
export interface EphemeralDeps {
|
|
39
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
40
|
+
readonly logger?: Logger;
|
|
38
41
|
readonly provider: ChatProvider;
|
|
39
42
|
readonly pool: Pool;
|
|
40
43
|
readonly sandboxClient: SandboxClient;
|
|
@@ -28,6 +28,7 @@ import { durableStep } from "../loop/run-step.js";
|
|
|
28
28
|
import { generateExecutionId } from "../sandbox/execution-id.js";
|
|
29
29
|
import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
30
30
|
import { createEphemeralExecutorAgent } from "../agents/sandbox/ephemeral-executor.js";
|
|
31
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
31
32
|
/** Sub-agent identity — appended to `callPath`, set as `agentId`. */
|
|
32
33
|
export const EPHEMERAL_AGENT_ID = "ephemeral-executor";
|
|
33
34
|
/**
|
|
@@ -85,13 +86,16 @@ export async function runEphemeralBody(input, deps) {
|
|
|
85
86
|
throw new Error(`runEphemeralBody requires an analysis-scoped session — got ${runSession.scope.kind}`);
|
|
86
87
|
}
|
|
87
88
|
const analysisId = runSession.scope.analysisId;
|
|
89
|
+
// Bound after the scope guard: `analysisId` comes off the session's scope,
|
|
90
|
+
// not the workflow input.
|
|
91
|
+
const logger = (deps.logger ?? createNoopLogger()).named("ephemeral").with({ analysisId });
|
|
88
92
|
const startedAt = performance.now();
|
|
89
93
|
const executionId = generateExecutionId(EPHEMERAL_AGENT_ID);
|
|
90
94
|
const workflowId = DBOS.workflowID ?? `${EPHEMERAL_WORKFLOW_PREFIX}${executionId}`;
|
|
91
95
|
const childSession = forSubAgent(runSession, EPHEMERAL_AGENT_ID);
|
|
92
96
|
// Resolver throw = loud workflow failure, per the workspace-root-resolution contract.
|
|
93
97
|
const workspaceRoot = deps.resolveWorkspaceRoot(analysisId);
|
|
94
|
-
|
|
98
|
+
logger.info("starting sandbox", { executionId });
|
|
95
99
|
const sandbox = await deps.sandboxClient.createSandbox({
|
|
96
100
|
runId: EPHEMERAL_RUN_LITERAL,
|
|
97
101
|
stepId: EPHEMERAL_RUN_LITERAL,
|
|
@@ -148,12 +152,12 @@ export async function runEphemeralBody(input, deps) {
|
|
|
148
152
|
const text = finalText(finalMessages);
|
|
149
153
|
const stepsUsed = countAssistantTurns(finalMessages);
|
|
150
154
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
151
|
-
|
|
155
|
+
logger.info("completed", { executionId, stepsUsed, durationMs });
|
|
152
156
|
return { text, stepsUsed, durationMs };
|
|
153
157
|
}
|
|
154
158
|
catch (err) {
|
|
155
159
|
const message = err instanceof Error ? err.message : String(err);
|
|
156
|
-
|
|
160
|
+
logger.error("failed", { executionId, err: message });
|
|
157
161
|
throw err;
|
|
158
162
|
}
|
|
159
163
|
finally {
|
|
@@ -163,7 +167,7 @@ export async function runEphemeralBody(input, deps) {
|
|
|
163
167
|
catch (teardownErr) {
|
|
164
168
|
// Teardown is idempotent — "already gone" is success. A failure here
|
|
165
169
|
// is best-effort logged so the chat turn doesn't fail on cleanup.
|
|
166
|
-
|
|
170
|
+
logger.warn("teardown failed (non-fatal)", { executionId, ...logger.errorFields(teardownErr) });
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
}
|
|
@@ -16,6 +16,7 @@ import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
|
16
16
|
import type { ResolveWorkspaceRoot } from "../workspace/paths.js";
|
|
17
17
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
18
18
|
import type { StepSummary } from "../schemas/step-summary.js";
|
|
19
|
+
import type { Logger } from "../lib/logger.js";
|
|
19
20
|
import type { ArtifactRegistry } from "./artifact-registry.js";
|
|
20
21
|
import { type FileMetadataEntry } from "./artifact-metadata.js";
|
|
21
22
|
import type { PostStepArtifacts, PostStepContext, StepOutputs } from "../workflows/sandbox-step.js";
|
|
@@ -25,6 +26,8 @@ import type { PostStepArtifacts, PostStepContext, StepOutputs } from "../workflo
|
|
|
25
26
|
*/
|
|
26
27
|
export interface PostStepPipelineDeps {
|
|
27
28
|
readonly pool: Pool;
|
|
29
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
30
|
+
readonly logger?: Logger;
|
|
28
31
|
/** Non-streaming chat — the metadata + summary sub-agent loops. */
|
|
29
32
|
readonly provider: AgentChat;
|
|
30
33
|
/** Write-side embedder for the vector index. */
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
15
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
15
16
|
import { writeFileWithinRoot } from "../lib/fs-helpers.js";
|
|
16
17
|
import { registerStepArtifacts } from "./artifact-registration.js";
|
|
17
18
|
import { reconcileManifestWithDisk } from "./reconcile-manifest.js";
|
|
@@ -79,7 +80,12 @@ export async function generateStepSummaryAndWrite(deps, postCtx, manifest) {
|
|
|
79
80
|
await writeFileWithinRoot(writePrefix, join(writePrefix, "output", "summary.md"), summary.markdown);
|
|
80
81
|
}
|
|
81
82
|
catch (err) {
|
|
82
|
-
|
|
83
|
+
const logger = (deps.logger ?? createNoopLogger()).named("post-step").named("summary");
|
|
84
|
+
logger.warn("writeFile output/summary.md failed", {
|
|
85
|
+
runId: input.runId,
|
|
86
|
+
stepId: input.stepId,
|
|
87
|
+
...logger.errorFields(err),
|
|
88
|
+
});
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
return summary;
|
|
@@ -102,6 +108,7 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
102
108
|
agentId: input.agentId,
|
|
103
109
|
manifest: [...manifest],
|
|
104
110
|
collector: lineageCollector,
|
|
111
|
+
...(deps.logger ? { logger: deps.logger } : {}),
|
|
105
112
|
});
|
|
106
113
|
if (reconciled.manifest.length === 0)
|
|
107
114
|
return reconciled.manifest;
|
|
@@ -111,7 +118,7 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
111
118
|
stepId: input.stepId,
|
|
112
119
|
artifacts: reconciled.manifest,
|
|
113
120
|
collector: lineageCollector,
|
|
114
|
-
}, session);
|
|
121
|
+
}, session, deps.logger);
|
|
115
122
|
if (reg.externalFailed > 0) {
|
|
116
123
|
const wholeActivityFailed = reg.externalRegistered === 0;
|
|
117
124
|
const detail = reg.failureDetails.map((f) => `${f.path}: ${f.error}`).join("\n ");
|
|
@@ -119,7 +126,19 @@ export async function reconcileAndRegisterStepArtifacts(deps, postCtx, manifest)
|
|
|
119
126
|
`${reg.externalFailed} row(s) rejected, ${reg.externalRegistered}/${reg.localCount} local artifact(s) registered` +
|
|
120
127
|
(wholeActivityFailed ? " (WHOLE ACTIVITY ROLLED BACK — outputs orphaned)" : "") +
|
|
121
128
|
(detail ? `\n ${detail}` : "");
|
|
122
|
-
|
|
129
|
+
// Logged as fields as well as thrown: the throw reaches `failStep` as one
|
|
130
|
+
// opaque string, so the per-path rejections are only queryable from here.
|
|
131
|
+
// This is what distinguishes a registry rejection from the attestation
|
|
132
|
+
// throws in `reconcileManifestWithDisk` when a step dies.
|
|
133
|
+
(deps.logger ?? createNoopLogger()).named("post-step").named("reconcile").error("external registration failed", {
|
|
134
|
+
runId: input.runId,
|
|
135
|
+
stepId: input.stepId,
|
|
136
|
+
externalFailed: reg.externalFailed,
|
|
137
|
+
externalRegistered: reg.externalRegistered,
|
|
138
|
+
localCount: reg.localCount,
|
|
139
|
+
wholeActivityFailed,
|
|
140
|
+
failures: reg.failureDetails,
|
|
141
|
+
});
|
|
123
142
|
throw new Error(msg);
|
|
124
143
|
}
|
|
125
144
|
return reconciled.manifest;
|
|
@@ -176,7 +195,8 @@ export async function vectorIndexStepOutputs(deps, postCtx, artifacts) {
|
|
|
176
195
|
}
|
|
177
196
|
}
|
|
178
197
|
catch (err) {
|
|
179
|
-
|
|
198
|
+
const logger = (deps.logger ?? createNoopLogger()).named("post-step").named("vector-index");
|
|
199
|
+
logger.warn("indexing failed", { runId: input.runId, stepId: input.stepId, ...logger.errorFields(err) });
|
|
180
200
|
}
|
|
181
201
|
}
|
|
182
202
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ArtifactManifestEntry } from "../schemas/artifact-manifest.js";
|
|
2
2
|
import type { ProvenanceCollector } from "../provenance/collector.js";
|
|
3
|
+
import type { Logger } from "../lib/logger.js";
|
|
3
4
|
export interface ReconcileManifestInput {
|
|
4
5
|
/** Absolute host root of the analysis's workspace tree. */
|
|
5
6
|
workspaceRoot: string;
|
|
@@ -15,6 +16,12 @@ export interface ReconcileManifestInput {
|
|
|
15
16
|
manifest: ArtifactManifestEntry[];
|
|
16
17
|
/** Collector backing the manifest; mutated to reflect drops/rehashes. */
|
|
17
18
|
collector: ProvenanceCollector;
|
|
19
|
+
/**
|
|
20
|
+
* Operational logging seam; omitted falls back to no-op. Attestation is
|
|
21
|
+
* fail-fast, so what this records — which entry was dropped, which input
|
|
22
|
+
* could not be hashed — is the account of why a step died.
|
|
23
|
+
*/
|
|
24
|
+
logger?: Logger;
|
|
18
25
|
}
|
|
19
26
|
export interface ReconcileManifestResult {
|
|
20
27
|
/** Reconciled manifest — phantoms removed, every entry rehashed from disk. */
|
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { stat } from "node:fs/promises";
|
|
23
23
|
import path from "node:path";
|
|
24
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
24
25
|
import { computeSha256File, hasValidContentHash } from "../lib/fs-helpers.js";
|
|
25
26
|
import { artifactReconcileDropped } from "../lib/metrics.js";
|
|
26
27
|
export async function reconcileManifestWithDisk(input) {
|
|
27
28
|
const { workspaceRoot, resourceId, runId, stepId, agentId, manifest, collector } = input;
|
|
29
|
+
const logger = (input.logger ?? createNoopLogger()).named("reconcile-manifest").with({ runId, stepId, agentId });
|
|
28
30
|
const stepRoot = path.join(workspaceRoot, "runs", runId, stepId);
|
|
29
31
|
const reconciled = [];
|
|
30
32
|
let droppedCount = 0;
|
|
@@ -36,7 +38,7 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
36
38
|
// ENOENT and silently drop a real artifact, or — worse — succeed
|
|
37
39
|
// against a host file the step never produced.
|
|
38
40
|
if (!absPath.startsWith(stepRoot + path.sep) && absPath !== stepRoot) {
|
|
39
|
-
|
|
41
|
+
logger.warn("skipping out-of-bounds entry", { path: entry.path });
|
|
40
42
|
continue;
|
|
41
43
|
}
|
|
42
44
|
let info;
|
|
@@ -45,7 +47,7 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
45
47
|
}
|
|
46
48
|
catch (err) {
|
|
47
49
|
if (err.code === "ENOENT") {
|
|
48
|
-
|
|
50
|
+
logger.debug("dropping phantom entry", { path: entry.path });
|
|
49
51
|
collector.removeRecord(entry.path);
|
|
50
52
|
droppedCount++;
|
|
51
53
|
artifactReconcileDropped.add(1, { agent_id: agentId, step_id: stepId });
|
|
@@ -58,7 +60,7 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
58
60
|
// uses `createReadStream`, which throws `EISDIR` on directories and would
|
|
59
61
|
// fail the whole step's reconciliation.
|
|
60
62
|
if (!info.isFile()) {
|
|
61
|
-
|
|
63
|
+
logger.debug("dropping non-file entry", { path: entry.path });
|
|
62
64
|
collector.removeRecord(entry.path);
|
|
63
65
|
droppedCount++;
|
|
64
66
|
artifactReconcileDropped.add(1, { agent_id: agentId, step_id: stepId });
|
|
@@ -72,7 +74,7 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
72
74
|
const hash = await computeSha256File(absPath);
|
|
73
75
|
reconciled.push({ ...entry, hash, size: info.size });
|
|
74
76
|
}
|
|
75
|
-
await fillInputHashesFromDisk(collector, workspaceRoot, resourceId, runId, stepId);
|
|
77
|
+
await fillInputHashesFromDisk(collector, workspaceRoot, resourceId, runId, stepId, logger);
|
|
76
78
|
return { manifest: reconciled, droppedCount };
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
@@ -88,22 +90,31 @@ export async function reconcileManifestWithDisk(input) {
|
|
|
88
90
|
* Lineage attestation is fail-fast: an input we cannot hash (gone, or resolving
|
|
89
91
|
* outside the analysis tree) throws rather than registering a hashless edge.
|
|
90
92
|
*/
|
|
91
|
-
async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, runId, stepId) {
|
|
93
|
+
async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, runId, stepId, logger) {
|
|
92
94
|
const resourceRoot = path.resolve(workspaceRoot);
|
|
93
95
|
for (const ref of collector.getTrackedInputs()) {
|
|
94
96
|
if (ref.source === "artifacts")
|
|
95
97
|
continue;
|
|
96
98
|
if (hasValidContentHash(ref.hash))
|
|
97
99
|
continue;
|
|
100
|
+
// Each throw below is logged before it is raised. The thrown message reaches
|
|
101
|
+
// `failStep` as one opaque string and everything downstream of that is
|
|
102
|
+
// scrubbed, so naming the site and the offending ref HERE is what makes a
|
|
103
|
+
// fail-fast attestation distinguishable from a registry rejection after the
|
|
104
|
+
// fact — the read's `source` in particular says whether the step declared
|
|
105
|
+
// this input at all.
|
|
106
|
+
const attestation = { path: ref.path, source: ref.source, refRunId: ref.runId, refStepId: ref.stepId };
|
|
98
107
|
// `ref.path` is the absolute container path `/{resourceId}/...`; strip the
|
|
99
108
|
// mount segment and map the tail onto the host workspace root. `path.join`
|
|
100
109
|
// normalizes any `..`, and the bound confines the read to the analysis tree.
|
|
101
110
|
const containerPrefix = `/${resourceId}`;
|
|
102
111
|
if (ref.path !== containerPrefix && !ref.path.startsWith(containerPrefix + "/")) {
|
|
112
|
+
logger.error("input read resolves outside the analysis tree", { ...attestation, throwSite: "container-prefix-bound" });
|
|
103
113
|
throw new Error(`[reconcile-manifest] input read resolves outside the analysis tree: path=${ref.path} stepId=${stepId} runId=${runId}`);
|
|
104
114
|
}
|
|
105
115
|
const hostPath = path.join(resourceRoot, ref.path.slice(containerPrefix.length + 1));
|
|
106
116
|
if (hostPath !== resourceRoot && !hostPath.startsWith(resourceRoot + path.sep)) {
|
|
117
|
+
logger.error("input read resolves outside the analysis tree", { ...attestation, hostPath, throwSite: "workspace-root-bound" });
|
|
107
118
|
throw new Error(`[reconcile-manifest] input read resolves outside the analysis tree: path=${ref.path} stepId=${stepId} runId=${runId}`);
|
|
108
119
|
}
|
|
109
120
|
let info;
|
|
@@ -112,10 +123,12 @@ async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, run
|
|
|
112
123
|
}
|
|
113
124
|
catch (err) {
|
|
114
125
|
if (err.code === "ENOENT") {
|
|
126
|
+
logger.error("cannot attest input — not present at reconcile", { ...attestation, hostPath, throwSite: "input-enoent" });
|
|
115
127
|
throw new Error(`[reconcile-manifest] cannot attest input ${ref.path}: not present at reconcile (stepId=${stepId} runId=${runId})`, {
|
|
116
128
|
cause: err,
|
|
117
129
|
});
|
|
118
130
|
}
|
|
131
|
+
logger.error("cannot attest input — stat failed", { ...attestation, hostPath, throwSite: "input-stat", ...logger.errorFields(err) });
|
|
119
132
|
throw err;
|
|
120
133
|
}
|
|
121
134
|
if (!info.isFile()) {
|
|
@@ -124,6 +137,7 @@ async function fillInputHashesFromDisk(collector, workspaceRoot, resourceId, run
|
|
|
124
137
|
// it from lineage rather than failing the step — mirrors the output-side
|
|
125
138
|
// non-file drop above. A genuinely missing FILE (ENOENT) still fails fast
|
|
126
139
|
// as drift; a directory is not drift.
|
|
140
|
+
logger.debug("dropping non-file input from lineage", attestation);
|
|
127
141
|
collector.dropInput(ref);
|
|
128
142
|
continue;
|
|
129
143
|
}
|
|
@@ -37,7 +37,10 @@ import type { EmitFn } from "../loop/types.js";
|
|
|
37
37
|
import type { ChatProvider } from "../providers/types.js";
|
|
38
38
|
import type { PreviewPublisher } from "../tools/report/preview-publisher.js";
|
|
39
39
|
import type { ChromeConfig } from "../lib/chrome.js";
|
|
40
|
+
import type { Logger } from "../lib/logger.js";
|
|
40
41
|
export interface ReportRunnerDeps {
|
|
42
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
43
|
+
readonly logger?: Logger;
|
|
41
44
|
readonly provider: ChatProvider;
|
|
42
45
|
readonly pool: Pool;
|
|
43
46
|
/** Model id — provenance / metric label; provider owns the wire model. */
|
|
@@ -41,10 +41,12 @@ import { withPreviewLock } from "../tools/lib/preview-lock.js";
|
|
|
41
41
|
import { createBuildReportTool, createSubmitReportTool, createMintPreviewUrlTool, createPreviewSnapshotTool, createVersionFsTools, } from "../tools/report/index.js";
|
|
42
42
|
import { createReportBuilderAgent, REPORT_BUILDER_AGENT_ID } from "../agents/report-builder.js";
|
|
43
43
|
import { createSkillTools } from "../tools/sandbox/skills.js";
|
|
44
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
44
45
|
/** Skill pack the report-builder prompt directs the model to read (design-system reference). */
|
|
45
46
|
const REPORT_BUILDER_SKILLS = ["report-html"];
|
|
46
47
|
const REPORT_AGENT_MAX_STEPS = 75;
|
|
47
48
|
export async function runReportIteration(deps, opts) {
|
|
49
|
+
const logger = (deps.logger ?? createNoopLogger()).named("report-runner").with({ previewId: opts.previewId });
|
|
48
50
|
const startTime = performance.now();
|
|
49
51
|
return withPreviewLock(opts.previewId, async () => {
|
|
50
52
|
// ── Resolve version directories ───────────────────────────────
|
|
@@ -169,9 +171,7 @@ export async function runReportIteration(deps, opts) {
|
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
if (!outcome || agentError || phantomSuccess) {
|
|
172
|
-
|
|
173
|
-
`submitted=${outcome?.ok === true} phantom=${phantomSuccess} ` +
|
|
174
|
-
`agentError=${agentError ?? "null"}`);
|
|
174
|
+
logger.warn("report.agent.finish", { version: newVersion, submitted: outcome?.ok === true, phantom: phantomSuccess, agentError });
|
|
175
175
|
const { reason, errorKind } = classifyFailure({
|
|
176
176
|
phantomSuccess,
|
|
177
177
|
agentError,
|
|
@@ -182,7 +182,7 @@ export async function runReportIteration(deps, opts) {
|
|
|
182
182
|
await rm(versionDirAbs, { recursive: true, force: true });
|
|
183
183
|
}
|
|
184
184
|
catch (err) {
|
|
185
|
-
|
|
185
|
+
logger.error("rollback failed", { versionDirAbs, ...logger.errorFields(err) });
|
|
186
186
|
}
|
|
187
187
|
return failure(opts.previewId, newVersion, reason, errorKind, startTime);
|
|
188
188
|
}
|
|
@@ -29,7 +29,10 @@ import type { LoopMessage } from "../loop/types.js";
|
|
|
29
29
|
import type { AgentChat } from "../providers/types.js";
|
|
30
30
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
31
31
|
import { type StepSummary } from "../schemas/step-summary.js";
|
|
32
|
+
import type { Logger } from "../lib/logger.js";
|
|
32
33
|
export interface GenerateStepSummaryOptions {
|
|
34
|
+
/** Operational logging seam; omitted falls back to no-op. */
|
|
35
|
+
readonly logger?: Logger;
|
|
33
36
|
readonly provider: AgentChat;
|
|
34
37
|
readonly session: AgentSession;
|
|
35
38
|
readonly modelId: string;
|