@alexkroman1/aai-cli 5.14.0 → 6.1.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-C8Jk-ZcP.mjs → _agent-vGW5SBnX.mjs} +23 -5
- package/dist/_agent.d.ts +18 -0
- package/dist/{_api-client-BX8Qv51n.mjs → _api-client-LgLksMhN.mjs} +45 -8
- package/dist/_api-client.d.ts +39 -0
- package/dist/{_bundler-BuIyDdvs.mjs → _bundler-BapCUUJN.mjs} +42 -14
- package/dist/_bundler.d.ts +43 -5
- package/dist/_cli-common.d.ts +67 -3
- package/dist/{_config-CzwAWOWr.mjs → _config-D_s09e7g.mjs} +36 -18
- package/dist/_deploy.d.ts +2 -5
- package/dist/_dev-restart.d.ts +6 -1
- package/dist/{_dev-server-Ch7K8e8i.mjs → _dev-server-DQ1t9sbw.mjs} +94 -38
- package/dist/_dev-server.d.ts +30 -2
- package/dist/_e2e-test-utils.d.ts +80 -0
- package/dist/_fault-mode.d.ts +175 -0
- package/dist/{_init-CbkrE2wq.mjs → _init-CpISqoR8.mjs} +3 -3
- package/dist/{_output-CC300DzW.mjs → _output-CKkmWs7i.mjs} +8 -1
- package/dist/_output.d.ts +9 -2
- package/dist/{_server-common-61fL__39.mjs → _server-common-6e0QI4mq.mjs} +1 -1
- package/dist/{_slug-api-D30zPmsn.mjs → _slug-api-D2heILuC.mjs} +2 -2
- package/dist/{_studio-D2ciIhbi.mjs → _studio-zAJycZ8j.mjs} +3 -3
- package/dist/{_templates-B1kt3BGt.mjs → _templates-iDGjGiPf.mjs} +4 -6
- package/dist/{_typecheck-gate-4v9UBZHh.mjs → _typecheck-gate-BXvUNnfs.mjs} +1 -1
- package/dist/{_utils-0DHbcqUb.mjs → _utils-B8QmtFhK.mjs} +21 -7
- package/dist/_utils.d.ts +18 -2
- package/dist/{_vite-env-Dg_QlVv0.mjs → _vite-env-BNveawd1.mjs} +31 -1
- package/dist/_vite-env.d.ts +30 -0
- package/dist/build-BO1Ni6oJ.mjs +62 -0
- package/dist/build.d.ts +19 -0
- package/dist/cli-workflow.d.ts +10 -0
- package/dist/cli.d.ts +1 -2
- package/dist/cli.mjs +406 -224
- package/dist/{client-bundler-C9qXL8A2.mjs → client-bundler-B8Q9F8gJ.mjs} +3 -22
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-DCRX3d96.mjs → delete-4I0uNR4a.mjs} +6 -7
- package/dist/delete.d.ts +2 -8
- package/dist/{deploy-DjsEV-40.mjs → deploy-DJ3HRcia.mjs} +13 -14
- package/dist/{dev-CvPH5dbF.mjs → dev-C6SWaAZP.mjs} +3 -3
- package/dist/{eject-LgBRfnbH.mjs → eject-Zrzk5KV3.mjs} +41 -15
- package/dist/eject.d.ts +30 -3
- package/dist/{init-BppxMR8r.mjs → init-BTRGiPQr.mjs} +61 -26
- package/dist/init.d.ts +11 -0
- package/dist/{login-BQQaxJhL.mjs → login-k0Z3VQu9.mjs} +5 -5
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +498 -91
- package/dist/scaffold/package.json +11 -8
- package/dist/scaffold/pnpm-workspace.yaml +8 -0
- package/dist/scaffold/server.mjs +53 -57
- package/dist/{secret-Ds9dvqAj.mjs → secret-DzSQx1Ds.mjs} +6 -3
- package/dist/{storage-DbrQSHFk.mjs → storage-C-Do0mIA.mjs} +18 -6
- package/dist/{studio-GSFd4s5O.mjs → studio-BGVmCA2G.mjs} +10 -10
- package/dist/templates/code-interpreter/agent.ts +0 -2
- package/dist/templates/dispatch-center/agent.test.ts +17 -19
- package/dist/templates/dispatch-center/agent.ts +2 -29
- package/dist/templates/dispatch-center/client.tsx +33 -16
- package/dist/templates/dispatch-center/shared.ts +53 -19
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +12 -14
- package/dist/templates/dispatch-center/tools/incident_create.ts +41 -44
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +59 -61
- package/dist/templates/dispatch-center/tools/incident_get.ts +3 -4
- package/dist/templates/dispatch-center/tools/incident_triage.ts +43 -45
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +39 -41
- package/dist/templates/dispatch-center/tools/ops_dashboard.ts +2 -5
- package/dist/templates/dispatch-center/tools/ops_protocols.ts +1 -1
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +20 -23
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +52 -54
- package/dist/templates/dispatch-center/tools/resources_get_available.ts +2 -4
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +39 -42
- package/dist/templates/embedded-assets/agent.test.ts +83 -0
- package/dist/templates/embedded-assets/agent.ts +1 -48
- package/dist/templates/embedded-assets/shared.ts +17 -0
- package/dist/templates/embedded-assets/system-prompt.md +13 -0
- package/dist/templates/embedded-assets/tools/list_topics.ts +9 -0
- package/dist/templates/embedded-assets/tools/search_knowledge.ts +26 -0
- package/dist/templates/health-assistant/agent.test.ts +169 -0
- package/dist/templates/health-assistant/agent.ts +1 -166
- package/dist/templates/health-assistant/fda.ts +100 -0
- package/dist/templates/health-assistant/tools/check_drug_interaction.ts +59 -0
- package/dist/templates/health-assistant/tools/medication_lookup.ts +31 -0
- package/dist/templates/infocom-adventure/agent.test.ts +205 -0
- package/dist/templates/infocom-adventure/agent.ts +3 -106
- package/dist/templates/infocom-adventure/client.tsx +28 -12
- package/dist/templates/infocom-adventure/shared.ts +28 -11
- package/dist/templates/infocom-adventure/tools/game_state_drop.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_flag.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_get.ts +16 -0
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +14 -0
- package/dist/templates/infocom-adventure/tools/game_state_restart.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_score.ts +13 -0
- package/dist/templates/infocom-adventure/tools/game_state_take.ts +13 -0
- package/dist/templates/link-digest/agent.test.ts +223 -0
- package/dist/templates/link-digest/agent.ts +79 -0
- package/dist/templates/link-digest/client.tsx +146 -0
- package/dist/templates/link-digest/workflows/digest.ts +248 -0
- package/dist/templates/math-buddy/agent.ts +0 -2
- package/dist/templates/night-owl/agent.test.ts +80 -0
- package/dist/templates/night-owl/agent.ts +1 -68
- package/dist/templates/night-owl/system-prompt.md +10 -0
- package/dist/templates/night-owl/tools/recommend.ts +64 -0
- package/dist/templates/personal-finance/agent.ts +0 -2
- package/dist/templates/pizza-ordering/agent.test.ts +52 -47
- package/dist/templates/pizza-ordering/agent.ts +9 -158
- package/dist/templates/pizza-ordering/shared.ts +35 -12
- package/dist/templates/pizza-ordering/tools/add_pizza.ts +31 -0
- package/dist/templates/pizza-ordering/tools/place_order.ts +22 -0
- package/dist/templates/pizza-ordering/tools/remove_pizza.ts +22 -0
- package/dist/templates/pizza-ordering/tools/set_customer_name.ts +13 -0
- package/dist/templates/pizza-ordering/tools/update_pizza.ts +28 -0
- package/dist/templates/pizza-ordering/tools/view_order.ts +21 -0
- package/dist/templates/plan-and-execute/agent.test.ts +411 -0
- package/dist/templates/plan-and-execute/agent.ts +23 -0
- package/dist/templates/plan-and-execute/client.tsx +115 -0
- package/dist/templates/plan-and-execute/graph.ts +178 -0
- package/dist/templates/plan-and-execute/prompts.ts +110 -0
- package/dist/templates/plan-and-execute/shared.ts +161 -0
- package/dist/templates/plan-and-execute/system-prompt.md +18 -0
- package/dist/templates/plan-and-execute/tools/plan_status.ts +20 -0
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +63 -0
- package/dist/templates/plan-and-execute/tools/start_plan.ts +51 -0
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +101 -0
- package/dist/templates/recap-workflow/agent.test.ts +762 -0
- package/dist/templates/recap-workflow/agent.ts +95 -0
- package/dist/templates/recap-workflow/shared.ts +43 -0
- package/dist/templates/recap-workflow/tools/cancel_recap.ts +23 -0
- package/dist/templates/recap-workflow/tools/keep_transcript.ts +31 -0
- package/dist/templates/recap-workflow/tools/recap_progress.ts +27 -0
- package/dist/templates/recap-workflow/tools/recap_status.ts +46 -0
- package/dist/templates/recap-workflow/tools/request_recap.ts +43 -0
- package/dist/templates/recap-workflow/workflows/recap.ts +544 -0
- package/dist/templates/recap-workflow/workflows/tokens.ts +26 -0
- package/dist/templates/redline/agent.test.ts +222 -0
- package/dist/templates/redline/agent.ts +89 -0
- package/dist/templates/redline/client.tsx +184 -0
- package/dist/templates/redline/workflows/prompts.ts +68 -0
- package/dist/templates/redline/workflows/redline.ts +258 -0
- package/dist/templates/research-workflow/agent.test.ts +525 -0
- package/dist/templates/research-workflow/agent.ts +94 -0
- package/dist/templates/research-workflow/shared.ts +35 -0
- package/dist/templates/research-workflow/tools/file_it_now.ts +22 -0
- package/dist/templates/research-workflow/tools/request_research.ts +33 -0
- package/dist/templates/research-workflow/tools/research_progress.ts +26 -0
- package/dist/templates/research-workflow/tools/research_status.ts +40 -0
- package/dist/templates/research-workflow/workflows/prompts.ts +143 -0
- package/dist/templates/research-workflow/workflows/research.ts +558 -0
- package/dist/templates/retail/agent.test.ts +236 -172
- package/dist/templates/retail/agent.ts +5 -42
- package/dist/templates/retail/client.tsx +33 -11
- package/dist/templates/retail/registry.test.ts +31 -16
- package/dist/templates/retail/resolve.ts +28 -64
- package/dist/templates/retail/shared.test.ts +4 -3
- package/dist/templates/retail/shared.ts +24 -7
- package/dist/templates/retail/store.test.ts +12 -10
- package/dist/templates/retail/store.ts +69 -30
- package/dist/templates/retail/tools/cancel_pending_order.ts +4 -9
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -11
- package/dist/templates/retail/tools/find_user_id_by_email.ts +5 -9
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +5 -7
- package/dist/templates/retail/tools/get_item_details.ts +5 -7
- package/dist/templates/retail/tools/get_order_details.ts +5 -7
- package/dist/templates/retail/tools/get_product_details.ts +4 -7
- package/dist/templates/retail/tools/get_user_details.ts +4 -7
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -7
- package/dist/templates/retail/tools/modify_pending_order_address.ts +4 -9
- package/dist/templates/retail/tools/modify_pending_order_items.ts +4 -16
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +3 -9
- package/dist/templates/retail/tools/modify_user_address.ts +4 -9
- package/dist/templates/retail/tools/return_delivered_order_items.ts +3 -9
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +1 -1
- package/dist/templates/solo-rpg/agent.test.ts +152 -29
- package/dist/templates/solo-rpg/agent.ts +3 -21
- package/dist/templates/solo-rpg/client.tsx +19 -6
- package/dist/templates/solo-rpg/shared.ts +19 -11
- package/dist/templates/solo-rpg/tools/action_roll.ts +2 -4
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +2 -4
- package/dist/templates/solo-rpg/tools/check_state.ts +6 -6
- package/dist/templates/solo-rpg/tools/load_game.ts +1 -1
- package/dist/templates/solo-rpg/tools/oracle.ts +18 -11
- package/dist/templates/solo-rpg/tools/save_game.ts +10 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +1 -1
- package/dist/templates/solo-rpg/tools/update_state.ts +3 -4
- package/dist/templates/support-line/agent.test.ts +343 -0
- package/dist/templates/support-line/agent.ts +27 -0
- package/dist/templates/support-line/client.tsx +122 -0
- package/dist/templates/support-line/graph.ts +224 -0
- package/dist/templates/support-line/knowledge.json +65 -0
- package/dist/templates/support-line/prompts.ts +95 -0
- package/dist/templates/support-line/shared.ts +240 -0
- package/dist/templates/support-line/system-prompt.md +17 -0
- package/dist/templates/support-line/tools/answer_question.ts +72 -0
- package/dist/templates/support-line/tools/list_topics.ts +22 -0
- package/dist/templates/support-line/tools/log_ticket.ts +30 -0
- package/dist/templates/transcription-workflow/agent.test.ts +563 -0
- package/dist/templates/transcription-workflow/agent.ts +92 -0
- package/dist/templates/transcription-workflow/client.tsx +234 -0
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +416 -0
- package/dist/templates/transcription-workflow/workflows/wav.ts +314 -0
- package/dist/templates/travel-concierge/agent.test.ts +338 -0
- package/dist/templates/travel-concierge/agent.ts +29 -0
- package/dist/templates/travel-concierge/client.tsx +161 -0
- package/dist/templates/travel-concierge/routing.ts +138 -0
- package/dist/templates/travel-concierge/shared.ts +547 -0
- package/dist/templates/travel-concierge/system-prompt.md +24 -0
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +20 -0
- package/dist/templates/travel-concierge/tools/book_excursion.ts +18 -0
- package/dist/templates/travel-concierge/tools/book_hotel.ts +20 -0
- package/dist/templates/travel-concierge/tools/cancel_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +16 -0
- package/dist/templates/travel-concierge/tools/complete_or_escalate.ts +7 -0
- package/dist/templates/travel-concierge/tools/confirm_action.ts +7 -0
- package/dist/templates/travel-concierge/tools/lookup_booking.ts +35 -0
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +33 -0
- package/dist/templates/travel-concierge/tools/search_excursions.ts +37 -0
- package/dist/templates/travel-concierge/tools/search_flights.ts +43 -0
- package/dist/templates/travel-concierge/tools/search_hotels.ts +36 -0
- package/dist/templates/travel-concierge/tools/to_car_rental_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_excursion_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_flight_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/to_hotel_assistant.ts +11 -0
- package/dist/templates/travel-concierge/tools/update_ticket.ts +22 -0
- package/dist/templates/web-researcher/agent.ts +0 -2
- package/dist/templates/web-researcher/system-prompt.md +10 -0
- package/dist/{test-2LWBcXvY.mjs → test-B6ZBxSpk.mjs} +2 -2
- package/dist/typecheck.mjs +17 -15
- package/dist/worker-bundler-CIdDhZ0c.mjs +398 -0
- package/dist/worker-bundler.d.ts +14 -0
- package/dist/worker-bundler.mjs +1 -108
- package/dist/workflow-B-O97AqI.mjs +125 -0
- package/dist/workflow-bundler.d.ts +133 -0
- package/dist/workflow.d.ts +67 -0
- package/package.json +13 -9
- package/dist/build-DQJ1ZSm9.mjs +0 -39
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-
|
|
2
|
+
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-D_s09e7g.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -100,16 +100,34 @@ async function resolveDeployTarget(cwd, explicitServer) {
|
|
|
100
100
|
apiKey: await ensureApiKey()
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The deployed slug a slug-scoped command needs, or the sentence naming
|
|
105
|
+
* `aai publish`.
|
|
106
|
+
*
|
|
107
|
+
* A pulled-but-never-published project has a config without a slug — for
|
|
108
|
+
* slug-scoped commands (secret, storage, delete) that is the same as never
|
|
109
|
+
* deployed. Exported so a command that has ALREADY resolved the target
|
|
110
|
+
* (`aai delete`) can demand the slug without resolving it a second time; one
|
|
111
|
+
* sentence, one place.
|
|
112
|
+
*
|
|
113
|
+
* The slug's SHAPE was already enforced by `resolveDeployTarget`, for every
|
|
114
|
+
* command rather than only the slug-scoped ones — see `assertValidConfigSlug`.
|
|
115
|
+
* Deliberately not re-checked here: two copies of that guard is what let
|
|
116
|
+
* `publish` ship without one.
|
|
117
|
+
*/
|
|
118
|
+
function requireDeployedSlug(config) {
|
|
119
|
+
if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
|
|
120
|
+
return config.slug;
|
|
121
|
+
}
|
|
103
122
|
/** Like resolveDeployTarget, but requires an existing deployment (project config). */
|
|
104
123
|
async function getServerInfo(cwd, explicitServer) {
|
|
105
124
|
const { config, serverUrl, apiKey } = await resolveDeployTarget(cwd, explicitServer);
|
|
106
|
-
if (!config?.slug) throw new Error("This project has no deployed agent — run `aai publish` first");
|
|
107
125
|
return {
|
|
108
126
|
serverUrl,
|
|
109
|
-
slug: config
|
|
127
|
+
slug: requireDeployedSlug(config),
|
|
110
128
|
apiKey,
|
|
111
|
-
studioProject: config
|
|
129
|
+
studioProject: config?.studioProject
|
|
112
130
|
};
|
|
113
131
|
}
|
|
114
132
|
//#endregion
|
|
115
|
-
export {
|
|
133
|
+
export { resolveDeployTarget as a, requireDeployedSlug as i, getServerInfo as n, resolveServerUrl as o, isDevMode as r, getMonorepoRoot as t };
|
package/dist/_agent.d.ts
CHANGED
|
@@ -33,6 +33,24 @@ export declare function resolveDeployTarget(cwd: string, explicitServer?: string
|
|
|
33
33
|
serverUrl: string;
|
|
34
34
|
apiKey: string;
|
|
35
35
|
}>;
|
|
36
|
+
/**
|
|
37
|
+
* The deployed slug a slug-scoped command needs, or the sentence naming
|
|
38
|
+
* `aai publish`.
|
|
39
|
+
*
|
|
40
|
+
* A pulled-but-never-published project has a config without a slug — for
|
|
41
|
+
* slug-scoped commands (secret, storage, delete) that is the same as never
|
|
42
|
+
* deployed. Exported so a command that has ALREADY resolved the target
|
|
43
|
+
* (`aai delete`) can demand the slug without resolving it a second time; one
|
|
44
|
+
* sentence, one place.
|
|
45
|
+
*
|
|
46
|
+
* The slug's SHAPE was already enforced by `resolveDeployTarget`, for every
|
|
47
|
+
* command rather than only the slug-scoped ones — see `assertValidConfigSlug`.
|
|
48
|
+
* Deliberately not re-checked here: two copies of that guard is what let
|
|
49
|
+
* `publish` ship without one.
|
|
50
|
+
*/
|
|
51
|
+
export declare function requireDeployedSlug(config: {
|
|
52
|
+
slug?: string | undefined;
|
|
53
|
+
} | null): string;
|
|
36
54
|
/** Like resolveDeployTarget, but requires an existing deployment (project config). */
|
|
37
55
|
export declare function getServerInfo(cwd: string, explicitServer?: string): Promise<{
|
|
38
56
|
serverUrl: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { t as CliError } from "./_output-CKkmWs7i.mjs";
|
|
3
|
+
import { isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
3
4
|
import { FetchError, ofetch } from "ofetch";
|
|
4
5
|
//#region _api-client.ts
|
|
5
6
|
/**
|
|
@@ -13,6 +14,17 @@ const HINT_INVALID_API_KEY = "Your API key may be invalid. Run `aai` to re-enter
|
|
|
13
14
|
/** 404 hint for requests scoped to a deployed agent's slug. */
|
|
14
15
|
const HINT_NOT_DEPLOYED = "The agent may not be deployed. Check `.aai/project.json` for the correct slug.";
|
|
15
16
|
/**
|
|
17
|
+
* {@link ApiTestSeam} as request options, with absent entries OMITTED rather
|
|
18
|
+
* than passed as `undefined` (which `exactOptionalPropertyTypes` refuses and
|
|
19
|
+
* which would override `apiRequest`'s own defaults with nothing).
|
|
20
|
+
*/
|
|
21
|
+
function apiTestSeam(opts) {
|
|
22
|
+
return omitUndefined({
|
|
23
|
+
fetch: opts.fetch,
|
|
24
|
+
retryDelay: opts.retryDelay
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
16
28
|
* Send an authenticated request to the platform API and return the parsed
|
|
17
29
|
* JSON response. Throws a descriptive error with status-specific hints on
|
|
18
30
|
* failure (the 401 hint is always included; pass more via `hints`).
|
|
@@ -36,6 +48,32 @@ async function apiRequest(url, opts) {
|
|
|
36
48
|
}
|
|
37
49
|
}
|
|
38
50
|
/**
|
|
51
|
+
* A 2xx body CHECKED against the shape the caller was promised, rather than
|
|
52
|
+
* cast to it.
|
|
53
|
+
*
|
|
54
|
+
* `apiRequest<T>` is a cast: `T` describes what the platform sends and nothing
|
|
55
|
+
* verifies it, so a 200 from something that is not our server — an intercepting
|
|
56
|
+
* proxy, a captive portal, a mismatched or half-deployed backend — flows on as
|
|
57
|
+
* a fully-typed object whose fields are `undefined`. That is not hypothetical
|
|
58
|
+
* and not merely cosmetic: a deploy whose response lacked `slug` printed
|
|
59
|
+
* `Deployed https://server/undefined` and wrote `slug: undefined` into
|
|
60
|
+
* `.aai/project.json`, where `JSON.stringify` DROPS it — so the next deploy saw
|
|
61
|
+
* no slug, minted a fresh one, and orphaned the running agent. `aai publish`
|
|
62
|
+
* grew a hand-written guard for exactly that (`studio.ts`); this is that guard
|
|
63
|
+
* with a name, so the next response shape gets it without the incident first.
|
|
64
|
+
*
|
|
65
|
+
* The predicate is the caller's because the shape is: this only owns the
|
|
66
|
+
* failure, whose code and hint are the same wherever it fires.
|
|
67
|
+
*/
|
|
68
|
+
function checkedResponse(value, isExpected, what) {
|
|
69
|
+
if (isExpected(value)) return value;
|
|
70
|
+
throw new CliError("bad_response", `Unexpected response from ${what}.`, "Check that --server points at an aai platform server, then try again.");
|
|
71
|
+
}
|
|
72
|
+
/** True when every element of `value` is a string (an empty array qualifies). */
|
|
73
|
+
function isStringArray(value) {
|
|
74
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
39
77
|
* Collapse `{"error": "..."}` payloads embedded in a message into their own
|
|
40
78
|
* text. Studio Publish runs the real `aai deploy` inside the sandbox, so its
|
|
41
79
|
* failures arrive wrapped twice and stringifying them produced a
|
|
@@ -57,11 +95,10 @@ function unwrapEmbeddedErrors(message, depth = 0) {
|
|
|
57
95
|
/** The messages of a Zod issue tree, deduped and flattened. */
|
|
58
96
|
function zodIssueMessages(value) {
|
|
59
97
|
if (Array.isArray(value)) return value.flatMap(zodIssueMessages);
|
|
60
|
-
if (value
|
|
61
|
-
const
|
|
62
|
-
const nested = zodIssueMessages(node.issues);
|
|
98
|
+
if (!isRecord(value)) return [];
|
|
99
|
+
const nested = zodIssueMessages(value.issues);
|
|
63
100
|
if (nested.length > 0) return nested;
|
|
64
|
-
return typeof
|
|
101
|
+
return typeof value.message === "string" ? [value.message] : [];
|
|
65
102
|
}
|
|
66
103
|
/**
|
|
67
104
|
* A human-readable one-liner for a server error body.
|
|
@@ -74,10 +111,10 @@ function zodIssueMessages(value) {
|
|
|
74
111
|
*/
|
|
75
112
|
function describeErrorBody(data) {
|
|
76
113
|
if (typeof data === "string") return data;
|
|
77
|
-
if (data
|
|
114
|
+
if (!isRecord(data)) return JSON.stringify(data ?? "");
|
|
78
115
|
const error = data.error;
|
|
79
116
|
if (typeof error === "string") return unwrapEmbeddedErrors(error);
|
|
80
|
-
if (error
|
|
117
|
+
if (isRecord(error)) {
|
|
81
118
|
const { message } = error;
|
|
82
119
|
if (typeof message === "string") {
|
|
83
120
|
try {
|
|
@@ -102,4 +139,4 @@ function toApiError(err, url, opts) {
|
|
|
102
139
|
return new Error(`${opts.action} failed: could not reach ${url}\n ${hint}`, { cause });
|
|
103
140
|
}
|
|
104
141
|
//#endregion
|
|
105
|
-
export { apiRequest as n, HINT_NOT_DEPLOYED as t };
|
|
142
|
+
export { isStringArray as a, checkedResponse as i, apiRequest as n, apiTestSeam as r, HINT_NOT_DEPLOYED as t };
|
package/dist/_api-client.d.ts
CHANGED
|
@@ -35,12 +35,51 @@ export type ApiRequestOptions = {
|
|
|
35
35
|
*/
|
|
36
36
|
allow404?: boolean;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The two options a caller only ever supplies from a TEST — a fetch
|
|
40
|
+
* implementation and a zeroed retry delay.
|
|
41
|
+
*
|
|
42
|
+
* ONE declaration, so a wrapper around `apiRequest` (`runDeploy`, `runDelete`)
|
|
43
|
+
* says "plus the test seam" instead of restating both fields and both doc
|
|
44
|
+
* comments. They were written out three times, and the two copies were already
|
|
45
|
+
* spelled differently from each other.
|
|
46
|
+
*/
|
|
47
|
+
export type ApiTestSeam = Pick<ApiRequestOptions, "fetch" | "retryDelay">;
|
|
48
|
+
/**
|
|
49
|
+
* {@link ApiTestSeam} as request options, with absent entries OMITTED rather
|
|
50
|
+
* than passed as `undefined` (which `exactOptionalPropertyTypes` refuses and
|
|
51
|
+
* which would override `apiRequest`'s own defaults with nothing).
|
|
52
|
+
*/
|
|
53
|
+
export declare function apiTestSeam(opts: ApiTestSeam): ApiTestSeam;
|
|
38
54
|
/**
|
|
39
55
|
* Send an authenticated request to the platform API and return the parsed
|
|
40
56
|
* JSON response. Throws a descriptive error with status-specific hints on
|
|
41
57
|
* failure (the 401 hint is always included; pass more via `hints`).
|
|
42
58
|
*/
|
|
43
59
|
export declare function apiRequest<T = unknown>(url: string, opts: ApiRequestOptions): Promise<T>;
|
|
60
|
+
/**
|
|
61
|
+
* A 2xx body CHECKED against the shape the caller was promised, rather than
|
|
62
|
+
* cast to it.
|
|
63
|
+
*
|
|
64
|
+
* `apiRequest<T>` is a cast: `T` describes what the platform sends and nothing
|
|
65
|
+
* verifies it, so a 200 from something that is not our server — an intercepting
|
|
66
|
+
* proxy, a captive portal, a mismatched or half-deployed backend — flows on as
|
|
67
|
+
* a fully-typed object whose fields are `undefined`. That is not hypothetical
|
|
68
|
+
* and not merely cosmetic: a deploy whose response lacked `slug` printed
|
|
69
|
+
* `Deployed https://server/undefined` and wrote `slug: undefined` into
|
|
70
|
+
* `.aai/project.json`, where `JSON.stringify` DROPS it — so the next deploy saw
|
|
71
|
+
* no slug, minted a fresh one, and orphaned the running agent. `aai publish`
|
|
72
|
+
* grew a hand-written guard for exactly that (`studio.ts`); this is that guard
|
|
73
|
+
* with a name, so the next response shape gets it without the incident first.
|
|
74
|
+
*
|
|
75
|
+
* The predicate is the caller's because the shape is: this only owns the
|
|
76
|
+
* failure, whose code and hint are the same wherever it fires.
|
|
77
|
+
*/
|
|
78
|
+
export declare function checkedResponse<T>(value: unknown, isExpected: (value: unknown) => value is T,
|
|
79
|
+
/** The route, named as a human would say it — "the deploy route at <url>". */
|
|
80
|
+
what: string): T;
|
|
81
|
+
/** True when every element of `value` is a string (an empty array qualifies). */
|
|
82
|
+
export declare function isStringArray(value: unknown): value is string[];
|
|
44
83
|
/**
|
|
45
84
|
* A human-readable one-liner for a server error body.
|
|
46
85
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { t as buildClient } from "./client-bundler-
|
|
4
|
-
import { buildWorker } from "./worker-bundler.mjs";
|
|
2
|
+
import { f as validateAgentExport } from "./_utils-B8QmtFhK.mjs";
|
|
3
|
+
import { t as buildClient } from "./client-bundler-B8Q9F8gJ.mjs";
|
|
4
|
+
import { n as buildWorkflows, t as buildWorker } from "./worker-bundler-CIdDhZ0c.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
@@ -17,10 +17,14 @@ import { hash } from "node:crypto";
|
|
|
17
17
|
* config and evaluates nothing, so every read of one happens here.
|
|
18
18
|
*/
|
|
19
19
|
async function buildAgentBundle(cwd, opts = {}) {
|
|
20
|
-
const [
|
|
20
|
+
const [workflows, clientFiles] = await Promise.all([buildWorkflows(cwd), buildClient(cwd)]);
|
|
21
21
|
return {
|
|
22
|
-
worker,
|
|
23
|
-
|
|
22
|
+
worker: await buildWorker(cwd, {
|
|
23
|
+
...opts,
|
|
24
|
+
workflows
|
|
25
|
+
}),
|
|
26
|
+
clientFiles,
|
|
27
|
+
...workflows && { workflows }
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
/**
|
|
@@ -47,6 +51,26 @@ async function evalWorkerBundle(code) {
|
|
|
47
51
|
validateAgentExport(agentDef);
|
|
48
52
|
return agentDef;
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* {@link evalWorkerBundle}, keeping the workflow exports the AgentDef cannot
|
|
56
|
+
* carry.
|
|
57
|
+
*
|
|
58
|
+
* A separate function rather than a wider return from `evalWorkerBundle`,
|
|
59
|
+
* because every other caller wants the agent and nothing else — and the two
|
|
60
|
+
* strings are ~76 KB that a deploy path has no use for.
|
|
61
|
+
*/
|
|
62
|
+
async function evalWorkerWithWorkflows(code) {
|
|
63
|
+
const mod = await importWorkerModule(code);
|
|
64
|
+
const agentDef = mod.default ?? mod;
|
|
65
|
+
validateAgentExport(agentDef);
|
|
66
|
+
const workflowCode = mod.__aaiWorkflowCode;
|
|
67
|
+
const stepCode = mod.__aaiStepCode;
|
|
68
|
+
return {
|
|
69
|
+
agent: agentDef,
|
|
70
|
+
workflowCode: typeof workflowCode === "string" ? workflowCode : void 0,
|
|
71
|
+
stepCode: typeof stepCode === "string" ? stepCode : void 0
|
|
72
|
+
};
|
|
73
|
+
}
|
|
50
74
|
/** Import a built worker from a temp file. See {@link evalWorkerBundle}. */
|
|
51
75
|
async function importWorkerModule(code) {
|
|
52
76
|
const dir = await mkdtemp(path.join(tmpdir(), "aai-worker-"));
|
|
@@ -78,23 +102,27 @@ async function evalWorkerConfig(code) {
|
|
|
78
102
|
return (await importWorkerModule(code)).__aaiConfig;
|
|
79
103
|
}
|
|
80
104
|
/**
|
|
81
|
-
* Memoizing wrapper around `
|
|
82
|
-
* dev server): byte-identical worker code returns the previously
|
|
83
|
-
*
|
|
105
|
+
* Memoizing wrapper around `evalWorkerWithWorkflows` for long-lived callers
|
|
106
|
+
* (the dev server): byte-identical worker code returns the previously
|
|
107
|
+
* evaluated result without touching the ESM registry. No-op saves and formatter
|
|
84
108
|
* churn are the common watcher events, so this caps the registry leak (see
|
|
85
109
|
* `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
|
|
86
110
|
* distinct-build leak is accepted for the reasons documented there.
|
|
111
|
+
*
|
|
112
|
+
* It evaluates the WORKFLOW-carrying variant because its one caller is
|
|
113
|
+
* `aai dev`, which has no guest to re-read the bundle: dropping the two strings
|
|
114
|
+
* here is what left the dev server unable to serve a workflow at all.
|
|
87
115
|
*/
|
|
88
116
|
function createWorkerEvaluator() {
|
|
89
117
|
let lastHash;
|
|
90
|
-
let
|
|
118
|
+
let lastWorker;
|
|
91
119
|
return async (code) => {
|
|
92
120
|
const codeHash = hash("sha256", code);
|
|
93
|
-
if (
|
|
94
|
-
const
|
|
121
|
+
if (lastWorker && codeHash === lastHash) return lastWorker;
|
|
122
|
+
const worker = await evalWorkerWithWorkflows(code);
|
|
95
123
|
lastHash = codeHash;
|
|
96
|
-
|
|
97
|
-
return
|
|
124
|
+
lastWorker = worker;
|
|
125
|
+
return worker;
|
|
98
126
|
};
|
|
99
127
|
}
|
|
100
128
|
//#endregion
|
package/dist/_bundler.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import type { AgentDef } from "@alexkroman1/aai";
|
|
2
2
|
import { type BuildWorkerOptions } from "./worker-bundler.ts";
|
|
3
|
-
|
|
3
|
+
import { type WorkflowBundleOutput } from "./workflow-bundler.ts";
|
|
4
|
+
/** Output from the bundler: worker ESM + client files + workflow artifacts. */
|
|
4
5
|
export type DirectoryBundleOutput = {
|
|
5
6
|
/** ESM bundle of agent.ts (tool execute functions + hook handlers). */
|
|
6
7
|
worker: string;
|
|
7
8
|
/** Static client files from Vite build. Empty if no client.tsx. */
|
|
8
9
|
clientFiles: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* The project's durable workflows, or undefined when it declares none.
|
|
12
|
+
*
|
|
13
|
+
* Built here rather than in the guest because the transform is per TENANT: the
|
|
14
|
+
* guest image is baked once and serves many agents, so there is no
|
|
15
|
+
* `workflows/` directory in existence when it is built. See
|
|
16
|
+
* `workflow-bundler.ts`.
|
|
17
|
+
*/
|
|
18
|
+
workflows?: WorkflowBundleOutput;
|
|
9
19
|
};
|
|
10
20
|
/**
|
|
11
21
|
* Bundle an agent directory: build agent.ts into worker ESM + client files.
|
|
@@ -35,6 +45,30 @@ export declare function buildAgentBundle(cwd: string, opts?: BuildWorkerOptions)
|
|
|
35
45
|
* experimental.
|
|
36
46
|
*/
|
|
37
47
|
export declare function evalWorkerBundle(code: string): Promise<AgentDef>;
|
|
48
|
+
/**
|
|
49
|
+
* The agent plus the compiled workflow surface a worker carries.
|
|
50
|
+
*
|
|
51
|
+
* `evalWorkerBundle` returns only the `AgentDef`, which is all a deploy needs —
|
|
52
|
+
* the guest re-reads the bundle itself. `aai dev` never hands the bundle to a
|
|
53
|
+
* guest, so this is the only path by which the workflow code reaches a running
|
|
54
|
+
* server locally, and dropping it is why `aai dev` served no workflows at first.
|
|
55
|
+
*/
|
|
56
|
+
export type EvaluatedWorker = {
|
|
57
|
+
agent: AgentDef;
|
|
58
|
+
/** `__aaiWorkflowCode` — the flow bundle, for `workflowEntrypoint`. */
|
|
59
|
+
workflowCode: string | undefined;
|
|
60
|
+
/** `__aaiStepCode` — evaluated to register the project's step functions. */
|
|
61
|
+
stepCode: string | undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* {@link evalWorkerBundle}, keeping the workflow exports the AgentDef cannot
|
|
65
|
+
* carry.
|
|
66
|
+
*
|
|
67
|
+
* A separate function rather than a wider return from `evalWorkerBundle`,
|
|
68
|
+
* because every other caller wants the agent and nothing else — and the two
|
|
69
|
+
* strings are ~76 KB that a deploy path has no use for.
|
|
70
|
+
*/
|
|
71
|
+
export declare function evalWorkerWithWorkflows(code: string): Promise<EvaluatedWorker>;
|
|
38
72
|
/**
|
|
39
73
|
* The config a built worker describes about itself — its `__aaiConfig`
|
|
40
74
|
* export, generated by `buildWorker`'s wrapper entry (`toAgentConfig(def)`
|
|
@@ -50,11 +84,15 @@ export declare function evalWorkerBundle(code: string): Promise<AgentDef>;
|
|
|
50
84
|
*/
|
|
51
85
|
export declare function evalWorkerConfig(code: string): Promise<unknown>;
|
|
52
86
|
/**
|
|
53
|
-
* Memoizing wrapper around `
|
|
54
|
-
* dev server): byte-identical worker code returns the previously
|
|
55
|
-
*
|
|
87
|
+
* Memoizing wrapper around `evalWorkerWithWorkflows` for long-lived callers
|
|
88
|
+
* (the dev server): byte-identical worker code returns the previously
|
|
89
|
+
* evaluated result without touching the ESM registry. No-op saves and formatter
|
|
56
90
|
* churn are the common watcher events, so this caps the registry leak (see
|
|
57
91
|
* `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
|
|
58
92
|
* distinct-build leak is accepted for the reasons documented there.
|
|
93
|
+
*
|
|
94
|
+
* It evaluates the WORKFLOW-carrying variant because its one caller is
|
|
95
|
+
* `aai dev`, which has no guest to re-read the bundle: dropping the two strings
|
|
96
|
+
* here is what left the dev server unable to serve a workflow at all.
|
|
59
97
|
*/
|
|
60
|
-
export declare function createWorkerEvaluator(): (code: string) => Promise<
|
|
98
|
+
export declare function createWorkerEvaluator(): (code: string) => Promise<EvaluatedWorker>;
|
package/dist/_cli-common.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ArgsDef, type CommandDef, type CommandMeta, type ParsedArgs } from "citty";
|
|
2
2
|
import { type CommandResult, type OutputMode } from "./_output.ts";
|
|
3
3
|
/** Shared arg definitions for citty commands. */
|
|
4
4
|
export declare const sharedArgs: {
|
|
@@ -33,6 +33,17 @@ export declare function findUnknownFlags(rawArgs: string[], argsDef: ArgsDef): s
|
|
|
33
33
|
* reads `subCommands` and `args`, and the concrete generics differ per command.
|
|
34
34
|
*/
|
|
35
35
|
type AnyCommandDef = CommandDef<ArgsDef>;
|
|
36
|
+
/**
|
|
37
|
+
* `aai secret put` for a subcommand, `aai` for the root — the name to put in
|
|
38
|
+
* front of `--help` when telling someone which usage to read.
|
|
39
|
+
*
|
|
40
|
+
* Beside {@link resolve} because it is the same job: `meta` is a `Resolvable`,
|
|
41
|
+
* so only the plain-object form carries a name synchronously and anything else
|
|
42
|
+
* degrades to the binary name rather than blocking an error path on a resolve.
|
|
43
|
+
* This module owns citty's `Resolvable` handling; `cli.ts` had its own copy of
|
|
44
|
+
* the narrowing.
|
|
45
|
+
*/
|
|
46
|
+
export declare function commandPath<T extends ArgsDef>(cmd: CommandDef<T>, parent?: CommandDef<T>): string;
|
|
36
47
|
/**
|
|
37
48
|
* Unknown flags in `argv` for whichever (possibly nested) subcommand it
|
|
38
49
|
* selects — `[]` when everything is declared.
|
|
@@ -56,10 +67,63 @@ export declare function setup(opts?: {
|
|
|
56
67
|
* key, and commands with no platform traffic never prompt at all.
|
|
57
68
|
*
|
|
58
69
|
* A thrown error and a returned `fail(...)` converge here on one emitter:
|
|
59
|
-
* human mode logs the message, JSON mode writes exactly one
|
|
60
|
-
* and both exit 1.
|
|
70
|
+
* human mode logs the message and the hint, JSON mode writes exactly one
|
|
71
|
+
* result line, and both exit 1.
|
|
72
|
+
*
|
|
73
|
+
* That convergence is the whole contract and it was HALF TRUE for a long
|
|
74
|
+
* time: only the `catch` printed anything, so a body that RETURNED a failure
|
|
75
|
+
* fell straight through to the JSON check and `process.exit(1)`. In human
|
|
76
|
+
* mode that is a bare exit 1 with an empty terminal — `aai test` with no
|
|
77
|
+
* runner binary on PATH (message and "is it on your PATH?" both discarded),
|
|
78
|
+
* every `aai workflow` verb against a booting sandbox (the agent's own
|
|
79
|
+
* sentence plus `HINT_BROKER` discarded), `aai secret put` with an empty
|
|
80
|
+
* value, and `aai storage disable`'s "re-run with --force". Nine paths, all
|
|
81
|
+
* silent. Emitting is therefore driven by the RESULT rather than by which
|
|
82
|
+
* arm produced it.
|
|
61
83
|
*/
|
|
62
84
|
export declare function runCommand(args: {
|
|
63
85
|
json?: boolean | undefined;
|
|
64
86
|
}, fn: (mode: OutputMode) => Promise<CommandResult<unknown>>): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* What a command needs from the working directory, stated rather than
|
|
89
|
+
* re-derived:
|
|
90
|
+
*
|
|
91
|
+
* - `"agent"` — an agent project. Refuses a directory with no `agent.ts`.
|
|
92
|
+
* - `"any"` — the working directory, whatever is in it.
|
|
93
|
+
* - `"none"` — no directory policy; the body resolves whatever it needs.
|
|
94
|
+
*/
|
|
95
|
+
export type CwdPolicy = "agent" | "any" | "none";
|
|
96
|
+
/** `undefined` under `"none"`, so a body cannot read a cwd it never asked for. */
|
|
97
|
+
type CwdFor<P extends CwdPolicy> = P extends "none" ? undefined : string;
|
|
98
|
+
/** Everything a command body gets, in one argument. */
|
|
99
|
+
export type ExecContext<T extends ArgsDef, P extends CwdPolicy> = {
|
|
100
|
+
args: ParsedArgs<T>;
|
|
101
|
+
mode: OutputMode;
|
|
102
|
+
cwd: CwdFor<P>;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Define a citty command whose body is an EXECUTOR — the shape all
|
|
106
|
+
* twenty-four of this CLI's leaf commands have.
|
|
107
|
+
*
|
|
108
|
+
* Written longhand, each one restates the same four lines (`async run({ args
|
|
109
|
+
* })`, `await runCommand(args, async (mode) => …)`, a cwd resolution, a lazy
|
|
110
|
+
* `await import`) and, in the middle of them, DECIDES the working-directory
|
|
111
|
+
* policy — which is the part that is not boilerplate and the part that has
|
|
112
|
+
* been wrong in production: `aai test` shipped calling `setup()` bare instead
|
|
113
|
+
* of `setup({ agent: true })`, so in a directory with no `agent.ts` it found
|
|
114
|
+
* no test file, reported `{ passed: true, skipped: true }` and exited 0. A
|
|
115
|
+
* green result for a project that is not there reads, in CI, exactly like a
|
|
116
|
+
* passing suite.
|
|
117
|
+
*
|
|
118
|
+
* `cwd` is a required field of the spec, so that decision is stated once per
|
|
119
|
+
* command in the one place a reader looks for it, and "forgot to pass
|
|
120
|
+
* `{ agent: true }`" stops being a representable mistake — the alternative
|
|
121
|
+
* spelling is a policy name that does not exist.
|
|
122
|
+
*/
|
|
123
|
+
export declare function defineExec<const T extends ArgsDef, P extends CwdPolicy>(spec: {
|
|
124
|
+
meta: CommandMeta;
|
|
125
|
+
args: T;
|
|
126
|
+
cwd: P;
|
|
127
|
+
run: (ctx: ExecContext<T, P>) => Promise<CommandResult<unknown>>;
|
|
128
|
+
}): CommandDef<T>;
|
|
65
129
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as CliError } from "./_output-
|
|
3
|
-
import {
|
|
2
|
+
import { t as CliError } from "./_output-CKkmWs7i.mjs";
|
|
3
|
+
import { c as isEexist, l as readJson, o as errorMessage, p as writeJson } from "./_utils-B8QmtFhK.mjs";
|
|
4
4
|
import { mkdtempSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
|
-
import {
|
|
8
|
+
import { sleep } from "@alexkroman1/aai/internal";
|
|
9
9
|
import envPaths from "env-paths";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
//#region _config.ts
|
|
@@ -146,32 +146,50 @@ const CONFIG_LOCK_STALE_MS = 1e4;
|
|
|
146
146
|
*/
|
|
147
147
|
async function withGlobalConfigLock(dir, fn) {
|
|
148
148
|
const lockPath = path.join(dir, "config.lock");
|
|
149
|
+
const held = await acquireConfigLock(dir, lockPath);
|
|
150
|
+
try {
|
|
151
|
+
return await fn();
|
|
152
|
+
} finally {
|
|
153
|
+
if (held) await fs.rm(lockPath, { force: true }).catch(() => void 0);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** True when this process created the lockfile; false means "proceed unlocked". */
|
|
157
|
+
async function acquireConfigLock(dir, lockPath) {
|
|
149
158
|
const deadline = Date.now() + CONFIG_LOCK_TIMEOUT_MS;
|
|
150
|
-
let held = false;
|
|
151
159
|
for (;;) try {
|
|
152
160
|
await fs.mkdir(dir, {
|
|
153
161
|
recursive: true,
|
|
154
162
|
mode: 448
|
|
155
163
|
});
|
|
156
164
|
await (await fs.open(lockPath, "wx", 384)).close();
|
|
157
|
-
|
|
158
|
-
break;
|
|
165
|
+
return true;
|
|
159
166
|
} catch (err) {
|
|
160
|
-
if (err
|
|
161
|
-
if (await
|
|
162
|
-
await fs.rm(lockPath, { force: true }).catch(() => void 0);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (Date.now() >= deadline) break;
|
|
166
|
-
await setTimeout(CONFIG_LOCK_RETRY_MS);
|
|
167
|
-
}
|
|
168
|
-
try {
|
|
169
|
-
return await fn();
|
|
170
|
-
} finally {
|
|
171
|
-
if (held) await fs.rm(lockPath, { force: true }).catch(() => void 0);
|
|
167
|
+
if (!isEexist(err)) return false;
|
|
168
|
+
if (!await contendForLock(lockPath, deadline)) return false;
|
|
172
169
|
}
|
|
173
170
|
}
|
|
174
171
|
/**
|
|
172
|
+
* One turn of the contention loop: break the lock if it is stale, otherwise
|
|
173
|
+
* wait a beat. False means STOP — the deadline passed, or the lock cannot be
|
|
174
|
+
* broken at all.
|
|
175
|
+
*
|
|
176
|
+
* That second case is the one worth naming. `fs.rm`'s `force` masks only
|
|
177
|
+
* ENOENT and there is no `recursive`, so an entry that is not an ordinary file
|
|
178
|
+
* — a `config.lock` DIRECTORY, an immutable or permission-denied file — makes
|
|
179
|
+
* the removal throw every time. Swallowing that throw and looping restarted
|
|
180
|
+
* the loop ABOVE the deadline check, so `aai login` (and every `--server`
|
|
181
|
+
* invocation) spun in a tight async loop with no output and no exit: bounded
|
|
182
|
+
* acquisition, the contract stated above, held in every case except the one
|
|
183
|
+
* where breaking the lock is impossible. The removal's OUTCOME is what decides
|
|
184
|
+
* whether looping is progress.
|
|
185
|
+
*/
|
|
186
|
+
async function contendForLock(lockPath, deadline) {
|
|
187
|
+
if (await fs.stat(lockPath).then((s) => Date.now() - s.mtimeMs).catch(() => 0) > CONFIG_LOCK_STALE_MS) return await fs.rm(lockPath, { force: true }).then(() => true, () => false) && Date.now() < deadline;
|
|
188
|
+
if (Date.now() >= deadline) return false;
|
|
189
|
+
await sleep(CONFIG_LOCK_RETRY_MS);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
175
193
|
* Apply `update` to the global config under the cross-process lock, re-reading
|
|
176
194
|
* inside it so the merge is against current contents rather than a snapshot
|
|
177
195
|
* taken before the lock was held. Every read-modify-write of the global config
|
package/dist/_deploy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type ApiTestSeam } from "./_api-client.ts";
|
|
1
2
|
import type { DirectoryBundleOutput } from "./_bundler.ts";
|
|
2
|
-
export type DeployOpts = {
|
|
3
|
+
export type DeployOpts = ApiTestSeam & {
|
|
3
4
|
url: string;
|
|
4
5
|
bundle: DirectoryBundleOutput;
|
|
5
6
|
/** Env var values from .env to send to the server. */
|
|
@@ -14,10 +15,6 @@ export type DeployOpts = {
|
|
|
14
15
|
* by the studio's own in-guest deploy, not by ordinary users.
|
|
15
16
|
*/
|
|
16
17
|
allowPreviewSlug?: boolean;
|
|
17
|
-
/** Retry delay override for tests (0 = no real sleeps on retry paths). */
|
|
18
|
-
retryDelay?: number;
|
|
19
|
-
/** Optional fetch implementation for testing. Defaults to globalThis.fetch. */
|
|
20
|
-
fetch?: typeof globalThis.fetch;
|
|
21
18
|
};
|
|
22
19
|
export type DeployResult = {
|
|
23
20
|
slug: string;
|
package/dist/_dev-restart.d.ts
CHANGED
|
@@ -31,7 +31,12 @@ export type RestartOps<S> = {
|
|
|
31
31
|
* swallowed so one leak can't strand the others.
|
|
32
32
|
*/
|
|
33
33
|
teardown?: () => Promise<void>;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Injectable so retry specs need neither wall-clock nor fake timers. The
|
|
36
|
+
* default is the repo's one `sleep`, which virtual time CAN drive — this seam
|
|
37
|
+
* predates it and existed because `node:timers/promises` cannot be driven at
|
|
38
|
+
* all (see `aai/sdk/sleep.ts`).
|
|
39
|
+
*/
|
|
35
40
|
sleep?: (ms: number) => Promise<void>;
|
|
36
41
|
};
|
|
37
42
|
export type RestartSupervisor<S> = {
|