@aglyn/plugins-ai 1.0.0-beta.239 → 1.0.0-beta.240

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-ai",
3
- "version": "1.0.0-beta.239",
3
+ "version": "1.0.0-beta.240",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,22 +25,22 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.239",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.239",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.239",
31
- "@aglyn/shared-data-types": "1.0.0-beta.239",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.239",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.239",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.239",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.239",
36
- "@aglyn/shared-util-email": "1.0.0-beta.239",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.239",
38
- "@aglyn/shared-util-http": "1.0.0-beta.239",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.239",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.239",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.239",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.239",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.239",
28
+ "@aglyn/aglyn": "1.0.0-beta.240",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.240",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.240",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.240",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.240",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.240",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.240",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.240",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.240",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.240",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.240",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.240",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.240",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.240",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.240",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.240",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -21,6 +21,11 @@ export interface AiJobDecision {
21
21
  * person's go-ahead with these figures and choices instead of starting it.
22
22
  */
23
23
  credits?: AiCreditsPrompt | null;
24
+ /**
25
+ * The usage strip's envelope the job door answered with, when it ran a step
26
+ * inline (AGL-3722): what the plan just spent, so the strip moves with it.
27
+ */
28
+ meter?: unknown;
24
29
  }
25
30
  /**
26
31
  * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed
@@ -79,10 +79,12 @@ import { publishAiJob } from "./ai-jobs-store.js";
79
79
  const payload = await response.json().catch(()=>null);
80
80
  const next = (_ref = payload == null ? void 0 : payload.job) != null ? _ref : null;
81
81
  if (response.ok && next) publishAiJob(next);
82
- return {
82
+ return _extends({
83
83
  job: response.ok ? next : null,
84
84
  error: response.ok && next ? null : String((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : failed)
85
- };
85
+ }, (payload == null ? void 0 : payload.meter) ? {
86
+ meter: payload.meter
87
+ } : {});
86
88
  } catch (unused) {
87
89
  return {
88
90
  job: null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-requests.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'use client'\n\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { AI_CREDITS_CONFIRM_CODE, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { assistBuildJobRequest, type AssistBuildProposal } from '../model/assist-build'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The two decisions a person makes about a job — confirm its plan (or try a\n * refused step again), and cancel it — as ONE request each, whichever\n * surface they press it on (AGL-3593). The AI jobs drawer and the dialog that\n * started the job both call these, so confirming in the dialog is the same\n * door, the same body and the same answer as confirming in the drawer.\n *\n * Each hands the job it gets back to every counting surface, so the top-bar\n * indicator stops saying \"plan ready\" the moment the plan is confirmed.\n */\n\n/** A door's answer: the job as it now stands, and the refusal when there is one. */\nexport interface AiJobDecision {\n job: AiJobSummary | null\n error: string | null\n /**\n * A Free job past what is left (AGL-3722): the door's 409 asks for the\n * person's go-ahead with these figures and choices instead of starting it.\n */\n credits?: AiCreditsPrompt | null\n}\n\n/**\n * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed\n * with its publish box ticked, or a finished build's failed items tried again.\n */\nexport interface AiJobResumeOptions {\n publish?: boolean\n retry?: 'failed-items'\n /** \"Build what fits\" (AGL-3722): a Free job past what is left, started on the person's go-ahead. */\n creditsConfirmed?: boolean\n /** \"Build the home page first\" (AGL-3722): the plan narrowed to its home page and what it needs. */\n reduce?: 'first-page'\n}\n\n/**\n * Confirms a plan, or tries again a step whose answer broke a building rule\n * (AGL-2935). The door runs the next step inline and answers with the job.\n */\nexport async function resumeAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n job: Pick<AiJobSummary, 'id' | 'hostId'>,\n options: AiJobResumeOptions = {},\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId: job.hostId,\n ...(options.publish ? { publish: true } : {}),\n ...(options.retry ? { retry: options.retry } : {}),\n ...(options.creditsConfirmed ? { creditsConfirmed: true } : {}),\n ...(options.reduce ? { reduce: options.reduce } : {}),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n const credits =\n response.status === 409 && payload?.code === AI_CREDITS_CONFIRM_CODE\n ? ((payload?.credits as AiCreditsPrompt | undefined) ?? null)\n : null\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be resumed — try again.'),\n ...(credits ? { credits } : {}),\n }\n } catch {\n return { job: null, error: 'The job could not be resumed — try again.' }\n }\n}\n\n/**\n * Starts the build a chat turn proposed (AGL-3616): one `build` job on the\n * proposal's site, through the same door every job is created by, which\n * climbs its own gates and plans inline. What comes back stops at its plan\n * card; nothing is built until the person confirms it.\n */\nexport async function startAssistBuildRequest(\n user: MaybeTokenSource,\n orgId: string,\n proposal: AssistBuildProposal,\n): Promise<AiJobDecision> {\n const failed = 'The build could not be planned — try asking again.'\n try {\n const response = await authorizedFetch(user, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(assistBuildJobRequest(orgId, proposal)),\n })\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n if (response.ok && next) publishAiJob(next)\n return {\n job: response.ok ? next : null,\n error: response.ok && next ? null : String(payload?.error ?? failed),\n }\n } catch {\n return { job: null, error: failed }\n }\n}\n\n/** Cancels a job that has not settled. */\nexport async function cancelAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n jobId: string,\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be canceled — try again.'),\n }\n } catch {\n return { job: null, error: 'The job could not be canceled — try again.' }\n }\n}\n"],"names":["authorizedFetch","AI_CREDITS_CONFIRM_CODE","assistBuildJobRequest","publishAiJob","resumeAiJobRequest","user","orgId","job","options","response","encodeURIComponent","id","method","headers","body","JSON","stringify","hostId","publish","retry","creditsConfirmed","reduce","payload","json","catch","next","credits","status","code","error","ok","String","startAssistBuildRequest","proposal","failed","cancelAiJobRequest","jobId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-requests.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'use client'\n\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { AI_CREDITS_CONFIRM_CODE, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { assistBuildJobRequest, type AssistBuildProposal } from '../model/assist-build'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The two decisions a person makes about a job — confirm its plan (or try a\n * refused step again), and cancel it — as ONE request each, whichever\n * surface they press it on (AGL-3593). The AI jobs drawer and the dialog that\n * started the job both call these, so confirming in the dialog is the same\n * door, the same body and the same answer as confirming in the drawer.\n *\n * Each hands the job it gets back to every counting surface, so the top-bar\n * indicator stops saying \"plan ready\" the moment the plan is confirmed.\n */\n\n/** A door's answer: the job as it now stands, and the refusal when there is one. */\nexport interface AiJobDecision {\n job: AiJobSummary | null\n error: string | null\n /**\n * A Free job past what is left (AGL-3722): the door's 409 asks for the\n * person's go-ahead with these figures and choices instead of starting it.\n */\n credits?: AiCreditsPrompt | null\n /**\n * The usage strip's envelope the job door answered with, when it ran a step\n * inline (AGL-3722): what the plan just spent, so the strip moves with it.\n */\n meter?: unknown\n}\n\n/**\n * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed\n * with its publish box ticked, or a finished build's failed items tried again.\n */\nexport interface AiJobResumeOptions {\n publish?: boolean\n retry?: 'failed-items'\n /** \"Build what fits\" (AGL-3722): a Free job past what is left, started on the person's go-ahead. */\n creditsConfirmed?: boolean\n /** \"Build the home page first\" (AGL-3722): the plan narrowed to its home page and what it needs. */\n reduce?: 'first-page'\n}\n\n/**\n * Confirms a plan, or tries again a step whose answer broke a building rule\n * (AGL-2935). The door runs the next step inline and answers with the job.\n */\nexport async function resumeAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n job: Pick<AiJobSummary, 'id' | 'hostId'>,\n options: AiJobResumeOptions = {},\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId: job.hostId,\n ...(options.publish ? { publish: true } : {}),\n ...(options.retry ? { retry: options.retry } : {}),\n ...(options.creditsConfirmed ? { creditsConfirmed: true } : {}),\n ...(options.reduce ? { reduce: options.reduce } : {}),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n const credits =\n response.status === 409 && payload?.code === AI_CREDITS_CONFIRM_CODE\n ? ((payload?.credits as AiCreditsPrompt | undefined) ?? null)\n : null\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be resumed — try again.'),\n ...(credits ? { credits } : {}),\n }\n } catch {\n return { job: null, error: 'The job could not be resumed — try again.' }\n }\n}\n\n/**\n * Starts the build a chat turn proposed (AGL-3616): one `build` job on the\n * proposal's site, through the same door every job is created by, which\n * climbs its own gates and plans inline. What comes back stops at its plan\n * card; nothing is built until the person confirms it.\n */\nexport async function startAssistBuildRequest(\n user: MaybeTokenSource,\n orgId: string,\n proposal: AssistBuildProposal,\n): Promise<AiJobDecision> {\n const failed = 'The build could not be planned — try asking again.'\n try {\n const response = await authorizedFetch(user, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(assistBuildJobRequest(orgId, proposal)),\n })\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n if (response.ok && next) publishAiJob(next)\n return {\n job: response.ok ? next : null,\n error: response.ok && next ? null : String(payload?.error ?? failed),\n ...(payload?.meter ? { meter: payload.meter } : {}),\n }\n } catch {\n return { job: null, error: failed }\n }\n}\n\n/** Cancels a job that has not settled. */\nexport async function cancelAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n jobId: string,\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be canceled — try again.'),\n }\n } catch {\n return { job: null, error: 'The job could not be canceled — try again.' }\n }\n}\n"],"names":["authorizedFetch","AI_CREDITS_CONFIRM_CODE","assistBuildJobRequest","publishAiJob","resumeAiJobRequest","user","orgId","job","options","response","encodeURIComponent","id","method","headers","body","JSON","stringify","hostId","publish","retry","creditsConfirmed","reduce","payload","json","catch","next","credits","status","code","error","ok","String","startAssistBuildRequest","proposal","failed","meter","cancelAiJobRequest","jobId"],"mappings":"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"}
@@ -41,8 +41,8 @@ import { aiJobsActivity, aiJobsLauncherLabel } from "../model/ai-job-activity.js
41
41
  import { AiModelSelector } from "./ai-model-selector.component.js";
42
42
  import { AiUsageStrip } from "./ai-usage-strip.component.js";
43
43
  import { useAiModelChoice } from "./use-ai-model-choice.js";
44
- import { followAiJobsForUsageMeter } from "./ai-usage-meter-refresh.js";
45
- import { usePublishAiUsageMeter } from "./use-ai-usage-meter.js";
44
+ import { followAiJobsForUsageMeter, refreshAiUsageMeterFree } from "./ai-usage-meter-refresh.js";
45
+ import { readAiUsageMeter, usePublishAiUsageMeter } from "./use-ai-usage-meter.js";
46
46
  import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
47
47
  import { HelpTip } from "@aglyn/shared-ui-jsx";
48
48
  /** The key a follow-up waiting for its canvas is kept under, per org (AGL-3616). */ const followUpKey = (orgId)=>`aglyn-assist-follow-up:${orgId}`;
@@ -821,6 +821,18 @@ export function AssistPanelComponent(props) {
821
821
  // through the job door, and plans inline; its card shows the plan.
822
822
  if (proposedBuild) {
823
823
  const decision = await startAssistBuildRequest(user, scopedOrgId, proposedBuild);
824
+ // The plan the door ran inline has just spent (AGL-3722), so the strip
825
+ // moves with it, to the figure the plan card quotes. Left to the jobs
826
+ // list's next read it said what was left BEFORE the plan, under a card
827
+ // that had it right: "117 free AI credits left" over "You have 71
828
+ // left", 2026-10-10. The card's figure is read after the plan's spend
829
+ // is counted once, so the strip takes it as it is.
830
+ publishMeter(decision.meter);
831
+ const uid = user == null ? void 0 : user.uid;
832
+ if (uid && scopedOrgId) {
833
+ var _decision_job_review, _decision_job;
834
+ refreshAiUsageMeterFree(uid, scopedOrgId, (_decision_job = decision.job) == null ? void 0 : (_decision_job_review = _decision_job.review) == null ? void 0 : _decision_job_review.freeCredits, readAiUsageMeter(uid, scopedOrgId));
835
+ }
824
836
  patchAnswer((message)=>_extends({}, message, {
825
837
  buildJob: decision.job,
826
838
  buildNotice: decision.error