@inflexa-ai/harness 0.48.3 → 0.48.4
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/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/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/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/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/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/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/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
|
@@ -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,
|
|
@@ -122,6 +122,10 @@ const BASELINE_EXPRESSION_PAGE_SIZE = 3000;
|
|
|
122
122
|
// that resolves empty — an unknown/retired id yields `{"target": null}`, which a
|
|
123
123
|
// bare `.optional()` rejects, turning a clean "not found" into a thrown error.
|
|
124
124
|
const DatatypeScoreSchema = z.object({ id: z.string().optional(), score: z.number().optional() });
|
|
125
|
+
/** The datatype-scores list follows the same nullable-optional reading rule as its row, and may be absent or null. */
|
|
126
|
+
const scoresField = z.array(DatatypeScoreSchema).nullable().optional();
|
|
127
|
+
/** The paged rows list of an association query: the API may omit or null the whole envelope. */
|
|
128
|
+
const rowsEnvelope = (item) => z.object({ rows: z.array(item).nullable().optional() });
|
|
125
129
|
export const TargetAssociationsDataSchema = z.object({
|
|
126
130
|
target: z
|
|
127
131
|
.object({
|
|
@@ -137,18 +141,11 @@ export const TargetAssociationsDataSchema = z.object({
|
|
|
137
141
|
.optional(),
|
|
138
142
|
associations: z
|
|
139
143
|
.object({
|
|
140
|
-
associatedDiseases: z
|
|
141
|
-
.object({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
score: z.number(),
|
|
146
|
-
datatypeScores: z.array(DatatypeScoreSchema).nullable().optional(),
|
|
147
|
-
}))
|
|
148
|
-
.nullable()
|
|
149
|
-
.optional(),
|
|
150
|
-
})
|
|
151
|
-
.optional(),
|
|
144
|
+
associatedDiseases: rowsEnvelope(z.object({
|
|
145
|
+
disease: z.object({ id: z.string(), name: z.string() }),
|
|
146
|
+
score: z.number(),
|
|
147
|
+
datatypeScores: scoresField,
|
|
148
|
+
})).optional(),
|
|
152
149
|
})
|
|
153
150
|
.nullable()
|
|
154
151
|
.optional(),
|
|
@@ -158,21 +155,14 @@ const DiseaseAssociationsDataSchema = z.object({
|
|
|
158
155
|
.object({
|
|
159
156
|
id: z.string().optional(),
|
|
160
157
|
name: z.string().optional(),
|
|
161
|
-
associatedTargets: z
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.array(z.object({
|
|
165
|
-
target: z
|
|
166
|
-
.object({ id: z.string().optional(), approvedSymbol: z.string().optional(), approvedName: z.string().optional() })
|
|
167
|
-
.nullable()
|
|
168
|
-
.optional(),
|
|
169
|
-
score: z.number(),
|
|
170
|
-
datatypeScores: z.array(DatatypeScoreSchema).nullable().optional(),
|
|
171
|
-
}))
|
|
158
|
+
associatedTargets: rowsEnvelope(z.object({
|
|
159
|
+
target: z
|
|
160
|
+
.object({ id: z.string().optional(), approvedSymbol: z.string().optional(), approvedName: z.string().optional() })
|
|
172
161
|
.nullable()
|
|
173
162
|
.optional(),
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
score: z.number(),
|
|
164
|
+
datatypeScores: scoresField,
|
|
165
|
+
})).optional(),
|
|
176
166
|
})
|
|
177
167
|
.nullable()
|
|
178
168
|
.optional(),
|
|
@@ -34,9 +34,8 @@ import type { DraftDocument } from "../../report-model/draft.js";
|
|
|
34
34
|
import type { ReportSnapshot } from "../../report-model/reference-resolver.js";
|
|
35
35
|
import type { DerivationRecord } from "../../state/report-session-state.js";
|
|
36
36
|
import { type Tool } from "../define-tool.js";
|
|
37
|
-
/** The flat destination fields
|
|
37
|
+
/** The flat destination fields of the add, plus the payload of the new block. */
|
|
38
38
|
declare const addBlockInput: z.ZodObject<{
|
|
39
|
-
block: z.ZodUnion<readonly [z.ZodType<import("../../report-model/authoring-grammar.js").AuthoringBlock, unknown, z.core.$ZodTypeInternals<import("../../report-model/authoring-grammar.js").AuthoringBlock, unknown>>, z.ZodUnknown]>;
|
|
40
39
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
40
|
place: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
42
41
|
end: "end";
|
|
@@ -44,6 +43,7 @@ declare const addBlockInput: z.ZodObject<{
|
|
|
44
43
|
}>>>;
|
|
45
44
|
before: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
45
|
after: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
block: z.ZodUnion<readonly [z.ZodType<import("../../report-model/authoring-grammar.js").AuthoringBlock, unknown, z.core.$ZodTypeInternals<import("../../report-model/authoring-grammar.js").AuthoringBlock, unknown>>, z.ZodUnknown]>;
|
|
47
47
|
}, z.core.$strip>;
|
|
48
48
|
/**
|
|
49
49
|
* The change payload is a title for a section target, or a block for an atom target, and exactly one of
|
|
@@ -59,7 +59,6 @@ declare const removeBlockInput: z.ZodObject<{
|
|
|
59
59
|
targetId: z.ZodString;
|
|
60
60
|
}, z.core.$strip>;
|
|
61
61
|
declare const moveBlockInput: z.ZodObject<{
|
|
62
|
-
targetId: z.ZodString;
|
|
63
62
|
parentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
64
63
|
place: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
65
64
|
end: "end";
|
|
@@ -67,6 +66,7 @@ declare const moveBlockInput: z.ZodObject<{
|
|
|
67
66
|
}>>>;
|
|
68
67
|
before: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
69
68
|
after: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
69
|
+
targetId: z.ZodString;
|
|
70
70
|
}, z.core.$strip>;
|
|
71
71
|
declare const readOutlineInput: z.ZodObject<{}, z.core.$strip>;
|
|
72
72
|
/** The read names its target with the same field name as each mutation, thus one id key serves them all. */
|
|
@@ -70,12 +70,16 @@ const DESTINATION_NOTES = {
|
|
|
70
70
|
after: "The id of a sibling block. The block lands directly after it.",
|
|
71
71
|
};
|
|
72
72
|
/** The flat destination fields. An anchor is `before` or `after`, and a place is `start` or `end`. */
|
|
73
|
-
const
|
|
74
|
-
block: blockPayload,
|
|
73
|
+
const destinationFields = {
|
|
75
74
|
parentId: absentable(z.string()).describe(DESTINATION_NOTES.parentId),
|
|
76
75
|
place: absentable(z.enum(["start", "end"])).describe(DESTINATION_NOTES.place),
|
|
77
76
|
before: absentable(z.string()).describe(DESTINATION_NOTES.before),
|
|
78
77
|
after: absentable(z.string()).describe(DESTINATION_NOTES.after),
|
|
78
|
+
};
|
|
79
|
+
/** The flat destination fields of the add, plus the payload of the new block. */
|
|
80
|
+
const addBlockInput = z.object({
|
|
81
|
+
block: blockPayload,
|
|
82
|
+
...destinationFields,
|
|
79
83
|
});
|
|
80
84
|
/**
|
|
81
85
|
* The change payload is a title for a section target, or a block for an atom target, and exactly one of
|
|
@@ -92,10 +96,7 @@ const removeBlockInput = z.object({
|
|
|
92
96
|
});
|
|
93
97
|
const moveBlockInput = z.object({
|
|
94
98
|
targetId: z.string().describe("The id of the block to move, as the outline gives it."),
|
|
95
|
-
|
|
96
|
-
place: absentable(z.enum(["start", "end"])).describe(DESTINATION_NOTES.place),
|
|
97
|
-
before: absentable(z.string()).describe(DESTINATION_NOTES.before),
|
|
98
|
-
after: absentable(z.string()).describe(DESTINATION_NOTES.after),
|
|
99
|
+
...destinationFields,
|
|
99
100
|
});
|
|
100
101
|
const readOutlineInput = z.object({});
|
|
101
102
|
/** The read names its target with the same field name as each mutation, thus one id key serves them all. */
|
|
@@ -260,7 +260,9 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
|
|
|
260
260
|
"drives a multi-step research sub-agent (up to 40 model turns) over live " +
|
|
261
261
|
"literature search, so it is slow; a fact that one lookup answers does " +
|
|
262
262
|
"not need it.\n" +
|
|
263
|
-
"RETURNS an AnalogyReport envelope
|
|
263
|
+
"RETURNS an AnalogyReport envelope. The analogy block shows the full " +
|
|
264
|
+
"report to the user, thus your reply names the analogies that matter by " +
|
|
265
|
+
"their `analogyTitle` and does not copy the report. An " +
|
|
264
266
|
"analogy the search could not fill carries a `coverage` tag " +
|
|
265
267
|
"(`queried_no_data` | `search_failed` | `not_loaded`) with an empty " +
|
|
266
268
|
"`solutions` array — a partial result, not an error. A top-level `error` " +
|
|
@@ -273,7 +275,13 @@ export function createGenerateAnalogyReportTool(deps, registerSubAgent) {
|
|
|
273
275
|
view: "none",
|
|
274
276
|
describeCall: "none",
|
|
275
277
|
startsWorkflow: true,
|
|
276
|
-
execute: async (input, ctx) =>
|
|
278
|
+
execute: async (input, ctx) => {
|
|
279
|
+
const report = await research(input, ctx);
|
|
280
|
+
// The execute runs in the body of the turn, where a stream write is checkpointed, thus a replay gives no second
|
|
281
|
+
// part. The root session keeps the part in the stored display, which drops each frame of a sub-agent.
|
|
282
|
+
await ctx.emit({ type: "data-analogy-report", source: ctx.session.provenance, data: { id: ctx.invocationId, report } });
|
|
283
|
+
return ok(report);
|
|
284
|
+
},
|
|
277
285
|
});
|
|
278
286
|
}
|
|
279
287
|
/** The envelope of a run that recorded no outcome, also after its salvage. */
|
|
@@ -590,17 +590,18 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
590
590
|
// the run authorization is already minted and rides in `input.runSession`.
|
|
591
591
|
const runId = input.runSession.runFrame.runId;
|
|
592
592
|
const workflowId = runId;
|
|
593
|
-
const
|
|
593
|
+
const pauseAs = (waiter) => workPause({
|
|
594
594
|
pool: deps.pool,
|
|
595
595
|
workId: runId,
|
|
596
596
|
workKind: "run",
|
|
597
|
+
waiter,
|
|
597
598
|
analysisId: input.analysisId,
|
|
598
599
|
emit: emitStreamPart,
|
|
599
600
|
retryDelaysMs: deps.pauseRetryDelaysMs,
|
|
600
601
|
run: { session: input.runSession, authorizer: deps.runAuthorizer },
|
|
601
602
|
logger,
|
|
602
603
|
});
|
|
603
|
-
const init = await validateAndInit(input, runId, deps,
|
|
604
|
+
const init = await validateAndInit(input, runId, deps, pauseAs({ kind: "run" }));
|
|
604
605
|
if (init.kind === "joined-existing") {
|
|
605
606
|
return {
|
|
606
607
|
runId: init.runId,
|
|
@@ -728,7 +729,7 @@ export async function runExecuteAnalysisBody(input, deps) {
|
|
|
728
729
|
completedSteps: [...final.completed],
|
|
729
730
|
session: input.runSession,
|
|
730
731
|
deps,
|
|
731
|
-
pause,
|
|
732
|
+
pause: pauseAs({ kind: "synthesis" }),
|
|
732
733
|
});
|
|
733
734
|
synthesisFindings = synthOut.findings;
|
|
734
735
|
synthesisOutcome = { status: synthOut.synthesisStatus, reason: synthOut.synthesisReason };
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* with the message of a resume, or with a stop. The server never sends that message by itself.
|
|
12
12
|
*/
|
|
13
13
|
import type { RunSession } from "../auth/types.js";
|
|
14
|
-
import type { PauseCause, WorkKind, PausePart } from "../contracts/chat-parts.js";
|
|
14
|
+
import type { PauseCause, PauseEnd, PauseWaiter, WorkKind, PausePart } from "../contracts/chat-parts.js";
|
|
15
15
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
16
16
|
import type { Logger } from "../lib/logger.js";
|
|
17
17
|
import type { Querier } from "../state/db.js";
|
|
@@ -29,14 +29,14 @@ export declare const HOLD_MESSAGE: {
|
|
|
29
29
|
export declare function pauseTopic(pauseId: string): string;
|
|
30
30
|
/** The cause of a pause in a failure, or `undefined` for a failure that no person or time can repair. */
|
|
31
31
|
export declare function pauseCauseOf(failure: unknown): PauseCause | undefined;
|
|
32
|
-
/** How a wait ended: the deadline of `retrying`, the message of a resume, or a stop of the work. */
|
|
33
|
-
export type PauseEnd = "retry" | "resume" | "abort";
|
|
34
32
|
/** The work that a pause belongs to, and where the part of the pause goes. */
|
|
35
33
|
export type PauseScope = {
|
|
36
34
|
readonly pool: Querier;
|
|
37
35
|
/** The turn id, the run id, or the id of the profile workflow. A sub-agent names its turn, and a step its run. */
|
|
38
36
|
readonly workId: string;
|
|
39
37
|
readonly workKind: WorkKind;
|
|
38
|
+
/** What waits in this workflow. */
|
|
39
|
+
readonly waiter: PauseWaiter;
|
|
40
40
|
readonly analysisId: string;
|
|
41
41
|
readonly threadId?: string;
|
|
42
42
|
/** Writes the part of the pause to the stream of the work. */
|
package/dist/workflows/pause.js
CHANGED
|
@@ -96,10 +96,11 @@ export function workPause(scope) {
|
|
|
96
96
|
const delayMs = cause.kind === "outage" ? delays[attempt] : undefined;
|
|
97
97
|
const state = delayMs === undefined ? "paused" : "retrying";
|
|
98
98
|
const pauseId = await DBOS.randomUUID();
|
|
99
|
-
const
|
|
100
|
-
const
|
|
99
|
+
const pausedAtMs = await DBOS.now();
|
|
100
|
+
const pausedAt = new Date(pausedAtMs).toISOString();
|
|
101
|
+
const retryAt = delayMs === undefined ? undefined : new Date(pausedAtMs + delayMs).toISOString();
|
|
101
102
|
const topic = pauseTopic(pauseId);
|
|
102
|
-
const work = { workId: scope.workId, workKind: scope.workKind };
|
|
103
|
+
const work = { workId: scope.workId, workKind: scope.workKind, waiter: scope.waiter };
|
|
103
104
|
await DBOS.runStep(async () => unwrapOrThrow(await recordPause(scope.pool, {
|
|
104
105
|
workflowId,
|
|
105
106
|
...work,
|
|
@@ -110,7 +111,7 @@ export function workPause(scope) {
|
|
|
110
111
|
attempt,
|
|
111
112
|
retryAt,
|
|
112
113
|
topic,
|
|
113
|
-
pausedAt
|
|
114
|
+
pausedAt,
|
|
114
115
|
runSession: scope.run?.session,
|
|
115
116
|
})), { name: "pause.record" });
|
|
116
117
|
// Only a person ends a wait in `paused`, thus the credential stops until the renewal of the resume.
|
|
@@ -122,7 +123,7 @@ export function workPause(scope) {
|
|
|
122
123
|
};
|
|
123
124
|
if (state === "paused")
|
|
124
125
|
await suspendCredential("pause.suspend");
|
|
125
|
-
await scope.emit({ type: "data-pause", id: workflowId, ...work, state, cause, attempt, retryAt });
|
|
126
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state, cause, pausedAt, attempt, retryAt });
|
|
126
127
|
log.warn("work paused", { workflowId, ...work, state, cause: cause.kind, attempt });
|
|
127
128
|
let end;
|
|
128
129
|
const untrack = trackWorkPaused({ workKind: scope.workKind });
|
|
@@ -139,11 +140,11 @@ export function workPause(scope) {
|
|
|
139
140
|
cause,
|
|
140
141
|
attempt,
|
|
141
142
|
topic,
|
|
142
|
-
pausedAt
|
|
143
|
+
pausedAt,
|
|
143
144
|
runSession: scope.run?.session,
|
|
144
145
|
})), { name: "pause.hold" });
|
|
145
146
|
await suspendCredential("pause.hold.suspend");
|
|
146
|
-
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "paused", cause, attempt });
|
|
147
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "paused", cause, pausedAt, attempt });
|
|
147
148
|
log.info("work held", { workflowId, ...work, attempt });
|
|
148
149
|
const held = await waitForMessage(topic, undefined, signal);
|
|
149
150
|
end = held === "hold" ? "resume" : held;
|
|
@@ -155,8 +156,13 @@ export function workPause(scope) {
|
|
|
155
156
|
finally {
|
|
156
157
|
untrack();
|
|
157
158
|
}
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
// The time is read inside the step, thus a recovery replays the recorded time and the end keeps one time.
|
|
160
|
+
// The replayed step output is `undefined` when the recorded step has no time.
|
|
161
|
+
const endedAt = await DBOS.runStep(async () => {
|
|
162
|
+
unwrapOrThrow(await clearPause(scope.pool, workflowId));
|
|
163
|
+
return new Date().toISOString();
|
|
164
|
+
}, { name: "pause.clear" });
|
|
165
|
+
await scope.emit({ type: "data-pause", id: workflowId, ...work, state: "running", cause, pausedAt, endedAt, end });
|
|
160
166
|
log.info("pause ended", { workflowId, ...work, end });
|
|
161
167
|
return end;
|
|
162
168
|
};
|
|
@@ -157,6 +157,7 @@ export async function runSandboxStepBody(input, deps) {
|
|
|
157
157
|
pool: deps.pool,
|
|
158
158
|
workId: input.runId,
|
|
159
159
|
workKind: "run",
|
|
160
|
+
waiter: { kind: "step", stepId: input.stepId },
|
|
160
161
|
analysisId: input.analysisId,
|
|
161
162
|
emit: safeEmit,
|
|
162
163
|
retryDelaysMs: deps.pauseRetryDelaysMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.4",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness — an agent loop on the AI SDK, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
"build": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
38
38
|
"design:fixture": "bun scripts/render-fixture.ts",
|
|
39
39
|
"design:gallery": "bun scripts/render-gallery.ts",
|
|
40
|
-
"
|
|
41
|
-
"format
|
|
40
|
+
"dupes": "jscpd src scripts --exit-code --fail-on-empty",
|
|
41
|
+
"format": "oxfmt src/",
|
|
42
|
+
"format:check": "oxfmt --check src/",
|
|
43
|
+
"format:file": "oxfmt",
|
|
42
44
|
"gallery:data": "bash scripts/gallery-data.sh",
|
|
43
45
|
"lint": "bun --bun oxlint",
|
|
44
46
|
"lint:python": "ruff check src/input-scan/decoder && ruff format --check src/input-scan/decoder",
|
|
@@ -119,9 +121,10 @@
|
|
|
119
121
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
120
122
|
"csstree-validator": "^4.0.1",
|
|
121
123
|
"html-validate": "^11.6.2",
|
|
124
|
+
"jscpd": "5.4.2",
|
|
125
|
+
"oxfmt": "0.71.0",
|
|
122
126
|
"oxlint": "1.85.0",
|
|
123
127
|
"oxlint-tsgolint": "7.0.2003",
|
|
124
|
-
"prettier": "^3.9.5",
|
|
125
128
|
"testcontainers": "^10.28.0",
|
|
126
129
|
"typescript": "^6.0.3"
|
|
127
130
|
},
|