@aglyn/plugins-ai 1.0.0-beta.225 → 1.0.0-beta.227

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.
Files changed (161) hide show
  1. package/README.md +2 -1
  2. package/package.json +17 -17
  3. package/src/lib/billing/ai-month-meter.js +5 -3
  4. package/src/lib/billing/ai-month-meter.js.map +1 -1
  5. package/src/lib/billing/ai-overage-ledger.js +3 -1
  6. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  7. package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
  8. package/src/lib/components/ai-brief-dialog.component.js +35 -17
  9. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  10. package/src/lib/components/ai-describe-automation.component.js +17 -2
  11. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  12. package/src/lib/components/ai-insight-dialog.component.js +24 -2
  13. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  14. package/src/lib/components/ai-job-events.d.ts +14 -0
  15. package/src/lib/components/ai-job-events.js +76 -0
  16. package/src/lib/components/ai-job-events.js.map +1 -0
  17. package/src/lib/components/ai-job-follow.component.d.ts +49 -0
  18. package/src/lib/components/ai-job-follow.component.js +210 -0
  19. package/src/lib/components/ai-job-follow.component.js.map +1 -0
  20. package/src/lib/components/ai-job-links.d.ts +42 -0
  21. package/src/lib/components/ai-job-links.js +140 -0
  22. package/src/lib/components/ai-job-links.js.map +1 -0
  23. package/src/lib/components/ai-job-plan.component.d.ts +13 -6
  24. package/src/lib/components/ai-job-plan.component.js +29 -3
  25. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  26. package/src/lib/components/ai-job-requests.d.ts +24 -0
  27. package/src/lib/components/ai-job-requests.js +76 -0
  28. package/src/lib/components/ai-job-requests.js.map +1 -0
  29. package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
  30. package/src/lib/components/ai-jobs-indicator.component.js +80 -0
  31. package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
  32. package/src/lib/components/ai-jobs-store.d.ts +81 -0
  33. package/src/lib/components/ai-jobs-store.js +218 -0
  34. package/src/lib/components/ai-jobs-store.js.map +1 -0
  35. package/src/lib/components/ai-page-brief-dialog.component.d.ts +3 -3
  36. package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
  37. package/src/lib/components/ai-site-build-page.component.d.ts +23 -0
  38. package/src/lib/components/ai-site-build-page.component.js +377 -0
  39. package/src/lib/components/ai-site-build-page.component.js.map +1 -0
  40. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  41. package/src/lib/components/ai-site-start-card.component.js +522 -130
  42. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  43. package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
  44. package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
  45. package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
  46. package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
  47. package/src/lib/components/assist-jobs-drawer.component.js +152 -172
  48. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  49. package/src/lib/components/assist-panel.component.js +62 -6
  50. package/src/lib/components/assist-panel.component.js.map +1 -1
  51. package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
  52. package/src/lib/components/staff-ai-credit-return.component.js +346 -0
  53. package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
  54. package/src/lib/components/staff-org-ai-card.component.js +364 -279
  55. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  56. package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
  57. package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
  58. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  59. package/src/lib/components/use-ai-job-run.js +5 -18
  60. package/src/lib/components/use-ai-job-run.js.map +1 -1
  61. package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
  62. package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
  63. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
  64. package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
  65. package/src/lib/jobs/ai-job-plan-step.js +161 -17
  66. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-site-step.d.ts +7 -0
  68. package/src/lib/jobs/ai-job-site-step.js +34 -4
  69. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
  71. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  72. package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
  73. package/src/lib/jobs/ai-jobs-notify.js +51 -0
  74. package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
  75. package/src/lib/jobs/ai-jobs.d.ts +31 -4
  76. package/src/lib/jobs/ai-jobs.js +196 -32
  77. package/src/lib/jobs/ai-jobs.js.map +1 -1
  78. package/src/lib/model/ai-job-activity.d.ts +112 -0
  79. package/src/lib/model/ai-job-activity.js +144 -0
  80. package/src/lib/model/ai-job-activity.js.map +1 -0
  81. package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
  82. package/src/lib/model/ai-job-failure-copy.js +78 -0
  83. package/src/lib/model/ai-job-failure-copy.js.map +1 -0
  84. package/src/lib/model/ai-job-notice.d.ts +61 -0
  85. package/src/lib/model/ai-job-notice.js +112 -0
  86. package/src/lib/model/ai-job-notice.js.map +1 -0
  87. package/src/lib/model/ai-jobs.types.d.ts +26 -0
  88. package/src/lib/model/ai-jobs.types.js +1 -0
  89. package/src/lib/model/ai-jobs.types.js.map +1 -1
  90. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  91. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  92. package/src/lib/model/ai-site-build-progress.d.ts +45 -0
  93. package/src/lib/model/ai-site-build-progress.js +76 -0
  94. package/src/lib/model/ai-site-build-progress.js.map +1 -0
  95. package/src/lib/model/ai-site-inventory.d.ts +9 -0
  96. package/src/lib/model/ai-site-inventory.js +7 -2
  97. package/src/lib/model/ai-site-inventory.js.map +1 -1
  98. package/src/lib/model/ai-site-job.d.ts +93 -2
  99. package/src/lib/model/ai-site-job.js +88 -7
  100. package/src/lib/model/ai-site-job.js.map +1 -1
  101. package/src/lib/model/ai-site-start.d.ts +3 -1
  102. package/src/lib/model/ai-site-start.js +6 -4
  103. package/src/lib/model/ai-site-start.js.map +1 -1
  104. package/src/lib/model/ai-template-subjects.d.ts +4 -3
  105. package/src/lib/model/ai-template-subjects.js +5 -4
  106. package/src/lib/model/ai-template-subjects.js.map +1 -1
  107. package/src/lib/plan-entitlements.d.ts +3 -3
  108. package/src/lib/plan-entitlements.js +3 -3
  109. package/src/lib/plan-entitlements.js.map +1 -1
  110. package/src/lib/plugin.js +28 -0
  111. package/src/lib/plugin.js.map +1 -1
  112. package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
  113. package/src/lib/runtime/ai-abuse-guards.js +10 -7
  114. package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
  115. package/src/lib/runtime/ai-doctrine-validators.js +31 -4
  116. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  117. package/src/lib/runtime/ai-gate.d.ts +3 -2
  118. package/src/lib/runtime/ai-gate.js +3 -2
  119. package/src/lib/runtime/ai-gate.js.map +1 -1
  120. package/src/lib/runtime/ai-palette.generated.js +12 -4
  121. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  122. package/src/lib/runtime/site-inventory.d.ts +16 -1
  123. package/src/lib/runtime/site-inventory.js +43 -3
  124. package/src/lib/runtime/site-inventory.js.map +1 -1
  125. package/src/lib/server/ai-admin-credits.d.ts +21 -0
  126. package/src/lib/server/ai-admin-credits.js +213 -0
  127. package/src/lib/server/ai-admin-credits.js.map +1 -0
  128. package/src/lib/server/ai-admin-org.js +12 -5
  129. package/src/lib/server/ai-admin-org.js.map +1 -1
  130. package/src/lib/server/ai-admin-signals.js +3 -1
  131. package/src/lib/server/ai-admin-signals.js.map +1 -1
  132. package/src/lib/server/ai-admin-user.js +12 -1
  133. package/src/lib/server/ai-admin-user.js.map +1 -1
  134. package/src/lib/server/ai-allotments.js +3 -2
  135. package/src/lib/server/ai-allotments.js.map +1 -1
  136. package/src/lib/server/ai-jobs-route.js +20 -2
  137. package/src/lib/server/ai-jobs-route.js.map +1 -1
  138. package/src/lib/server/assist-chat.js +3 -2
  139. package/src/lib/server/assist-chat.js.map +1 -1
  140. package/src/lib/server/billing-credits.js +4 -3
  141. package/src/lib/server/billing-credits.js.map +1 -1
  142. package/src/lib/server/billing-overage.js +3 -2
  143. package/src/lib/server/billing-overage.js.map +1 -1
  144. package/src/lib/server.js +11 -0
  145. package/src/lib/server.js.map +1 -1
  146. package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
  147. package/src/lib/usage/assist-credit-returns-write.js +150 -0
  148. package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
  149. package/src/lib/usage/assist-credit-returns.d.ts +68 -0
  150. package/src/lib/usage/assist-credit-returns.js +79 -0
  151. package/src/lib/usage/assist-credit-returns.js.map +1 -0
  152. package/src/lib/usage/assist-free-taste.js +6 -3
  153. package/src/lib/usage/assist-free-taste.js.map +1 -1
  154. package/src/lib/usage/assist-job-refund.d.ts +100 -0
  155. package/src/lib/usage/assist-job-refund.js +109 -0
  156. package/src/lib/usage/assist-job-refund.js.map +1 -0
  157. package/src/lib/usage/assist-usage.js +5 -2
  158. package/src/lib/usage/assist-usage.js.map +1 -1
  159. package/src/lib/usage/staff-org-ai.d.ts +31 -1
  160. package/src/lib/usage/staff-org-ai.js +19 -2
  161. package/src/lib/usage/staff-org-ai.js.map +1 -1
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { AiJob } from '../model/ai-jobs.types';
18
+ import { AI_JOB_AUTO_CONFIRM_INPUT } from '../model/ai-site-job';
19
+ /**
20
+ * A guided site start confirms its own plan (AGL-3594).
21
+ *
22
+ * Every planned job stops after its plan for a person to read and confirm it.
23
+ * For the guided start on a new site that stop read as a step nobody knew they
24
+ * had to take: a person who had just answered "what is your site?" was shown a
25
+ * plan to approve, and a site nobody approved was a site never built. So the
26
+ * guided start asks for its plan to be confirmed for it, with this input, and
27
+ * the machine confirms it the moment the plan step keeps a plan — the same
28
+ * confirmation the resume door makes, by the job's creator, with the same
29
+ * hold on what the plan is estimated to cost.
30
+ *
31
+ * Honored only where it is safe to skip the read: a `site` job, on a site
32
+ * that has published nothing of its own yet — the site the guided start is
33
+ * offered on. Everywhere else the create door drops it, so a page, a form or
34
+ * a site job on an established site still waits for a person. What bounds the
35
+ * spend is unchanged: the plan step's own checks (the Free page cap and the
36
+ * Free wall among them), the plan's admission, and the reservation each step
37
+ * takes.
38
+ */
39
+ /** The job input the guided start sets. */
40
+ export { AI_JOB_AUTO_CONFIRM_INPUT };
41
+ /** Whether a job's plan is confirmed for it when the plan step keeps one. */
42
+ export declare function aiJobAutoConfirms(job: Pick<AiJob, 'kind' | 'inputs'>): boolean;
43
+ /**
44
+ * A job's inputs as the create door stores them: `autoConfirm` kept only on a
45
+ * `site` job for a site that publishes nothing of the owner's yet, and dropped
46
+ * from every other request, which then waits for a person as it always has.
47
+ */
48
+ export declare function aiJobAdmittedInputs(firestore: FirebaseFirestore.Firestore, request: {
49
+ kind: string;
50
+ hostId: string | null;
51
+ inputs: Record<string, unknown>;
52
+ }): Promise<Record<string, unknown>>;
@@ -0,0 +1,62 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { _ as _to_property_key } from "@swc/helpers/_/_to_property_key";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import { isFirstPublishedRoute } from "@aglyn/aglyn/app-utils/analytics-events";
20
+ import { AI_JOB_AUTO_CONFIRM_INPUT } from "../model/ai-site-job.js";
21
+ /**
22
+ * A guided site start confirms its own plan (AGL-3594).
23
+ *
24
+ * Every planned job stops after its plan for a person to read and confirm it.
25
+ * For the guided start on a new site that stop read as a step nobody knew they
26
+ * had to take: a person who had just answered "what is your site?" was shown a
27
+ * plan to approve, and a site nobody approved was a site never built. So the
28
+ * guided start asks for its plan to be confirmed for it, with this input, and
29
+ * the machine confirms it the moment the plan step keeps a plan — the same
30
+ * confirmation the resume door makes, by the job's creator, with the same
31
+ * hold on what the plan is estimated to cost.
32
+ *
33
+ * Honored only where it is safe to skip the read: a `site` job, on a site
34
+ * that has published nothing of its own yet — the site the guided start is
35
+ * offered on. Everywhere else the create door drops it, so a page, a form or
36
+ * a site job on an established site still waits for a person. What bounds the
37
+ * spend is unchanged: the plan step's own checks (the Free page cap and the
38
+ * Free wall among them), the plan's admission, and the reservation each step
39
+ * takes.
40
+ */ /** The job input the guided start sets. */ export { AI_JOB_AUTO_CONFIRM_INPUT };
41
+ /** Whether a job's plan is confirmed for it when the plan step keeps one. */ export function aiJobAutoConfirms(job) {
42
+ var _job_inputs;
43
+ return job.kind === 'site' && ((_job_inputs = job.inputs) != null ? _job_inputs : {})[AI_JOB_AUTO_CONFIRM_INPUT] === true;
44
+ }
45
+ /**
46
+ * A job's inputs as the create door stores them: `autoConfirm` kept only on a
47
+ * `site` job for a site that publishes nothing of the owner's yet, and dropped
48
+ * from every other request, which then waits for a person as it always has.
49
+ */ export async function aiJobAdmittedInputs(firestore, request) {
50
+ if (!(AI_JOB_AUTO_CONFIRM_INPUT in request.inputs)) return request.inputs;
51
+ const _request_inputs = request.inputs, { [AI_JOB_AUTO_CONFIRM_INPUT]: asked } = _request_inputs, rest = _object_without_properties_loose(_request_inputs, [
52
+ _to_property_key(AI_JOB_AUTO_CONFIRM_INPUT)
53
+ ]);
54
+ if (request.kind !== 'site' || asked !== true || !request.hostId) return rest;
55
+ const host = await firestore.collection('hosts').doc(request.hostId).get();
56
+ const blank = host.exists && isFirstPublishedRoute(host.get('screens'), host.get('defaultHomeScreenId'));
57
+ return blank ? _extends({}, rest, {
58
+ [AI_JOB_AUTO_CONFIRM_INPUT]: true
59
+ }) : rest;
60
+ }
61
+
62
+ //# sourceMappingURL=ai-job-auto-confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-auto-confirm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isFirstPublishedRoute } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { AI_JOB_AUTO_CONFIRM_INPUT } from '../model/ai-site-job'\n\n/**\n * A guided site start confirms its own plan (AGL-3594).\n *\n * Every planned job stops after its plan for a person to read and confirm it.\n * For the guided start on a new site that stop read as a step nobody knew they\n * had to take: a person who had just answered \"what is your site?\" was shown a\n * plan to approve, and a site nobody approved was a site never built. So the\n * guided start asks for its plan to be confirmed for it, with this input, and\n * the machine confirms it the moment the plan step keeps a plan — the same\n * confirmation the resume door makes, by the job's creator, with the same\n * hold on what the plan is estimated to cost.\n *\n * Honored only where it is safe to skip the read: a `site` job, on a site\n * that has published nothing of its own yet — the site the guided start is\n * offered on. Everywhere else the create door drops it, so a page, a form or\n * a site job on an established site still waits for a person. What bounds the\n * spend is unchanged: the plan step's own checks (the Free page cap and the\n * Free wall among them), the plan's admission, and the reservation each step\n * takes.\n */\n\n/** The job input the guided start sets. */\nexport { AI_JOB_AUTO_CONFIRM_INPUT }\n\n/** Whether a job's plan is confirmed for it when the plan step keeps one. */\nexport function aiJobAutoConfirms(job: Pick<AiJob, 'kind' | 'inputs'>): boolean {\n return job.kind === 'site' && (job.inputs ?? {})[AI_JOB_AUTO_CONFIRM_INPUT] === true\n}\n\n/**\n * A job's inputs as the create door stores them: `autoConfirm` kept only on a\n * `site` job for a site that publishes nothing of the owner's yet, and dropped\n * from every other request, which then waits for a person as it always has.\n */\nexport async function aiJobAdmittedInputs(\n firestore: FirebaseFirestore.Firestore,\n request: { kind: string; hostId: string | null; inputs: Record<string, unknown> },\n): Promise<Record<string, unknown>> {\n if (!(AI_JOB_AUTO_CONFIRM_INPUT in request.inputs)) return request.inputs\n const { [AI_JOB_AUTO_CONFIRM_INPUT]: asked, ...rest } = request.inputs\n if (request.kind !== 'site' || asked !== true || !request.hostId) return rest\n const host = await firestore.collection('hosts').doc(request.hostId).get()\n const blank =\n host.exists &&\n isFirstPublishedRoute(\n host.get('screens') as Record<string, unknown> | undefined,\n host.get('defaultHomeScreenId') as string | undefined,\n )\n return blank ? { ...rest, [AI_JOB_AUTO_CONFIRM_INPUT]: true } : rest\n}\n"],"names":["isFirstPublishedRoute","AI_JOB_AUTO_CONFIRM_INPUT","aiJobAutoConfirms","job","kind","inputs","aiJobAdmittedInputs","firestore","request","asked","rest","hostId","host","collection","doc","get","blank","exists"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,qBAAqB,QAAQ,0CAAyC;AAE/E,SAASC,yBAAyB,QAAQ,0BAAsB;AAEhE;;;;;;;;;;;;;;;;;;;CAmBC,GAED,yCAAyC,GACzC,SAASA,yBAAyB,GAAE;AAEpC,2EAA2E,GAC3E,OAAO,SAASC,kBAAkBC,GAAmC;QACpCA;IAA/B,OAAOA,IAAIC,IAAI,KAAK,UAAU,EAACD,cAAAA,IAAIE,MAAM,YAAVF,cAAc,CAAC,EAAE,CAACF,0BAA0B,KAAK;AAClF;AAEA;;;;CAIC,GACD,OAAO,eAAeK,oBACpBC,SAAsC,EACtCC,OAAiF;IAEjF,IAAI,CAAEP,CAAAA,6BAA6BO,QAAQH,MAAM,AAAD,GAAI,OAAOG,QAAQH,MAAM;IACzE,MAAwDG,kBAAAA,QAAQH,MAAM,EAAhE,EAAE,CAACJ,0BAA0B,EAAEQ,KAAK,EAAW,GAAGD,iBAATE,wCAASF;yBAA/CP;;IACT,IAAIO,QAAQJ,IAAI,KAAK,UAAUK,UAAU,QAAQ,CAACD,QAAQG,MAAM,EAAE,OAAOD;IACzE,MAAME,OAAO,MAAML,UAAUM,UAAU,CAAC,SAASC,GAAG,CAACN,QAAQG,MAAM,EAAEI,GAAG;IACxE,MAAMC,QACJJ,KAAKK,MAAM,IACXjB,sBACEY,KAAKG,GAAG,CAAC,YACTH,KAAKG,GAAG,CAAC;IAEb,OAAOC,QAAQ,aAAKN;QAAM,CAACT,0BAA0B,EAAE;SAASS;AAClE"}
@@ -84,7 +84,19 @@ export declare const AI_JOB_PLAN_REVIEW_COPY = "The plan is ready. Review what t
84
84
  * (AGL-3030). Per workspace and per site, which is why they ride here and
85
85
  * never in a system block the platform's cache entry is keyed on.
86
86
  */
87
- export declare function aiJobPlanPrompt(job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>, capabilities?: AiPlanCapabilities | null): string;
87
+ export declare function aiJobPlanPrompt(job: Pick<AiJob, 'kind' | 'brief' | 'inputs'>, capabilities?: AiPlanCapabilities | null, inventory?: AiSiteInventory | null): string;
88
+ /**
89
+ * What a SITE job's plan is told beside the inventory (AGL-3594): the pages
90
+ * the site already has and that their addresses are taken, which one — the
91
+ * untouched starter home — a planned home page at `/` replaces, the Free page
92
+ * cap and its guidance, and that a plan is an outline.
93
+ *
94
+ * A site job only. A new site is born with a published home page at `/`
95
+ * (AGL-3497), and a plan that did not know it could replace it planned its own
96
+ * home there and was refused for the clash on every new site. Kept off a page
97
+ * job's turn, whose length the Free page's wall is proven at.
98
+ */
99
+ export declare function aiPlanSiteLines(job: Pick<AiJob, 'kind'>, inventory: AiSiteInventory | null, capabilities: AiPlanCapabilities | null): string[];
88
100
  /**
89
101
  * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143
90
102
  * §11): the subject's binding tokens, listed so the planner promises only
@@ -107,7 +119,9 @@ export declare function aiPlanTemplateTokenLines(job: Pick<AiJob, 'kind' | 'inpu
107
119
  /** A kind that builds only some plans: the creations it makes, and the shapes it refuses. */
108
120
  export interface AiJobPlanScope extends AiPlanJobScope {
109
121
  /** Why a job of the kind cannot build a plan of this shape; `null` when it can. Pure. */
110
- shapeRefusal: (plan: AiBuildPlan) => string | null;
122
+ shapeRefusal: (plan: AiBuildPlan, options?: {
123
+ freeTaste?: boolean;
124
+ }) => string | null;
111
125
  }
112
126
  /**
113
127
  * The kinds whose confirm door builds only some plans (AGL-3030). A page job
@@ -211,6 +225,23 @@ export interface AiJobPlanStepDeps {
211
225
  admissionRefusal?: typeof aiJobAdmissionRefusal;
212
226
  }
213
227
  export declare function createAiJobPlanStep(deps?: AiJobPlanStepDeps): AiJobStepRunner;
228
+ /**
229
+ * A site job's capabilities on the Free taste (AGL-3594): its plan holds at
230
+ * most the pages the member asked for within the Free band, which the Free
231
+ * wall then holds it to, and it changes no theme — the site was born with
232
+ * one (AGL-3497), and a palette pass is credits the two pages need. Every
233
+ * other job's capabilities pass through.
234
+ */
235
+ export declare function aiSitePlanCapabilities(job: Pick<AiJob, 'kind' | 'inputs'>, capabilities: AiPlanCapabilities | null): AiPlanCapabilities | null;
236
+ /**
237
+ * The model a plan runs on (AGL-3594): the resolved one, except a Free site
238
+ * plans on its provider's fast tier — an outline the plan rules hold and
239
+ * re-ask, at a fifth of the balanced tier's output rate. A model the creator
240
+ * picked by name stays theirs.
241
+ */
242
+ export declare function aiSitePlanModel(job: Pick<AiJob, 'kind' | 'model'>, resolved: string | undefined, freeTaste: boolean): string | undefined;
243
+ /** The plan's answer ceiling: the step's own, and a scaffold's outline ceiling under it (AGL-3594). */
244
+ export declare function aiSitePlanMaxTokens(job: Pick<AiJob, 'kind'>, model: string, freeTaste: boolean): number;
214
245
  export declare const runAiJobPlanStep: AiJobStepRunner;
215
246
  /**
216
247
  * Registers the plan step every planned kind runs first, with the least time
@@ -20,10 +20,15 @@ import { AI_TEMPLATE_SUBJECT_DEFINITIONS, aiTemplateShownTokens } from "../model
20
20
  import { AI_JOB_CREATE_KINDS, AI_JOB_CREATE_NOUNS } from "../model/ai-job-creations.js";
21
21
  import { AI_PAGE_CREATE_KINDS, aiPagePlanShapeRefusal } from "../model/ai-page-job.js";
22
22
  import { aiPlanCapabilitiesForJob, aiPlanCapabilityLines } from "../model/ai-plan-capabilities.js";
23
- import { AI_SITE_CREATE_KINDS, aiSitePlanShapeRefusal } from "../model/ai-site-job.js";
23
+ import { AI_FREE_SITE_WORST_CASE_CREDITS, AI_SITE_CREATE_KINDS, AI_SITE_FREE_PAGES, AI_SITE_PLAN_MAX_TOKENS, aiFreeSiteSectionsWithin, aiSitePlanShapeRefusal } from "../model/ai-site-job.js";
24
+ import { aiPlanFailureCopy, aiPlanRetryRefusal } from "../model/ai-job-failure-copy.js";
25
+ import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
24
26
  import { aiDoctrineSystemBlocks, runValidatedGeneration } from "../runtime/ai-doctrine.js";
25
- import { AI_STEP_TIERS } from "../providers/catalog.js";
26
- import { aiPlanEmbedBriefViolations } from "../runtime/ai-doctrine-validators.js";
27
+ import { AI_STEP_TIERS, aiCatalogEntry, aiDefaultModelFor } from "../providers/catalog.js";
28
+ import { AI_FREE_PAGE_WORST_CASE_CREDITS, aiPlanEmbedBriefViolations } from "../runtime/ai-doctrine-validators.js";
29
+ import { assistCreditsFromUsd } from "../usage/assist-credits.js";
30
+ import { ASSIST_RETURNED_USD_FIELD, assistSpendAfterReturnsUsd } from "../usage/assist-credit-returns.js";
31
+ import { freeAccountUsageRef, freeAssistAccount } from "../usage/assist-free-taste.js";
27
32
  import { AI_ROUTING_TABLE, aiModelForStep } from "../providers/routing.js";
28
33
  import { readSiteInventory } from "../runtime/site-inventory.js";
29
34
  import { aiJobAdmissionRefusal } from "./ai-job-admission.js";
@@ -95,7 +100,7 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
95
100
  * inputs and, where the job read them, what it may create on its site
96
101
  * (AGL-3030). Per workspace and per site, which is why they ride here and
97
102
  * never in a system block the platform's cache entry is keyed on.
98
- */ export function aiJobPlanPrompt(job, capabilities = null) {
103
+ */ export function aiJobPlanPrompt(job, capabilities = null, inventory = null) {
99
104
  var _job_inputs;
100
105
  const lines = [
101
106
  `Job kind: ${job.kind}`,
@@ -108,8 +113,46 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
108
113
  }
109
114
  if (capabilities) lines.push(...aiPlanCapabilityLines(capabilities));
110
115
  lines.push(...aiPlanTemplateTokenLines(job));
116
+ lines.push(...aiPlanSiteLines(job, inventory, capabilities));
111
117
  return lines.join('\n');
112
118
  }
119
+ /**
120
+ * What a SITE job's plan is told beside the inventory (AGL-3594): the pages
121
+ * the site already has and that their addresses are taken, which one — the
122
+ * untouched starter home — a planned home page at `/` replaces, the Free page
123
+ * cap and its guidance, and that a plan is an outline.
124
+ *
125
+ * A site job only. A new site is born with a published home page at `/`
126
+ * (AGL-3497), and a plan that did not know it could replace it planned its own
127
+ * home there and was refused for the clash on every new site. Kept off a page
128
+ * job's turn, whose length the Free page's wall is proven at.
129
+ */ export function aiPlanSiteLines(job, inventory, capabilities) {
130
+ var _ref;
131
+ if (job.kind !== 'site') return [];
132
+ const lines = [];
133
+ const pages = ((_ref = inventory == null ? void 0 : inventory.screens) != null ? _ref : []).filter((screen)=>!screen.template);
134
+ const starter = pages.find((screen)=>screen.replaceable);
135
+ if (pages.length) {
136
+ lines.push('Pages the site already has; their addresses are taken unless a line says otherwise:');
137
+ for (const page of pages){
138
+ lines.push(page.replaceable ? `- ${page.name} at ${page.slug}: the starter home page the site was created with. Plan this site's home page at / and it replaces this one.` : `- ${page.name} at ${page.slug}`);
139
+ }
140
+ if (!starter && pages.some((page)=>page.slug.trim() === '/' || page.slug.trim() === '')) {
141
+ lines.push("The home page at / is the owner's own and stays: give every page you plan another address.");
142
+ }
143
+ }
144
+ const cap = capabilities == null ? void 0 : capabilities.freeSitePages;
145
+ if (cap !== undefined) {
146
+ var _ref1;
147
+ const sections = aiFreeSiteSectionsWithin({
148
+ layouts: ((_ref1 = inventory == null ? void 0 : inventory.layouts.length) != null ? _ref1 : 0) ? 0 : 1,
149
+ pages: cap
150
+ }, FREE_AI_TASTE_CREDITS_PER_MONTH);
151
+ lines.push(`This is a Free workspace: plan at most ${cap} ${cap === 1 ? 'page' : 'pages'} — the home page at / and the one page the brief most needs, such as services, booking or contact — with at most ${sections} sections across them. Put the contact form on one of them.`);
152
+ }
153
+ lines.push("Keep the plan an outline: each page's title, address, a short search title and description, and its sections named in a few words. The build writes the copy.");
154
+ return lines;
155
+ }
113
156
  /**
114
157
  * What a TEMPLATE job's plan is told its creation's `fields` are (AGL-3143
115
158
  * §11): the subject's binding tokens, listed so the planner promises only
@@ -167,10 +210,12 @@ import { AI_JOBS_COLLECTION, registerAiJobPlanStep } from "./ai-jobs.js";
167
210
  * violations the plan's one re-ask names: the players the plan lists against
168
211
  * the brief it answers (AGL-3433), which only this step reads beside the plan,
169
212
  * and a kind's shape refusal.
170
- */ function planViolations(scope, brief) {
213
+ */ function planViolations(scope, brief, freeTaste = false) {
171
214
  return (plan)=>{
172
215
  var _ref;
173
- const message = (_ref = scope == null ? void 0 : scope.shapeRefusal(plan)) != null ? _ref : null;
216
+ const message = (_ref = scope == null ? void 0 : scope.shapeRefusal(plan, {
217
+ freeTaste
218
+ })) != null ? _ref : null;
174
219
  return [
175
220
  ...aiPlanEmbedBriefViolations(plan, brief),
176
221
  ...message ? [
@@ -350,14 +395,17 @@ export function createAiJobPlanStep(deps = {}) {
350
395
  })
351
396
  ]);
352
397
  const scope = (_AI_JOB_PLAN_SCOPES_job_kind = AI_JOB_PLAN_SCOPES[job.kind]) != null ? _AI_JOB_PLAN_SCOPES_job_kind : null;
353
- const capabilities = workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null;
398
+ const capabilities = aiSitePlanCapabilities(job, workspace ? aiPlanCapabilitiesForJob(workspace, scope) : null);
399
+ const freeTaste = (capabilities == null ? void 0 : capabilities.freeTaste) === true;
400
+ const site = job.kind === 'site';
354
401
  // The model switch's answer for this job (AGL-2942): the creator's pick
355
402
  // where the plan, the org restriction and the allotment allowlists allow
356
403
  // it, and Auto held to those same lists otherwise. Without a resolver the
357
- // doctrine asks the routing table itself.
358
- const model = modelFor == null ? void 0 : modelFor('job.plan');
404
+ // doctrine asks the routing table itself. A Free site plans on its
405
+ // provider's fast tier (AGL-3594), unless the creator picked a model.
406
+ const model = aiSitePlanModel(job, modelFor == null ? void 0 : modelFor('job.plan'), freeTaste);
359
407
  const route = AI_ROUTING_TABLE['job.plan'];
360
- const prompt = aiJobPlanPrompt(job, capabilities);
408
+ const prompt = aiJobPlanPrompt(job, capabilities, inventory);
361
409
  /**
362
410
  * The confirm door's answer for this plan, asked before the plan is kept
363
411
  * (AGL-3030): the kind's own admission, handed the plan as the resume
@@ -452,9 +500,12 @@ export function createAiJobPlanStep(deps = {}) {
452
500
  ],
453
501
  tool,
454
502
  // The routing ceiling, lowered on a tier too slow to look records up,
455
- // answer and ask again inside the least time the step registered.
456
- maxTokens: aiJobPlanMaxTokens(resolved)
457
- }, route.thinking ? {
503
+ // answer and ask again inside the least time the step registered; a
504
+ // scaffold's outline is held to its own, far lower ceiling (AGL-3594).
505
+ maxTokens: aiSitePlanMaxTokens(job, resolved, freeTaste)
506
+ }, site ? {
507
+ thinking: 'off'
508
+ } : route.thinking ? {
458
509
  thinking: route.thinking
459
510
  } : {}, route.effort ? {
460
511
  effort: route.effort
@@ -462,7 +513,7 @@ export function createAiJobPlanStep(deps = {}) {
462
513
  signal
463
514
  } : {}, {
464
515
  capabilities,
465
- extend: planViolations(scope, job.brief)
516
+ extend: planViolations(scope, job.brief, freeTaste)
466
517
  }));
467
518
  const spent = _extends({
468
519
  outputs: [],
@@ -476,9 +527,34 @@ export function createAiJobPlanStep(deps = {}) {
476
527
  if (result.status === 'refused') return _extends({}, spent, {
477
528
  refused: true
478
529
  });
479
- if (result.status === 'needs_input') return _extends({}, spent, {
480
- review: aiDoctrineReview(result)
481
- });
530
+ if (result.status === 'needs_input') {
531
+ // Refused by our own checks, after the one re-ask (AGL-3594): the member
532
+ // reads a sentence and an action, staff the rules; the machine gives the
533
+ // credits back (`uncredited`), and on the Free taste Try again says when
534
+ // it cannot work. Whether the give-back was made is the job's to say
535
+ // (`refundedCredits`), so this sentence does not promise it.
536
+ const review = aiDoctrineReview(result);
537
+ const retryRefusal = freeTaste ? await aiFreePlanRetryRefusal({
538
+ job,
539
+ org,
540
+ firestore,
541
+ now,
542
+ site
543
+ }) : null;
544
+ return _extends({}, spent, {
545
+ uncredited: true,
546
+ review: _extends({}, review, {
547
+ message: aiPlanFailureCopy({
548
+ kind: job.kind,
549
+ codes: result.violations.map((violation)=>violation.code),
550
+ refunded: false
551
+ }),
552
+ detail: review.message
553
+ }, retryRefusal ? {
554
+ retryRefusal
555
+ } : {})
556
+ });
557
+ }
482
558
  // Each draft the plan decides is named as the plan is kept, before anything is built (AGL-3079).
483
559
  const plan = aiPlanWithDraftIds(job.kind, _extends({}, result.value, {
484
560
  status: 'proposed',
@@ -499,6 +575,74 @@ export function createAiJobPlanStep(deps = {}) {
499
575
  });
500
576
  };
501
577
  }
578
+ /**
579
+ * A site job's capabilities on the Free taste (AGL-3594): its plan holds at
580
+ * most the pages the member asked for within the Free band, which the Free
581
+ * wall then holds it to, and it changes no theme — the site was born with
582
+ * one (AGL-3497), and a palette pass is credits the two pages need. Every
583
+ * other job's capabilities pass through.
584
+ */ export function aiSitePlanCapabilities(job, capabilities) {
585
+ var _job_inputs;
586
+ if (job.kind !== 'site' || !(capabilities == null ? void 0 : capabilities.freeTaste)) return capabilities;
587
+ const asked = Number(((_job_inputs = job.inputs) != null ? _job_inputs : {})['pages']);
588
+ const pages = Number.isInteger(asked) && asked >= AI_SITE_FREE_PAGES.min ? Math.min(asked, AI_SITE_FREE_PAGES.max) : AI_SITE_FREE_PAGES.max;
589
+ return _extends({}, capabilities, {
590
+ freeSitePages: pages,
591
+ create: _extends({}, capabilities.create, {
592
+ 'theme-change': {
593
+ allowed: false,
594
+ left: null,
595
+ reason: "a Free workspace's site start keeps the theme the site was created with"
596
+ }
597
+ })
598
+ });
599
+ }
600
+ /**
601
+ * The model a plan runs on (AGL-3594): the resolved one, except a Free site
602
+ * plans on its provider's fast tier — an outline the plan rules hold and
603
+ * re-ask, at a fifth of the balanced tier's output rate. A model the creator
604
+ * picked by name stays theirs.
605
+ */ export function aiSitePlanModel(job, resolved, freeTaste) {
606
+ var _ref;
607
+ if (job.kind !== 'site' || !freeTaste) return resolved;
608
+ const picked = typeof job.model === 'string' ? job.model.trim() : '';
609
+ if (picked && picked !== 'auto') return resolved;
610
+ const entry = aiCatalogEntry(resolved != null ? resolved : aiModelForStep('job.plan'));
611
+ return (_ref = entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) != null ? _ref : resolved;
612
+ }
613
+ /** The plan's answer ceiling: the step's own, and a scaffold's outline ceiling under it (AGL-3594). */ export function aiSitePlanMaxTokens(job, model, freeTaste) {
614
+ const ceiling = aiJobPlanMaxTokens(model);
615
+ if (job.kind !== 'site') return ceiling;
616
+ return Math.min(ceiling, freeTaste ? AI_SITE_PLAN_MAX_TOKENS.free : AI_SITE_PLAN_MAX_TOKENS.paid);
617
+ }
618
+ /**
619
+ * Why trying a Free plan again cannot work this month, or `null` (AGL-3594):
620
+ * the owner's Free allowance left, read off the account month the meter
621
+ * writes net of give-backs, against what a plan of this kind costs at its
622
+ * worst. Read before this step is metered, and the machine gives a refused
623
+ * plan's credits back, so the figure read is the figure left. A read that fails
624
+ * leaves the button on: the reservation still refuses at the wall.
625
+ */ async function aiFreePlanRetryRefusal(input) {
626
+ const account = freeAssistAccount(input.org);
627
+ if (!(account == null ? void 0 : account.accountUid)) return null;
628
+ try {
629
+ // The meter's month key, `YYYY-MM` in UTC (`assistUsageMonth`).
630
+ const month = input.now.toISOString().slice(0, 7);
631
+ const snapshot = await freeAccountUsageRef(input.firestore, account.accountUid, month).get();
632
+ const spent = assistCreditsFromUsd(assistSpendAfterReturnsUsd(snapshot.get('estCostUsd'), snapshot.get(ASSIST_RETURNED_USD_FIELD)));
633
+ return aiPlanRetryRefusal({
634
+ creditsLeft: FREE_AI_TASTE_CREDITS_PER_MONTH - spent,
635
+ planCredits: input.site ? AI_FREE_SITE_WORST_CASE_CREDITS.plan : AI_FREE_PAGE_WORST_CASE_CREDITS.plan
636
+ });
637
+ } catch (error) {
638
+ console.error('ai plan retry allowance read failed', {
639
+ orgId: input.job.orgId,
640
+ jobId: input.job.$id,
641
+ error
642
+ });
643
+ return null;
644
+ }
645
+ }
502
646
  export const runAiJobPlanStep = createAiJobPlanStep();
503
647
  /**
504
648
  * Registers the plan step every planned kind runs first, with the least time