@alexkroman1/aai-cli 13.3.0 → 14.0.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/{_agent-Clbq3a4h.mjs → _agent-BzUeqOdj.mjs} +32 -9
- package/dist/_agent.d.ts +23 -0
- package/dist/{_bundler-DEkjO1d8.mjs → _bundler-DolUCMxu.mjs} +8 -7
- package/dist/_cli-common.d.ts +45 -6
- package/dist/{_config-ZY2euhdh.mjs → _config-DYzC6WMD.mjs} +2 -2
- package/dist/_dev-env.d.ts +1 -1
- package/dist/{_dev-server-C13xuGB1.mjs → _dev-server-CSMqF8PN.mjs} +20 -22
- package/dist/_dev-vite-config.d.ts +3 -3
- package/dist/{_init-Ci4QGjp8.mjs → _init-CQ8idAwo.mjs} +9 -23
- package/dist/{_output-DbJt9gsl.mjs → _output-DBP9Op_d.mjs} +22 -13
- package/dist/_output.d.ts +10 -9
- package/dist/{_preflight-B2laWPdU.mjs → _preflight-CcAR577V.mjs} +2 -2
- package/dist/_resource-commands.d.ts +9 -9
- package/dist/{_server-common-BV4_CrP3.mjs → _server-common-vILJp3it.mjs} +1 -1
- package/dist/{_slug-api-DlKpj91X.mjs → _slug-api-CGvc2YSQ.mjs} +18 -9
- package/dist/_slug-api.d.ts +19 -0
- package/dist/{_studio-DTi3EdXQ.mjs → _studio-DnR_BqFp.mjs} +7 -3
- package/dist/_studio-commands.d.ts +13 -13
- package/dist/{_templates-V4l5sZJy.mjs → _templates-CK4oKoeX.mjs} +3 -3
- package/dist/{_typecheck-gate-sV4N5F93.mjs → _typecheck-gate-U3jcil8L.mjs} +3 -2
- package/dist/_typecheck-gate.d.ts +3 -1
- package/dist/{_utils-Dhd058D9.mjs → _utils-D5JGcjiW.mjs} +43 -1
- package/dist/_utils.d.ts +35 -0
- package/dist/{_workflow-determinism-C9l8uC2R.mjs → _workflow-determinism-CvpqMpdh.mjs} +18 -26
- package/dist/_workflow-determinism.d.ts +3 -3
- package/dist/{build-CWyFGe-W.mjs → build-Mxk8gWvX.mjs} +46 -7
- package/dist/build.d.ts +5 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +117 -135
- package/dist/{client-bundler-CT-_26c5.mjs → client-bundler-BJgREAh6.mjs} +1 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-RqlQZ6Y_.mjs → delete-BdFnbnVR.mjs} +4 -4
- package/dist/{deploy-B7f3CK7v.mjs → deploy-uAJ4NukN.mjs} +11 -11
- package/dist/{dev-DamXieW_.mjs → dev-DApPSaE_.mjs} +3 -3
- package/dist/{eval-h3DTVfRw.mjs → eval-BK47A_K5.mjs} +3 -3
- package/dist/{init-D-vFrV8h.mjs → init-DukDxECd.mjs} +11 -19
- package/dist/{login-CDiemNmC.mjs → login-DaLrPOFg.mjs} +4 -6
- package/dist/{logs-DGwPNCzB.mjs → logs-7BRwq0cG.mjs} +21 -14
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +84 -56
- package/dist/scaffold/package.json +4 -4
- package/dist/scaffold/server.mjs +129 -25
- package/dist/{secret-V1w7y9-j.mjs → secret-Cjp912yF.mjs} +5 -5
- package/dist/secret.d.ts +8 -6
- package/dist/{studio-BpMCfUNx.mjs → studio-CpHlNHUZ.mjs} +13 -15
- package/dist/templates/call-audit/agent.test.ts +11 -6
- package/dist/templates/call-audit/agent.ts +2 -2
- package/dist/templates/call-audit/client.tsx +27 -42
- package/dist/templates/call-audit/workflows/audit.ts +7 -5
- package/dist/templates/call-audit/workflows/ingest.ts +7 -7
- package/dist/templates/call-audit/workflows/media.ts +12 -37
- package/dist/templates/call-audit/workflows/summarize.ts +8 -8
- package/dist/templates/call-audit/workflows/sync-api.ts +3 -3
- package/dist/templates/dispatch-center/agent.test.ts +7 -7
- package/dist/templates/dispatch-center/client.tsx +53 -61
- package/dist/templates/dispatch-center/shared.ts +0 -8
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +4 -1
- package/dist/templates/embedded-assets/agent.eval.test.ts +4 -2
- package/dist/templates/health-assistant/agent.eval.test.ts +4 -2
- package/dist/templates/infocom-adventure/client.tsx +40 -36
- package/dist/templates/link-digest/agent.test.ts +3 -3
- package/dist/templates/link-digest/agent.ts +2 -2
- package/dist/templates/link-digest/client.tsx +27 -17
- package/dist/templates/link-digest/workflows/digest.ts +13 -16
- package/dist/templates/night-owl/agent.eval.test.ts +4 -2
- package/dist/templates/night-owl/client.tsx +8 -2
- package/dist/templates/pipeline-simple/agent.test.ts +2 -2
- package/dist/templates/pizza-ordering/client.tsx +5 -4
- package/dist/templates/pizza-ordering/shared.ts +3 -8
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/place_order.ts +3 -2
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +3 -2
- package/dist/templates/pizza-ordering/tools/view_order.ts +3 -2
- package/dist/templates/plan-and-execute/agent.test.ts +10 -5
- package/dist/templates/plan-and-execute/client.tsx +2 -2
- package/dist/templates/podcast-digest/agent.test.ts +20 -11
- package/dist/templates/podcast-digest/client.tsx +16 -22
- package/dist/templates/podcast-digest/workflows/digest.ts +21 -19
- package/dist/templates/podcast-digest/workflows/feeds.ts +61 -33
- package/dist/templates/podcast-digest/workflows/slack.ts +7 -7
- package/dist/templates/recap-workflow/agent.eval.test.ts +21 -13
- package/dist/templates/recap-workflow/agent.test.ts +27 -23
- package/dist/templates/recap-workflow/workflows/recap.ts +24 -28
- package/dist/templates/redline/agent.eval.test.ts +5 -8
- package/dist/templates/redline/agent.test.ts +1 -1
- package/dist/templates/redline/client.tsx +36 -34
- package/dist/templates/redline/workflows/redline.ts +17 -17
- package/dist/templates/research-workflow/agent.eval.test.ts +5 -5
- package/dist/templates/research-workflow/agent.test.ts +10 -7
- package/dist/templates/research-workflow/workflows/research.ts +28 -30
- package/dist/templates/retail/agent.test.ts +17 -15
- package/dist/templates/retail/cancel.ts +7 -6
- package/dist/templates/retail/client.tsx +39 -58
- package/dist/templates/retail/payment.ts +4 -3
- package/dist/templates/retail/pending.test.ts +19 -19
- package/dist/templates/retail/store.test.ts +5 -5
- package/dist/templates/retail/swap.ts +8 -11
- package/dist/templates/solo-rpg/agent.eval.test.ts +2 -16
- package/dist/templates/solo-rpg/agent.test.ts +12 -12
- package/dist/templates/solo-rpg/client.tsx +3 -3
- package/dist/templates/spoken-summary/agent.test.ts +9 -9
- package/dist/templates/spoken-summary/agent.ts +3 -3
- package/dist/templates/spoken-summary/client.tsx +23 -30
- package/dist/templates/spoken-summary/workflows/summarize.ts +11 -11
- package/dist/templates/spoken-summary/workflows/transcribe.ts +14 -14
- package/dist/templates/support-line/client.tsx +4 -3
- package/dist/templates/support-line/prompts.ts +0 -2
- package/dist/templates/transcription-workflow/agent.test.ts +20 -12
- package/dist/templates/transcription-workflow/agent.ts +2 -2
- package/dist/templates/transcription-workflow/client.tsx +15 -14
- package/dist/templates/transcription-workflow/recover.ts +1 -1
- package/dist/templates/transcription-workflow/run-panel.tsx +34 -14
- package/dist/templates/transcription-workflow/total-latency.tsx +46 -20
- package/dist/templates/transcription-workflow/workflows/batch.ts +15 -15
- package/dist/templates/transcription-workflow/workflows/downsample.ts +1 -1
- package/dist/templates/transcription-workflow/workflows/normalize.ts +15 -19
- package/dist/templates/transcription-workflow/workflows/stream.ts +12 -12
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +3 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +23 -34
- package/dist/templates/travel-concierge/agent.eval.test.ts +2 -2
- package/dist/templates/travel-concierge/agent.test.ts +15 -7
- package/dist/templates/travel-concierge/client.tsx +2 -2
- package/dist/templates/travel-concierge/shared.ts +10 -14
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +4 -3
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_excursions.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_flights.ts +3 -2
- package/dist/templates/travel-concierge/tools/search_hotels.ts +3 -2
- package/dist/{test-guenJUxi.mjs → test-D032V2cA.mjs} +4 -17
- package/dist/typecheck.mjs +1 -1
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-BZyKLkhV.mjs → workflow-CVioyO3V.mjs} +6 -5
- package/package.json +4 -4
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* **The branch this file used to name as its biggest gap no longer exists.** It
|
|
34
34
|
* was `recapFlow`'s `if (isWorkflowSuspend(err)) throw err;` — the guard whose
|
|
35
35
|
* absence had once deleted the transcript the run was waiting for — and it was
|
|
36
|
-
* unpinnable here by construction, since `
|
|
36
|
+
* unpinnable here by construction, since `createWorkflowContext`'s `sleep` is
|
|
37
37
|
* RECORDED and its `waitFor` answers out of `hooks`, so no wait it serves ever
|
|
38
38
|
* suspended. A wait now hands the body a promise that never settles, so a
|
|
39
39
|
* suspension cannot reach a `catch` at all and there is no branch left to test:
|
|
@@ -47,7 +47,7 @@ import type { WorkflowClient } from "@alexkroman1/aai";
|
|
|
47
47
|
import {
|
|
48
48
|
createRunSnapshot,
|
|
49
49
|
createToolContext,
|
|
50
|
-
|
|
50
|
+
createWorkflowContext,
|
|
51
51
|
parseSchemaInput,
|
|
52
52
|
schemaInputIssues,
|
|
53
53
|
stubGatewayRoute,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
} from "@alexkroman1/aai/testing";
|
|
56
56
|
import {
|
|
57
57
|
installStubStepFetch,
|
|
58
|
-
|
|
58
|
+
installStubWorkflows,
|
|
59
59
|
installStubGateway as stubGateway,
|
|
60
60
|
} from "@alexkroman1/aai/testing/vitest";
|
|
61
61
|
import type { WorkflowRunSnapshot } from "@alexkroman1/aai/workflow-api";
|
|
@@ -89,14 +89,14 @@ const run = toolRunner(agentDef);
|
|
|
89
89
|
/**
|
|
90
90
|
* A `ctx.workflows` that records `start` and answers the lookups from a fixture.
|
|
91
91
|
*
|
|
92
|
-
* `
|
|
92
|
+
* `installStubWorkflows` (`@alexkroman1/aai/testing/vitest`) is the whole thing — a
|
|
93
93
|
* `vi.fn` per method over one `runs` list, with `stream`/`streamTail` left
|
|
94
94
|
* rejecting because `recap_progress` reads progress through `lastLine` and
|
|
95
95
|
* composing those two by hand is the hazard `lastLine` exists to remove. What
|
|
96
96
|
* is local is only which workflow this desk declares.
|
|
97
97
|
*/
|
|
98
98
|
function stubWorkflows(runs: WorkflowRunSnapshot[] = []): WorkflowClient {
|
|
99
|
-
return
|
|
99
|
+
return installStubWorkflows({ runs, names: ["recap"] });
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/** A finished recap, as the workflow's output reaches the tools. */
|
|
@@ -399,7 +399,7 @@ describe("cancel_recap", () => {
|
|
|
399
399
|
*
|
|
400
400
|
* Published into `stepFetch`'s OWN slot, not over `globalThis.fetch`. Every
|
|
401
401
|
* request in this file goes through `stepFetch` — `request()` and
|
|
402
|
-
* `discardTranscript` reach it directly, `
|
|
402
|
+
* `discardTranscript` reach it directly, `stepTranscribeSubmitOrFail`
|
|
403
403
|
* through the SDK — and `step-fetch.ts` falls back to `globalThis.fetch` only
|
|
404
404
|
* when nothing is published. A global stub therefore passed while exercising a
|
|
405
405
|
* path production never takes; every sibling template already stubs the slot,
|
|
@@ -686,7 +686,7 @@ describe("summarize", () => {
|
|
|
686
686
|
// The policy is an argument to `ctx.step` now, so it is observable only at
|
|
687
687
|
// the call. `runSteps: false` and a skeleton of results: the subject is what
|
|
688
688
|
// the body ASKED FOR.
|
|
689
|
-
const ctx =
|
|
689
|
+
const ctx = createWorkflowContext({
|
|
690
690
|
runSteps: false,
|
|
691
691
|
results: {
|
|
692
692
|
submitRecording: { id: "t_1" },
|
|
@@ -724,7 +724,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
724
724
|
{ status: "processing" },
|
|
725
725
|
{ status: "completed", text: "Done.", audio_duration: 60 },
|
|
726
726
|
]);
|
|
727
|
-
const state = await awaitTranscript("t_1",
|
|
727
|
+
const state = await awaitTranscript("t_1", createWorkflowContext());
|
|
728
728
|
expect(state).toMatchObject({ status: "completed", text: "Done." });
|
|
729
729
|
expect(polls()).toBe(3);
|
|
730
730
|
});
|
|
@@ -733,13 +733,15 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
733
733
|
// `error` is terminal: polling a failed job to the bound would spend twenty
|
|
734
734
|
// minutes learning what the first answer already said.
|
|
735
735
|
const polls = stubStatuses([{ status: "error", error: "Transcoding failed" }]);
|
|
736
|
-
await expect(awaitTranscript("t_1",
|
|
736
|
+
await expect(awaitTranscript("t_1", createWorkflowContext())).rejects.toThrow(
|
|
737
|
+
/Transcoding failed/,
|
|
738
|
+
);
|
|
737
739
|
expect(polls()).toBe(1);
|
|
738
740
|
});
|
|
739
741
|
|
|
740
742
|
test("gives up at the bound rather than polling a stuck job forever", async () => {
|
|
741
743
|
stubStatuses([{ status: "processing" }]);
|
|
742
|
-
await expect(awaitTranscript("t_1",
|
|
744
|
+
await expect(awaitTranscript("t_1", createWorkflowContext())).rejects.toThrow(/Gave up/);
|
|
743
745
|
});
|
|
744
746
|
|
|
745
747
|
/** A job still `processing` for `polls` turns, then completed. */
|
|
@@ -758,7 +760,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
758
760
|
// the constant promises is eight sleeps, so the earliest honest turn to say
|
|
759
761
|
// it is the ninth. Here the job finishes on that ninth poll, two minutes in
|
|
760
762
|
// and not a second over, so the caller is told nothing.
|
|
761
|
-
const ctx =
|
|
763
|
+
const ctx = createWorkflowContext();
|
|
762
764
|
stubSlowJob(8);
|
|
763
765
|
|
|
764
766
|
await awaitTranscript("t_1", ctx);
|
|
@@ -773,7 +775,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
773
775
|
// two minutes of waiting behind it — so the note goes out, and the eleven
|
|
774
776
|
// polls after it say nothing more. A note per poll would be a caller told
|
|
775
777
|
// the same sentence every fifteen seconds.
|
|
776
|
-
const ctx =
|
|
778
|
+
const ctx = createWorkflowContext();
|
|
777
779
|
stubSlowJob(20);
|
|
778
780
|
|
|
779
781
|
await awaitTranscript("t_1", ctx);
|
|
@@ -788,7 +790,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
788
790
|
// cases are the REGRESSION guard for this section: the poll-only arm has to
|
|
789
791
|
// keep behaving exactly as it did before there was a callback at all.
|
|
790
792
|
//
|
|
791
|
-
// `
|
|
793
|
+
// `createWorkflowContext` is the only tier that can drive the answered branch:
|
|
792
794
|
// its `waitFor` reads `hooks` by token, so supplying a payload IS the delivery
|
|
793
795
|
// landing and omitting one IS the window closing. The eval tier cannot —
|
|
794
796
|
// nothing there can signal — so it only ever sees the fallback, which is
|
|
@@ -806,7 +808,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
806
808
|
{ status: "processing" },
|
|
807
809
|
{ status: "completed", text: "Done.", audio_duration: 60 },
|
|
808
810
|
]);
|
|
809
|
-
const ctx =
|
|
811
|
+
const ctx = createWorkflowContext({
|
|
810
812
|
hooks: { [NUDGE]: { transcript_id: "t_1", status: "completed" } },
|
|
811
813
|
});
|
|
812
814
|
|
|
@@ -831,7 +833,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
831
833
|
{ status: "processing" },
|
|
832
834
|
{ status: "completed", text: "The real transcript.", audio_duration: 60 },
|
|
833
835
|
]);
|
|
834
|
-
const ctx =
|
|
836
|
+
const ctx = createWorkflowContext({
|
|
835
837
|
hooks: { [NUDGE]: { transcript_id: "t_1", status: "completed", text: "A LIE." } },
|
|
836
838
|
});
|
|
837
839
|
|
|
@@ -850,7 +852,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
850
852
|
// read as a failed run and answer by deleting the transcript. So the wait
|
|
851
853
|
// may not be inside the loop, and this is what pins that: twenty turns, ONE
|
|
852
854
|
// `waitFor`, and every other wait a plain sleep.
|
|
853
|
-
const ctx =
|
|
855
|
+
const ctx = createWorkflowContext({ hooks: { [NUDGE]: {} } });
|
|
854
856
|
stubSlowJob(20);
|
|
855
857
|
|
|
856
858
|
await awaitTranscript("t_1", ctx, NUDGE);
|
|
@@ -869,7 +871,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
869
871
|
{ status: "processing" },
|
|
870
872
|
{ status: "completed", text: "Done.", audio_duration: 60 },
|
|
871
873
|
]);
|
|
872
|
-
const ctx =
|
|
874
|
+
const ctx = createWorkflowContext();
|
|
873
875
|
|
|
874
876
|
const state = await awaitTranscript("t_1", ctx, NUDGE);
|
|
875
877
|
|
|
@@ -885,7 +887,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
885
887
|
// window — so whichever arm a run is on, a caller hears the sentence at the
|
|
886
888
|
// same point. The note goes out at the TOP of a poll, so attempt 2 is the
|
|
887
889
|
// first turn with a whole closed window behind it.
|
|
888
|
-
const ctx =
|
|
890
|
+
const ctx = createWorkflowContext();
|
|
889
891
|
stubSlowJob(20);
|
|
890
892
|
|
|
891
893
|
await awaitTranscript("t_1", ctx, NUDGE);
|
|
@@ -904,7 +906,7 @@ describe("awaitTranscript — the polling port, and the callback over it", () =>
|
|
|
904
906
|
// The mirror of the case above, and what stops the note being a thing every
|
|
905
907
|
// callback run says: the delivery lands, attempt 2 finds the job done, and
|
|
906
908
|
// nobody is told a recording is a long one.
|
|
907
|
-
const ctx =
|
|
909
|
+
const ctx = createWorkflowContext({ hooks: { [NUDGE]: {} } });
|
|
908
910
|
stubSlowJob(1);
|
|
909
911
|
|
|
910
912
|
await awaitTranscript("t_1", ctx, NUDGE);
|
|
@@ -930,7 +932,9 @@ describe("askWhetherToKeep — the expense port", () => {
|
|
|
930
932
|
* What is pinned is unchanged: the three outcomes and the safe default.
|
|
931
933
|
*/
|
|
932
934
|
const gateCtx = (answer?: { keep: boolean }) =>
|
|
933
|
-
|
|
935
|
+
createWorkflowContext(
|
|
936
|
+
answer === undefined ? {} : { hooks: { [retentionToken("s_1")]: answer } },
|
|
937
|
+
);
|
|
934
938
|
|
|
935
939
|
test("keeps the transcript when the caller says to, and deletes nothing", async () => {
|
|
936
940
|
const provider = installStubStepFetch();
|
|
@@ -1002,7 +1006,7 @@ describe("compensate — the saga port", () => {
|
|
|
1002
1006
|
{ label: "first", undo: async () => void order.push("first") },
|
|
1003
1007
|
],
|
|
1004
1008
|
"because",
|
|
1005
|
-
|
|
1009
|
+
createWorkflowContext(),
|
|
1006
1010
|
);
|
|
1007
1011
|
expect(order).toEqual(["second", "first"]);
|
|
1008
1012
|
});
|
|
@@ -1024,7 +1028,7 @@ describe("compensate — the saga port", () => {
|
|
|
1024
1028
|
{ label: "fine", undo: async () => void order.push("fine") },
|
|
1025
1029
|
],
|
|
1026
1030
|
"because",
|
|
1027
|
-
|
|
1031
|
+
createWorkflowContext(),
|
|
1028
1032
|
),
|
|
1029
1033
|
).resolves.toBeUndefined();
|
|
1030
1034
|
expect(order).toEqual(["fine"]);
|
|
@@ -1035,7 +1039,7 @@ describe("compensate — the saga port", () => {
|
|
|
1035
1039
|
// run that narrated an unwind it did not perform would be lying to the
|
|
1036
1040
|
// caller reading its progress.
|
|
1037
1041
|
await expect(
|
|
1038
|
-
compensate([], "nothing was acquired",
|
|
1042
|
+
compensate([], "nothing was acquired", createWorkflowContext()),
|
|
1039
1043
|
).resolves.toBeUndefined();
|
|
1040
1044
|
});
|
|
1041
1045
|
});
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
* Everything the desk claims to do. `submitRecording`, `checkTranscript` and
|
|
100
100
|
* `discardTranscript` are AssemblyAI's pre-recorded API (`POST`, `GET` and
|
|
101
101
|
* `DELETE` on `/v2/transcript`), and `summarize` is a real model call through
|
|
102
|
-
* `
|
|
102
|
+
* `stepGenerateJsonOrFail`. The BATCH API is what makes the polling port honest: it
|
|
103
103
|
* answers with a job id in milliseconds and finishes minutes later, so the wait
|
|
104
104
|
* is the provider's, not a `setTimeout` this template chose. (Its sibling
|
|
105
105
|
* `transcription-workflow` takes the other endpoint — the sync one, which answers in
|
|
@@ -111,13 +111,13 @@
|
|
|
111
111
|
* just your shell.
|
|
112
112
|
*/
|
|
113
113
|
|
|
114
|
-
import type {
|
|
115
|
-
import {
|
|
114
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
115
|
+
import { requireStepEnv, stepFetch, stepReport, stepWebhookUrl } from "@alexkroman1/aai/step";
|
|
116
116
|
import {
|
|
117
117
|
FatalError,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
stepFetchOrFail,
|
|
119
|
+
stepGenerateJsonOrFail,
|
|
120
|
+
stepTranscribeSubmitOrFail,
|
|
121
121
|
toStepError,
|
|
122
122
|
} from "@alexkroman1/aai/step-errors";
|
|
123
123
|
import { errorMessage, omitUndefined } from "@alexkroman1/aai/utils";
|
|
@@ -239,7 +239,7 @@ const POINTS = 3;
|
|
|
239
239
|
/**
|
|
240
240
|
* The shape the model must answer in.
|
|
241
241
|
*
|
|
242
|
-
* `
|
|
242
|
+
* `stepGenerateJsonOrFail` validates against this and throws PLAINLY when the reply
|
|
243
243
|
* misses, which is the retry policy in one distinction: a model that answered in
|
|
244
244
|
* prose may answer correctly next time, where a 401 will not. `spoken` is the
|
|
245
245
|
* field this template exists for — without it the announced turn has nothing to
|
|
@@ -347,7 +347,7 @@ export type Compensation = { label: string; undo: () => Promise<void> };
|
|
|
347
347
|
* `agent.ts` reads back down the phone, so it is shaped for an ear rather than
|
|
348
348
|
* a page.
|
|
349
349
|
*/
|
|
350
|
-
export async function recapFlow(input: { url: string; requestedBy: string }, ctx:
|
|
350
|
+
export async function recapFlow(input: { url: string; requestedBy: string }, ctx: WorkflowContext) {
|
|
351
351
|
// The compensation stack, newest first — `unshift` after each successful
|
|
352
352
|
// acquisition, exactly as Temporal's `openAccount` does. Registering the undo
|
|
353
353
|
// AFTER the step it undoes is the whole discipline: a step that never
|
|
@@ -462,7 +462,7 @@ export async function recapFlow(input: { url: string; requestedBy: string }, ctx
|
|
|
462
462
|
*/
|
|
463
463
|
export async function awaitTranscript(
|
|
464
464
|
id: string,
|
|
465
|
-
ctx:
|
|
465
|
+
ctx: WorkflowContext,
|
|
466
466
|
nudge?: string,
|
|
467
467
|
): Promise<TranscriptState> {
|
|
468
468
|
// Which turn says "still going". A pure function of `nudge`, which the body
|
|
@@ -518,7 +518,7 @@ export async function askWhetherToKeep(
|
|
|
518
518
|
requestedBy: string,
|
|
519
519
|
transcriptId: string,
|
|
520
520
|
compensations: Compensation[],
|
|
521
|
-
ctx:
|
|
521
|
+
ctx: WorkflowContext,
|
|
522
522
|
): Promise<Retention> {
|
|
523
523
|
await ctx.step("noteGate", () =>
|
|
524
524
|
note(
|
|
@@ -560,7 +560,7 @@ export async function askWhetherToKeep(
|
|
|
560
560
|
export async function compensate(
|
|
561
561
|
compensations: Compensation[],
|
|
562
562
|
because: string,
|
|
563
|
-
ctx:
|
|
563
|
+
ctx: WorkflowContext,
|
|
564
564
|
): Promise<void> {
|
|
565
565
|
if (compensations.length === 0) return;
|
|
566
566
|
// The narration is a STEP like every other, so an unwind interrupted by a
|
|
@@ -607,9 +607,9 @@ export async function submitRecording(
|
|
|
607
607
|
url: string,
|
|
608
608
|
webhookUrl?: string,
|
|
609
609
|
): Promise<{ id: string; callback: boolean }> {
|
|
610
|
-
await
|
|
610
|
+
await stepReport(`Submitting ${new URL(url).hostname} for transcription…`);
|
|
611
611
|
|
|
612
|
-
// `
|
|
612
|
+
// `stepTranscribeSubmitOrFail` owns the endpoint, the raw-key auth, the
|
|
613
613
|
// PLURAL `speech_models` field and the failure classification — the
|
|
614
614
|
// `Classified` suffix being that last part: it is `stepTranscribeSubmit` with
|
|
615
615
|
// `throwStepError` already applied, so a provider refusal stays terminal and a
|
|
@@ -625,7 +625,7 @@ export async function submitRecording(
|
|
|
625
625
|
// What must not creep in is a `?? null` or a `?? ""` to "be explicit": either
|
|
626
626
|
// one puts the key back, and a provider handed a null for a URL is entitled to
|
|
627
627
|
// refuse the whole submission.
|
|
628
|
-
const job = await
|
|
628
|
+
const job = await stepTranscribeSubmitOrFail(url, {
|
|
629
629
|
params: { speaker_labels: true, webhook_url: webhookUrl },
|
|
630
630
|
});
|
|
631
631
|
return { id: job.id, callback: webhookUrl !== undefined };
|
|
@@ -698,7 +698,7 @@ export async function checkTranscript(id: string): Promise<TranscriptState> {
|
|
|
698
698
|
);
|
|
699
699
|
}
|
|
700
700
|
|
|
701
|
-
await
|
|
701
|
+
await stepReport(`Transcript ${parsed.data.status}.`);
|
|
702
702
|
return {
|
|
703
703
|
status: parsed.data.status,
|
|
704
704
|
// `omitUndefined` rather than a spread-ternary per field: under
|
|
@@ -730,7 +730,7 @@ export async function checkTranscript(id: string): Promise<TranscriptState> {
|
|
|
730
730
|
* a replay is exactly that world.
|
|
731
731
|
*/
|
|
732
732
|
export async function discardTranscript(id: string): Promise<void> {
|
|
733
|
-
await
|
|
733
|
+
await stepReport(`Discarding transcript ${id}.`);
|
|
734
734
|
// Not through `request` above, because a 404 is a SUCCESS here — see below.
|
|
735
735
|
// `stepFetch` for the same reason it does; only the status handling differs.
|
|
736
736
|
const response = await stepFetch(`${TRANSCRIPT_ENDPOINT}/${id}`, {
|
|
@@ -756,7 +756,7 @@ export async function discardTranscript(id: string): Promise<void> {
|
|
|
756
756
|
* journal instead of submitting the recording again.
|
|
757
757
|
*/
|
|
758
758
|
export async function summarize(url: string, transcript: TranscriptState): Promise<Recap> {
|
|
759
|
-
await
|
|
759
|
+
await stepReport("Writing the recap.");
|
|
760
760
|
|
|
761
761
|
const text = (transcript.text ?? "").slice(0, MAX_TRANSCRIPT_CHARS);
|
|
762
762
|
if (text.trim() === "") {
|
|
@@ -765,13 +765,13 @@ export async function summarize(url: string, transcript: TranscriptState): Promi
|
|
|
765
765
|
throw new FatalError("That recording came back with no speech in it.");
|
|
766
766
|
}
|
|
767
767
|
|
|
768
|
-
// `
|
|
768
|
+
// `stepGenerateJsonOrFail` unwraps the fence a model puts around JSON
|
|
769
769
|
// however firmly it is told not to, parses it, and validates it — all four
|
|
770
770
|
// things this step used to re-derive. The `Classified` half is what makes a
|
|
771
771
|
// terminal gateway failure (a bad key, a rejected request) stop rather than
|
|
772
772
|
// burn the remaining attempts, where a reply that missed the SHAPE throws
|
|
773
773
|
// plainly and retries.
|
|
774
|
-
const parsed = await
|
|
774
|
+
const parsed = await stepGenerateJsonOrFail(text, {
|
|
775
775
|
schema: RecapReply,
|
|
776
776
|
system:
|
|
777
777
|
"You write up recordings for someone who will hear the result on a phone call. " +
|
|
@@ -792,13 +792,13 @@ export async function summarize(url: string, transcript: TranscriptState): Promi
|
|
|
792
792
|
/**
|
|
793
793
|
* Say one line into the run's progress channel.
|
|
794
794
|
*
|
|
795
|
-
* A step for one reason: the body REPLAYS, so a `
|
|
795
|
+
* A step for one reason: the body REPLAYS, so a `stepReport()` written there is
|
|
796
796
|
* re-emitted on every resume. Everything the body itself wants to narrate —
|
|
797
797
|
* the slow-recording note, the unwind — comes through here, and `agent.ts`'s
|
|
798
798
|
* `recap_progress` is what reads it back down the phone.
|
|
799
799
|
*/
|
|
800
800
|
export async function note(line: string): Promise<void> {
|
|
801
|
-
await
|
|
801
|
+
await stepReport(line);
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
// ---- HTTP -------------------------------------------------------------------
|
|
@@ -810,10 +810,7 @@ export async function note(line: string): Promise<void> {
|
|
|
810
810
|
* Note the header is a bare key: AssemblyAI's `authorization` takes the key
|
|
811
811
|
* itself, with no `Bearer` prefix.
|
|
812
812
|
*/
|
|
813
|
-
async function request(
|
|
814
|
-
url: string,
|
|
815
|
-
init: { method?: string; body?: string } = {},
|
|
816
|
-
): Promise<Response> {
|
|
813
|
+
async function request(url: string): Promise<Response> {
|
|
817
814
|
// Through `stepFetch`, not `fetch`: it pins HTTP/1.1, so several concurrent
|
|
818
815
|
// runs (and this workflow POLLS, so one run is many requests) get a socket
|
|
819
816
|
// each rather than N streams on one connection — and a connection failure
|
|
@@ -821,14 +818,13 @@ async function request(
|
|
|
821
818
|
// `TypeError: fetch failed`, which for a template whose whole subject is
|
|
822
819
|
// durability is the difference between a diagnosable resume and a mystery.
|
|
823
820
|
// `sdk/step-fetch.ts` carries the measurements.
|
|
824
|
-
// `
|
|
821
|
+
// `stepFetchOrFail` makes the three-way retry decision: a 401 or a 400 answers the
|
|
825
822
|
// same way on the fourth attempt and burns the step, a 429 or a 5xx is what
|
|
826
823
|
// retries are for, and a `Retry-After` the provider named is waited out rather
|
|
827
824
|
// than replaced by the DevKit's one-second default — which matters here more
|
|
828
825
|
// than usual, because a fan-out of segments hits a rate limit together. The
|
|
829
826
|
// DELETE below stays on plain `stepFetch`, because there a 404 is a SUCCESS.
|
|
830
|
-
return await
|
|
831
|
-
...init,
|
|
827
|
+
return await stepFetchOrFail(url, {
|
|
832
828
|
headers: { authorization: requireStepEnv(API_KEY_ENV), "content-type": "application/json" },
|
|
833
829
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
834
830
|
});
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// journal, no replay, and no per-step retry, so a rate-limited live run FAILS
|
|
30
30
|
// where a deployed one would have ridden it out. The tier that really resumes a
|
|
31
31
|
// run is `aai-cli`'s `dev-workflow.scenario.test.ts`.
|
|
32
|
-
import { stubGatewayRoute } from "@alexkroman1/aai/testing";
|
|
32
|
+
import { routeStepFetch, stubGatewayRoute } from "@alexkroman1/aai/testing";
|
|
33
33
|
import { installStubStepFetch } from "@alexkroman1/aai/testing/vitest";
|
|
34
34
|
import { describeWorkflowEval } from "@alexkroman1/aai-runtime/eval/vitest";
|
|
35
35
|
import { expect } from "vitest";
|
|
@@ -75,13 +75,10 @@ const critique = (verdict: "ship" | "revise", score = 8): string =>
|
|
|
75
75
|
*/
|
|
76
76
|
function scriptGateway(contents: readonly string[]) {
|
|
77
77
|
const model = stubGatewayRoute(contents);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
return answered;
|
|
84
|
-
});
|
|
78
|
+
// `routeStepFetch` defaults to throwing on an unrecognised request, which is
|
|
79
|
+
// what this file wants and what it used to spell out: every step in this body
|
|
80
|
+
// is a model call, so anything the route does not recognise is a finding.
|
|
81
|
+
installStubStepFetch(routeStepFetch([model.route]));
|
|
85
82
|
return model;
|
|
86
83
|
}
|
|
87
84
|
|
|
@@ -228,7 +228,7 @@ describe("the steps", () => {
|
|
|
228
228
|
* to take the same branch on every walk.
|
|
229
229
|
*
|
|
230
230
|
* The model is the whole world here (`writeDraft`, `critiqueDraft` and
|
|
231
|
-
* `reviseDraft` are all `stepGenerate*
|
|
231
|
+
* `reviseDraft` are all `stepGenerate*OrFail`), so `stubGateway`'s scripted
|
|
232
232
|
* replies ARE the run, and its call log is what proves a replay did not pay for
|
|
233
233
|
* a round twice. Scripted in body order, with the last reply repeating.
|
|
234
234
|
*/
|
|
@@ -59,9 +59,11 @@ import "@alexkroman1/aai-ui/styles.css";
|
|
|
59
59
|
import { plural } from "@alexkroman1/aai/utils";
|
|
60
60
|
import type { WorkflowInputOf, WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
|
|
61
61
|
import {
|
|
62
|
+
BulletList,
|
|
63
|
+
Facts,
|
|
62
64
|
Form,
|
|
63
65
|
type FormValues,
|
|
64
|
-
|
|
66
|
+
mountPage,
|
|
65
67
|
SubmitButton,
|
|
66
68
|
TextAreaField,
|
|
67
69
|
useWorkflowSubmit,
|
|
@@ -70,7 +72,6 @@ import {
|
|
|
70
72
|
WorkflowProgress,
|
|
71
73
|
type WorkflowRun,
|
|
72
74
|
} from "@alexkroman1/aai-ui";
|
|
73
|
-
import { useState } from "react";
|
|
74
75
|
import type { redline } from "./agent.ts";
|
|
75
76
|
|
|
76
77
|
/**
|
|
@@ -124,13 +125,10 @@ export function toInput(values: FormValues): WorkflowInputOf<typeof redline> {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
function RedlineDesk() {
|
|
127
|
-
// Did THIS load start the run? A reload cannot have, and that is the only way
|
|
128
|
-
// the page can tell "writing what you just briefed" from "picking up where
|
|
129
|
-
// you left off" — the hook reports the run, not who asked for it.
|
|
130
|
-
const [startedHere, setStartedHere] = useState(false);
|
|
131
128
|
// The reload is covered by the hook's own key — see the module doc for why
|
|
132
129
|
// this desk wants the tab-scoped one it mints rather than a key of its own.
|
|
133
|
-
const { submit, run, pending, error, reset } =
|
|
130
|
+
const { submit, run, pending, error, reset, startedHere } =
|
|
131
|
+
useWorkflowSubmit<typeof redline>(WORKFLOW);
|
|
134
132
|
|
|
135
133
|
return (
|
|
136
134
|
<main className="mx-auto flex max-w-2xl flex-col gap-8 p-8">
|
|
@@ -142,13 +140,7 @@ function RedlineDesk() {
|
|
|
142
140
|
</p>
|
|
143
141
|
</header>
|
|
144
142
|
|
|
145
|
-
<Form
|
|
146
|
-
onSubmit={(values) => {
|
|
147
|
-
setStartedHere(true);
|
|
148
|
-
return submit(toInput(values));
|
|
149
|
-
}}
|
|
150
|
-
error={error}
|
|
151
|
-
>
|
|
143
|
+
<Form onSubmit={(values) => submit(toInput(values))} error={error}>
|
|
152
144
|
{/* The scalars: brief, audience, rounds. Declared, not written. */}
|
|
153
145
|
<WorkflowFields workflow={WORKFLOW} />
|
|
154
146
|
{/* The array the schema declares and no generic control can render. */}
|
|
@@ -177,7 +169,6 @@ function RedlineDesk() {
|
|
|
177
169
|
// The recovered run is dismissed as deliberately as one this load
|
|
178
170
|
// started: `reset()` is not undone by a second lookup (the lookup
|
|
179
171
|
// is a mount-time act), so Clear really does clear.
|
|
180
|
-
setStartedHere(false);
|
|
181
172
|
reset();
|
|
182
173
|
}}
|
|
183
174
|
/>
|
|
@@ -193,15 +184,16 @@ function Rounds({ rounds }: { rounds: Redline["rounds"] }) {
|
|
|
193
184
|
<ol className="flex flex-col gap-3">
|
|
194
185
|
{rounds.map((entry) => (
|
|
195
186
|
<li key={entry.round} className="flex flex-col gap-1 border-l pl-4">
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
187
|
+
<Facts
|
|
188
|
+
size="xs"
|
|
189
|
+
className="uppercase tracking-[1.2px]"
|
|
190
|
+
items={[
|
|
191
|
+
`Round ${entry.round}`,
|
|
192
|
+
`${entry.critique.score}/10`,
|
|
193
|
+
entry.critique.verdict === "ship" ? "ship it" : "revise",
|
|
194
|
+
]}
|
|
195
|
+
/>
|
|
196
|
+
<BulletList items={entry.critique.notes} size="sm" />
|
|
205
197
|
</li>
|
|
206
198
|
))}
|
|
207
199
|
</ol>
|
|
@@ -224,7 +216,7 @@ function RunPanel({ run, onClear }: { run: WorkflowRun<Redline>; onClear: () =>
|
|
|
224
216
|
{/* The run's own narration — the complement of the status line, which is
|
|
225
217
|
`running` for a run's whole life, so a one-round redline and a
|
|
226
218
|
three-round one look identical while they happen. These lines come from
|
|
227
|
-
the run itself (`
|
|
219
|
+
the run itself (`stepReport()` in `workflows/redline.ts`), and they REPLAY,
|
|
228
220
|
so a reload mid-run catches up rather than starting from whatever
|
|
229
221
|
arrives next. */}
|
|
230
222
|
<WorkflowProgress runId={run.runId} />
|
|
@@ -233,20 +225,30 @@ function RunPanel({ run, onClear }: { run: WorkflowRun<Redline>; onClear: () =>
|
|
|
233
225
|
without a cast. */}
|
|
234
226
|
{run.status === "completed" && (
|
|
235
227
|
<>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
|
|
228
|
+
{/* Which of the two stop conditions ended the loop is the one thing a
|
|
229
|
+
reader cannot infer from the round count alone, so it is a fact of
|
|
230
|
+
its own rather than something left to the round count. */}
|
|
231
|
+
<Facts
|
|
232
|
+
size="xs"
|
|
233
|
+
items={[
|
|
234
|
+
`${run.output.words} words`,
|
|
235
|
+
`${run.output.roundsRun} ${plural(run.output.roundsRun, "round")}`,
|
|
236
|
+
run.output.shipped ? "the critic stopped it" : "the round budget stopped it",
|
|
237
|
+
]}
|
|
238
|
+
/>
|
|
243
239
|
<Rounds rounds={run.output.rounds} />
|
|
244
240
|
<article className="whitespace-pre-wrap text-sm leading-relaxed">
|
|
245
241
|
{run.output.draft}
|
|
246
242
|
</article>
|
|
247
243
|
</>
|
|
248
244
|
)}
|
|
249
|
-
{
|
|
245
|
+
{/* `role="alert"`, the same contract `<Form>` gives the submit error: this
|
|
246
|
+
is the outcome the reader waited minutes for. */}
|
|
247
|
+
{run.status === "failed" && (
|
|
248
|
+
<p role="alert" className="text-red-600">
|
|
249
|
+
{run.error}
|
|
250
|
+
</p>
|
|
251
|
+
)}
|
|
250
252
|
</section>
|
|
251
253
|
);
|
|
252
254
|
}
|
|
@@ -263,4 +265,4 @@ function RunPanel({ run, onClear }: { run: WorkflowRun<Redline>; onClear: () =>
|
|
|
263
265
|
*/
|
|
264
266
|
const STATUS_LINE = { ...WORKFLOW_STATUS_LABELS, running: "Writing…" };
|
|
265
267
|
|
|
266
|
-
|
|
268
|
+
mountPage({ name: "Redline", component: RedlineDesk });
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
* three functions read more tidily than one.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import type {
|
|
36
|
-
import {
|
|
35
|
+
import type { WorkflowContext } from "@alexkroman1/aai";
|
|
36
|
+
import { stepReport } from "@alexkroman1/aai/step";
|
|
37
37
|
import {
|
|
38
38
|
FatalError,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
stepGenerateJsonOrFail,
|
|
40
|
+
stepGenerateOrFail,
|
|
41
41
|
} from "@alexkroman1/aai/step-errors";
|
|
42
42
|
import { countWords } from "@alexkroman1/aai/utils";
|
|
43
43
|
import { z } from "zod";
|
|
@@ -98,7 +98,7 @@ export interface Round {
|
|
|
98
98
|
* the page's render model — and `WorkflowOutputOf<typeof redline>` in
|
|
99
99
|
* `client.tsx` is that type, derived rather than restated.
|
|
100
100
|
*/
|
|
101
|
-
export async function redlineFlow(input: RedlineInput, ctx:
|
|
101
|
+
export async function redlineFlow(input: RedlineInput, ctx: WorkflowContext) {
|
|
102
102
|
// The three `maxAttempts` below were `maxRetries` properties on the functions
|
|
103
103
|
// (3, 5, 3 — retries AFTER the first attempt, so 4, 6, 4 in all). The policy
|
|
104
104
|
// is an argument to the CALL now, which is where it belongs: the same function
|
|
@@ -153,12 +153,12 @@ export async function writeDraft(input: RedlineInput): Promise<string> {
|
|
|
153
153
|
);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
await
|
|
156
|
+
await stepReport(`Writing the first draft for ${input.audience}.`);
|
|
157
157
|
// No empty-reply guard here or in `reviseDraft`, and that is not an omission:
|
|
158
158
|
// `stepGenerate` already refuses an empty completion, as a RETRYABLE
|
|
159
159
|
// `StepGenerateError` — which is the right answer, and one a hand-written
|
|
160
160
|
// check would have to re-derive.
|
|
161
|
-
const draft = await
|
|
161
|
+
const draft = await stepGenerateOrFail(briefBlock(input), { system: WRITER_SYSTEM });
|
|
162
162
|
return draft.trim();
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -175,14 +175,14 @@ export async function critiqueDraft(
|
|
|
175
175
|
input: RedlineInput,
|
|
176
176
|
round: number,
|
|
177
177
|
): Promise<Critique> {
|
|
178
|
-
await
|
|
178
|
+
await stepReport(`Round ${round}: reading it back critically.`);
|
|
179
179
|
// `stepGenerateJson` owns the fence, the parse, the non-object case and the
|
|
180
180
|
// shape — and throws PLAINLY when any of them misses, unlike the fatal one
|
|
181
181
|
// above: a model that answered with prose may well obey on the next attempt.
|
|
182
|
-
const parsed = await
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
);
|
|
182
|
+
const parsed = await stepGenerateJsonOrFail(`${briefBlock(input)}\n\nThe submission:\n${draft}`, {
|
|
183
|
+
schema: CritiqueReply,
|
|
184
|
+
system: CRITIC_SYSTEM,
|
|
185
|
+
});
|
|
186
186
|
|
|
187
187
|
const critique: Critique = {
|
|
188
188
|
verdict: parsed.verdict,
|
|
@@ -192,7 +192,7 @@ export async function critiqueDraft(
|
|
|
192
192
|
score: clampScore(parsed.score),
|
|
193
193
|
notes: parsed.notes.slice(0, MAX_NOTES),
|
|
194
194
|
};
|
|
195
|
-
await
|
|
195
|
+
await stepReport(
|
|
196
196
|
critique.verdict === "ship"
|
|
197
197
|
? `Round ${round}: the critic would ship it (${critique.score}/10).`
|
|
198
198
|
: `Round ${round}: ${critique.notes.length} note(s) to address (${critique.score}/10).`,
|
|
@@ -207,8 +207,8 @@ export async function reviseDraft(
|
|
|
207
207
|
input: RedlineInput,
|
|
208
208
|
round: number,
|
|
209
209
|
): Promise<string> {
|
|
210
|
-
await
|
|
211
|
-
const revised = await
|
|
210
|
+
await stepReport(`Round ${round}: revising.`);
|
|
211
|
+
const revised = await stepGenerateOrFail(
|
|
212
212
|
[
|
|
213
213
|
briefBlock(input),
|
|
214
214
|
`Your current draft:\n${draft}`,
|
|
@@ -241,8 +241,8 @@ export function clampScore(score: number): number {
|
|
|
241
241
|
// There is no local `ask()` any more, and its absence is the point. The SDK
|
|
242
242
|
// classifies the gateway's failure (`StepGenerateError.retryable`) and stops
|
|
243
243
|
// there — whether a terminal failure should burn the step's remaining attempts
|
|
244
|
-
// is the caller's call — so `
|
|
245
|
-
// `
|
|
244
|
+
// is the caller's call — so `stepGenerateOrFail` and
|
|
245
|
+
// `stepGenerateJsonOrFail` (`@alexkroman1/aai/step-errors`) are that call
|
|
246
246
|
// made one way: terminal stays terminal, and a rate limit becomes a
|
|
247
247
|
// `RetryableError` carrying the delay the gateway itself named, which beats
|
|
248
248
|
// `RetryableError`'s own one-second default. Three templates each wrapped the
|