@inflexa-ai/harness 0.16.0 → 0.18.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/conversation-agent.d.ts +3 -0
- package/dist/agents/conversation-agent.js +5 -2
- package/dist/agents/report-builder.d.ts +2 -0
- package/dist/agents/report-builder.js +2 -0
- package/dist/agents/sandbox/shared.d.ts +3 -0
- package/dist/agents/sandbox/shared.js +5 -0
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn.d.ts +2 -0
- package/dist/app/chat-turn.js +11 -2
- package/dist/app/spawn-report-session.d.ts +85 -0
- package/dist/app/spawn-report-session.js +83 -0
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +2 -1
- package/dist/citations/__fixtures__/resolver.d.ts +3 -0
- package/dist/citations/__fixtures__/resolver.js +9 -0
- package/dist/citations/aggregate.d.ts +3 -0
- package/dist/citations/aggregate.js +44 -0
- package/dist/citations/clients/arxiv.d.ts +6 -0
- package/dist/citations/clients/arxiv.js +34 -0
- package/dist/citations/clients/common.d.ts +11 -0
- package/dist/citations/clients/common.js +43 -0
- package/dist/citations/clients/crossref.d.ts +8 -0
- package/dist/citations/clients/crossref.js +98 -0
- package/dist/citations/clients/doi-registry.d.ts +3 -0
- package/dist/citations/clients/doi-registry.js +95 -0
- package/dist/citations/clients/pubmed.d.ts +6 -0
- package/dist/citations/clients/pubmed.js +83 -0
- package/dist/citations/clients/semantic-scholar.d.ts +6 -0
- package/dist/citations/clients/semantic-scholar.js +60 -0
- package/dist/citations/compare.d.ts +9 -0
- package/dist/citations/compare.js +72 -0
- package/dist/citations/match.d.ts +11 -0
- package/dist/citations/match.js +173 -0
- package/dist/citations/normalize.d.ts +31 -0
- package/dist/citations/normalize.js +187 -0
- package/dist/citations/plan.d.ts +2 -0
- package/dist/citations/plan.js +59 -0
- package/dist/citations/resolve.d.ts +28 -0
- package/dist/citations/resolve.js +238 -0
- package/dist/citations/types.d.ts +583 -0
- package/dist/citations/types.js +129 -0
- package/dist/contracts/chat-events.d.ts +9 -0
- package/dist/contracts/index.d.ts +6 -2
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/organ-system.d.ts +49 -0
- package/dist/contracts/organ-system.js +67 -0
- package/dist/contracts/report-blocks.d.ts +564 -0
- package/dist/contracts/report-blocks.js +117 -0
- package/dist/contracts/report-reference.d.ts +459 -0
- package/dist/contracts/report-reference.js +256 -0
- package/dist/contracts/schemas/chat-events.d.ts +2 -0
- package/dist/contracts/schemas/chat-events.js +2 -0
- package/dist/contracts/schemas/chat-parts.d.ts +8 -8
- package/dist/contracts/severity.d.ts +15 -0
- package/dist/contracts/severity.js +10 -0
- package/dist/contracts/target-dossier.d.ts +8673 -12970
- package/dist/contracts/target-dossier.js +534 -591
- package/dist/data/safety-panel-schema.d.ts +18 -5
- package/dist/data/safety-panel-schema.js +4 -4
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/report-runner.js +1 -3
- package/dist/execution/run-synthesis.d.ts +3 -0
- package/dist/execution/run-synthesis.js +3 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +22 -0
- package/dist/lib/async-utils.d.ts +16 -4
- package/dist/lib/async-utils.js +20 -15
- package/dist/lib/cache.d.ts +8 -0
- package/dist/lib/cache.js +35 -0
- package/dist/lib/host-concurrency.js +1 -0
- package/dist/lib/rate-limit.d.ts +35 -0
- package/dist/lib/rate-limit.js +121 -0
- package/dist/literature/sources/arxiv.d.ts +22 -0
- package/dist/literature/sources/arxiv.js +103 -0
- package/dist/literature/sources/http.d.ts +28 -0
- package/dist/literature/sources/http.js +97 -0
- package/dist/literature/sources/pubmed.d.ts +68 -0
- package/dist/literature/sources/pubmed.js +266 -0
- package/dist/literature/sources/semantic-scholar.d.ts +24 -0
- package/dist/literature/sources/semantic-scholar.js +71 -0
- package/dist/loop/__fixtures__/scripted-provider.js +23 -2
- package/dist/loop/run-agent.js +58 -10
- package/dist/loop/tool-detail.d.ts +40 -0
- package/dist/loop/tool-detail.js +35 -3
- package/dist/loop/types.d.ts +13 -0
- package/dist/memory/thread-history.d.ts +10 -0
- package/dist/memory/thread-history.js +12 -1
- package/dist/memory/thread-store.d.ts +142 -25
- package/dist/memory/thread-store.js +228 -40
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +2 -0
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
- package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
- package/dist/prompts/target-assessment/investigation/index.js +3 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
- package/dist/providers/__fixtures__/session.d.ts +5 -4
- package/dist/providers/__fixtures__/session.js +4 -4
- package/dist/report-model/block-walk.d.ts +56 -0
- package/dist/report-model/block-walk.js +89 -0
- package/dist/report-model/draft-finish.d.ts +60 -0
- package/dist/report-model/draft-finish.js +56 -0
- package/dist/report-model/draft-operations.d.ts +125 -0
- package/dist/report-model/draft-operations.js +341 -0
- package/dist/report-model/draft-read.d.ts +59 -0
- package/dist/report-model/draft-read.js +109 -0
- package/dist/report-model/draft.d.ts +51 -0
- package/dist/report-model/draft.js +32 -0
- package/dist/report-model/fixture-resolver.d.ts +8 -0
- package/dist/report-model/fixture-resolver.js +252 -0
- package/dist/report-model/mint-snapshot.d.ts +30 -0
- package/dist/report-model/mint-snapshot.js +40 -0
- package/dist/report-model/reference-resolver.d.ts +113 -0
- package/dist/report-model/reference-resolver.js +60 -0
- package/dist/report-model/structural-validation.d.ts +22 -0
- package/dist/report-model/structural-validation.js +68 -0
- package/dist/report-model/validate.d.ts +40 -0
- package/dist/report-model/validate.js +80 -0
- package/dist/report-render/chart.d.ts +33 -0
- package/dist/report-render/chart.js +538 -0
- package/dist/report-render/page.d.ts +127 -0
- package/dist/report-render/page.js +307 -0
- package/dist/report-render/references.d.ts +32 -0
- package/dist/report-render/references.js +44 -0
- package/dist/report-render/render.d.ts +25 -0
- package/dist/report-render/render.js +125 -0
- package/dist/report-render/script-json.d.ts +13 -0
- package/dist/report-render/script-json.js +15 -0
- package/dist/report-render/types.d.ts +39 -0
- package/dist/report-render/types.js +10 -0
- package/dist/report-render/views/chart-view.d.ts +11 -0
- package/dist/report-render/views/chart-view.js +23 -0
- package/dist/report-render/views/page-view.d.ts +20 -0
- package/dist/report-render/views/page-view.js +49 -0
- package/dist/report-render/views/prose.d.ts +28 -0
- package/dist/report-render/views/prose.js +75 -0
- package/dist/report-render/views/references-view.d.ts +20 -0
- package/dist/report-render/views/references-view.js +64 -0
- package/dist/report-render/views/values.d.ts +42 -0
- package/dist/report-render/views/values.js +43 -0
- package/dist/runtime/assemble.d.ts +50 -4
- package/dist/runtime/assemble.js +47 -3
- package/dist/runtime/boot.d.ts +3 -3
- package/dist/runtime/boot.js +22 -5
- package/dist/runtime/dbos.d.ts +4 -4
- package/dist/state/artifacts.d.ts +16 -0
- package/dist/state/artifacts.js +18 -0
- package/dist/state/init.js +77 -1
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-versions.d.ts +132 -0
- package/dist/state/report-versions.js +194 -0
- package/dist/state/schema.d.ts +2 -2
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -1
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/keys.d.ts +4 -2
- package/dist/tools/bio/keys.js +3 -2
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.d.ts +3 -3
- package/dist/tools/bio/opentargets.js +4 -3
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/pubmed.js +4 -4
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/target-safety.d.ts +4 -6
- package/dist/tools/bio/target-safety.js +7 -6
- package/dist/tools/define-tool.d.ts +19 -12
- package/dist/tools/define-tool.js +4 -2
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/lib/chembl-client.d.ts +2 -0
- package/dist/tools/lib/chembl-client.js +1 -0
- package/dist/tools/lib/monarch-client.d.ts +42 -0
- package/dist/tools/lib/monarch-client.js +100 -0
- package/dist/tools/lib/ncbi-utils.d.ts +3 -58
- package/dist/tools/lib/ncbi-utils.js +2 -286
- package/dist/tools/lib/pubmed-client.d.ts +5 -12
- package/dist/tools/lib/pubmed-client.js +20 -79
- package/dist/tools/report/build-report.js +1 -0
- package/dist/tools/report/mint-preview-url.js +1 -0
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report/submit-report.js +1 -0
- package/dist/tools/report/version-fs.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
- package/dist/tools/report-authoring/authoring-tools.js +352 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +3 -2
- package/dist/tools/research/generate-plan.js +3 -0
- package/dist/tools/research/index.d.ts +1 -0
- package/dist/tools/research/index.js +1 -0
- package/dist/tools/research/inspect-data-profile.js +4 -0
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +3 -0
- package/dist/tools/research/resolve-citation.d.ts +3 -0
- package/dist/tools/research/resolve-citation.js +15 -0
- package/dist/tools/research/search-arxiv.d.ts +3 -19
- package/dist/tools/research/search-arxiv.js +10 -105
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.d.ts +8 -33
- package/dist/tools/research/search-semantic-scholar.js +30 -88
- package/dist/tools/sandbox/list-available-packages.js +23 -0
- package/dist/tools/sandbox/list-available-refs.js +37 -2
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/workspace/file-stat.js +1 -0
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +8 -1
- package/dist/workflows/execute-analysis.js +6 -5
- package/dist/workflows/execute-target-assessment.d.ts +13 -2
- package/dist/workflows/execute-target-assessment.js +78 -16
- package/dist/workflows/sandbox-step.d.ts +5 -0
- package/dist/workflows/sandbox-step.js +1 -0
- package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
- package/dist/workflows/target-assessment/assemblers/index.js +1 -1
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
- package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
- package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
- package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
- package/dist/workflows/target-assessment/collectors/index.js +48 -5
- package/dist/workflows/target-assessment/coverage.d.ts +43 -27
- package/dist/workflows/target-assessment/coverage.js +33 -29
- package/dist/workflows/target-assessment/fanout/index.js +1 -0
- package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
- package/dist/workflows/target-assessment/investigation/index.js +419 -0
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
- package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
- package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
- package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
- package/dist/workflows/target-assessment/metrics.d.ts +1 -6
- package/dist/workflows/target-assessment/metrics.js +1 -13
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +2149 -809
- package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
- package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
- package/dist/workflows/target-assessment/phase5-persist.js +56 -173
- package/dist/workflows/target-assessment/schemas.d.ts +48 -3
- package/dist/workflows/target-assessment/schemas.js +19 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +27 -63
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +28 -155
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
- package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
- package/dist/workflows/target-assessment/synthesis/index.js +4 -1
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
- package/package.json +4 -1
package/dist/loop/run-agent.js
CHANGED
|
@@ -155,8 +155,12 @@ export async function runAgent(agent, initial, session, opts) {
|
|
|
155
155
|
* in what they count. An error tool result is otherwise invisible to every sink —
|
|
156
156
|
* it is not a thrown failure, so nothing reports it and the loop simply feeds it
|
|
157
157
|
* back — and a run that ends badly is usually a run whose tool calls were failing.
|
|
158
|
+
*
|
|
159
|
+
* `details` and `durations` are positionally aligned with `calls`. `dispatchTools`
|
|
160
|
+
* measures a duration around the call itself, because this sink emits every finish
|
|
161
|
+
* event only after the whole round settles.
|
|
158
162
|
*/
|
|
159
|
-
const settleRound = async (calls, results, details) => {
|
|
163
|
+
const settleRound = async (calls, results, details, durations) => {
|
|
160
164
|
const errored = [];
|
|
161
165
|
for (let idx = 0; idx < calls.length; idx++) {
|
|
162
166
|
const tu = calls[idx];
|
|
@@ -169,7 +173,15 @@ export async function runAgent(agent, initial, session, opts) {
|
|
|
169
173
|
toolErrorCount++;
|
|
170
174
|
errored.push(tu.toolName);
|
|
171
175
|
}
|
|
172
|
-
await emit({
|
|
176
|
+
await emit({
|
|
177
|
+
type: "tool-finished",
|
|
178
|
+
source,
|
|
179
|
+
toolUseId: tu.toolCallId,
|
|
180
|
+
name: tu.toolName,
|
|
181
|
+
outcome,
|
|
182
|
+
...detailField(details[idx]),
|
|
183
|
+
...durationField(durations[idx]),
|
|
184
|
+
});
|
|
173
185
|
}
|
|
174
186
|
return errored;
|
|
175
187
|
};
|
|
@@ -226,8 +238,8 @@ export async function runAgent(agent, initial, session, opts) {
|
|
|
226
238
|
for (const [idx, tu] of earlier.entries()) {
|
|
227
239
|
await emit({ type: "tool-started", source, toolUseId: tu.toolCallId, name: tu.toolName, input: tu.input, ...detailField(earlierDetails[idx]) });
|
|
228
240
|
}
|
|
229
|
-
const results = await dispatchTools(earlier, toolsById, toolCtx, isFatalLoopError, runStep, formatStepName.tool);
|
|
230
|
-
const errored = await settleRound(earlier, results, earlierDetails);
|
|
241
|
+
const { results, durations } = await dispatchTools(earlier, toolsById, toolCtx, isFatalLoopError, runStep, formatStepName.tool);
|
|
242
|
+
const errored = await settleRound(earlier, results, earlierDetails, durations);
|
|
231
243
|
results.push(errorResult(trailing, TRUNCATED_TOOL_USE_ERROR));
|
|
232
244
|
// The trailing call was never dispatched, but it reaches the model as an error
|
|
233
245
|
// result like any other — so it counts, or `toolErrors` reports a cleaner run
|
|
@@ -256,8 +268,8 @@ export async function runAgent(agent, initial, session, opts) {
|
|
|
256
268
|
for (const [idx, tu] of toolCalls.entries()) {
|
|
257
269
|
await emit({ type: "tool-started", source, toolUseId: tu.toolCallId, name: tu.toolName, input: tu.input, ...detailField(details[idx]) });
|
|
258
270
|
}
|
|
259
|
-
const results = await dispatchTools(toolCalls, toolsById, toolCtx, isFatalLoopError, runStep, formatStepName.tool);
|
|
260
|
-
const errored = await settleRound(toolCalls, results, details);
|
|
271
|
+
const { results, durations } = await dispatchTools(toolCalls, toolsById, toolCtx, isFatalLoopError, runStep, formatStepName.tool);
|
|
272
|
+
const errored = await settleRound(toolCalls, results, details, durations);
|
|
261
273
|
if (errored.length > 0)
|
|
262
274
|
log.debug("tool results returned errors", { iteration: i, tools: errored });
|
|
263
275
|
messages.push({ role: "tool", content: results });
|
|
@@ -373,8 +385,22 @@ function markLastLoopAssistant(messages, initialCount) {
|
|
|
373
385
|
}
|
|
374
386
|
}
|
|
375
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Dispatch one round of tool calls, and measure the time of each call.
|
|
390
|
+
*
|
|
391
|
+
* `results` and `durations` are positionally aligned with `toolUses`.
|
|
392
|
+
*
|
|
393
|
+
* Each measurement brackets the same unit that the loop awaits for that call. For a
|
|
394
|
+
* step-mode call that unit is `runStep`, thus the figure includes the durable-step
|
|
395
|
+
* wrapper. The wrapper is part of what the call cost, and a cached replay of a step
|
|
396
|
+
* is genuinely fast. A bracket inside the step would report a body that did not run.
|
|
397
|
+
*/
|
|
376
398
|
async function dispatchTools(toolUses, toolsById, toolCtx, isFatalLoopError, runStep, toolStepName) {
|
|
377
399
|
const results = new Array(toolUses.length);
|
|
400
|
+
// The array starts with holes, which read as `undefined`. The element type
|
|
401
|
+
// says so, thus it agrees with what `settleRound` accepts. Every index is in
|
|
402
|
+
// fact assigned, because the mode partition below covers each call.
|
|
403
|
+
const durations = new Array(toolUses.length);
|
|
378
404
|
const stepTools = [];
|
|
379
405
|
const workflowTools = [];
|
|
380
406
|
const inlineTools = [];
|
|
@@ -387,16 +413,28 @@ async function dispatchTools(toolUses, toolsById, toolCtx, isFatalLoopError, run
|
|
|
387
413
|
else
|
|
388
414
|
stepTools.push({ tu, idx });
|
|
389
415
|
}
|
|
390
|
-
await Promise.all(stepTools.map(({ tu, idx }) =>
|
|
391
|
-
|
|
392
|
-
|
|
416
|
+
await Promise.all(stepTools.map(({ tu, idx }) => {
|
|
417
|
+
const startedAt = performance.now();
|
|
418
|
+
return runStep(toolStepName(tu.toolName, tu.toolCallId), () => dispatchTool(tu, toolsById, toolCtx(tu), isFatalLoopError)).then((r) => {
|
|
419
|
+
durations[idx] = elapsedMs(startedAt);
|
|
420
|
+
results[idx] = r;
|
|
421
|
+
});
|
|
422
|
+
}));
|
|
393
423
|
for (const { tu, idx } of workflowTools) {
|
|
424
|
+
const startedAt = performance.now();
|
|
394
425
|
results[idx] = await dispatchTool(tu, toolsById, toolCtx(tu), isFatalLoopError);
|
|
426
|
+
durations[idx] = elapsedMs(startedAt);
|
|
395
427
|
}
|
|
396
428
|
for (const { tu, idx } of inlineTools) {
|
|
429
|
+
const startedAt = performance.now();
|
|
397
430
|
results[idx] = await dispatchTool(tu, toolsById, toolCtx(tu), isFatalLoopError);
|
|
431
|
+
durations[idx] = elapsedMs(startedAt);
|
|
398
432
|
}
|
|
399
|
-
return results;
|
|
433
|
+
return { results, durations };
|
|
434
|
+
}
|
|
435
|
+
/** Whole milliseconds since `startedAt`, on the monotonic clock that took that mark. */
|
|
436
|
+
function elapsedMs(startedAt) {
|
|
437
|
+
return Math.round(performance.now() - startedAt);
|
|
400
438
|
}
|
|
401
439
|
async function dispatchTool(tu, toolsById, ctx, isFatalLoopError) {
|
|
402
440
|
const tool = toolsById.get(tu.toolName);
|
|
@@ -500,6 +538,16 @@ function outcomeOf(result) {
|
|
|
500
538
|
function detailField(detail) {
|
|
501
539
|
return detail === undefined ? {} : { detail };
|
|
502
540
|
}
|
|
541
|
+
/**
|
|
542
|
+
* The optional `durationMs` field, present only for a call that the loop measured.
|
|
543
|
+
*
|
|
544
|
+
* Spread rather than assigned, for the same reason as {@link detailField}: an
|
|
545
|
+
* unmeasured call emits no `durationMs` key. A zero here is a real measurement of a
|
|
546
|
+
* call that took under half a millisecond, and it is never a stand-in for no figure.
|
|
547
|
+
*/
|
|
548
|
+
function durationField(durationMs) {
|
|
549
|
+
return durationMs === undefined ? {} : { durationMs };
|
|
550
|
+
}
|
|
503
551
|
function hasDenial(results) {
|
|
504
552
|
return results.some((r) => r.output.type === "execution-denied");
|
|
505
553
|
}
|
|
@@ -25,6 +25,46 @@ export type { ToolCallDetail };
|
|
|
25
25
|
* channel.
|
|
26
26
|
*/
|
|
27
27
|
export declare const DETAIL_MAX_LENGTH = 120;
|
|
28
|
+
/**
|
|
29
|
+
* The bound a hook gives a free-form needle that it marks inside a detail.
|
|
30
|
+
*
|
|
31
|
+
* A needle is model-authored text of no fixed length, thus it is the one part of
|
|
32
|
+
* a detail that can run away. Left to {@link DETAIL_MAX_LENGTH}, a long needle
|
|
33
|
+
* takes the whole line, and the cut lands inside the mark that names it: the
|
|
34
|
+
* closing quote goes, and a second fact behind it goes with no trace. A hook
|
|
35
|
+
* bounds the needle first. Then the mark always closes, and the rest of the
|
|
36
|
+
* detail keeps its room.
|
|
37
|
+
*
|
|
38
|
+
* 32 code points shows the shape of a search — enough to recognize the needle a
|
|
39
|
+
* caller typed, and short enough that a target beside it survives whole.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DETAIL_NEEDLE_MAX_LENGTH = 32;
|
|
42
|
+
/**
|
|
43
|
+
* Cap `text` at `max` CODE POINTS, not UTF-16 units.
|
|
44
|
+
*
|
|
45
|
+
* A hook calls this to pre-empt the emit-site cap: that cap cuts the TAIL, so a
|
|
46
|
+
* detail with two parts loses the second one when the first runs long. One
|
|
47
|
+
* implementation serves both sites, thus a hook's own bound cuts and marks
|
|
48
|
+
* exactly as {@link normalizeDetail} does.
|
|
49
|
+
*
|
|
50
|
+
* `String.prototype.slice` cuts at a fixed unit index, which can land between the
|
|
51
|
+
* two halves of a surrogate pair and emit a lone surrogate — a terminal paints
|
|
52
|
+
* that as a replacement character, so the cap would corrupt the very tail it is
|
|
53
|
+
* supposed to merely shorten. Iterating the string yields whole code points.
|
|
54
|
+
*
|
|
55
|
+
* Code points, not display columns, is the right unit here: this cap is a safety
|
|
56
|
+
* valve against a runaway hook, so its job is to bound the payload. A column
|
|
57
|
+
* measurement would claim knowledge of the renderer's font metrics that the
|
|
58
|
+
* harness does not have — the host measures columns, because only the host knows
|
|
59
|
+
* how wide its own line is.
|
|
60
|
+
*
|
|
61
|
+
* A cut text carries `…` so that a reader can tell a shortened detail from a
|
|
62
|
+
* complete one. The mark costs one of the `max` code points, thus the cut goes to
|
|
63
|
+
* `max - 1`: a hard bound that overshoots by the width of its own mark is not a
|
|
64
|
+
* hard bound. `trimEnd` runs BEFORE the append, because a cut that lands on a
|
|
65
|
+
* space must give `word…` and not `word …`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function capCodePoints(text: string, max: number): string;
|
|
28
68
|
/**
|
|
29
69
|
* Normalize whatever a hook returned into an emittable detail, or `undefined`.
|
|
30
70
|
*
|
package/dist/loop/tool-detail.js
CHANGED
|
@@ -22,9 +22,28 @@ import { normalizeUnicode, redactSecrets } from "../input-sanitization.js";
|
|
|
22
22
|
* channel.
|
|
23
23
|
*/
|
|
24
24
|
export const DETAIL_MAX_LENGTH = 120;
|
|
25
|
+
/**
|
|
26
|
+
* The bound a hook gives a free-form needle that it marks inside a detail.
|
|
27
|
+
*
|
|
28
|
+
* A needle is model-authored text of no fixed length, thus it is the one part of
|
|
29
|
+
* a detail that can run away. Left to {@link DETAIL_MAX_LENGTH}, a long needle
|
|
30
|
+
* takes the whole line, and the cut lands inside the mark that names it: the
|
|
31
|
+
* closing quote goes, and a second fact behind it goes with no trace. A hook
|
|
32
|
+
* bounds the needle first. Then the mark always closes, and the rest of the
|
|
33
|
+
* detail keeps its room.
|
|
34
|
+
*
|
|
35
|
+
* 32 code points shows the shape of a search — enough to recognize the needle a
|
|
36
|
+
* caller typed, and short enough that a target beside it survives whole.
|
|
37
|
+
*/
|
|
38
|
+
export const DETAIL_NEEDLE_MAX_LENGTH = 32;
|
|
25
39
|
/**
|
|
26
40
|
* Cap `text` at `max` CODE POINTS, not UTF-16 units.
|
|
27
41
|
*
|
|
42
|
+
* A hook calls this to pre-empt the emit-site cap: that cap cuts the TAIL, so a
|
|
43
|
+
* detail with two parts loses the second one when the first runs long. One
|
|
44
|
+
* implementation serves both sites, thus a hook's own bound cuts and marks
|
|
45
|
+
* exactly as {@link normalizeDetail} does.
|
|
46
|
+
*
|
|
28
47
|
* `String.prototype.slice` cuts at a fixed unit index, which can land between the
|
|
29
48
|
* two halves of a surrogate pair and emit a lone surrogate — a terminal paints
|
|
30
49
|
* that as a replacement character, so the cap would corrupt the very tail it is
|
|
@@ -35,10 +54,19 @@ export const DETAIL_MAX_LENGTH = 120;
|
|
|
35
54
|
* measurement would claim knowledge of the renderer's font metrics that the
|
|
36
55
|
* harness does not have — the host measures columns, because only the host knows
|
|
37
56
|
* how wide its own line is.
|
|
57
|
+
*
|
|
58
|
+
* A cut text carries `…` so that a reader can tell a shortened detail from a
|
|
59
|
+
* complete one. The mark costs one of the `max` code points, thus the cut goes to
|
|
60
|
+
* `max - 1`: a hard bound that overshoots by the width of its own mark is not a
|
|
61
|
+
* hard bound. `trimEnd` runs BEFORE the append, because a cut that lands on a
|
|
62
|
+
* space must give `word…` and not `word …`.
|
|
38
63
|
*/
|
|
39
|
-
function capCodePoints(text, max) {
|
|
64
|
+
export function capCodePoints(text, max) {
|
|
40
65
|
const points = Array.from(text);
|
|
41
|
-
|
|
66
|
+
if (points.length <= max)
|
|
67
|
+
return text;
|
|
68
|
+
const cut = points.slice(0, max - 1).join("");
|
|
69
|
+
return `${cut.trimEnd()}…`;
|
|
42
70
|
}
|
|
43
71
|
/**
|
|
44
72
|
* Normalize whatever a hook returned into an emittable detail, or `undefined`.
|
|
@@ -78,8 +106,12 @@ export function normalizeDetail(raw) {
|
|
|
78
106
|
* second time from the repaired value costs more than the case is worth.
|
|
79
107
|
*/
|
|
80
108
|
export function computeDetail(tool, rawInput, log) {
|
|
109
|
+
// The guard tests for a function, not for presence. A packaged tool comes from
|
|
110
|
+
// an open list — an embedder contributes its own through the host-tools seam —
|
|
111
|
+
// so a `describeCall` that is not callable is reachable. Such a tool counts as
|
|
112
|
+
// undescribed, because the alternative is a TypeError on every one of its calls.
|
|
81
113
|
const describeCall = tool.describeCall;
|
|
82
|
-
if (describeCall
|
|
114
|
+
if (typeof describeCall !== "function")
|
|
83
115
|
return undefined;
|
|
84
116
|
try {
|
|
85
117
|
// The parse is INSIDE the guard, not before it. `safeParse` returns an
|
package/dist/loop/types.d.ts
CHANGED
|
@@ -81,6 +81,19 @@ export type EmitEvent = {
|
|
|
81
81
|
readonly outcome: ToolOutcome;
|
|
82
82
|
/** The same detail the matching `tool-started` carried. */
|
|
83
83
|
readonly detail?: ToolCallDetail;
|
|
84
|
+
/**
|
|
85
|
+
* The time around this call's own dispatch, in milliseconds.
|
|
86
|
+
*
|
|
87
|
+
* A host cannot measure this itself. The loop emits every
|
|
88
|
+
* `tool-started` of a round before it dispatches anything, and every
|
|
89
|
+
* `tool-finished` after the round settles. Thus a host that brackets
|
|
90
|
+
* the two events measures the round, and each call of a multi-call
|
|
91
|
+
* round reports the same figure.
|
|
92
|
+
*
|
|
93
|
+
* The field is optional, and it is absent when no measurement was
|
|
94
|
+
* taken. As a result a host can fall back to its own observation.
|
|
95
|
+
*/
|
|
96
|
+
readonly durationMs?: number;
|
|
84
97
|
};
|
|
85
98
|
/**
|
|
86
99
|
* A UI presentation event a tool streams to chat — an AI-SDK `data-*`
|
|
@@ -161,6 +161,16 @@ export interface ThreadHistory {
|
|
|
161
161
|
* empty thread reports `empty-thread`.
|
|
162
162
|
*/
|
|
163
163
|
retractLastTurn(threadId: string): ResultAsync<RetractOutcome, DbError>;
|
|
164
|
+
/**
|
|
165
|
+
* The greatest `messages.seq` in the thread, or `null` when the thread holds
|
|
166
|
+
* no messages. The read takes no lock: it reports the tail at the read
|
|
167
|
+
* moment, and a concurrent append can move the tail one row later.
|
|
168
|
+
*
|
|
169
|
+
* `appendTurn` computes the same value under its lock to place the next row.
|
|
170
|
+
* This read shares the table, not that statement. A lock here buys a caller
|
|
171
|
+
* no guarantee, because the tail can move one append later without it.
|
|
172
|
+
*/
|
|
173
|
+
latestSeq(threadId: string): ResultAsync<number | null, DbError>;
|
|
164
174
|
}
|
|
165
175
|
/**
|
|
166
176
|
* Drop the memoized instruments so the next `loadRecent` rebinds to a
|
|
@@ -418,5 +418,16 @@ export function createThreadHistory(pool) {
|
|
|
418
418
|
return tryMutation("thread-history.retractLastTurn.delete", () => client.query("DELETE FROM messages WHERE thread_id = $1 AND seq >= $2::bigint", [threadId, boundary])).map((res) => ({ kind: "retracted", messages: res.rowCount ?? 0 }));
|
|
419
419
|
}));
|
|
420
420
|
}
|
|
421
|
-
|
|
421
|
+
function latestSeq(threadId) {
|
|
422
|
+
// `MAX(seq)` over no rows is SQL NULL, so a thread with no messages reads
|
|
423
|
+
// as `null`, not as a seq — the absence a caller reads before it treats
|
|
424
|
+
// the value as an anchor. The `::text` cast carries a seq past 2^53 across
|
|
425
|
+
// the driver intact, the way every other read of this bigint column does,
|
|
426
|
+
// and `Number` makes the crossing in one place.
|
|
427
|
+
return tryQuery("thread-history.latestSeq", () => pool.query("SELECT MAX(seq)::text AS max_seq FROM messages WHERE thread_id = $1", [threadId])).map(({ rows }) => {
|
|
428
|
+
const maxSeq = rows[0]?.max_seq ?? null;
|
|
429
|
+
return maxSeq === null ? null : Number(maxSeq);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return { appendTurn, loadRecent, loadPage, retractLastTurn, latestSeq };
|
|
422
433
|
}
|
|
@@ -10,20 +10,24 @@
|
|
|
10
10
|
* the vocabulary is conversation-shaped on purpose — reaching for it inside
|
|
11
11
|
* a workflow step feels immediately wrong.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* A thread may name another as its parent, so the rows under one analysis form
|
|
14
|
+
* a forest rather than a flat list. The three lifecycle verbs each answer that
|
|
15
|
+
* edge differently, and the differences are the substance of this module.
|
|
16
|
+
*
|
|
17
|
+
* `archiveThread` is recoverable: it stamps `deleted_at` on the named thread
|
|
18
|
+
* and on every descendant, and because `getThread` and the default
|
|
15
19
|
* `listThreads` filter `deleted_at IS NULL` an archived thread is
|
|
16
20
|
* indistinguishable from an absent one while its row and every one of its
|
|
17
|
-
* `messages` rows stay in storage. `unarchiveThread` clears the stamp
|
|
18
|
-
* thread reads as it did; a listing widened with `includeArchived`
|
|
19
|
-
* way to obtain the id it takes, so the recovery is reachable by a
|
|
20
|
-
* holds no thread ids it obtained elsewhere.
|
|
21
|
+
* `messages` rows stay in storage. `unarchiveThread` clears the stamp on ONE
|
|
22
|
+
* row and the thread reads as it did; a listing widened with `includeArchived`
|
|
23
|
+
* is the only way to obtain the id it takes, so the recovery is reachable by a
|
|
24
|
+
* host that holds no thread ids it obtained elsewhere.
|
|
21
25
|
* `purgeThread` is not recoverable: it removes the `messages` rows and the
|
|
22
|
-
* metadata
|
|
23
|
-
* thread stripped of its transcript, nor a transcript
|
|
24
|
-
* It is the thread-scoped member of this package's
|
|
25
|
-
* `purgeAnalysis` reclaims an analysis's whole
|
|
26
|
-
* `purgeThread` one
|
|
26
|
+
* metadata rows of the whole subtree in one transaction, so a failure partway
|
|
27
|
+
* leaves both — never a thread stripped of its transcript, nor a transcript
|
|
28
|
+
* with nothing naming it. It is the thread-scoped member of this package's
|
|
29
|
+
* reclamation vocabulary — `purgeAnalysis` reclaims an analysis's whole
|
|
30
|
+
* persisted footprint, `purgeThread` one subtree's.
|
|
27
31
|
*
|
|
28
32
|
* A purge creates no orphan of its own, but it is NOT serialized against a
|
|
29
33
|
* concurrent `appendTurn`: `messages` carries no foreign key to
|
|
@@ -31,17 +35,51 @@
|
|
|
31
35
|
* metadata row. A turn committing after the purge therefore persists messages
|
|
32
36
|
* under a `thread_id` that no longer resolves to an analysis, leaving them
|
|
33
37
|
* unreachable by any later thread- or analysis-scoped reclamation. Stop writes
|
|
34
|
-
* to
|
|
35
|
-
* store cannot observe a host's in-flight turns,
|
|
38
|
+
* to every thread in the subtree — unbind each from any live conversation —
|
|
39
|
+
* before purging its root; this store cannot observe a host's in-flight turns,
|
|
40
|
+
* so it does not enforce that.
|
|
36
41
|
*/
|
|
37
42
|
import { type ResultAsync } from "neverthrow";
|
|
38
43
|
import type { Pool } from "pg";
|
|
39
44
|
import { type DbError } from "../lib/db-result.js";
|
|
45
|
+
/**
|
|
46
|
+
* The kind of session a thread holds. `conversation` is the analysis
|
|
47
|
+
* conversation the product has always had; `report` is a report session spawned
|
|
48
|
+
* from one.
|
|
49
|
+
*
|
|
50
|
+
* The set is closed rather than free-form because a thread's type is what
|
|
51
|
+
* selects the agent that runs it. That resolution is a separate capability, and
|
|
52
|
+
* it can only be exhaustive over a membership something can enumerate — a
|
|
53
|
+
* free-form column pushes an unmatched value into a fallback no reader has the
|
|
54
|
+
* list to write.
|
|
55
|
+
*
|
|
56
|
+
* The array is the single declaration and the union is derived from it, so the
|
|
57
|
+
* run-time check `createThread` needs — for a value that reached the store from
|
|
58
|
+
* outside the compiler's reach — cannot drift from the type callers program
|
|
59
|
+
* against.
|
|
60
|
+
*/
|
|
61
|
+
declare const THREAD_TYPES: readonly ["conversation", "report"];
|
|
62
|
+
export type ThreadType = (typeof THREAD_TYPES)[number];
|
|
40
63
|
/** One conversation thread's metadata, as returned by the store. */
|
|
41
64
|
export interface Thread {
|
|
42
65
|
readonly threadId: string;
|
|
43
66
|
readonly analysisId: string;
|
|
44
67
|
readonly title: string | null;
|
|
68
|
+
/** Which kind of session this row holds. A thread created without one is a `conversation`. */
|
|
69
|
+
readonly threadType: ThreadType;
|
|
70
|
+
/** The thread this one was spawned from — `null` on a thread that stands on its own. */
|
|
71
|
+
readonly parentThreadId: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* The parent's `messages.seq` at the moment of the spawn: the frozen point
|
|
74
|
+
* in the parent's transcript this thread was built on. `null` exactly when
|
|
75
|
+
* `parentThreadId` is — half the pair names nothing a consumer can resolve.
|
|
76
|
+
*
|
|
77
|
+
* It records the spawn point, not a promise that the prefix still reads the
|
|
78
|
+
* same: `retractLastTurn` can cut the parent's tail behind an anchor, so a
|
|
79
|
+
* reader must treat an anchor past the parent's current end as a normal
|
|
80
|
+
* state rather than a corruption.
|
|
81
|
+
*/
|
|
82
|
+
readonly parentSeq: number | null;
|
|
45
83
|
readonly createdAt: Date;
|
|
46
84
|
readonly updatedAt: Date;
|
|
47
85
|
/**
|
|
@@ -56,7 +94,51 @@ export interface CreateThreadInput {
|
|
|
56
94
|
readonly threadId: string;
|
|
57
95
|
readonly analysisId: string;
|
|
58
96
|
readonly title?: string | null;
|
|
97
|
+
/** Omitted is `conversation` — what a thread is when nothing says otherwise. */
|
|
98
|
+
readonly type?: ThreadType;
|
|
99
|
+
/**
|
|
100
|
+
* The thread this one is spawned from. It must belong to the same analysis
|
|
101
|
+
* as the child; whether it exists at all is the foreign key's verdict, not
|
|
102
|
+
* this store's.
|
|
103
|
+
*/
|
|
104
|
+
readonly parentThreadId?: string;
|
|
105
|
+
/**
|
|
106
|
+
* The parent's `messages.seq` at the spawn. Supply it with `parentThreadId`
|
|
107
|
+
* or neither: the two are one fact — which transcript, and how much of it —
|
|
108
|
+
* and either half alone describes no place.
|
|
109
|
+
*/
|
|
110
|
+
readonly parentSeq?: number;
|
|
59
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* A create the store refuses on its own, before any row is written. Distinct
|
|
114
|
+
* from `DbError`: nothing failed at the driver — the request described a thread
|
|
115
|
+
* the store will not write, and each variant carries the identifiers that say
|
|
116
|
+
* which values disagreed.
|
|
117
|
+
*
|
|
118
|
+
* Only the rules the store enforces itself live here. A `parentThreadId` naming
|
|
119
|
+
* no row is not one of them: the foreign key refuses that insert and
|
|
120
|
+
* `tryMutation` classifies the refusal as the `constraint_violation` variant of
|
|
121
|
+
* `DbError`.
|
|
122
|
+
*/
|
|
123
|
+
export type ThreadInputError = {
|
|
124
|
+
readonly type: "parent_analysis_mismatch";
|
|
125
|
+
readonly op: string;
|
|
126
|
+
readonly threadId: string;
|
|
127
|
+
readonly analysisId: string;
|
|
128
|
+
readonly parentThreadId: string;
|
|
129
|
+
readonly parentAnalysisId: string;
|
|
130
|
+
} | {
|
|
131
|
+
readonly type: "parent_anchor_unpaired";
|
|
132
|
+
readonly op: string;
|
|
133
|
+
readonly threadId: string;
|
|
134
|
+
readonly parentThreadId: string | null;
|
|
135
|
+
readonly parentSeq: number | null;
|
|
136
|
+
} | {
|
|
137
|
+
readonly type: "unknown_thread_type";
|
|
138
|
+
readonly op: string;
|
|
139
|
+
readonly threadId: string;
|
|
140
|
+
readonly threadType: string;
|
|
141
|
+
};
|
|
60
142
|
export interface ListThreadsInput {
|
|
61
143
|
readonly analysisId: string;
|
|
62
144
|
readonly page?: number;
|
|
@@ -68,6 +150,20 @@ export interface ListThreadsInput {
|
|
|
68
150
|
* widened back out of.
|
|
69
151
|
*/
|
|
70
152
|
readonly includeArchived?: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Narrow to one kind of session, exact match. Omitting it narrows NOTHING —
|
|
155
|
+
* a caller that supplies no filter receives every type, which is what a
|
|
156
|
+
* session picker wants: a user browsing an analysis expects to reach a
|
|
157
|
+
* report session directly.
|
|
158
|
+
*
|
|
159
|
+
* That is the opposite polarity to `includeArchived`, which widens a listing
|
|
160
|
+
* that is narrow by default, and the difference is deliberate. An archived
|
|
161
|
+
* thread is hidden state — showing it takes an explicit ask. A report
|
|
162
|
+
* session is not hidden state, so nothing has to ask for it.
|
|
163
|
+
*/
|
|
164
|
+
readonly type?: ThreadType;
|
|
165
|
+
/** Narrow to one thread's direct children, exact match. Same polarity as `type`: omitting it narrows nothing. */
|
|
166
|
+
readonly parentThreadId?: string;
|
|
71
167
|
}
|
|
72
168
|
export interface ThreadPage {
|
|
73
169
|
readonly threads: Thread[];
|
|
@@ -80,9 +176,16 @@ export interface ThreadStore {
|
|
|
80
176
|
/**
|
|
81
177
|
* Create a thread row. Idempotent on `thread_id` — a second create for an
|
|
82
178
|
* existing id is a no-op that preserves the existing row (including its
|
|
83
|
-
* `created_at`)
|
|
179
|
+
* `created_at`) and reads none of the supplied type, parent, or anchor. A
|
|
180
|
+
* caller that needs those persisted compares them against the returned row.
|
|
181
|
+
* Returns the live row.
|
|
182
|
+
*
|
|
183
|
+
* Refused with a `ThreadInputError`, no row written: a parent belonging to
|
|
184
|
+
* another analysis, a parent supplied without its anchor or an anchor
|
|
185
|
+
* without its parent, and a type outside the closed set. A parent naming no
|
|
186
|
+
* row is the foreign key's refusal and arrives as a `DbError`.
|
|
84
187
|
*/
|
|
85
|
-
createThread(input: CreateThreadInput): ResultAsync<Thread, DbError>;
|
|
188
|
+
createThread(input: CreateThreadInput): ResultAsync<Thread, DbError | ThreadInputError>;
|
|
86
189
|
/** The live thread by id, or `null` if absent or archived. */
|
|
87
190
|
getThread(threadId: string): ResultAsync<Thread | null, DbError>;
|
|
88
191
|
/**
|
|
@@ -91,26 +194,39 @@ export interface ThreadStore {
|
|
|
91
194
|
*/
|
|
92
195
|
updateTitle(threadId: string, title: string): ResultAsync<Thread | null, DbError>;
|
|
93
196
|
/**
|
|
94
|
-
* Soft-delete: stamp `deleted_at`
|
|
95
|
-
*
|
|
96
|
-
*
|
|
197
|
+
* Soft-delete the subtree: stamp `deleted_at` on the named thread and on
|
|
198
|
+
* every descendant reachable through `parent_thread_id`, at any depth, so a
|
|
199
|
+
* hidden thread never leaves a visible child behind. Every row and every one
|
|
200
|
+
* of their messages remain, and any thread in the subtree that was already
|
|
201
|
+
* archived keeps the stamp it got the first time.
|
|
97
202
|
*/
|
|
98
203
|
archiveThread(threadId: string): ResultAsync<void, DbError>;
|
|
99
204
|
/**
|
|
100
|
-
* Clear `deleted_at
|
|
101
|
-
* as they read before the archive
|
|
205
|
+
* Clear `deleted_at` on the named thread ALONE, returning it and its
|
|
206
|
+
* messages to view exactly as they read before the archive; every descendant
|
|
207
|
+
* is left as it was found. No-op on a live or absent thread.
|
|
208
|
+
*
|
|
209
|
+
* The asymmetry against `archiveThread` is what keeps the schema honest. A
|
|
210
|
+
* symmetric cascade would restore a child the user had archived on its own
|
|
211
|
+
* beforehand, so a row would have to record whether a cascade or a
|
|
212
|
+
* deliberate action set its tombstone — a fourth column carrying nothing
|
|
213
|
+
* else. With the asymmetry no row needs that distinction, and every archived
|
|
214
|
+
* thread is recovered the same way: by naming it, which a listing widened
|
|
215
|
+
* with `includeArchived` supplies.
|
|
102
216
|
*/
|
|
103
217
|
unarchiveThread(threadId: string): ResultAsync<void, DbError>;
|
|
104
218
|
/**
|
|
105
|
-
* Reclaim the
|
|
106
|
-
* `
|
|
107
|
-
*
|
|
108
|
-
*
|
|
219
|
+
* Reclaim the subtree's whole footprint: the named thread, every descendant
|
|
220
|
+
* reachable through `parent_thread_id` at any depth, and the `messages` rows
|
|
221
|
+
* of every one of them, removed together in one transaction. Unrecoverable —
|
|
222
|
+
* nothing survives, and no tombstone marks that any of it existed. A
|
|
223
|
+
* `thread_id` with no row succeeds as a no-op.
|
|
109
224
|
*/
|
|
110
225
|
purgeThread(threadId: string): ResultAsync<void, DbError>;
|
|
111
226
|
/**
|
|
112
227
|
* Threads for one analysis, newest-updated first, paginated. Live threads
|
|
113
|
-
* only unless `includeArchived` widens the set
|
|
228
|
+
* only unless `includeArchived` widens the set, and every type under every
|
|
229
|
+
* parent unless `type` / `parentThreadId` narrow it; `total` and `hasMore`
|
|
114
230
|
* always describe whichever set the page was drawn from.
|
|
115
231
|
*/
|
|
116
232
|
listThreads(input: ListThreadsInput): ResultAsync<ThreadPage, DbError>;
|
|
@@ -121,3 +237,4 @@ export interface ThreadStore {
|
|
|
121
237
|
* `cortex_analysis_threads` table is provisioned by the state-init DDL.
|
|
122
238
|
*/
|
|
123
239
|
export declare function createThreadStore(pool: Pool): ThreadStore;
|
|
240
|
+
export {};
|