@inflexa-ai/harness 0.43.1 → 0.45.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/dist/agents/report-session-agent.d.ts +1 -1
- package/dist/app/approve-plan.d.ts +48 -0
- package/dist/app/approve-plan.js +84 -0
- package/dist/app/data-profile-view.d.ts +1 -1
- package/dist/app/run-activity.js +4 -6
- package/dist/app/synthesize-run.js +1 -1
- package/dist/contracts/chat-events.d.ts +2 -0
- package/dist/contracts/chat-frame.d.ts +7 -0
- package/dist/contracts/chat-frame.js +18 -4
- package/dist/contracts/chat-parts.d.ts +73 -2
- package/dist/contracts/chat-parts.js +3 -1
- package/dist/contracts/index.d.ts +3 -1
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/part-registry.js +3 -0
- package/dist/contracts/schemas/chat-events.d.ts +13 -0
- package/dist/contracts/schemas/chat-events.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +200 -15
- package/dist/contracts/schemas/chat-parts.js +63 -12
- package/dist/contracts/schemas/index.d.ts +1 -1
- package/dist/contracts/schemas/index.js +1 -1
- package/dist/contracts/subagent-activity.d.ts +12 -0
- package/dist/contracts/subagent-activity.js +76 -0
- package/dist/execution/artifact-metadata.js +1 -3
- package/dist/execution/post-step-pipeline.js +1 -1
- package/dist/execution/run-canceler.js +1 -1
- package/dist/execution/run-event-parts.d.ts +6 -5
- package/dist/execution/run-event-parts.js +6 -5
- package/dist/execution/run-event-stream.d.ts +18 -6
- package/dist/execution/run-event-stream.js +67 -17
- package/dist/execution/run-synthesis.d.ts +21 -0
- package/dist/execution/run-synthesis.js +77 -3
- package/dist/index.d.ts +24 -10
- package/dist/index.js +17 -3
- package/dist/lib/hooks.d.ts +1 -1
- package/dist/lib/metrics.d.ts +2 -2
- package/dist/loop/run-agent.js +1 -0
- package/dist/loop/types.d.ts +2 -0
- package/dist/memory/card-builders.d.ts +11 -0
- package/dist/memory/card-builders.js +35 -21
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +10 -1
- package/dist/memory/conversation-display-storage.d.ts +9 -2
- package/dist/memory/conversation-display-storage.js +18 -3
- package/dist/memory/thread-history.d.ts +8 -1
- package/dist/memory/thread-history.js +13 -3
- package/dist/memory/thread-store.d.ts +13 -0
- package/dist/memory/thread-store.js +15 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +3 -1
- package/dist/provenance/seam.d.ts +16 -5
- package/dist/providers/ai-sdk.js +6 -4
- package/dist/reference-data/receipt.d.ts +16 -4
- package/dist/reference-data/receipt.js +20 -3
- package/dist/report-model/pin-snapshot.d.ts +1 -1
- package/dist/report-model/pin-snapshot.js +4 -52
- package/dist/runtime/assemble.d.ts +9 -2
- package/dist/runtime/assemble.js +13 -2
- package/dist/runtime/boot.d.ts +3 -0
- package/dist/runtime/boot.js +10 -1
- package/dist/sandbox/client.d.ts +1 -1
- package/dist/sandbox/create-sandbox.js +1 -1
- package/dist/sandbox/docker-client.js +1 -1
- package/dist/sandbox/k8s-client.js +5 -5
- package/dist/sandbox/sandbox-step-translate.d.ts +23 -0
- package/dist/sandbox/sandbox-step-translate.js +24 -0
- package/dist/schemas/artifact-manifest.d.ts +1 -1
- package/dist/schemas/artifact-manifest.js +11 -9
- package/dist/schemas/plan-schemas.d.ts +18 -18
- package/dist/schemas/run-synthesis.d.ts +2 -2
- package/dist/state/analyses-metrics.d.ts +79 -7
- package/dist/state/analyses-metrics.js +141 -6
- package/dist/state/artifacts.d.ts +50 -3
- package/dist/state/artifacts.js +67 -7
- package/dist/state/data-profile.d.ts +4 -2
- package/dist/state/data-profile.js +7 -5
- package/dist/state/index.d.ts +9 -8
- package/dist/state/index.js +5 -5
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.d.ts +8 -0
- package/dist/state/migrations/20261012120000_index_the_runs_by_start.js +12 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.d.ts +8 -0
- package/dist/state/migrations/20261013120000_index_the_ended_runs_and_the_recent_threads.js +13 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.d.ts +8 -0
- package/dist/state/migrations/20261013180000_narrow_the_active_run_index.js +13 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.d.ts +8 -0
- package/dist/state/migrations/20261014120000_index_the_plans_by_parent_and_the_runs_by_plan.js +13 -0
- package/dist/state/migrations/index.js +8 -0
- package/dist/state/plans.d.ts +21 -0
- package/dist/state/plans.js +25 -0
- package/dist/state/runs.d.ts +36 -5
- package/dist/state/runs.js +65 -19
- package/dist/state/schema.d.ts +2 -0
- package/dist/tasks/data-profile-activity.d.ts +7 -1
- package/dist/tasks/data-profile-activity.js +18 -9
- package/dist/tasks/data-profile.d.ts +15 -1
- package/dist/tasks/data-profile.js +18 -13
- package/dist/tools/approval/gateway.js +28 -5
- package/dist/tools/display/show-user.d.ts +1 -1
- package/dist/tools/execute-analysis.d.ts +51 -0
- package/dist/tools/execute-analysis.js +241 -109
- package/dist/tools/report-session/derive-table.js +1 -1
- package/dist/tools/report-session/examine-page.js +1 -1
- package/dist/tools/report-session/list-artifacts.js +2 -2
- package/dist/tools/report-session/preview-report.d.ts +1 -1
- package/dist/tools/report-session/preview-report.js +6 -6
- package/dist/tools/report-session/record-version.d.ts +1 -1
- package/dist/tools/report-session/record-version.js +4 -4
- package/dist/tools/research/inspect-data-profile.d.ts +1 -1
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/inspect-run.js +7 -7
- package/dist/tools/workspace/mutator.js +2 -2
- package/dist/workflows/execute-analysis.d.ts +4 -3
- package/dist/workflows/execute-analysis.js +38 -25
- package/dist/workflows/plan/graph.d.ts +16 -0
- package/dist/workflows/plan/graph.js +36 -0
- package/dist/workflows/sandbox-step.d.ts +2 -2
- package/dist/workflows/sandbox-step.js +16 -14
- package/dist/workspace/filesystem.js +18 -22
- package/dist/workspace/paths.d.ts +7 -4
- package/package.json +4 -4
|
@@ -17,54 +17,11 @@
|
|
|
17
17
|
* keeps the membership role, thus the record map is optional and it changes no resolution.
|
|
18
18
|
*/
|
|
19
19
|
import { err, ok } from "neverthrow";
|
|
20
|
-
import {
|
|
21
|
-
import { join } from "node:path";
|
|
22
|
-
import { tryFs } from "../lib/fs-result.js";
|
|
20
|
+
import { readSynthesisText } from "../execution/run-synthesis.js";
|
|
23
21
|
import { queryAnalysisArtifacts } from "../state/artifacts.js";
|
|
24
22
|
import { queryRunsByAnalysis } from "../state/runs.js";
|
|
25
|
-
import { isSafeId, runDir } from "../workspace/paths.js";
|
|
26
|
-
/** The file name of the synthesis record inside the run directory. */
|
|
27
|
-
const SYNTHESIS_FILE = "synthesis.json";
|
|
28
|
-
/**
|
|
29
|
-
* The cap of one synthesis read. A synthesis record is small by construction, and the cap bounds the
|
|
30
|
-
* cost of a file that is not. A file over the cap gives no key, because a cut JSON text cannot parse.
|
|
31
|
-
*/
|
|
32
|
-
const SYNTHESIS_CAP_BYTES = 1024 * 1024;
|
|
33
23
|
/** The page size of the run listing. The listing walks the pages until a short page ends the walk. */
|
|
34
24
|
const RUN_PAGE_SIZE = 200;
|
|
35
|
-
/**
|
|
36
|
-
* Read a synthesis record under the cap, or give `undefined` when the bytes do not come back.
|
|
37
|
-
*
|
|
38
|
-
* The read takes a bounded byte window, thus a file of any size costs the cap. An absent file, a genuine
|
|
39
|
-
* read fault, and a file over the cap each give `undefined`, because the collection reports no error for
|
|
40
|
-
* one record.
|
|
41
|
-
*/
|
|
42
|
-
async function readSynthesisText(absolute) {
|
|
43
|
-
return tryFs("pinReportSnapshot.readSynthesis", async () => {
|
|
44
|
-
const handle = await open(absolute, "r");
|
|
45
|
-
try {
|
|
46
|
-
// The window takes one byte more than the cap. A read that fills it states that the file
|
|
47
|
-
// holds more bytes than the cap admits, thus the two conditions stay apart.
|
|
48
|
-
const buffer = Buffer.alloc(SYNTHESIS_CAP_BYTES + 1);
|
|
49
|
-
// One read of a network-backed file can give fewer bytes than the window holds. Such a
|
|
50
|
-
// short read cuts the text in the middle of the JSON, and the record then parses to no
|
|
51
|
-
// key at all. Thus the loop reads again from the offset that the last read reached, and
|
|
52
|
-
// it stops when the window fills or when a read gives no byte.
|
|
53
|
-
let filled = 0;
|
|
54
|
-
while (filled < buffer.length) {
|
|
55
|
-
const { bytesRead } = await handle.read(buffer, filled, buffer.length - filled, filled);
|
|
56
|
-
if (bytesRead === 0) {
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
filled += bytesRead;
|
|
60
|
-
}
|
|
61
|
-
return filled > SYNTHESIS_CAP_BYTES ? undefined : buffer.subarray(0, filled).toString("utf8");
|
|
62
|
-
}
|
|
63
|
-
finally {
|
|
64
|
-
await handle.close();
|
|
65
|
-
}
|
|
66
|
-
}, { path: absolute, onAbsent: () => undefined }).unwrapOr(undefined);
|
|
67
|
-
}
|
|
68
25
|
/** The trimmed text of a field, or `undefined` when the field is not a non-empty string. */
|
|
69
26
|
function trimmedText(value) {
|
|
70
27
|
if (typeof value !== "string") {
|
|
@@ -166,10 +123,10 @@ function citationsOf(text) {
|
|
|
166
123
|
async function collectCitations(pool, resolveWorkspaceRoot, analysisId) {
|
|
167
124
|
let root;
|
|
168
125
|
try {
|
|
169
|
-
root = resolveWorkspaceRoot(analysisId);
|
|
126
|
+
root = await resolveWorkspaceRoot(analysisId);
|
|
170
127
|
}
|
|
171
128
|
catch {
|
|
172
|
-
// The seam signals an unresolvable resource by a
|
|
129
|
+
// The seam signals an unresolvable resource by a rejection. The artifact map still states the
|
|
173
130
|
// membership of the session, thus the fault costs the citation list alone.
|
|
174
131
|
return ok({ keys: [], records: new Map() });
|
|
175
132
|
}
|
|
@@ -181,12 +138,7 @@ async function collectCitations(pool, resolveWorkspaceRoot, analysisId) {
|
|
|
181
138
|
return err({ kind: "run-listing-failed", cause: page.error.cause });
|
|
182
139
|
}
|
|
183
140
|
for (const run of page.value) {
|
|
184
|
-
|
|
185
|
-
// the guard keeps the collection on the value channel.
|
|
186
|
-
if (!isSafeId(run.runId)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const text = await readSynthesisText(join(root, runDir(run.runId), SYNTHESIS_FILE));
|
|
141
|
+
const text = await readSynthesisText({ workspaceRoot: root, runId: run.runId });
|
|
190
142
|
if (text === undefined) {
|
|
191
143
|
continue;
|
|
192
144
|
}
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* `applicationVersion` stamp because all registration happens in this one call,
|
|
15
15
|
* before launch — a blue/green drain treats them as a single cohort.
|
|
16
16
|
*/
|
|
17
|
-
import { type Result } from "neverthrow";
|
|
17
|
+
import { type Result, type ResultAsync } from "neverthrow";
|
|
18
18
|
import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
|
|
19
|
+
import { type ApprovePlanInput, type ApprovePlanRefusal, type PlanApproval } from "../app/approve-plan.js";
|
|
19
20
|
import { type ReportSessionRuntime } from "../app/report-session-runtime.js";
|
|
20
21
|
import type { UsageRecorder } from "../billing/usage-recorder.js";
|
|
21
22
|
import type { ResourcePolicy } from "../config/resource-limits.js";
|
|
@@ -97,7 +98,7 @@ export interface ReportResolverScope {
|
|
|
97
98
|
* The factory that gives the reference resolver of one report tool call. The harness wires the production
|
|
98
99
|
* realization, and an embedder can bind its own at its composition root.
|
|
99
100
|
*/
|
|
100
|
-
export type MakeReportReferenceResolver = (scope: ReportResolverScope) => ReferenceResolver
|
|
101
|
+
export type MakeReportReferenceResolver = (scope: ReportResolverScope) => Promise<ReferenceResolver>;
|
|
101
102
|
export interface CoreRuntimeDeps {
|
|
102
103
|
readonly conversation: ConversationAssemblyDeps;
|
|
103
104
|
readonly workflows: CoreWorkflowDeps;
|
|
@@ -235,6 +236,12 @@ export interface CoreRuntime {
|
|
|
235
236
|
readonly reportSession: Pick<ReportSessionRuntime, "ensureSessionState">;
|
|
236
237
|
readonly workflows: RegisteredWorkflows;
|
|
237
238
|
readonly citationResolver: CitationResolver;
|
|
239
|
+
/**
|
|
240
|
+
* Approve a stored plan by its id and start its run with no model call. It runs over the deps that
|
|
241
|
+
* reach the `execute_analysis` tool of the conversation agent, thus the approval and the tool launch
|
|
242
|
+
* a plan the same way.
|
|
243
|
+
*/
|
|
244
|
+
readonly approvePlan: (input: ApprovePlanInput) => ResultAsync<PlanApproval, ApprovePlanRefusal>;
|
|
238
245
|
}
|
|
239
246
|
/**
|
|
240
247
|
* The eyes of the composition, or none.
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { err, ok } from "neverthrow";
|
|
18
18
|
import { createConversationAgent } from "../agents/conversation-agent.js";
|
|
19
19
|
import { createReportSessionAgent } from "../agents/report-session-agent.js";
|
|
20
|
+
import { approvePlan } from "../app/approve-plan.js";
|
|
20
21
|
import { createReportSessionRuntime } from "../app/report-session-runtime.js";
|
|
21
22
|
import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
|
|
22
23
|
import { hasBrowserUrl } from "../lib/chrome.js";
|
|
@@ -152,10 +153,10 @@ export function assembleCoreRuntime(deps) {
|
|
|
152
153
|
// for each tool call. The extraction arm falls through to the registered extract-values
|
|
153
154
|
// callable for an over-cap file or a host parse fault. The cap gates the host read, and
|
|
154
155
|
// the resolver defaults it to 16 MiB.
|
|
155
|
-
const makeProductionReportResolver = (scope) => {
|
|
156
|
+
const makeProductionReportResolver = async (scope) => {
|
|
156
157
|
const arm = createExtractionArm(bindExtractionTrigger({ runAuthorizer: conversation.runAuthorizer, workflow: extractValues }, { auth: scope.auth, analysisId: scope.analysisId }));
|
|
157
158
|
return createProductionResolver({
|
|
158
|
-
workspaceRoot: conversation.resolveWorkspaceRoot(scope.analysisId),
|
|
159
|
+
workspaceRoot: await conversation.resolveWorkspaceRoot(scope.analysisId),
|
|
159
160
|
analysisId: scope.analysisId,
|
|
160
161
|
extractionArm: arm,
|
|
161
162
|
readCache: reportArtifactReads.forAnalysis(scope.analysisId),
|
|
@@ -210,5 +211,15 @@ export function assembleCoreRuntime(deps) {
|
|
|
210
211
|
deriveTableExec,
|
|
211
212
|
},
|
|
212
213
|
citationResolver,
|
|
214
|
+
approvePlan: (input) => approvePlan({
|
|
215
|
+
pool: conversation.pool,
|
|
216
|
+
executeAnalysisWorkflow: executeAnalysis,
|
|
217
|
+
runAuthorizer: conversation.runAuthorizer,
|
|
218
|
+
runLauncher: conversation.runLauncher,
|
|
219
|
+
...(resourcePolicy ? { resourcePolicy } : {}),
|
|
220
|
+
...(conversation.extendAnalysisFarm ? { extendAnalysisFarm: conversation.extendAnalysisFarm } : {}),
|
|
221
|
+
...(conversation.logger ? { logger: conversation.logger } : {}),
|
|
222
|
+
...(deps.provenance ? { provenance: deps.provenance } : {}),
|
|
223
|
+
}, input),
|
|
213
224
|
};
|
|
214
225
|
}
|
package/dist/runtime/boot.d.ts
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
* legacy-workflow reap, an agent-switch install). Runs after registration
|
|
26
26
|
* so it may close over the registered callables.
|
|
27
27
|
* 7. `launchDbos(...)` — the last registration-dependent step.
|
|
28
|
+
* 8. `reconcileOrphanedRuns(pool)` — fails each `running` run whose workflow never started. It runs
|
|
29
|
+
* after launch, thus a workflow that recovery re-queued counts as started. A failure is logged and does
|
|
30
|
+
* not stop the boot, because the repair only frees the slot of a plan.
|
|
28
31
|
*
|
|
29
32
|
* No step reads or rewrites stored conversation display. A boot that validated
|
|
30
33
|
* every `messages` row is what let a single retired part key fail startup for a
|
package/dist/runtime/boot.js
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
* legacy-workflow reap, an agent-switch install). Runs after registration
|
|
26
26
|
* so it may close over the registered callables.
|
|
27
27
|
* 7. `launchDbos(...)` — the last registration-dependent step.
|
|
28
|
+
* 8. `reconcileOrphanedRuns(pool)` — fails each `running` run whose workflow never started. It runs
|
|
29
|
+
* after launch, thus a workflow that recovery re-queued counts as started. A failure is logged and does
|
|
30
|
+
* not stop the boot, because the repair only frees the slot of a plan.
|
|
28
31
|
*
|
|
29
32
|
* No step reads or rewrites stored conversation display. A boot that validated
|
|
30
33
|
* every `messages` row is what let a single retired part key fail startup for a
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
import { SANDBOX_AGENT_META } from "../agents/sandbox/index.js";
|
|
39
42
|
import { validateAgentSkills } from "../agents/sandbox/validate-skills.js";
|
|
40
43
|
import { initCortexState } from "../state/init.js";
|
|
44
|
+
import { reconcileOrphanedRuns } from "../state/runs.js";
|
|
41
45
|
import { assembleCoreRuntime } from "./assemble.js";
|
|
42
46
|
import { assertConnectionBudget } from "./connection-budget.js";
|
|
43
47
|
import { launchDbos, shutdownDbos } from "./dbos.js";
|
|
@@ -56,7 +60,12 @@ export async function bootHarness(deps) {
|
|
|
56
60
|
const runtime = assembleCoreRuntime(core);
|
|
57
61
|
await deps.beforeLaunch?.();
|
|
58
62
|
await launchDbos({ config: deps.dbos, logger });
|
|
59
|
-
logger.named("boot")
|
|
63
|
+
const bootLog = logger.named("boot");
|
|
64
|
+
(await reconcileOrphanedRuns(pool)).match((runIds) => {
|
|
65
|
+
if (runIds.length > 0)
|
|
66
|
+
bootLog.warn("failed the runs whose workflow never started", { runIds });
|
|
67
|
+
}, (error) => bootLog.warn("the repair of the orphaned runs failed", bootLog.errorFields(error)));
|
|
68
|
+
bootLog.info("harness booted", { executorId: deps.dbos.executorId });
|
|
60
69
|
const shutdown = (signal) => runShutdownSequence({
|
|
61
70
|
signal,
|
|
62
71
|
logger,
|
package/dist/sandbox/client.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface SandboxClient {
|
|
|
43
43
|
* Each spawn failure is an `err` value, the refusal of the label hook
|
|
44
44
|
* (`labels_refused`) included: a spawn path splits the result with
|
|
45
45
|
* `keepSuspendingRefusal`. Only a defect of the deployment config, or a
|
|
46
|
-
*
|
|
46
|
+
* rejection of the workspace-root resolver, throws, and it fails the workflow.
|
|
47
47
|
*/
|
|
48
48
|
createSandbox(session: SpawnSession, spec: SandboxSpec, identity: SandboxIdentity): ResultAsync<SandboxRef, SandboxError>;
|
|
49
49
|
/**
|
|
@@ -57,7 +57,7 @@ export async function precreateStepTree(deps, coords) {
|
|
|
57
57
|
// `tailWritePrefix` (not a raw `join`) so every segment runs through the same
|
|
58
58
|
// id validation the mount builders apply — a crafted tail cannot escape the
|
|
59
59
|
// resolved root.
|
|
60
|
-
const writeDir = tailWritePrefix({ workspaceRoot: deps.resolveWorkspaceRoot(coords.analysisId), tail: write.tail });
|
|
60
|
+
const writeDir = tailWritePrefix({ workspaceRoot: await deps.resolveWorkspaceRoot(coords.analysisId), tail: write.tail });
|
|
61
61
|
await mkdir(writeDir, { recursive: true });
|
|
62
62
|
await Promise.all(write.subdirs.map((sub) => mkdir(join(writeDir, sub), { recursive: true })));
|
|
63
63
|
if (deps.stepTreeAccess === "world-writable") {
|
|
@@ -287,7 +287,7 @@ export function createDockerSandboxOps(config) {
|
|
|
287
287
|
toolchainSource: config.toolchainSource,
|
|
288
288
|
cache: cacheBindable,
|
|
289
289
|
});
|
|
290
|
-
const hostTreePath = config.resolveWorkspaceRoot(analysisId);
|
|
290
|
+
const hostTreePath = await config.resolveWorkspaceRoot(analysisId);
|
|
291
291
|
// `tailWritePrefix` (not a raw `join`) so the RW bind source runs through
|
|
292
292
|
// the same validation as the pre-created write tree — a crafted stepId or
|
|
293
293
|
// a crafted tail cannot escape the resolved root into the container. The
|
|
@@ -88,11 +88,11 @@ function buildKubeApi() {
|
|
|
88
88
|
* mount a directory the harness never wrote to — fail loudly instead. `createSandbox` runs
|
|
89
89
|
* inside a DBOS workflow body, where a throw is the durable failure signal.
|
|
90
90
|
*/
|
|
91
|
-
function workspaceSubPathFor(config, analysisId) {
|
|
91
|
+
async function workspaceSubPathFor(config, analysisId) {
|
|
92
92
|
if (!config.sessionPvcRoot) {
|
|
93
93
|
throw new Error("k8s sandbox: sessionPvc is set but sessionPvcRoot is not — the PVC subPath of a workspace root cannot be derived");
|
|
94
94
|
}
|
|
95
|
-
const rel = relativePath(config.sessionPvcRoot, config.resolveWorkspaceRoot(analysisId));
|
|
95
|
+
const rel = relativePath(config.sessionPvcRoot, await config.resolveWorkspaceRoot(analysisId));
|
|
96
96
|
const posix = rel.split(sep).join("/");
|
|
97
97
|
if (posix.length === 0 || posix.startsWith("..")) {
|
|
98
98
|
throw new Error(`k8s sandbox: workspace root for ${analysisId} does not live under sessionPvcRoot (${config.sessionPvcRoot})`);
|
|
@@ -111,7 +111,7 @@ function pvcSubPath(value, what) {
|
|
|
111
111
|
}
|
|
112
112
|
return value;
|
|
113
113
|
}
|
|
114
|
-
function buildJobSpec(session, sandboxSpec, hostLabels, config, identity, farm, libsMounted) {
|
|
114
|
+
async function buildJobSpec(session, sandboxSpec, hostLabels, config, identity, farm, libsMounted) {
|
|
115
115
|
const { sandboxId } = identity;
|
|
116
116
|
// The lock gate of the package-store spec: `farmPath` is PVC-relative,
|
|
117
117
|
// thus this host can prove the lock only through `libStorePvcRoot`. The
|
|
@@ -148,7 +148,7 @@ function buildJobSpec(session, sandboxSpec, hostLabels, config, identity, farm,
|
|
|
148
148
|
const volumes = [];
|
|
149
149
|
const volumeMounts = [];
|
|
150
150
|
if (config.sessionPvc) {
|
|
151
|
-
const subPaths = buildSessionSubPaths(coords, workspaceSubPathFor(config, session.scope.analysisId));
|
|
151
|
+
const subPaths = buildSessionSubPaths(coords, await workspaceSubPathFor(config, session.scope.analysisId));
|
|
152
152
|
volumes.push({
|
|
153
153
|
name: SESSION_VOLUME_NAME,
|
|
154
154
|
persistentVolumeClaim: { claimName: config.sessionPvc },
|
|
@@ -491,7 +491,7 @@ export function createK8sSandboxOps(config) {
|
|
|
491
491
|
.warn("the farm of the analysis has no usable inflexa.lock at sandbox creation — mounting no package store (sandbox degrades to available:false)", { libStorePvc: config.libStorePvc, farmPath: farm.farmPath, lockError: lock.error.type, sandboxId });
|
|
492
492
|
}
|
|
493
493
|
}
|
|
494
|
-
const job = buildJobSpec(session, spec, hostLabels, config, identity, farm, libsMounted);
|
|
494
|
+
const job = await buildJobSpec(session, spec, hostLabels, config, identity, farm, libsMounted);
|
|
495
495
|
const adopted = await createOrAdoptJob(batchApi, coreApi, config.namespace, sandboxId, spec.childWorkflowId, job);
|
|
496
496
|
if (adopted.isErr())
|
|
497
497
|
return err(adopted.error);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* extraction/fold, and the chat-data-part narrowing a workflow body composes
|
|
6
6
|
* into its emitters.
|
|
7
7
|
*/
|
|
8
|
+
import type { ActivityEntryPart, StepActivityPart, StepPhase, SynthesisPhase, SynthesisProgressPart } from "../contracts/chat-parts.js";
|
|
8
9
|
import type { ChatDataPart, EmitFn } from "../loop/types.js";
|
|
9
10
|
import type { ToolDetailResolver } from "../tools/detail-resolver.js";
|
|
10
11
|
/**
|
|
@@ -19,6 +20,28 @@ import type { ToolDetailResolver } from "../tools/detail-resolver.js";
|
|
|
19
20
|
* producers construct it identically.
|
|
20
21
|
*/
|
|
21
22
|
export declare function stepPartId(kind: string, runId: string, stepId: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* The two parts of one activity emission: the reconciling `data-step-activity` that holds the current activity,
|
|
25
|
+
* and the `data-activity-entry` that keeps the emission in the run stream. Both carry the same phase, activity,
|
|
26
|
+
* and time. `seq` is the position of the emission in the body of its workflow, so each entry has its own id, and a
|
|
27
|
+
* recovered body counts the same emissions again and gives the same ids. Every producer mints the two parts here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function activityEmission(frame: {
|
|
30
|
+
readonly runId: string;
|
|
31
|
+
readonly stepId: string;
|
|
32
|
+
}, seq: number, phase: StepPhase, activity: string, at: string): {
|
|
33
|
+
readonly activity: StepActivityPart;
|
|
34
|
+
readonly entry: ActivityEntryPart;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The two parts of one synthesis progress report: the reconciling `data-synthesis-progress` that holds the current
|
|
38
|
+
* phase, and the `data-activity-entry` that keeps the report in the run stream under the synthesis step. `seq` is the
|
|
39
|
+
* position of the report in the synthesis, so each entry has its own id. The `extra` fields stay on the progress part.
|
|
40
|
+
*/
|
|
41
|
+
export declare function synthesisEmission(runId: string, stepId: string, seq: number, phase: SynthesisPhase, activity: string, at: string, extra: Pick<SynthesisProgressPart, "delegationCount" | "reason" | "error">): {
|
|
42
|
+
readonly progress: SynthesisProgressPart;
|
|
43
|
+
readonly entry: ActivityEntryPart;
|
|
44
|
+
};
|
|
22
45
|
/**
|
|
23
46
|
* The live-activity phrase for one tool call: the tool's name, then its own
|
|
24
47
|
* description of this call when it has one.
|
|
@@ -19,6 +19,30 @@
|
|
|
19
19
|
export function stepPartId(kind, runId, stepId) {
|
|
20
20
|
return `${kind}-${runId}-${stepId}`;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The two parts of one activity emission: the reconciling `data-step-activity` that holds the current activity,
|
|
24
|
+
* and the `data-activity-entry` that keeps the emission in the run stream. Both carry the same phase, activity,
|
|
25
|
+
* and time. `seq` is the position of the emission in the body of its workflow, so each entry has its own id, and a
|
|
26
|
+
* recovered body counts the same emissions again and gives the same ids. Every producer mints the two parts here.
|
|
27
|
+
*/
|
|
28
|
+
export function activityEmission(frame, seq, phase, activity, at) {
|
|
29
|
+
const { runId, stepId } = frame;
|
|
30
|
+
return {
|
|
31
|
+
activity: { type: "data-step-activity", id: stepPartId("step-activity", runId, stepId), runId, stepId, phase, activity, at },
|
|
32
|
+
entry: { type: "data-activity-entry", id: `${stepPartId("activity-entry", runId, stepId)}-${seq}`, runId, stepId, phase, activity, at },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The two parts of one synthesis progress report: the reconciling `data-synthesis-progress` that holds the current
|
|
37
|
+
* phase, and the `data-activity-entry` that keeps the report in the run stream under the synthesis step. `seq` is the
|
|
38
|
+
* position of the report in the synthesis, so each entry has its own id. The `extra` fields stay on the progress part.
|
|
39
|
+
*/
|
|
40
|
+
export function synthesisEmission(runId, stepId, seq, phase, activity, at, extra) {
|
|
41
|
+
return {
|
|
42
|
+
progress: { type: "data-synthesis-progress", id: `synthesis-progress-${runId}`, runId, phase, activity, at, ...extra },
|
|
43
|
+
entry: { type: "data-activity-entry", id: `${stepPartId("activity-entry", runId, stepId)}-${seq}`, runId, stepId, phase, activity, at },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
22
46
|
/**
|
|
23
47
|
* The live-activity phrase for one tool call: the tool's name, then its own
|
|
24
48
|
* description of this call when it has one.
|
|
@@ -30,5 +30,5 @@ export declare const ArtifactManifestEntrySchema: z.ZodObject<{
|
|
|
30
30
|
hash: z.ZodOptional<z.ZodString>;
|
|
31
31
|
}, z.core.$strip>;
|
|
32
32
|
export type ArtifactManifestEntry = z.infer<typeof ArtifactManifestEntrySchema>;
|
|
33
|
-
/** Infer artifact type from a file path
|
|
33
|
+
/** Infer artifact type from a file path. A folder segment at the start of the path or after a `/` gives its type. */
|
|
34
34
|
export declare function inferArtifactType(filePath: string): ArtifactType;
|
|
@@ -22,15 +22,17 @@ export const ArtifactManifestEntrySchema = z.object({
|
|
|
22
22
|
/** SHA-256 content hash for integrity verification (e.g., sha256:abcdef...). */
|
|
23
23
|
hash: z.string().optional(),
|
|
24
24
|
});
|
|
25
|
-
|
|
25
|
+
const FOLDER_TYPES = [
|
|
26
|
+
["figures", "figure"],
|
|
27
|
+
["scripts", "script"],
|
|
28
|
+
["logs", "log"],
|
|
29
|
+
["notebooks", "notebook"],
|
|
30
|
+
];
|
|
31
|
+
/** Infer artifact type from a file path. A folder segment at the start of the path or after a `/` gives its type. */
|
|
26
32
|
export function inferArtifactType(filePath) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (filePath.includes("/logs/"))
|
|
32
|
-
return "log";
|
|
33
|
-
if (filePath.includes("/notebooks/"))
|
|
34
|
-
return "notebook";
|
|
33
|
+
for (const [folder, type] of FOLDER_TYPES) {
|
|
34
|
+
if (filePath.startsWith(`${folder}/`) || filePath.includes(`/${folder}/`))
|
|
35
|
+
return type;
|
|
36
|
+
}
|
|
35
37
|
return "output";
|
|
36
38
|
}
|
|
@@ -11,13 +11,8 @@ export declare const PlanStepSchema: z.ZodObject<{
|
|
|
11
11
|
context: z.ZodOptional<z.ZodString>;
|
|
12
12
|
description: z.ZodOptional<z.ZodString>;
|
|
13
13
|
id: z.ZodString;
|
|
14
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
15
|
-
track: z.ZodString;
|
|
16
|
-
step_type: z.ZodString;
|
|
17
|
-
question: z.ZodString;
|
|
18
|
-
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
19
14
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
20
|
-
|
|
15
|
+
question: z.ZodString;
|
|
21
16
|
depends_on: z.ZodArray<z.ZodString>;
|
|
22
17
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
23
18
|
cpu: z.ZodNumber;
|
|
@@ -26,6 +21,11 @@ export declare const PlanStepSchema: z.ZodObject<{
|
|
|
26
21
|
count: z.ZodNumber;
|
|
27
22
|
}, z.core.$strip>>;
|
|
28
23
|
}, z.core.$strip>>>;
|
|
24
|
+
track: z.ZodString;
|
|
25
|
+
step_type: z.ZodString;
|
|
26
|
+
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
27
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
29
29
|
agent: z.ZodEnum<{
|
|
30
30
|
[x: string]: string;
|
|
31
31
|
}>;
|
|
@@ -44,13 +44,8 @@ export declare const PlannerPlanSchema: z.ZodObject<{
|
|
|
44
44
|
context: z.ZodOptional<z.ZodString>;
|
|
45
45
|
description: z.ZodOptional<z.ZodString>;
|
|
46
46
|
id: z.ZodString;
|
|
47
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
48
|
-
track: z.ZodString;
|
|
49
|
-
step_type: z.ZodString;
|
|
50
|
-
question: z.ZodString;
|
|
51
|
-
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
52
47
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
53
|
-
|
|
48
|
+
question: z.ZodString;
|
|
54
49
|
depends_on: z.ZodArray<z.ZodString>;
|
|
55
50
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
56
51
|
cpu: z.ZodNumber;
|
|
@@ -59,6 +54,11 @@ export declare const PlannerPlanSchema: z.ZodObject<{
|
|
|
59
54
|
count: z.ZodNumber;
|
|
60
55
|
}, z.core.$strip>>;
|
|
61
56
|
}, z.core.$strip>>>;
|
|
57
|
+
track: z.ZodString;
|
|
58
|
+
step_type: z.ZodString;
|
|
59
|
+
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
60
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
61
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
62
62
|
agent: z.ZodEnum<{
|
|
63
63
|
[x: string]: string;
|
|
64
64
|
}>;
|
|
@@ -86,13 +86,8 @@ export declare const PlanningAgentOutputSchema: z.ZodObject<{
|
|
|
86
86
|
context: z.ZodOptional<z.ZodString>;
|
|
87
87
|
description: z.ZodOptional<z.ZodString>;
|
|
88
88
|
id: z.ZodString;
|
|
89
|
-
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
90
|
-
track: z.ZodString;
|
|
91
|
-
step_type: z.ZodString;
|
|
92
|
-
question: z.ZodString;
|
|
93
|
-
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
94
89
|
caveats: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
95
|
-
|
|
90
|
+
question: z.ZodString;
|
|
96
91
|
depends_on: z.ZodArray<z.ZodString>;
|
|
97
92
|
resources: z.ZodNonOptional<z.ZodOptional<z.ZodObject<{
|
|
98
93
|
cpu: z.ZodNumber;
|
|
@@ -101,6 +96,11 @@ export declare const PlanningAgentOutputSchema: z.ZodObject<{
|
|
|
101
96
|
count: z.ZodNumber;
|
|
102
97
|
}, z.core.$strip>>;
|
|
103
98
|
}, z.core.$strip>>>;
|
|
99
|
+
track: z.ZodString;
|
|
100
|
+
step_type: z.ZodString;
|
|
101
|
+
acceptance_criteria: z.ZodArray<z.ZodString>;
|
|
102
|
+
constraints: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
103
|
+
packages: z.ZodNonOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
104
104
|
agent: z.ZodEnum<{
|
|
105
105
|
[x: string]: string;
|
|
106
106
|
}>;
|
|
@@ -31,11 +31,11 @@ export declare const SynthesizedFindingSchema: z.ZodObject<{
|
|
|
31
31
|
high: "high";
|
|
32
32
|
}>;
|
|
33
33
|
noveltyStatus: z.ZodEnum<{
|
|
34
|
-
expected: "expected";
|
|
35
34
|
novel: "novel";
|
|
36
35
|
confirmed: "confirmed";
|
|
37
36
|
partially_confirmed: "partially_confirmed";
|
|
38
37
|
contradicted: "contradicted";
|
|
38
|
+
expected: "expected";
|
|
39
39
|
}>;
|
|
40
40
|
literatureInterpretation: z.ZodString;
|
|
41
41
|
references: z.ZodArray<z.ZodObject<{
|
|
@@ -97,11 +97,11 @@ export declare const RunSynthesisSchema: z.ZodObject<{
|
|
|
97
97
|
high: "high";
|
|
98
98
|
}>;
|
|
99
99
|
noveltyStatus: z.ZodEnum<{
|
|
100
|
-
expected: "expected";
|
|
101
100
|
novel: "novel";
|
|
102
101
|
confirmed: "confirmed";
|
|
103
102
|
partially_confirmed: "partially_confirmed";
|
|
104
103
|
contradicted: "contradicted";
|
|
104
|
+
expected: "expected";
|
|
105
105
|
}>;
|
|
106
106
|
literatureInterpretation: z.ZodString;
|
|
107
107
|
references: z.ZodArray<z.ZodObject<{
|
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cross-entity aggregate reads
|
|
3
|
-
*
|
|
4
|
-
* the
|
|
2
|
+
* Cross-entity aggregate reads for a list of analyses. Each read takes the ids
|
|
3
|
+
* of a page and sends one statement, whatever the length of the list: an
|
|
4
|
+
* embedder builds the per-analysis summary of a list from these reads, never
|
|
5
|
+
* from one read for each analysis.
|
|
5
6
|
*/
|
|
6
7
|
import { type ResultAsync } from "neverthrow";
|
|
8
|
+
import type { DataProfileLifecycleStatus } from "../contracts/data-profile.js";
|
|
7
9
|
import { type DbError } from "../lib/db-result.js";
|
|
8
10
|
import type { Querier } from "./db.js";
|
|
11
|
+
import type { RunStatus, TerminalRunStatus } from "./schema.js";
|
|
12
|
+
/** The newest run of one analysis, with the title of its plan and the counts of its step rows. */
|
|
13
|
+
export interface LatestRunSummary {
|
|
14
|
+
readonly analysisId: string;
|
|
15
|
+
readonly runId: string;
|
|
16
|
+
readonly status: RunStatus;
|
|
17
|
+
readonly planTitle: string | null;
|
|
18
|
+
readonly doneSteps: number;
|
|
19
|
+
readonly totalSteps: number;
|
|
20
|
+
readonly startedAt: string;
|
|
21
|
+
readonly completedAt: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** A run of a list of run ids, with the fields of the newest-run read and the thread of the run. */
|
|
24
|
+
export interface RunProgressSummary extends LatestRunSummary {
|
|
25
|
+
/** The thread of the run as the ledger stores it, or `null`. */
|
|
26
|
+
readonly threadId: string | null;
|
|
27
|
+
}
|
|
28
|
+
/** A terminal run that ended at or after a time. */
|
|
29
|
+
export interface EndedRunSummary {
|
|
30
|
+
readonly analysisId: string;
|
|
31
|
+
readonly runId: string;
|
|
32
|
+
readonly status: TerminalRunStatus;
|
|
33
|
+
readonly completedAt: string;
|
|
34
|
+
}
|
|
35
|
+
/** The data profile state of one analysis that has a profile status. */
|
|
36
|
+
export interface DataProfileSummary {
|
|
37
|
+
readonly analysisId: string;
|
|
38
|
+
readonly status: DataProfileLifecycleStatus;
|
|
39
|
+
readonly completedAt: string | null;
|
|
40
|
+
readonly profiledFiles: number | null;
|
|
41
|
+
}
|
|
9
42
|
export declare function queryRunCountsByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<Array<{
|
|
10
43
|
analysisId: string;
|
|
11
44
|
status: string;
|
|
@@ -15,7 +48,46 @@ export declare function queryThreadCountsByAnalyses(pool: Querier, analysisIds:
|
|
|
15
48
|
analysisId: string;
|
|
16
49
|
count: number;
|
|
17
50
|
}>, DbError>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The newest run of each analysis of a list, in one statement. An analysis with
|
|
53
|
+
* no run has no entry. The newest run has the latest `started_at`, and `run_id`
|
|
54
|
+
* breaks a tie in descending order. The `LATERAL` subquery reads
|
|
55
|
+
* `idx_cortex_runs_analysis_started`, thus one index entry for each analysis
|
|
56
|
+
* and not a sort of the run history.
|
|
57
|
+
*
|
|
58
|
+
* The plan joins on `plan_id` and `analysis_id`: a plan of a different analysis
|
|
59
|
+
* gives no title, the scope rule of `loadPlan`. The statement gives the title
|
|
60
|
+
* only when it is a string, and the row mapping trims it with `trim()`, the
|
|
61
|
+
* rule of the cli `planTitleOf`. A blank title or a title that is not a string
|
|
62
|
+
* reads as `null`. The step counts
|
|
63
|
+
* cover the ledger rows of the selected run only, the reserved synthesis row
|
|
64
|
+
* included.
|
|
65
|
+
*/
|
|
66
|
+
export declare function queryLatestRunsByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<LatestRunSummary[], DbError>;
|
|
67
|
+
/**
|
|
68
|
+
* The progress of each run of a list of run ids, in one statement. An id with
|
|
69
|
+
* no run row has no entry. The entries come newest first by `started_at`, and
|
|
70
|
+
* `run_id` breaks a tie in descending order. The plan title and the step counts
|
|
71
|
+
* follow the rules of `queryLatestRunsByAnalyses`, and each entry adds the
|
|
72
|
+
* thread of the run.
|
|
73
|
+
*/
|
|
74
|
+
export declare function queryRunProgressByRuns(pool: Querier, runIds: string[]): ResultAsync<RunProgressSummary[], DbError>;
|
|
75
|
+
/**
|
|
76
|
+
* The newest `limit` terminal runs of every analysis that ended at or after
|
|
77
|
+
* `since`, newest completion first, with `run_id` breaking a tie in descending
|
|
78
|
+
* order. `since` is in the form of `toISOString()`, the form of each
|
|
79
|
+
* `completed_at` stamp, thus the text comparison is the time comparison. The
|
|
80
|
+
* statement walks `idx_cortex_runs_completed`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function queryRunsEndedSince(pool: Querier, input: {
|
|
83
|
+
since: string;
|
|
84
|
+
limit: number;
|
|
85
|
+
}): ResultAsync<EndedRunSummary[], DbError>;
|
|
86
|
+
/**
|
|
87
|
+
* The profile state of each analysis of a list, in one statement. An analysis
|
|
88
|
+
* with no ledger row, or with a cleared row whose status is null, has no entry:
|
|
89
|
+
* the same collapsed absence as `loadDataProfileStatus`. The statement selects
|
|
90
|
+
* only the three parts of the stored result that the file count reads, and the
|
|
91
|
+
* count itself is `profileDatasetFileCount`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function queryDataProfileStatusByAnalyses(pool: Querier, analysisIds: string[]): ResultAsync<DataProfileSummary[], DbError>;
|