@inflexa-ai/harness 0.48.3 → 0.48.5
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/conversation-agent.js +5 -1
- package/dist/agents/sandbox/index.js +3 -0
- package/dist/agents/sandbox/mdv-preparation-agent.d.ts +9 -0
- package/dist/agents/sandbox/mdv-preparation-agent.js +37 -0
- package/dist/agents/sandbox/network-agent.js +1 -1
- package/dist/agents/sandbox/shared.js +3 -1
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn-workflow.js +1 -0
- package/dist/app/chat-turn.d.ts +4 -2
- package/dist/app/chat-turn.js +46 -46
- package/dist/app/work.d.ts +7 -3
- package/dist/app/work.js +17 -16
- package/dist/contracts/chat-parts.d.ts +46 -5
- package/dist/contracts/chat-parts.js +2 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/part-registry.js +1 -0
- package/dist/contracts/schemas/chat-parts.d.ts +168 -4
- package/dist/contracts/schemas/chat-parts.js +21 -1
- package/dist/execution/run-event-stream.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/lib/regulatory-corpus/fetch-ich.js +1 -16
- package/dist/lib/slug.d.ts +18 -0
- package/dist/lib/slug.js +29 -0
- package/dist/lib/test/pdf-fixture.d.ts +5 -0
- package/dist/lib/test/pdf-fixture.js +14 -0
- package/dist/loop/chat-stream.d.ts +4 -1
- package/dist/loop/chat-stream.js +30 -13
- package/dist/loop/run-loop.js +23 -35
- package/dist/loop/sub-agent.d.ts +2 -2
- package/dist/loop/sub-agent.js +1 -1
- package/dist/loop/test/metric-reader.d.ts +9 -0
- package/dist/loop/test/metric-reader.js +33 -0
- package/dist/loop/test/unnamed-model.d.ts +6 -0
- package/dist/loop/test/unnamed-model.js +24 -0
- package/dist/memory/conversation-display-recorder.d.ts +2 -0
- package/dist/memory/conversation-display-recorder.js +6 -2
- package/dist/memory/conversation-display-storage.d.ts +4 -2
- package/dist/memory/conversation-display-storage.js +19 -1
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +6 -4
- package/dist/prompts/sandbox/mdv-preparation-agent.d.ts +1 -0
- package/dist/prompts/sandbox/mdv-preparation-agent.js +56 -0
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +4 -0
- package/dist/providers/errors.js +2 -1
- package/dist/report-model/fixture-resolver.js +9 -135
- package/dist/report-model/production-resolver.d.ts +3 -3
- package/dist/report-model/production-resolver.js +12 -127
- package/dist/report-model/reference-read.d.ts +38 -0
- package/dist/report-model/reference-read.js +149 -0
- package/dist/report-model/reference-resolver.d.ts +7 -0
- package/dist/report-model/reference-resolver.js +1 -1
- package/dist/report-render/chart.js +48 -46
- package/dist/report-render/figures/common.d.ts +34 -0
- package/dist/report-render/figures/common.js +4 -1
- package/dist/report-render/figures/equal-units.js +2 -4
- package/dist/report-render/figures/sankey.js +1 -3
- package/dist/report-render/figures/upset.js +2 -5
- package/dist/runtime/assemble.js +2 -1
- package/dist/sandbox/execution-id.js +2 -14
- package/dist/sandbox/leases.js +46 -9
- package/dist/schemas/validate-plan.js +2 -14
- package/dist/state/active-turns.d.ts +6 -2
- package/dist/state/active-turns.js +11 -7
- package/dist/state/data-profile.d.ts +8 -0
- package/dist/state/data-profile.js +18 -0
- package/dist/state/index.d.ts +1 -1
- package/dist/state/index.js +1 -1
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.d.ts +6 -0
- package/dist/state/migrations/20261102120000_record_the_waiter_of_a_pause.js +10 -0
- package/dist/state/migrations/index.js +2 -0
- package/dist/state/paused-work.d.ts +5 -3
- package/dist/state/paused-work.js +11 -7
- package/dist/state/runs.d.ts +3 -2
- package/dist/state/runs.js +4 -3
- package/dist/tasks/data-profile-index.js +3 -17
- package/dist/tasks/data-profile-resolve.js +3 -17
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/humanbase.d.ts +32 -0
- package/dist/tools/bio/humanbase.js +180 -0
- package/dist/tools/bio/index.d.ts +3 -1
- package/dist/tools/bio/index.js +3 -1
- package/dist/tools/bio/resolve-gene-ids.d.ts +13 -0
- package/dist/tools/bio/resolve-gene-ids.js +67 -0
- package/dist/tools/lib/humanbase-client.d.ts +215 -0
- package/dist/tools/lib/humanbase-client.js +187 -0
- package/dist/tools/lib/mygene-client.d.ts +150 -0
- package/dist/tools/lib/mygene-client.js +109 -0
- package/dist/tools/lib/opentargets-client.js +15 -25
- package/dist/tools/report-authoring/authoring-tools.d.ts +3 -3
- package/dist/tools/report-authoring/authoring-tools.js +7 -6
- package/dist/tools/research/generate-analogy-report.js +10 -2
- package/dist/tools/script/run-script.js +48 -51
- package/dist/workflows/execute-analysis.js +4 -3
- package/dist/workflows/pause.d.ts +3 -3
- package/dist/workflows/pause.js +15 -9
- package/dist/workflows/sandbox-step.js +1 -0
- package/package.json +7 -4
package/dist/runtime/assemble.js
CHANGED
|
@@ -95,9 +95,10 @@ export function assembleCoreRuntime(deps) {
|
|
|
95
95
|
const toolOutputStore = createToolOutputStore(conversation.pool);
|
|
96
96
|
const pauseRetryDelaysMs = deps.pauseRetryDelaysMs;
|
|
97
97
|
// Each child of a sub-agent pauses with the work of its caller, thus a resume of the turn or of the run ends its wait.
|
|
98
|
-
const subAgentPauses = ({ runSession, ...work }, emit) => workPause({
|
|
98
|
+
const subAgentPauses = ({ runSession, ...work }, waiter, emit) => workPause({
|
|
99
99
|
pool: conversation.pool,
|
|
100
100
|
...work,
|
|
101
|
+
waiter,
|
|
101
102
|
emit,
|
|
102
103
|
retryDelaysMs: pauseRetryDelaysMs,
|
|
103
104
|
run: runSession === undefined ? undefined : { session: runSession, authorizer: conversation.runAuthorizer },
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Format: {sanitizedAgentName}-{nanoid8} (e.g., qc-agent-a1b2c3d4)
|
|
8
8
|
*/
|
|
9
9
|
import { customAlphabet } from "nanoid";
|
|
10
|
+
import { slugify } from "../lib/slug.js";
|
|
10
11
|
/** Length of the nanoid suffix (8 characters). */
|
|
11
12
|
export const EXECUTION_ID_NANOID_LENGTH = 8;
|
|
12
13
|
/** Maximum length for the sanitized agent name portion. */
|
|
@@ -27,20 +28,7 @@ const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789");
|
|
|
27
28
|
* - Truncated to 40 characters
|
|
28
29
|
*/
|
|
29
30
|
export function sanitizeAgentName(name) {
|
|
30
|
-
|
|
31
|
-
let inRun = false;
|
|
32
|
-
for (const char of name.toLowerCase()) {
|
|
33
|
-
const keeps = (char >= "a" && char <= "z") || (char >= "0" && char <= "9");
|
|
34
|
-
if (!keeps) {
|
|
35
|
-
inRun = true;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (inRun && out.length > 0)
|
|
39
|
-
out += "-";
|
|
40
|
-
inRun = false;
|
|
41
|
-
out += char;
|
|
42
|
-
}
|
|
43
|
-
return out.slice(0, AGENT_NAME_MAX_LENGTH);
|
|
31
|
+
return slugify(name, { maxLength: AGENT_NAME_MAX_LENGTH });
|
|
44
32
|
}
|
|
45
33
|
/**
|
|
46
34
|
* Generate a unified execution ID for an agent invocation.
|
package/dist/sandbox/leases.js
CHANGED
|
@@ -109,6 +109,21 @@ export function createSandboxLeases(config) {
|
|
|
109
109
|
recordMachineReplaced({ cause });
|
|
110
110
|
};
|
|
111
111
|
// ── The lease workflow ────────────────────────────────────────────
|
|
112
|
+
const waiterOfLease = (input) => {
|
|
113
|
+
const kind = input.work.kind;
|
|
114
|
+
switch (kind) {
|
|
115
|
+
case "profile":
|
|
116
|
+
return { kind: "profile" };
|
|
117
|
+
case "run":
|
|
118
|
+
return { kind: "step", stepId: input.session.runFrame.stepId };
|
|
119
|
+
case "turn":
|
|
120
|
+
throw new Error("A chat turn holds no sandbox lease.");
|
|
121
|
+
default: {
|
|
122
|
+
const exhaustive = kind;
|
|
123
|
+
throw new Error(`Unknown work kind: ${String(exhaustive)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
112
127
|
const runLease = async (input) => {
|
|
113
128
|
const log = logger.with({ leaseWorkflowId: input.leaseWorkflowId, ownerWorkflowId: input.ownerWorkflowId });
|
|
114
129
|
const id = input.leaseWorkflowId;
|
|
@@ -116,6 +131,7 @@ export function createSandboxLeases(config) {
|
|
|
116
131
|
pool,
|
|
117
132
|
workId: input.work.id,
|
|
118
133
|
workKind: input.work.kind,
|
|
134
|
+
waiter: waiterOfLease(input),
|
|
119
135
|
analysisId: input.analysisId,
|
|
120
136
|
// A host reads the streams of the work, not of the lease workflow. The wait of the consumer mirrors the pause.
|
|
121
137
|
emit: () => Promise.resolve(),
|
|
@@ -317,9 +333,8 @@ export function createSandboxLeases(config) {
|
|
|
317
333
|
for (;;) {
|
|
318
334
|
const value = await dbos.getEvent(lease.leaseWorkflowId, key, { timeoutSeconds: WAIT_SLICE_SECONDS, pollingIntervalMs: EVENT_POLL_MS });
|
|
319
335
|
if (value !== null) {
|
|
320
|
-
if (mirrored !== undefined && mirror !== undefined)
|
|
321
|
-
await
|
|
322
|
-
}
|
|
336
|
+
if (mirrored !== undefined && mirror !== undefined)
|
|
337
|
+
await mirrorEnd(lease, mirror, mirrored);
|
|
323
338
|
return value;
|
|
324
339
|
}
|
|
325
340
|
if (await callerCanceled())
|
|
@@ -327,33 +342,55 @@ export function createSandboxLeases(config) {
|
|
|
327
342
|
const status = await dbos.getWorkflow(lease.leaseWorkflowId);
|
|
328
343
|
if (status !== undefined && TERMINAL_STATUSES.has(status.status)) {
|
|
329
344
|
const last = await dbos.getEvent(lease.leaseWorkflowId, key, { timeoutSeconds: 0 });
|
|
330
|
-
|
|
345
|
+
if (last === null)
|
|
346
|
+
return "ended";
|
|
347
|
+
if (mirrored !== undefined && mirror !== undefined)
|
|
348
|
+
await mirrorEnd(lease, mirror, mirrored);
|
|
349
|
+
return last;
|
|
331
350
|
}
|
|
332
351
|
if (mirror !== undefined)
|
|
333
352
|
mirrored = await mirrorPause(lease, mirror, mirrored);
|
|
334
353
|
}
|
|
335
354
|
};
|
|
355
|
+
/**
|
|
356
|
+
* A lease workflow pauses only at its label gate with the cause `host`, thus its wait is in `paused`, and nothing
|
|
357
|
+
* but a resume ends it.
|
|
358
|
+
*/
|
|
359
|
+
const mirrorEnd = (lease, mirror, last) => mirror({
|
|
360
|
+
type: "data-pause",
|
|
361
|
+
id: lease.leaseWorkflowId,
|
|
362
|
+
workId: lease.workId,
|
|
363
|
+
workKind: lease.workKind,
|
|
364
|
+
waiter: last.waiter,
|
|
365
|
+
state: "running",
|
|
366
|
+
cause: last.cause,
|
|
367
|
+
pausedAt: last.pausedAt,
|
|
368
|
+
endedAt: new Date().toISOString(),
|
|
369
|
+
end: "resume",
|
|
370
|
+
});
|
|
336
371
|
const mirrorPause = async (lease, mirror, mirrored) => {
|
|
337
372
|
const pauses = unwrapOrThrow(await queryPausesOfWork(pool, lease.analysisId, lease.workId));
|
|
338
373
|
const pause = pauses.find((row) => row.workflowId === lease.leaseWorkflowId);
|
|
339
|
-
const state = pause === undefined ? undefined : `${pause.state}:${pause.attempt}`;
|
|
340
|
-
if (state === mirrored)
|
|
341
|
-
return mirrored;
|
|
342
374
|
if (pause === undefined) {
|
|
343
|
-
|
|
375
|
+
if (mirrored !== undefined)
|
|
376
|
+
await mirrorEnd(lease, mirror, mirrored);
|
|
344
377
|
return undefined;
|
|
345
378
|
}
|
|
379
|
+
if (mirrored?.state === pause.state && mirrored.attempt === pause.attempt && mirrored.pausedAt === pause.pausedAt)
|
|
380
|
+
return mirrored;
|
|
346
381
|
await mirror({
|
|
347
382
|
type: "data-pause",
|
|
348
383
|
id: lease.leaseWorkflowId,
|
|
349
384
|
workId: lease.workId,
|
|
350
385
|
workKind: lease.workKind,
|
|
386
|
+
waiter: pause.waiter,
|
|
351
387
|
state: pause.state,
|
|
352
388
|
cause: pause.cause,
|
|
389
|
+
pausedAt: pause.pausedAt,
|
|
353
390
|
attempt: pause.attempt,
|
|
354
391
|
retryAt: pause.retryAt,
|
|
355
392
|
});
|
|
356
|
-
return
|
|
393
|
+
return pause;
|
|
357
394
|
};
|
|
358
395
|
const enqueue = async (binding, lease, size) => {
|
|
359
396
|
const dbos = await engine();
|
|
@@ -17,6 +17,7 @@ import { CycleError, DependencyError, topoSortIntoWaves } from "../execution/top
|
|
|
17
17
|
import { parseQuery } from "../sandbox/package-identity.js";
|
|
18
18
|
import { isLabelValue } from "../sandbox/labels.js";
|
|
19
19
|
import { isSafeId, STEP_SUBDIRS, SYNTHESIS_STEP_ID } from "../workspace/paths.js";
|
|
20
|
+
import { slugify } from "../lib/slug.js";
|
|
20
21
|
const KNOWN_AGENTS = new Set(KNOWN_AGENT_IDS);
|
|
21
22
|
/**
|
|
22
23
|
* Reserved step-id names. A step id equal to an artifact subdirectory name
|
|
@@ -53,20 +54,7 @@ function describeParseError(stepId, entry, error) {
|
|
|
53
54
|
}
|
|
54
55
|
/** Derive a filesystem-safe output prefix from a step ID. */
|
|
55
56
|
export function deriveOutputPrefix(stepId) {
|
|
56
|
-
|
|
57
|
-
let inRun = false;
|
|
58
|
-
for (const char of stepId.toLowerCase()) {
|
|
59
|
-
const isSlugChar = (char >= "a" && char <= "z") || (char >= "0" && char <= "9");
|
|
60
|
-
if (!isSlugChar) {
|
|
61
|
-
inRun = true;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (inRun && out.length > 0)
|
|
65
|
-
out += "-";
|
|
66
|
-
inRun = false;
|
|
67
|
-
out += char;
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
57
|
+
return slugify(stepId);
|
|
70
58
|
}
|
|
71
59
|
/**
|
|
72
60
|
* Validate an analysis plan for structural correctness.
|
|
@@ -27,8 +27,12 @@ export declare function claimThreadForTurn(pool: Querier, threadId: string, turn
|
|
|
27
27
|
export declare function closeOpenTurnRecords(pool: Querier, threadId: string): ResultAsync<void, DbError>;
|
|
28
28
|
/** The turn that runs on the thread of an ask, which waits for its answer. `undefined` when no turn runs there. */
|
|
29
29
|
export declare function turnOfAsk(pool: Querier, askId: string): ResultAsync<string | undefined, DbError>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The turn that holds each live thread of an analysis, or of each analysis when `analysisId` is absent. A claim can
|
|
32
|
+
* outlive its turn, thus a caller checks that the turn runs.
|
|
33
|
+
*/
|
|
34
|
+
export declare function queryTurns(pool: Querier, analysisId?: string): ResultAsync<{
|
|
35
|
+
readonly analysisId: string;
|
|
32
36
|
readonly threadId: string;
|
|
33
37
|
readonly turnId: string;
|
|
34
38
|
}[], DbError>;
|
|
@@ -49,15 +49,19 @@ export function turnOfAsk(pool, askId) {
|
|
|
49
49
|
return row === undefined ? undefined : (AskTurnRowSchema.parse(row).active_turn_id ?? undefined);
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
const TurnOfThreadRowSchema = z.object({ thread_id: z.string(), active_turn_id: z.string() });
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const TurnOfThreadRowSchema = z.object({ analysis_id: z.string(), thread_id: z.string(), active_turn_id: z.string() });
|
|
53
|
+
/**
|
|
54
|
+
* The turn that holds each live thread of an analysis, or of each analysis when `analysisId` is absent. A claim can
|
|
55
|
+
* outlive its turn, thus a caller checks that the turn runs.
|
|
56
|
+
*/
|
|
57
|
+
export function queryTurns(pool, analysisId) {
|
|
58
|
+
return tryQuery("activeTurns.list", async () => {
|
|
59
|
+
const { rows } = await pool.query(`SELECT analysis_id, thread_id, active_turn_id FROM cortex_analysis_threads
|
|
60
|
+
WHERE ($1::text IS NULL OR analysis_id = $1) AND active_turn_id IS NOT NULL AND deleted_at IS NULL
|
|
61
|
+
ORDER BY thread_id`, [analysisId ?? null]);
|
|
58
62
|
return rows.map((row) => {
|
|
59
63
|
const parsed = TurnOfThreadRowSchema.parse(row);
|
|
60
|
-
return { threadId: parsed.thread_id, turnId: parsed.active_turn_id };
|
|
64
|
+
return { analysisId: parsed.analysis_id, threadId: parsed.thread_id, turnId: parsed.active_turn_id };
|
|
61
65
|
});
|
|
62
66
|
});
|
|
63
67
|
}
|
|
@@ -240,6 +240,14 @@ export declare function completeEmptyDataProfile(pool: Querier, analysisId: stri
|
|
|
240
240
|
* (e.g. the seed-first guard in `triggerDataProfile`).
|
|
241
241
|
*/
|
|
242
242
|
export declare function loadDataProfileStatus(pool: Querier, analysisId: string): ResultAsync<DataProfileStatus | null, DbError>;
|
|
243
|
+
/**
|
|
244
|
+
* The data profile that runs in an analysis, or in each analysis when `analysisId` is absent: the row is `running`
|
|
245
|
+
* and it names its workflow. A caller checks that the workflow is active.
|
|
246
|
+
*/
|
|
247
|
+
export declare function queryRunningProfiles(pool: Querier, analysisId?: string): ResultAsync<{
|
|
248
|
+
readonly analysisId: string;
|
|
249
|
+
readonly workflowId: string;
|
|
250
|
+
}[], DbError>;
|
|
243
251
|
/**
|
|
244
252
|
* Read an analysis's raw `seed_input_file_ids`, without the NULL-status collapse
|
|
245
253
|
* {@link loadDataProfileStatus} applies (which hides the seed of any cleared row).
|
|
@@ -385,6 +385,24 @@ export function loadDataProfileStatus(pool, analysisId) {
|
|
|
385
385
|
};
|
|
386
386
|
});
|
|
387
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* The data profile that runs in an analysis, or in each analysis when `analysisId` is absent: the row is `running`
|
|
390
|
+
* and it names its workflow. A caller checks that the workflow is active.
|
|
391
|
+
*/
|
|
392
|
+
export function queryRunningProfiles(pool, analysisId) {
|
|
393
|
+
return tryQuery("dataProfile.queryRunningProfiles", async () => {
|
|
394
|
+
const result = await pool.query({
|
|
395
|
+
text: `SELECT analysis_id, data_profile_workflow_id
|
|
396
|
+
FROM cortex_analysis_state
|
|
397
|
+
WHERE ($1::text IS NULL OR analysis_id = $1)
|
|
398
|
+
AND data_profile_status = 'running'
|
|
399
|
+
AND data_profile_workflow_id IS NOT NULL
|
|
400
|
+
ORDER BY analysis_id`,
|
|
401
|
+
values: [analysisId ?? null],
|
|
402
|
+
});
|
|
403
|
+
return result.rows.map((row) => ({ analysisId: row.analysis_id, workflowId: row.data_profile_workflow_id }));
|
|
404
|
+
});
|
|
405
|
+
}
|
|
388
406
|
/**
|
|
389
407
|
* Read an analysis's raw `seed_input_file_ids`, without the NULL-status collapse
|
|
390
408
|
* {@link loadDataProfileStatus} applies (which hides the seed of any cleared row).
|
package/dist/state/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, up
|
|
|
18
18
|
export type { InsertStepExecutionInput, RunStepCounts, SeedStepExecutionRow, UpdateStepExecutionInput } from "./step-executions.js";
|
|
19
19
|
export { insertPlan, upsertPlan, loadPlan, loadPlanRecord, queryPlanPage } from "./plans.js";
|
|
20
20
|
export type { InsertPlanInput, UpsertPlanInput, PlanRecord, PlanRowQuerier, PlanPage, PlanPageEntry } from "./plans.js";
|
|
21
|
-
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
21
|
+
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, queryRunningProfiles, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
22
22
|
export type { DataProfileStatus, DataProfileTerminalWrite, DataProfileResult, DataProfileChecked, DataProfileCompanionCompleteness, DataProfileDimension, DataProfileGroup, DataProfileGroupSlot, DataProfileFile, DataProfileMemberAnnotation, DataProfileObservation, DataProfilePartition, DataProfileProbeReport, DataProfileQualityAssessment, DataProfileQuarantine, DataProfileRecipeStep, DataProfileKind, DataProfileAxis, DataProfileInputSignature, DataProfileCoverage, } from "./data-profile.js";
|
|
23
23
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses, queryLatestRunsByAnalyses, queryRunProgressByRuns, queryRunsEndedSince, } from "./analyses-metrics.js";
|
|
24
24
|
export type { DataProfileSummary, EndedRunSummary, LatestRunSummary, RunProgressSummary } from "./analyses-metrics.js";
|
package/dist/state/index.js
CHANGED
|
@@ -13,7 +13,7 @@ export { insertRun, reserveRunById, RunDedupCollisionError, RunIdentityCollision
|
|
|
13
13
|
setRunSynthesisOutcome, queryRun, queryActiveRun, queryNonTerminalRunsByAnalysis, queryRunsForInspection, queryRunsByAnalysis, countRunsByAnalysis, queryRunsByThread, queryRunIdsByPlan, queryNewestPlanRun, } from "./runs.js";
|
|
14
14
|
export { insertStepExecution, seedStepExecutions, sweepPendingStepExecutions, updateStepExecution, queryStepsByRun, countStepsByRuns, } from "./step-executions.js";
|
|
15
15
|
export { insertPlan, upsertPlan, loadPlan, loadPlanRecord, queryPlanPage } from "./plans.js";
|
|
16
|
-
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
16
|
+
export { tryStartDataProfile, tryRetryDataProfile, tryRerunDataProfile, completeDataProfile, failDataProfile, expireStaleDataProfile, reconcileOrphanedDataProfile, clearDataProfile, completeEmptyDataProfile, loadDataProfileStatus, loadSeedInputFileIds, queryRunningProfiles, recordDataProfileWorkflowId, } from "./data-profile.js";
|
|
17
17
|
export { queryRunCountsByAnalyses, queryThreadCountsByAnalyses, queryDataProfileStatusByAnalyses, queryLatestRunsByAnalyses, queryRunProgressByRuns, queryRunsEndedSince, } from "./analyses-metrics.js";
|
|
18
18
|
// Not per-entity: one analysis's whole Postgres footprint, reached from its id
|
|
19
19
|
// alone, with the workflow ledger behind an injected seam.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pause row names what waits. A row that a wait wrote before this column keeps a NULL waiter, which reads as no
|
|
3
|
+
* waiter, and the migration deletes no row.
|
|
4
|
+
*/
|
|
5
|
+
import type { Migration } from "kysely/migration";
|
|
6
|
+
export declare const recordTheWaiterOfAPause: Migration;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pause row names what waits. A row that a wait wrote before this column keeps a NULL waiter, which reads as no
|
|
3
|
+
* waiter, and the migration deletes no row.
|
|
4
|
+
*/
|
|
5
|
+
import { sql } from "kysely";
|
|
6
|
+
export const recordTheWaiterOfAPause = {
|
|
7
|
+
async up(db) {
|
|
8
|
+
await sql `ALTER TABLE cortex_paused_work ADD COLUMN IF NOT EXISTS waiter JSONB`.execute(db);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -24,6 +24,7 @@ import { recordTheRunSessionOfAPause } from "./20261029120100_record_the_run_ses
|
|
|
24
24
|
import { leaseTheSandboxes } from "./20261030120000_lease_the_sandboxes.js";
|
|
25
25
|
import { dropTheReportDerivations } from "./20261030120100_drop_the_report_derivations.js";
|
|
26
26
|
import { keepTheReportDerivations } from "./20261030120200_keep_the_report_derivations.js";
|
|
27
|
+
import { recordTheWaiterOfAPause } from "./20261102120000_record_the_waiter_of_a_pause.js";
|
|
27
28
|
export function cortexMigrations(logger) {
|
|
28
29
|
return {
|
|
29
30
|
"20260927120000_baseline": baseline(logger),
|
|
@@ -45,5 +46,6 @@ export function cortexMigrations(logger) {
|
|
|
45
46
|
"20261030120000_lease_the_sandboxes": leaseTheSandboxes,
|
|
46
47
|
"20261030120100_drop_the_report_derivations": dropTheReportDerivations,
|
|
47
48
|
"20261030120200_keep_the_report_derivations": keepTheReportDerivations,
|
|
49
|
+
"20261102120000_record_the_waiter_of_a_pause": recordTheWaiterOfAPause,
|
|
48
50
|
};
|
|
49
51
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { ResultAsync } from "neverthrow";
|
|
6
6
|
import type { QueryConfig } from "pg";
|
|
7
7
|
import type { RunSession } from "../auth/types.js";
|
|
8
|
-
import type { PauseCause, WorkKind } from "../contracts/chat-parts.js";
|
|
8
|
+
import type { PauseCause, PauseWaiter, WorkKind } from "../contracts/chat-parts.js";
|
|
9
9
|
import { type DbError } from "../lib/db-result.js";
|
|
10
10
|
import type { Querier } from "./db.js";
|
|
11
11
|
/** One waiting workflow, as its row records it. */
|
|
@@ -13,6 +13,8 @@ export type PauseRecord = {
|
|
|
13
13
|
readonly workflowId: string;
|
|
14
14
|
readonly workId: string;
|
|
15
15
|
readonly workKind: WorkKind;
|
|
16
|
+
/** What waits. Absent for a row that a wait wrote before the waiter was recorded. */
|
|
17
|
+
readonly waiter?: PauseWaiter;
|
|
16
18
|
readonly analysisId: string;
|
|
17
19
|
readonly threadId?: string;
|
|
18
20
|
readonly state: "retrying" | "paused";
|
|
@@ -46,7 +48,7 @@ export declare function clearPause(pool: Querier, workflowId: string): ResultAsy
|
|
|
46
48
|
export declare function clearPauses(pool: Querier, workflowIds: readonly string[]): ResultAsync<void, DbError>;
|
|
47
49
|
/** Delete the rows of each workflow of the work, for a stop of the work. */
|
|
48
50
|
export declare function clearPausesOfWork(pool: Querier, workId: string): ResultAsync<void, DbError>;
|
|
49
|
-
/** The rows of the paused work of an analysis,
|
|
50
|
-
export declare function
|
|
51
|
+
/** The rows of the paused work of an analysis, or of each analysis when `analysisId` is absent. The oldest pause is first. */
|
|
52
|
+
export declare function queryPauses(pool: PauseRowQuerier, analysisId?: string): ResultAsync<PauseRecord[], DbError>;
|
|
51
53
|
/** The rows of the waiting workflows of one work in an analysis. */
|
|
52
54
|
export declare function queryPausesOfWork(pool: PauseRowQuerier, analysisId: string, workId: string): ResultAsync<PauseRecord[], DbError>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* before it waits and deletes it when the wait ends. A stop of the work deletes the rows of the work.
|
|
4
4
|
*/
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { PauseWaiterSchema } from "../contracts/schemas/chat-parts.js";
|
|
6
7
|
import { tryMutation, tryQuery } from "../lib/db-result.js";
|
|
7
8
|
const PauseCauseSchema = z.discriminatedUnion("kind", [
|
|
8
9
|
z.object({ kind: z.literal("outage") }),
|
|
@@ -13,6 +14,7 @@ const PauseRowSchema = z.object({
|
|
|
13
14
|
workflow_id: z.string(),
|
|
14
15
|
work_id: z.string(),
|
|
15
16
|
work_kind: z.enum(["turn", "run", "profile"]),
|
|
17
|
+
waiter: PauseWaiterSchema.nullable(),
|
|
16
18
|
analysis_id: z.string(),
|
|
17
19
|
thread_id: z.string().nullable(),
|
|
18
20
|
state: z.enum(["retrying", "paused"]),
|
|
@@ -33,13 +35,14 @@ const PauseRowSchema = z.object({
|
|
|
33
35
|
})
|
|
34
36
|
.nullable(),
|
|
35
37
|
});
|
|
36
|
-
const COLUMNS = "workflow_id, work_id, work_kind, analysis_id, thread_id, state, cause, attempt, retry_at, topic, paused_at, run_session";
|
|
38
|
+
const COLUMNS = "workflow_id, work_id, work_kind, waiter, analysis_id, thread_id, state, cause, attempt, retry_at, topic, paused_at, run_session";
|
|
37
39
|
function recordOf(row) {
|
|
38
40
|
const parsed = PauseRowSchema.parse(row);
|
|
39
41
|
return {
|
|
40
42
|
workflowId: parsed.workflow_id,
|
|
41
43
|
workId: parsed.work_id,
|
|
42
44
|
workKind: parsed.work_kind,
|
|
45
|
+
waiter: parsed.waiter ?? undefined,
|
|
43
46
|
analysisId: parsed.analysis_id,
|
|
44
47
|
threadId: parsed.thread_id ?? undefined,
|
|
45
48
|
state: parsed.state,
|
|
@@ -54,15 +57,16 @@ function recordOf(row) {
|
|
|
54
57
|
/** Write the row of a waiting workflow. A workflow waits in one pause at a time, thus a new pause replaces its row. */
|
|
55
58
|
export function recordPause(pool, record) {
|
|
56
59
|
return tryMutation("pausedWork.record", () => pool.query(`INSERT INTO cortex_paused_work (${COLUMNS})
|
|
57
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
60
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
58
61
|
ON CONFLICT (workflow_id) DO UPDATE SET
|
|
59
|
-
work_id = EXCLUDED.work_id, work_kind = EXCLUDED.work_kind, analysis_id = EXCLUDED.analysis_id,
|
|
62
|
+
work_id = EXCLUDED.work_id, work_kind = EXCLUDED.work_kind, waiter = EXCLUDED.waiter, analysis_id = EXCLUDED.analysis_id,
|
|
60
63
|
thread_id = EXCLUDED.thread_id, state = EXCLUDED.state, cause = EXCLUDED.cause, attempt = EXCLUDED.attempt,
|
|
61
64
|
retry_at = EXCLUDED.retry_at, topic = EXCLUDED.topic, paused_at = EXCLUDED.paused_at,
|
|
62
65
|
run_session = EXCLUDED.run_session`, [
|
|
63
66
|
record.workflowId,
|
|
64
67
|
record.workId,
|
|
65
68
|
record.workKind,
|
|
69
|
+
record.waiter === undefined ? null : JSON.stringify(record.waiter),
|
|
66
70
|
record.analysisId,
|
|
67
71
|
record.threadId ?? null,
|
|
68
72
|
record.state,
|
|
@@ -86,10 +90,10 @@ export function clearPauses(pool, workflowIds) {
|
|
|
86
90
|
export function clearPausesOfWork(pool, workId) {
|
|
87
91
|
return tryMutation("pausedWork.clearWork", () => pool.query("DELETE FROM cortex_paused_work WHERE work_id = $1", [workId])).map(() => undefined);
|
|
88
92
|
}
|
|
89
|
-
/** The rows of the paused work of an analysis,
|
|
90
|
-
export function
|
|
91
|
-
return tryQuery("pausedWork.
|
|
92
|
-
const { rows } = await pool.query(`SELECT ${COLUMNS} FROM cortex_paused_work WHERE analysis_id = $1 ORDER BY paused_at, workflow_id`, [analysisId]);
|
|
93
|
+
/** The rows of the paused work of an analysis, or of each analysis when `analysisId` is absent. The oldest pause is first. */
|
|
94
|
+
export function queryPauses(pool, analysisId) {
|
|
95
|
+
return tryQuery("pausedWork.list", async () => {
|
|
96
|
+
const { rows } = await pool.query(`SELECT ${COLUMNS} FROM cortex_paused_work WHERE ($1::text IS NULL OR analysis_id = $1) ORDER BY paused_at, workflow_id`, [analysisId ?? null]);
|
|
93
97
|
return rows.map(recordOf);
|
|
94
98
|
});
|
|
95
99
|
}
|
package/dist/state/runs.d.ts
CHANGED
|
@@ -99,9 +99,10 @@ export type RunTextFilter = {
|
|
|
99
99
|
* in step: a new non-terminal status added to one and not the other makes a live run invisible to
|
|
100
100
|
* exactly one of its readers.
|
|
101
101
|
*
|
|
102
|
-
* With `q`, only the runs that the text filter selects are members.
|
|
102
|
+
* With `q`, only the runs that the text filter selects are members. With no `analysisId`, the runs of each
|
|
103
|
+
* analysis are members, and each row names its analysis.
|
|
103
104
|
*/
|
|
104
|
-
export declare function queryActiveRunsByAnalysis(pool: Querier, analysisId: string, { q }?: RunTextFilter): ResultAsync<CortexRunRow[], DbError>;
|
|
105
|
+
export declare function queryActiveRunsByAnalysis(pool: Querier, analysisId: string | undefined, { q }?: RunTextFilter): ResultAsync<CortexRunRow[], DbError>;
|
|
105
106
|
/**
|
|
106
107
|
* How long a reserved run may wait for its workflow before {@link reconcileOrphanedRuns} fails it. A launch writes
|
|
107
108
|
* the workflow row seconds after the reservation, and the margin covers a slow authorization gate of a managed host.
|
package/dist/state/runs.js
CHANGED
|
@@ -165,18 +165,19 @@ function runTextCondition(placeholder) {
|
|
|
165
165
|
* in step: a new non-terminal status added to one and not the other makes a live run invisible to
|
|
166
166
|
* exactly one of its readers.
|
|
167
167
|
*
|
|
168
|
-
* With `q`, only the runs that the text filter selects are members.
|
|
168
|
+
* With `q`, only the runs that the text filter selects are members. With no `analysisId`, the runs of each
|
|
169
|
+
* analysis are members, and each row names its analysis.
|
|
169
170
|
*/
|
|
170
171
|
export function queryActiveRunsByAnalysis(pool, analysisId, { q } = {}) {
|
|
171
172
|
return tryQuery("runs.queryActiveRunsByAnalysis", async () => {
|
|
172
|
-
const values = [analysisId];
|
|
173
|
+
const values = [analysisId ?? null];
|
|
173
174
|
const text = q === undefined ? "" : `AND ${runTextCondition(`$${values.push(q)}`)}`;
|
|
174
175
|
const result = await pool.query({
|
|
175
176
|
text: `SELECT run_id, analysis_id, thread_id, workflow_name,
|
|
176
177
|
status, started_at, completed_at, error, synthesis_status, synthesis_reason, parts,
|
|
177
178
|
mandate_id, plan_id -- oss-core-managed-ok: run-mandate ledger (nullable; OSS leaves null)
|
|
178
179
|
FROM cortex_runs
|
|
179
|
-
WHERE analysis_id = $1
|
|
180
|
+
WHERE ($1::text IS NULL OR analysis_id = $1)
|
|
180
181
|
AND status = 'running'
|
|
181
182
|
${text}
|
|
182
183
|
ORDER BY started_at DESC`,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* `workspace_search` documents, so a consumer needs no id parsing to tell them apart.
|
|
22
22
|
*/
|
|
23
23
|
import { presentFirst } from "../lib/present.js";
|
|
24
|
+
import { slugify } from "../lib/slug.js";
|
|
24
25
|
import { dimensionCardinalities, profileDimensions, profileFileRecords, profileGroups } from "../app/data-profile-view.js";
|
|
25
26
|
/** Entity entries built for a legacy profile. Past this the entity tier is a liability, not a search aid. */
|
|
26
27
|
export const MAX_ENTITY_ENTRIES = 20_000;
|
|
@@ -36,25 +37,10 @@ export const MAX_ENTITY_ENTRIES = 20_000;
|
|
|
36
37
|
export const PROFILE_INDEX_TYPES = ["input-group", "input-dimension", "input", "input-kind"];
|
|
37
38
|
/** Group names named in one legacy entity entry's text. */
|
|
38
39
|
const MAX_GROUPS_IN_ENTITY_TEXT = 6;
|
|
40
|
+
/** The profile entry slug: capped at 60 characters, and "unnamed" when the name holds no slug character. */
|
|
39
41
|
function slug(value) {
|
|
40
|
-
|
|
41
|
-
let inRun = false;
|
|
42
|
-
for (const char of value.toLowerCase()) {
|
|
43
|
-
if (out.length >= SLUG_MAX_LENGTH)
|
|
44
|
-
break;
|
|
45
|
-
const isSlugChar = (char >= "a" && char <= "z") || (char >= "0" && char <= "9");
|
|
46
|
-
if (!isSlugChar) {
|
|
47
|
-
inRun = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (inRun && out.length > 0)
|
|
51
|
-
out += "-";
|
|
52
|
-
inRun = false;
|
|
53
|
-
out += char;
|
|
54
|
-
}
|
|
55
|
-
return out.slice(0, SLUG_MAX_LENGTH) || "unnamed";
|
|
42
|
+
return slugify(value, { maxLength: 60, fallback: "unnamed" });
|
|
56
43
|
}
|
|
57
|
-
const SLUG_MAX_LENGTH = 60;
|
|
58
44
|
function slotSummary(slot) {
|
|
59
45
|
return { id: slot.id, location: slot.location, tokenClass: slot.tokenClass, distinctValues: slot.distinctValues };
|
|
60
46
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { assessCompleteness } from "../input-scan/companions.js";
|
|
20
20
|
import { presentAt, presentFirst, presentGet } from "../lib/present.js";
|
|
21
|
+
import { slugify } from "../lib/slug.js";
|
|
21
22
|
import { buildSetMenu } from "../input-scan/menu.js";
|
|
22
23
|
import { MAX_SLOT_SAMPLE_VALUES } from "../input-scan/tuning.js";
|
|
23
24
|
import { dimensionScope } from "../contracts/profile-vocabulary.js";
|
|
@@ -35,25 +36,10 @@ export const UNCLASSIFIED_GROUP_ID = "unclassified";
|
|
|
35
36
|
export const MAX_UNCLASSIFIED_RECIPE_PATHS = 1000;
|
|
36
37
|
/** Contested paths named in the accounting before the count stands alone. */
|
|
37
38
|
export const MAX_CONTESTED_SAMPLE = 10;
|
|
39
|
+
/** The recipe group slug: capped at 60 characters, and "group" when the label holds no slug character. */
|
|
38
40
|
function slug(value) {
|
|
39
|
-
|
|
40
|
-
let inRun = false;
|
|
41
|
-
for (const char of value.toLowerCase()) {
|
|
42
|
-
if (out.length >= SLUG_MAX_LENGTH)
|
|
43
|
-
break;
|
|
44
|
-
const isSlugChar = (char >= "a" && char <= "z") || (char >= "0" && char <= "9");
|
|
45
|
-
if (!isSlugChar) {
|
|
46
|
-
inRun = true;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (inRun && out.length > 0)
|
|
50
|
-
out += "-";
|
|
51
|
-
inRun = false;
|
|
52
|
-
out += char;
|
|
53
|
-
}
|
|
54
|
-
return out.slice(0, SLUG_MAX_LENGTH) || "group";
|
|
41
|
+
return slugify(value, { maxLength: 60, fallback: "group" });
|
|
55
42
|
}
|
|
56
|
-
const SLUG_MAX_LENGTH = 60;
|
|
57
43
|
function fileCountOf(members) {
|
|
58
44
|
return members.reduce((total, member) => total + 1 + member.companions.length, 0);
|
|
59
45
|
}
|
|
@@ -418,6 +418,7 @@ export async function runDataProfileBody(input, deps) {
|
|
|
418
418
|
pool: deps.pool,
|
|
419
419
|
workId: workflowId,
|
|
420
420
|
workKind: "profile",
|
|
421
|
+
waiter: { kind: "profile" },
|
|
421
422
|
analysisId,
|
|
422
423
|
emit: (part) => DBOS.writeStream("events", part),
|
|
423
424
|
retryDelaysMs: deps.pauseRetryDelaysMs,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* humanbase — the GIANT tissue-specific functional networks of HumanBase.
|
|
3
|
+
*
|
|
4
|
+
* One database, three questions, thus one tool with an `action` (the barrel
|
|
5
|
+
* rule in `index.ts`). The input is a flat object with an `action` enum, not a
|
|
6
|
+
* `z.discriminatedUnion`, because model tool calling needs a top-level
|
|
7
|
+
* `"type":"object"`. The argument of each action is optional in the schema, and
|
|
8
|
+
* a `.refine` makes it required for its action.
|
|
9
|
+
*
|
|
10
|
+
* The GIANT version is not an input: the client derives it from the slug.
|
|
11
|
+
*/
|
|
12
|
+
import { type HumanBaseContext, type HumanBaseNetwork, type HumanBaseScoredContext } from "../lib/humanbase-client.js";
|
|
13
|
+
export type HumanBaseOutput = ({
|
|
14
|
+
found: true;
|
|
15
|
+
} & HumanBaseNetwork) | {
|
|
16
|
+
found: false;
|
|
17
|
+
context: string;
|
|
18
|
+
} | {
|
|
19
|
+
contexts: HumanBaseScoredContext[];
|
|
20
|
+
totalFound: number;
|
|
21
|
+
} | {
|
|
22
|
+
contexts: HumanBaseContext[];
|
|
23
|
+
totalFound: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const humanbaseTool: import("../define-tool.js").Tool<{
|
|
26
|
+
action: "contexts" | "network" | "relevant";
|
|
27
|
+
entrez?: number[] | undefined;
|
|
28
|
+
context?: string | undefined;
|
|
29
|
+
nodeSize?: number | undefined;
|
|
30
|
+
query?: string | undefined;
|
|
31
|
+
limit?: number | undefined;
|
|
32
|
+
}, HumanBaseOutput>;
|