@alexkroman1/aai-cli 6.11.0 → 7.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-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-B4RqNF5Z.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-CiKFH9qw.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-CE4HKdgV.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-nRFaBEPJ.mjs} +2 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-FI3xPEMj.mjs} +1 -1
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-6jfaakgc.mjs} +5 -2
- package/dist/cli.mjs +30 -25
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DvYeD8ia.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-B59JIfWO.mjs} +1 -1
- package/dist/{eject-31gjtaHF.mjs → eject-C9WJyyr2.mjs} +1 -1
- package/dist/{init-l6xfU4xX.mjs → init-CpJVjEg0.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-9q-psa4q.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +40 -19
- package/dist/scaffold/package.json +5 -3
- package/dist/scaffold/server.mjs +1 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-CVvSLIDV.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-BvUrnvM3.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio--MUV0cid.mjs} +4 -4
- package/dist/templates/call-audit/agent.test.ts +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.test.ts +4 -6
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/pizza-ordering/agent.test.ts +27 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.test.ts +746 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +411 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +209 -0
- package/dist/templates/recap-workflow/agent.test.ts +97 -94
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/agent.test.ts +26 -23
- package/dist/templates/retail/client.tsx +226 -117
- package/dist/templates/retail/registry.test.ts +38 -6
- package/dist/templates/retail/store.test.ts +82 -15
- package/dist/templates/retail/store.ts +174 -47
- package/dist/templates/retail/system-prompt.md +11 -2
- package/dist/templates/retail/tools/cancel_pending_order.ts +2 -2
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +2 -2
- package/dist/templates/retail/tools/modify_pending_order_items.ts +2 -3
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +2 -2
- package/dist/templates/retail/tools/modify_user_address.ts +2 -2
- package/dist/templates/retail/tools/return_delivered_order_items.ts +2 -2
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +8 -4
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.test.ts +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/tools/answer_question.ts +17 -4
- package/dist/templates/transcription-workflow/agent.test.ts +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
- package/dist/templates/travel-concierge/agent.test.ts +64 -33
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +70 -3
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_excursion.ts +2 -2
- package/dist/templates/travel-concierge/tools/book_hotel.ts +2 -2
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +2 -2
- package/dist/templates/travel-concierge/tools/update_ticket.ts +2 -2
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-COxnqstQ.mjs} +221 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-D2AQf2Pl.mjs} +28 -1
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
|
@@ -13,9 +13,16 @@
|
|
|
13
13
|
* a different template (`solo-rpg` has the save-slot version).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type DeepReadonly,
|
|
18
|
+
type DialogPosition,
|
|
19
|
+
type DialogSpec,
|
|
20
|
+
dialog,
|
|
21
|
+
isToolFailure,
|
|
22
|
+
pushCapped,
|
|
23
|
+
sessionSlot,
|
|
24
|
+
} from "@alexkroman1/aai";
|
|
17
25
|
import { webSearch } from "@alexkroman1/aai/tools";
|
|
18
|
-
import { isToolFailure } from "@alexkroman1/aai/utils";
|
|
19
26
|
|
|
20
27
|
/** One completed step — their `past_steps`, as a pair rather than a tuple. */
|
|
21
28
|
export interface PastStep {
|
|
@@ -61,6 +68,68 @@ export function emptyPlan(): PlanState {
|
|
|
61
68
|
|
|
62
69
|
export const planSlot = sessionSlot("plan", emptyPlan);
|
|
63
70
|
|
|
71
|
+
/**
|
|
72
|
+
* The plan's LIFECYCLE, as a declared machine rather than a guard per tool.
|
|
73
|
+
*
|
|
74
|
+
* Three of the four tools used to open with the same shape — `if
|
|
75
|
+
* (!plan.objective) return toolFailure("There is no plan yet …")`, and
|
|
76
|
+
* `work_next_step` carried a second one for the already-answered case. Those are
|
|
77
|
+
* not data checks, they are the question "where is this conversation", and
|
|
78
|
+
* getting one wrong is silent: the tool runs, the model reads a plausible
|
|
79
|
+
* result, and the caller is told about a plan that does not exist. A fifth tool
|
|
80
|
+
* would have had to remember both.
|
|
81
|
+
*
|
|
82
|
+
* `when` is that check now, and it is the SDK's rather than this template's — so
|
|
83
|
+
* the refusal names where the call actually is and quotes the instruction below,
|
|
84
|
+
* which is what lets the model recover on its own turn instead of apologizing.
|
|
85
|
+
*
|
|
86
|
+
* The states are the notebook's own, read off `PlanExecute`: no `input` yet,
|
|
87
|
+
* an `input` with steps left, and a `response`. `PLANNED` is accepted from all
|
|
88
|
+
* three because `start_plan` is always legal — a caller may re-plan from
|
|
89
|
+
* scratch at any point, which is the one transition that is not a progression.
|
|
90
|
+
*/
|
|
91
|
+
const planSpec = {
|
|
92
|
+
initial: "idle",
|
|
93
|
+
states: {
|
|
94
|
+
idle: {
|
|
95
|
+
instruction:
|
|
96
|
+
"There is no plan yet. Find out what the caller wants to get done, then use start_plan.",
|
|
97
|
+
on: { PLANNED: "working" },
|
|
98
|
+
},
|
|
99
|
+
working: {
|
|
100
|
+
instruction:
|
|
101
|
+
"Work the plan one step at a time with work_next_step, reporting after each step.",
|
|
102
|
+
on: { ANSWERED: "answered", PLANNED: "working" },
|
|
103
|
+
},
|
|
104
|
+
answered: {
|
|
105
|
+
instruction:
|
|
106
|
+
"The plan is finished — give the caller the answer. Use revise_plan if they change their mind.",
|
|
107
|
+
on: { REOPENED: "working", PLANNED: "working" },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
} as const satisfies DialogSpec;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The flow. Its own slot key, because a flow stores an actor snapshot and
|
|
114
|
+
* {@link planSlot} stores the plan — the position and the payload are two
|
|
115
|
+
* things, and one tool call moves both.
|
|
116
|
+
*/
|
|
117
|
+
export const planFlow = dialog("planFlow", planSpec);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* How the stage reads to a caller, from the flow's own position.
|
|
121
|
+
*
|
|
122
|
+
* A helper over a {@link DialogPosition} rather than over {@link PlanState}, which
|
|
123
|
+
* is the point: "where is this call" is the machine's answer, and deriving it a
|
|
124
|
+
* second time from the plan's fields is what the three removed guards were
|
|
125
|
+
* doing. `plan_status` reads this, and so would any prompt that wants to say it
|
|
126
|
+
* aloud.
|
|
127
|
+
*/
|
|
128
|
+
export function stageLabel(at: DialogPosition): string {
|
|
129
|
+
if (at.state === "idle") return "no plan yet";
|
|
130
|
+
return at.state === "answered" ? "finished" : "in progress";
|
|
131
|
+
}
|
|
132
|
+
|
|
64
133
|
export function noteRevision(state: PlanState, entry: string): void {
|
|
65
134
|
pushCapped(state.revisions, entry, MAX_REVISIONS);
|
|
66
135
|
}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { planFlow, planSlot, stageLabel } from "../shared.ts";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* Where the plan is. Read-only, so plain `tool` semantics — a synchronous
|
|
5
|
-
*
|
|
5
|
+
* Where the plan is. Read-only, so plain `tool` semantics — a synchronous read
|
|
6
|
+
* cannot interleave with anything.
|
|
7
|
+
*
|
|
8
|
+
* **Legal in every state, which is why it is not a `planFlow.tool`**, and it is
|
|
9
|
+
* the one tool that reports the flow's own POSITION: `stage` and `next` come
|
|
10
|
+
* from the machine rather than from a second reading of the plan's fields, so
|
|
11
|
+
* "no plan yet" is the same fact here as the refusal `work_next_step` would
|
|
12
|
+
* give. It used to derive that from `!plan.objective`, which was a third copy of
|
|
13
|
+
* the same question.
|
|
6
14
|
*/
|
|
7
|
-
export default
|
|
15
|
+
export default tool({
|
|
8
16
|
description:
|
|
9
17
|
"Say where the plan has got to: what is done, what is left, and the answer " +
|
|
10
18
|
"if there is one. Use it when the caller asks, or to pick a call back up.",
|
|
11
|
-
execute(_args,
|
|
12
|
-
|
|
19
|
+
execute(_args, ctx) {
|
|
20
|
+
const at = planFlow.position(ctx);
|
|
21
|
+
const plan = planSlot.get(ctx);
|
|
13
22
|
return {
|
|
23
|
+
stage: at.state,
|
|
24
|
+
reads: stageLabel(at),
|
|
25
|
+
next: at.instruction,
|
|
14
26
|
objective: plan.objective,
|
|
15
27
|
done: plan.pastSteps.map((past) => ({ step: past.step, result: past.result })),
|
|
16
28
|
remaining: plan.plan,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { errorMessage,
|
|
1
|
+
import { errorMessage, toolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { replanNode } from "../
|
|
3
|
+
import { replanNode } from "../procedure.ts";
|
|
4
4
|
import { REVISE_SYSTEM } from "../prompts.ts";
|
|
5
|
-
import { noteRevision, planSlot } from "../shared.ts";
|
|
5
|
+
import { noteRevision, planFlow, planSlot } from "../shared.ts";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The replanner, driven by the caller instead of by a step result.
|
|
@@ -16,8 +16,14 @@ import { noteRevision, planSlot } from "../shared.ts";
|
|
|
16
16
|
* The await-then-mutate shape is `start_plan`'s, for the reason it gives — and
|
|
17
17
|
* note the READ before the await is `planSlot.get`, which the replanner only
|
|
18
18
|
* needs to look at.
|
|
19
|
+
*
|
|
20
|
+
* **Legal in `working` AND `answered`, which is the whole point of it.** A
|
|
21
|
+
* finished plan is exactly what a caller most often wants changed, so this is
|
|
22
|
+
* the one tool that reopens one — `REOPENED` takes the flow back to `working`,
|
|
23
|
+
* and the body's clearing of `plan.response` is the same decision at the data
|
|
24
|
+
* level. Its `!objective` guard is gone: `when` is that check now.
|
|
19
25
|
*/
|
|
20
|
-
export default tool({
|
|
26
|
+
export default planFlow.tool({
|
|
21
27
|
description:
|
|
22
28
|
"Rewrite the remaining plan because the caller changed what they want. " +
|
|
23
29
|
"Pass their instruction as they said it. Completed steps are never redone.",
|
|
@@ -27,11 +33,8 @@ export default tool({
|
|
|
27
33
|
.max(400)
|
|
28
34
|
.describe("What the caller now wants changed, in their own words"),
|
|
29
35
|
}),
|
|
36
|
+
when: ["working", "answered"],
|
|
30
37
|
async execute(args, ctx) {
|
|
31
|
-
if (!planSlot.get(ctx).objective) {
|
|
32
|
-
return toolFailure("There is no plan to revise — use start_plan first.");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
38
|
try {
|
|
36
39
|
const act = await replanNode(ctx.generate, planSlot.get(ctx), {
|
|
37
40
|
system: REVISE_SYSTEM,
|
|
@@ -43,7 +46,7 @@ export default tool({
|
|
|
43
46
|
if (act.kind === "respond") {
|
|
44
47
|
plan.plan = [];
|
|
45
48
|
plan.response = act.response;
|
|
46
|
-
return {
|
|
49
|
+
return { finished: true, response: act.response, message: "Nothing is left to do." };
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
plan.plan = act.steps;
|
|
@@ -51,7 +54,7 @@ export default tool({
|
|
|
51
54
|
// moved the goalposts, so the old answer is no longer the answer.
|
|
52
55
|
plan.response = null;
|
|
53
56
|
return {
|
|
54
|
-
|
|
57
|
+
finished: false,
|
|
55
58
|
remaining: act.steps,
|
|
56
59
|
message: "Read the revised steps back and ask if that is right.",
|
|
57
60
|
};
|
|
@@ -60,4 +63,9 @@ export default tool({
|
|
|
60
63
|
return toolFailure(`The plan could not be revised: ${errorMessage(err)}`);
|
|
61
64
|
}
|
|
62
65
|
},
|
|
66
|
+
// Below `execute` deliberately — see the note on `work_next_step`'s own
|
|
67
|
+
// `sendFrom`: this body's return type is itself inferred, so a `sendFrom`
|
|
68
|
+
// written first has nothing to be contextually typed against.
|
|
69
|
+
sendFrom: (outcome) =>
|
|
70
|
+
outcome.finished ? ({ type: "ANSWERED" } as const) : ({ type: "REOPENED" } as const),
|
|
63
71
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { errorMessage, tool, toolFailure } from "@alexkroman1/aai";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { planNode } from "../
|
|
4
|
-
import { noteRevision, planSlot } from "../shared.ts";
|
|
3
|
+
import { planNode } from "../procedure.ts";
|
|
4
|
+
import { noteRevision, planFlow, planSlot } from "../shared.ts";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Their `plan_step`, as the call's opening move.
|
|
@@ -13,6 +13,12 @@ import { noteRevision, planSlot } from "../shared.ts";
|
|
|
13
13
|
* version guarded against is unchanged (the LLM loop runs a step's tool calls
|
|
14
14
|
* concurrently, so two plans started at once must not interleave); what changed
|
|
15
15
|
* is that the window is now too short to interleave IN.
|
|
16
|
+
*
|
|
17
|
+
* **This one is an ordinary `tool()` and NOT a `planFlow.tool`, deliberately.**
|
|
18
|
+
* `PLANNED` is accepted in all three states — re-planning from scratch is always
|
|
19
|
+
* legal — so a `when` listing every state would be a gate that gates nothing,
|
|
20
|
+
* paying the wrapper for it. It sends the event itself instead, which is what
|
|
21
|
+
* `dialog.send` is public for.
|
|
16
22
|
*/
|
|
17
23
|
export default tool({
|
|
18
24
|
description:
|
|
@@ -32,6 +38,11 @@ export default tool({
|
|
|
32
38
|
return toolFailure(`The planner failed: ${errorMessage(err)}`);
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
// The flow moves first: `work_next_step` gates on `working`, and a plan
|
|
42
|
+
// written to the slot while the flow still said `idle` would be refused by
|
|
43
|
+
// its own next tool call.
|
|
44
|
+
planFlow.send(ctx, { type: "PLANNED" });
|
|
45
|
+
|
|
35
46
|
return planSlot.update(ctx, (plan) => {
|
|
36
47
|
plan.objective = args.objective;
|
|
37
48
|
plan.plan = steps;
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { errorMessage,
|
|
2
|
-
import { executeStep, replanNode } from "../
|
|
3
|
-
import {
|
|
1
|
+
import { errorMessage, type ToolFailure, toolFailure } from "@alexkroman1/aai";
|
|
2
|
+
import { executeStep, replanNode } from "../procedure.ts";
|
|
3
|
+
import {
|
|
4
|
+
liveSearch,
|
|
5
|
+
noteRevision,
|
|
6
|
+
type PastStep,
|
|
7
|
+
planFlow,
|
|
8
|
+
planSlot,
|
|
9
|
+
recordStep,
|
|
10
|
+
} from "../shared.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What the claim window decided, as a DISCRIMINATED union.
|
|
14
|
+
*
|
|
15
|
+
* The three arms used to be told apart by `"done" in claimed`, which stopped
|
|
16
|
+
* working once the outcome had a declared type: union normalization gives every
|
|
17
|
+
* arm the other arms' keys as `?: never`, and `in` cannot rule out a key that is
|
|
18
|
+
* optional-never. A `kind` tag is both narrower and easier to read.
|
|
19
|
+
*/
|
|
20
|
+
type Claim =
|
|
21
|
+
| { kind: "failed"; failure: ToolFailure }
|
|
22
|
+
| { kind: "dry" }
|
|
23
|
+
| { kind: "step"; step: string; objective: string; pastSteps: PastStep[] };
|
|
4
24
|
|
|
5
25
|
/**
|
|
6
26
|
* One turn of their execute→replan loop: do the head step, then let the
|
|
@@ -22,33 +42,54 @@ import { liveSearch, noteRevision, type PastStep, planSlot, recordStep } from ".
|
|
|
22
42
|
* behind each other to get the same guarantee. A step whose work then FAILS is
|
|
23
43
|
* put back, because a failed model call is worth retrying and a silently dropped
|
|
24
44
|
* step leaves the caller with a plan that skipped something.
|
|
45
|
+
*
|
|
46
|
+
* **`when: "working"` replaced two hand-rolled guards** — the `!plan.objective`
|
|
47
|
+
* check and the already-answered one — and neither was a data check: both asked
|
|
48
|
+
* where the conversation was. See {@link planFlow}. The flow is what refuses now,
|
|
49
|
+
* naming the state and quoting its instruction, so this body only ever runs when
|
|
50
|
+
* there is a plan in progress.
|
|
51
|
+
*
|
|
52
|
+
* **`ANSWERED` is sent only when the replanner produced a RESPONSE.** A plan
|
|
53
|
+
* that merely ran out of steps stays in `working`: nothing was concluded, and
|
|
54
|
+
* the caller can still revise or add to it. Conflating the two would announce an
|
|
55
|
+
* answer that does not exist.
|
|
25
56
|
*/
|
|
26
|
-
export default tool({
|
|
57
|
+
export default planFlow.tool({
|
|
27
58
|
description:
|
|
28
59
|
"Do the next step of the plan and report what it found. Call this once per " +
|
|
29
60
|
"step — never in a loop. Say a short 'let me look into that' first, since " +
|
|
30
61
|
"the step may take a few seconds.",
|
|
62
|
+
when: "working",
|
|
31
63
|
async execute(_args, ctx) {
|
|
32
64
|
// The whole read-and-claim, in one window nothing can interleave with.
|
|
33
|
-
const claimed = planSlot.update(ctx, (plan) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
65
|
+
const claimed = planSlot.update(ctx, (plan): Claim => {
|
|
66
|
+
// `when: "working"` means there IS a plan, so this arm is unreachable by
|
|
67
|
+
// the flow's own guarantee — kept because the slot and the flow are two
|
|
68
|
+
// values, and a plan cleared by something else should refuse rather than
|
|
69
|
+
// claim a step off an empty objective.
|
|
70
|
+
if (!plan.objective) {
|
|
71
|
+
return { kind: "failed", failure: toolFailure("There is no plan yet — use start_plan.") };
|
|
41
72
|
}
|
|
42
73
|
const step = plan.plan.shift();
|
|
43
|
-
if (!step) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
if (!step) return { kind: "dry" };
|
|
75
|
+
return {
|
|
76
|
+
kind: "step",
|
|
77
|
+
step,
|
|
78
|
+
objective: plan.objective,
|
|
79
|
+
pastSteps: [...plan.pastSteps] as PastStep[],
|
|
80
|
+
};
|
|
50
81
|
});
|
|
51
|
-
if (
|
|
82
|
+
if (claimed.kind === "failed") return claimed.failure;
|
|
83
|
+
if (claimed.kind === "dry") {
|
|
84
|
+
// `response: undefined` is spelled out rather than omitted: `sendFrom`
|
|
85
|
+
// below reads that field to decide whether to send ANSWERED, and a key
|
|
86
|
+
// missing from one arm of a union is not readable on the union at all.
|
|
87
|
+
return {
|
|
88
|
+
finished: true,
|
|
89
|
+
response: undefined,
|
|
90
|
+
message: "No steps are left. Ask the caller what they want next.",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
52
93
|
const { step, objective, pastSteps } = claimed;
|
|
53
94
|
|
|
54
95
|
try {
|
|
@@ -69,7 +110,7 @@ export default tool({
|
|
|
69
110
|
plan.plan = [];
|
|
70
111
|
noteRevision(plan, `Finished after ${plan.pastSteps.length} step(s)`);
|
|
71
112
|
return {
|
|
72
|
-
|
|
113
|
+
finished: true,
|
|
73
114
|
step,
|
|
74
115
|
result: outcome.result,
|
|
75
116
|
searches: outcome.searches,
|
|
@@ -82,7 +123,7 @@ export default tool({
|
|
|
82
123
|
plan.plan = act.steps;
|
|
83
124
|
if (changed) noteRevision(plan, `Replanned to ${act.steps.length} step(s) after: ${step}`);
|
|
84
125
|
return {
|
|
85
|
-
|
|
126
|
+
finished: false,
|
|
86
127
|
step,
|
|
87
128
|
result: outcome.result,
|
|
88
129
|
searches: outcome.searches,
|
|
@@ -98,4 +139,11 @@ export default tool({
|
|
|
98
139
|
return toolFailure(`That step could not be worked: ${errorMessage(err)}`);
|
|
99
140
|
}
|
|
100
141
|
},
|
|
142
|
+
// Written BELOW `execute` deliberately. `sendFrom`'s parameter is
|
|
143
|
+
// `Exclude<NoInfer<R>, ToolFailure>`, and `NoInfer` keeps it from bidding on
|
|
144
|
+
// `R` — but this body's own return type is itself an inference
|
|
145
|
+
// (`planSlot.update`'s), so with `sendFrom` first there is no candidate to
|
|
146
|
+
// contextually type it against and the parameter lands as `unknown`.
|
|
147
|
+
sendFrom: (outcome) =>
|
|
148
|
+
outcome.response === undefined ? undefined : ({ type: "ANSWERED" } as const),
|
|
101
149
|
});
|