@avocadostudio-ai/orchestrator-core 0.3.2 → 0.4.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.
Files changed (88) hide show
  1. package/dist/chat/anthropic-planner.d.ts +8 -0
  2. package/dist/chat/anthropic-planner.js +166 -12
  3. package/dist/chat/chat-pipeline-translation.d.ts +13 -0
  4. package/dist/chat/chat-pipeline-translation.js +109 -45
  5. package/dist/chat/chat-pipeline.d.ts +1 -1
  6. package/dist/chat/chat-pipeline.js +312 -54
  7. package/dist/chat/gemini-planner.d.ts +2 -0
  8. package/dist/chat/gemini-planner.js +2 -1
  9. package/dist/chat/planner-types.d.ts +15 -0
  10. package/dist/chat/planner-types.js +2 -2
  11. package/dist/chat/planner.d.ts +12 -0
  12. package/dist/chat/planner.js +16 -2
  13. package/dist/chat/prompts.d.ts +5 -0
  14. package/dist/chat/prompts.js +92 -9
  15. package/dist/chat/translation-chunking.d.ts +124 -0
  16. package/dist/chat/translation-chunking.js +371 -0
  17. package/dist/checks/field-walk.d.ts +42 -0
  18. package/dist/checks/field-walk.js +198 -0
  19. package/dist/checks/index.d.ts +5 -0
  20. package/dist/checks/index.js +4 -0
  21. package/dist/checks/page-weight.d.ts +22 -0
  22. package/dist/checks/page-weight.js +200 -0
  23. package/dist/checks/rules-draft.d.ts +2 -0
  24. package/dist/checks/rules-draft.js +439 -0
  25. package/dist/checks/run-checks.d.ts +42 -0
  26. package/dist/checks/run-checks.js +159 -0
  27. package/dist/checks/session-runner.d.ts +19 -0
  28. package/dist/checks/session-runner.js +99 -0
  29. package/dist/checks/types.d.ts +109 -0
  30. package/dist/checks/types.js +1 -0
  31. package/dist/cms/adapter.d.ts +74 -1
  32. package/dist/cms/adapter.js +1 -0
  33. package/dist/cms/index.d.ts +1 -1
  34. package/dist/cms/index.js +1 -1
  35. package/dist/cms/media-sources.d.ts +29 -1
  36. package/dist/cms/media-sources.js +188 -7
  37. package/dist/durable/durable-store-singleton.d.ts +37 -0
  38. package/dist/durable/durable-store-singleton.js +179 -0
  39. package/dist/durable/finding-impact.d.ts +30 -0
  40. package/dist/durable/finding-impact.js +53 -0
  41. package/dist/durable/in-memory-durable-store.d.ts +203 -0
  42. package/dist/durable/in-memory-durable-store.js +363 -0
  43. package/dist/durable/index.d.ts +5 -0
  44. package/dist/durable/index.js +4 -0
  45. package/dist/durable/pending-plan-store.d.ts +28 -0
  46. package/dist/durable/pending-plan-store.js +156 -0
  47. package/dist/durable/sqlite-durable-store.d.ts +71 -0
  48. package/dist/durable/sqlite-durable-store.js +631 -0
  49. package/dist/durable/types.d.ts +265 -0
  50. package/dist/durable/types.js +1 -0
  51. package/dist/handler/create-orchestrator.d.ts +4 -0
  52. package/dist/handler/create-orchestrator.js +283 -32
  53. package/dist/http/audio-actions.d.ts +1 -1
  54. package/dist/http/checks-actions.d.ts +39 -0
  55. package/dist/http/checks-actions.js +122 -0
  56. package/dist/http/history-actions.d.ts +44 -1
  57. package/dist/http/history-actions.js +122 -0
  58. package/dist/http/image-generate-actions.d.ts +2 -2
  59. package/dist/http/ops-actions.d.ts +2 -2
  60. package/dist/http/publish-actions.d.ts +15 -4
  61. package/dist/http/publish-actions.js +3 -3
  62. package/dist/http/restore-actions.d.ts +3 -3
  63. package/dist/http/screenshot-actions.d.ts +2 -2
  64. package/dist/http/session-actions.d.ts +1 -1
  65. package/dist/http/telemetry-feedback-actions.d.ts +2 -2
  66. package/dist/http/unsplash-actions.d.ts +2 -2
  67. package/dist/http/variations-actions.d.ts +2 -2
  68. package/dist/index.d.ts +9 -2
  69. package/dist/index.js +28 -1
  70. package/dist/nlp/deterministic-planner-context.d.ts +16 -0
  71. package/dist/nlp/deterministic-planner-context.js +33 -7
  72. package/dist/nlp/intent-detection.d.ts +16 -0
  73. package/dist/nlp/intent-detection.js +15 -1
  74. package/dist/nlp/plan-normalizer.js +66 -32
  75. package/dist/ops/destructive-action-gate.js +7 -2
  76. package/dist/ops/ops-engine.d.ts +12 -1
  77. package/dist/ops/ops-engine.js +41 -14
  78. package/dist/publish/publish-helpers.d.ts +12 -2
  79. package/dist/publish/publish-helpers.js +10 -3
  80. package/dist/publish/publish-selection.d.ts +84 -0
  81. package/dist/publish/publish-selection.js +113 -0
  82. package/dist/publish/publish-target-registry.js +1 -1
  83. package/dist/publish/publish-target.d.ts +1 -1
  84. package/dist/publish/targets/git.js +2 -2
  85. package/dist/state/session-state.js +8 -1
  86. package/dist/state/site-assets.d.ts +41 -0
  87. package/dist/state/site-assets.js +40 -0
  88. package/package.json +3 -3
@@ -0,0 +1,156 @@
1
+ import { pendingApprovalPlanBySession } from "../state/session-state.js";
2
+ import { getDurableStore, isDiscardInFlight, noteDurableFailure } from "./durable-store-singleton.js";
3
+ /*
4
+ * Write-through durability for the pending approval plan.
5
+ *
6
+ * `pendingApprovalPlanBySession` stays the hot path — a Map read is what the
7
+ * chat pipeline wants and there is no reason to make it a query. What changes is
8
+ * that the Map is no longer the only copy: every plan is mirrored into the
9
+ * `proposals` table, and a read that misses rehydrates from there.
10
+ *
11
+ * The case this exists for is not a crash. It is the ordinary one: an agent
12
+ * runs at 03:00, produces a plan, and the process that produced it is gone long
13
+ * before anyone looks. Today that plan is in a Map that was never persisted and
14
+ * is evicted by age, so the morning's "approve" finds nothing and silently
15
+ * re-plans from the original message — at best spending tokens to reproduce
16
+ * work, at worst producing a different plan than the one that was reviewed.
17
+ *
18
+ * Every durable write here is best-effort. If the store is unavailable the chat
19
+ * path must behave exactly as it did before this module existed: in-memory, and
20
+ * honest about it through `durableHealth()`. A findings feature is not permitted
21
+ * to take chat down with it.
22
+ */
23
+ /*
24
+ * Two different TTLs, because they answer two different questions.
25
+ *
26
+ * The Map's hour (`APPROVAL_PLAN_TTL_MS` in session-state) is "how long do we
27
+ * keep an abandoned approval warm in this process" — a memory-pressure answer.
28
+ * Reusing it for the durable row would defeat the entire point of the row: the
29
+ * agent that ran at 03:00 and the person who approves at 09:00 are six hours
30
+ * apart, so an hour-old row is expired before anybody could possibly have seen
31
+ * it, and `loadPendingPlan` falls straight back to the silent re-plan this
32
+ * module exists to prevent.
33
+ *
34
+ * A durable proposal is kept for a working week. The Map still forgets it after
35
+ * an hour; the next read rehydrates.
36
+ */
37
+ const DURABLE_PLAN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
38
+ /**
39
+ * Marks the proposals this module owns.
40
+ *
41
+ * `proposals` is shared with agent-authored plans by design. "The newest
42
+ * pending row for this session" is therefore the wrong question — once a
43
+ * checker writes one, it would shadow a genuinely pending chat plan behind it,
44
+ * and `clearPendingPlan` would resolve somebody else's proposal as approved.
45
+ */
46
+ const CHAT_ORIGIN_PREFIX = "chat:";
47
+ function createdAtMs(plan) {
48
+ const parsed = Date.parse(plan.createdAt);
49
+ return Number.isFinite(parsed) ? parsed : Date.now();
50
+ }
51
+ function noteFailure(err, what) {
52
+ noteDurableFailure(`${what}: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ function isChatProposal(record) {
55
+ return record.origin.startsWith(CHAT_ORIGIN_PREFIX);
56
+ }
57
+ async function findChatProposal(session) {
58
+ const rows = await getDurableStore().listProposals({ scopeKey: session, status: "pending" });
59
+ return rows.find(isChatProposal);
60
+ }
61
+ /**
62
+ * Map-only read. For call sites that only need to know whether a plan is live
63
+ * in this process and must not pay for a query.
64
+ */
65
+ export function peekPendingPlan(session) {
66
+ return pendingApprovalPlanBySession.get(session);
67
+ }
68
+ /**
69
+ * Read the pending plan, rehydrating from the durable store on a Map miss.
70
+ *
71
+ * An expired row is treated as absent rather than deleted: eviction is the
72
+ * sweeper's job, and a read is not the place to discover that a plan the user
73
+ * is looking at right now has just aged out mid-request.
74
+ */
75
+ export async function loadPendingPlan(session) {
76
+ const live = pendingApprovalPlanBySession.get(session);
77
+ if (live)
78
+ return live;
79
+ if (isDiscardInFlight(session))
80
+ return undefined;
81
+ try {
82
+ const record = await findChatProposal(session);
83
+ if (!record?.payload)
84
+ return undefined;
85
+ if (record.expiresAt != null && record.expiresAt <= Date.now())
86
+ return undefined;
87
+ const plan = record.payload;
88
+ // A payload that cannot be executed is worse than none: the approval path
89
+ // would hand a malformed plan to the ops engine on the user's click.
90
+ if (!plan?.id || !plan.plan || !Array.isArray(plan.plan.ops))
91
+ return undefined;
92
+ pendingApprovalPlanBySession.set(session, plan);
93
+ return plan;
94
+ }
95
+ catch (err) {
96
+ noteFailure(err, "read pending plan");
97
+ return undefined;
98
+ }
99
+ }
100
+ /** Set the plan in memory and mirror it to the durable store. */
101
+ export async function savePendingPlan(session, plan) {
102
+ pendingApprovalPlanBySession.set(session, plan);
103
+ const createdAt = createdAtMs(plan);
104
+ try {
105
+ await getDurableStore().putProposal({
106
+ id: plan.id,
107
+ scopeKey: session,
108
+ origin: `${CHAT_ORIGIN_PREFIX}${plan.source}`,
109
+ summary: plan.summary,
110
+ ops: plan.plan.ops,
111
+ slugs: plan.effectiveSlug ? [plan.effectiveSlug] : [],
112
+ payload: plan,
113
+ createdAt,
114
+ expiresAt: createdAt + DURABLE_PLAN_TTL_MS
115
+ });
116
+ }
117
+ catch (err) {
118
+ noteFailure(err, "save pending plan");
119
+ }
120
+ }
121
+ /**
122
+ * Drop the plan from memory and resolve its durable row.
123
+ *
124
+ * `outcome` is recorded rather than deleted, so "how often does a human discard
125
+ * what we proposed" stays answerable — which is the same signal the corrections
126
+ * table is for, and the reason `expectedId` matters. A turn that applies
127
+ * something unrelated must not record a held plan as `approved`: it was never
128
+ * reviewed, and writing that down poisons the one number this is here to make
129
+ * available. Pass the id of the plan actually acted on; omit it only where the
130
+ * intent really is "whatever chat plan is pending, it is over".
131
+ */
132
+ export async function clearPendingPlan(session, outcome, expectedId) {
133
+ const plan = pendingApprovalPlanBySession.get(session);
134
+ if (expectedId && plan && plan.id !== expectedId) {
135
+ // The live plan is not the one being resolved — leave both alone.
136
+ return;
137
+ }
138
+ pendingApprovalPlanBySession.delete(session);
139
+ try {
140
+ const store = getDurableStore();
141
+ const id = expectedId ?? plan?.id;
142
+ if (id) {
143
+ const record = await store.getProposal(id);
144
+ if (record && isChatProposal(record))
145
+ await store.setProposalStatus(id, outcome);
146
+ return;
147
+ }
148
+ // No live plan: a durable row may still be pending from a previous process.
149
+ const record = await findChatProposal(session);
150
+ if (record)
151
+ await store.setProposalStatus(record.id, outcome);
152
+ }
153
+ catch (err) {
154
+ noteFailure(err, "clear pending plan");
155
+ }
156
+ }
@@ -0,0 +1,71 @@
1
+ import type { Database as BetterSqliteDatabase } from "better-sqlite3";
2
+ import type { CheckRunInput, CheckRunPatch, CheckRunRecord, CorrectionInput, CorrectionQuery, CorrectionRecord, DurableStore, FindingInput, FindingQuery, FindingRecord, FindingStatus, MemoryInput, MemoryQuery, MemoryRecord, MemoryStatus, ProposalInput, ProposalQuery, ProposalRecord, ProposalStatus } from "./types.ts";
3
+ export declare const PROPOSAL_CAP = 200;
4
+ export declare const CORRECTION_CAP = 500;
5
+ export declare const CHECK_RUN_CAP = 200;
6
+ /**
7
+ * How long a snooze lasts.
8
+ *
9
+ * Seven days, because the button competes with Dismiss and has to mean
10
+ * something different from it: long enough to clear a finding out of the way
11
+ * for the week somebody is shipping a campaign, short enough that "I'll get to
12
+ * it" does not quietly become "never". Exported so both implementations, and
13
+ * the tests that hold them to the same behaviour, derive it from one number.
14
+ */
15
+ export declare const SNOOZE_WINDOW_MS: number;
16
+ export type SqliteDurableStoreOptions = {
17
+ /** Injectable clock, so tests can assert on ordering without sleeping. */
18
+ now?: () => number;
19
+ };
20
+ export declare class SqliteDurableStore implements DurableStore {
21
+ private readonly db;
22
+ private readonly now;
23
+ constructor(db: BetterSqliteDatabase, options?: SqliteDurableStoreOptions);
24
+ private ensureColumns;
25
+ private trim;
26
+ recordFindings(runId: string, findings: FindingInput[]): Promise<{
27
+ opened: number;
28
+ updated: number;
29
+ }>;
30
+ reconcileFindings(args: {
31
+ runId: string;
32
+ scopeKey: string;
33
+ slugs: string[];
34
+ agent?: string;
35
+ at?: number;
36
+ }): Promise<{
37
+ closed: number;
38
+ }>;
39
+ listFindings(query: FindingQuery): Promise<FindingRecord[]>;
40
+ getFinding(id: string): Promise<FindingRecord | null>;
41
+ setFindingStatus(id: string, status: FindingStatus, at?: number): Promise<void>;
42
+ wakeSnoozedFindings(scopeKey: string, now?: number): Promise<{
43
+ woken: number;
44
+ }>;
45
+ startCheckRun(run: CheckRunInput): Promise<{
46
+ pagesScanned: number;
47
+ findingsOpened: number;
48
+ findingsClosed: number;
49
+ costUsd: number;
50
+ id: string;
51
+ scopeKey: string;
52
+ agent: string;
53
+ trigger: import("./types.ts").CheckRunTrigger;
54
+ startedAt: number;
55
+ }>;
56
+ finishCheckRun(id: string, patch: CheckRunPatch): Promise<void>;
57
+ listCheckRuns(scopeKey: string, limit?: number): Promise<CheckRunRecord[]>;
58
+ putMemory(input: MemoryInput, at?: number): Promise<MemoryRecord>;
59
+ listMemory(query?: MemoryQuery): Promise<MemoryRecord[]>;
60
+ touchMemory(ids: string[], at?: number): Promise<void>;
61
+ setMemoryStatus(id: string, status: MemoryStatus): Promise<void>;
62
+ recordCorrection(input: CorrectionInput, at?: number): Promise<CorrectionRecord>;
63
+ listCorrections(query?: CorrectionQuery): Promise<CorrectionRecord[]>;
64
+ putProposal(input: ProposalInput): Promise<ProposalRecord>;
65
+ getProposal(id: string): Promise<ProposalRecord | null>;
66
+ listProposals(query: ProposalQuery): Promise<ProposalRecord[]>;
67
+ setProposalStatus(id: string, status: ProposalStatus, at?: number): Promise<void>;
68
+ expireProposals(now?: number): Promise<{
69
+ expired: number;
70
+ }>;
71
+ }