@avocadostudio-ai/orchestrator-core 0.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/LICENSE +201 -0
- package/dist/agent/agent-context.d.ts +19 -0
- package/dist/agent/agent-context.js +67 -0
- package/dist/agent/agent-logger.d.ts +5 -0
- package/dist/agent/agent-logger.js +22 -0
- package/dist/agent/agent-loop-openai.d.ts +8 -0
- package/dist/agent/agent-loop-openai.js +172 -0
- package/dist/agent/agent-loop.d.ts +56 -0
- package/dist/agent/agent-loop.js +167 -0
- package/dist/agent/agent-provider.d.ts +28 -0
- package/dist/agent/agent-provider.js +63 -0
- package/dist/agent/agent-tools.d.ts +28 -0
- package/dist/agent/agent-tools.js +899 -0
- package/dist/agent/context/editing-guidelines.md +46 -0
- package/dist/agent/context/role.md +39 -0
- package/dist/agent/integration-prompt.d.ts +9 -0
- package/dist/agent/integration-prompt.js +154 -0
- package/dist/agent/sites-agent-context.d.ts +12 -0
- package/dist/agent/sites-agent-context.js +316 -0
- package/dist/agent/sites-agent-shared.d.ts +161 -0
- package/dist/agent/sites-agent-shared.js +1101 -0
- package/dist/agent/sites-agent-tools.d.ts +18 -0
- package/dist/agent/sites-agent-tools.js +1227 -0
- package/dist/chat/anthropic-cache.d.ts +20 -0
- package/dist/chat/anthropic-cache.js +54 -0
- package/dist/chat/anthropic-planner.d.ts +98 -0
- package/dist/chat/anthropic-planner.js +1012 -0
- package/dist/chat/changelog-coverage-validator.d.ts +37 -0
- package/dist/chat/changelog-coverage-validator.js +215 -0
- package/dist/chat/chat-pipeline-context.d.ts +211 -0
- package/dist/chat/chat-pipeline-context.js +249 -0
- package/dist/chat/chat-pipeline-deterministic.d.ts +61 -0
- package/dist/chat/chat-pipeline-deterministic.js +407 -0
- package/dist/chat/chat-pipeline-image.d.ts +86 -0
- package/dist/chat/chat-pipeline-image.js +897 -0
- package/dist/chat/chat-pipeline-shared.d.ts +69 -0
- package/dist/chat/chat-pipeline-shared.js +212 -0
- package/dist/chat/chat-pipeline-translation.d.ts +27 -0
- package/dist/chat/chat-pipeline-translation.js +417 -0
- package/dist/chat/chat-pipeline-ui.d.ts +14 -0
- package/dist/chat/chat-pipeline-ui.js +244 -0
- package/dist/chat/chat-pipeline.d.ts +99 -0
- package/dist/chat/chat-pipeline.js +3999 -0
- package/dist/chat/decomposer.d.ts +21 -0
- package/dist/chat/decomposer.js +65 -0
- package/dist/chat/gemini-planner.d.ts +70 -0
- package/dist/chat/gemini-planner.js +541 -0
- package/dist/chat/hallucination-validator.d.ts +36 -0
- package/dist/chat/hallucination-validator.js +110 -0
- package/dist/chat/locale-strings.d.ts +47 -0
- package/dist/chat/locale-strings.js +100 -0
- package/dist/chat/plan-json-schema.d.ts +133 -0
- package/dist/chat/plan-json-schema.js +112 -0
- package/dist/chat/planner-types.d.ts +120 -0
- package/dist/chat/planner-types.js +66 -0
- package/dist/chat/planner.d.ts +148 -0
- package/dist/chat/planner.js +1361 -0
- package/dist/chat/prompts.d.ts +67 -0
- package/dist/chat/prompts.js +356 -0
- package/dist/chat/provider-routing.d.ts +14 -0
- package/dist/chat/provider-routing.js +27 -0
- package/dist/chat/variation-pipeline.d.ts +135 -0
- package/dist/chat/variation-pipeline.js +837 -0
- package/dist/chat/vision-alt-generator.d.ts +35 -0
- package/dist/chat/vision-alt-generator.js +152 -0
- package/dist/cms/adapter.d.ts +62 -0
- package/dist/cms/adapter.js +1 -0
- package/dist/cms/bootstrap.d.ts +17 -0
- package/dist/cms/bootstrap.js +85 -0
- package/dist/cms/editor-api-adapter.d.ts +23 -0
- package/dist/cms/editor-api-adapter.js +71 -0
- package/dist/cms/index.d.ts +4 -0
- package/dist/cms/index.js +3 -0
- package/dist/cms/json-file-adapter.d.ts +11 -0
- package/dist/cms/json-file-adapter.js +62 -0
- package/dist/demo-mode.d.ts +59 -0
- package/dist/demo-mode.js +201 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +129 -0
- package/dist/http/chat-stream-resumable.d.ts +108 -0
- package/dist/http/chat-stream-resumable.js +290 -0
- package/dist/http/chat-stream.d.ts +99 -0
- package/dist/http/chat-stream.js +92 -0
- package/dist/image/gdrive-client.d.ts +22 -0
- package/dist/image/gdrive-client.js +215 -0
- package/dist/image/image-helpers.d.ts +95 -0
- package/dist/image/image-helpers.js +488 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/jira/jira-approval.d.ts +22 -0
- package/dist/jira/jira-approval.js +51 -0
- package/dist/jira/jira-client.d.ts +44 -0
- package/dist/jira/jira-client.js +313 -0
- package/dist/jira/jira-poller.d.ts +46 -0
- package/dist/jira/jira-poller.js +184 -0
- package/dist/jira/jira-processor.d.ts +103 -0
- package/dist/jira/jira-processor.js +1085 -0
- package/dist/jira/jira-types.d.ts +117 -0
- package/dist/jira/jira-types.js +38 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +28 -0
- package/dist/migration/mcp-server-stdio.d.ts +8 -0
- package/dist/migration/mcp-server-stdio.js +672 -0
- package/dist/migration/migration-prompt.d.ts +7 -0
- package/dist/migration/migration-prompt.js +197 -0
- package/dist/migration/migration-tools.d.ts +17 -0
- package/dist/migration/migration-tools.js +159 -0
- package/dist/migration/scrape-cache.d.ts +9 -0
- package/dist/migration/scrape-cache.js +19 -0
- package/dist/nlp/deterministic-planner-context.d.ts +141 -0
- package/dist/nlp/deterministic-planner-context.js +362 -0
- package/dist/nlp/deterministic-planner-pages.d.ts +26 -0
- package/dist/nlp/deterministic-planner-pages.js +170 -0
- package/dist/nlp/deterministic-planner-patches.d.ts +80 -0
- package/dist/nlp/deterministic-planner-patches.js +508 -0
- package/dist/nlp/deterministic-planner-refs.d.ts +33 -0
- package/dist/nlp/deterministic-planner-refs.js +164 -0
- package/dist/nlp/deterministic-planner-suggestions.d.ts +49 -0
- package/dist/nlp/deterministic-planner-suggestions.js +579 -0
- package/dist/nlp/deterministic-planner.d.ts +85 -0
- package/dist/nlp/deterministic-planner.js +1631 -0
- package/dist/nlp/intent-detection.d.ts +309 -0
- package/dist/nlp/intent-detection.js +730 -0
- package/dist/nlp/intent-helpers.d.ts +15 -0
- package/dist/nlp/intent-helpers.js +243 -0
- package/dist/nlp/intent-patterns.d.ts +40 -0
- package/dist/nlp/intent-patterns.js +223 -0
- package/dist/nlp/plan-normalizer.d.ts +41 -0
- package/dist/nlp/plan-normalizer.js +1537 -0
- package/dist/ops/destructive-action-gate.d.ts +44 -0
- package/dist/ops/destructive-action-gate.js +90 -0
- package/dist/ops/ops-engine.d.ts +151 -0
- package/dist/ops/ops-engine.js +1394 -0
- package/dist/publish/diff-engine.d.ts +18 -0
- package/dist/publish/diff-engine.js +305 -0
- package/dist/publish/publish-helpers.d.ts +87 -0
- package/dist/publish/publish-helpers.js +521 -0
- package/dist/publish/publish-target-registry.d.ts +7 -0
- package/dist/publish/publish-target-registry.js +61 -0
- package/dist/publish/publish-target.d.ts +81 -0
- package/dist/publish/publish-target.js +1 -0
- package/dist/publish/targets/deploy-hook.d.ts +13 -0
- package/dist/publish/targets/deploy-hook.js +123 -0
- package/dist/publish/targets/git.d.ts +13 -0
- package/dist/publish/targets/git.js +55 -0
- package/dist/publish/targets/site-contract.d.ts +19 -0
- package/dist/publish/targets/site-contract.js +124 -0
- package/dist/state/content-source.d.ts +17 -0
- package/dist/state/content-source.js +1 -0
- package/dist/state/in-memory-content-source.d.ts +27 -0
- package/dist/state/in-memory-content-source.js +51 -0
- package/dist/state/session-lock.d.ts +13 -0
- package/dist/state/session-lock.js +29 -0
- package/dist/state/session-state.d.ts +310 -0
- package/dist/state/session-state.js +1083 -0
- package/dist/state/sqlite-store-singleton.d.ts +31 -0
- package/dist/state/sqlite-store-singleton.js +170 -0
- package/dist/state/sqlite-store.d.ts +135 -0
- package/dist/state/sqlite-store.js +421 -0
- package/dist/telemetry/chat-telemetry.d.ts +105 -0
- package/dist/telemetry/chat-telemetry.js +247 -0
- package/dist/telemetry/eval-candidate-store.d.ts +50 -0
- package/dist/telemetry/eval-candidate-store.js +120 -0
- package/dist/telemetry/feedback-store.d.ts +34 -0
- package/dist/telemetry/feedback-store.js +76 -0
- package/dist/telemetry/jira-telemetry.d.ts +57 -0
- package/dist/telemetry/jira-telemetry.js +68 -0
- package/dist/telemetry/migration-telemetry.d.ts +35 -0
- package/dist/telemetry/migration-telemetry.js +40 -0
- package/dist/telemetry/usage.d.ts +24 -0
- package/dist/telemetry/usage.js +80 -0
- package/dist/tools/builtin-registrations.d.ts +12 -0
- package/dist/tools/builtin-registrations.js +33 -0
- package/dist/tools/builtins/gdrive-browse.d.ts +3 -0
- package/dist/tools/builtins/gdrive-browse.js +68 -0
- package/dist/tools/builtins/image-generate.d.ts +3 -0
- package/dist/tools/builtins/image-generate.js +211 -0
- package/dist/tools/builtins/unsplash-get-by-id.d.ts +23 -0
- package/dist/tools/builtins/unsplash-get-by-id.js +119 -0
- package/dist/tools/builtins/unsplash-search.d.ts +3 -0
- package/dist/tools/builtins/unsplash-search.js +74 -0
- package/dist/tools/executor.d.ts +23 -0
- package/dist/tools/executor.js +169 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/registry.d.ts +21 -0
- package/dist/tools/registry.js +75 -0
- package/dist/tools/runtime.d.ts +27 -0
- package/dist/tools/runtime.js +48 -0
- package/dist/tools/schema-validator.d.ts +24 -0
- package/dist/tools/schema-validator.js +88 -0
- package/dist/tools/types.d.ts +86 -0
- package/dist/tools/types.js +1 -0
- package/dist/variation-images.d.ts +19 -0
- package/dist/variation-images.js +12 -0
- package/package.json +78 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PublishContext, PublishOutcome, PublishTarget } from "../publish-target.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Publish by POSTing to a raw Vercel deploy hook URL. Useful when the
|
|
4
|
+
* orchestrator runs outside the git repo (e.g. in a managed environment) and
|
|
5
|
+
* cannot commit + push on its own.
|
|
6
|
+
*
|
|
7
|
+
* Selected when no `siteOrigin` is supplied and `PUBLISH_MODE=deploy_hook`.
|
|
8
|
+
* Requires `VERCEL_DEPLOY_HOOK_URL` to be set.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DeployHookPublishTarget implements PublishTarget {
|
|
11
|
+
readonly name = "deploy-hook";
|
|
12
|
+
publish(ctx: PublishContext): Promise<PublishOutcome>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { toErrorDetail } from "../../ops/ops-engine.js";
|
|
2
|
+
import { firstUrlFromText } from "../../chat/chat-pipeline.js";
|
|
3
|
+
import { parseJsonMaybe } from "../../chat/variation-pipeline.js";
|
|
4
|
+
import { deploymentIdFromAny } from "../publish-helpers.js";
|
|
5
|
+
/**
|
|
6
|
+
* Publish by POSTing to a raw Vercel deploy hook URL. Useful when the
|
|
7
|
+
* orchestrator runs outside the git repo (e.g. in a managed environment) and
|
|
8
|
+
* cannot commit + push on its own.
|
|
9
|
+
*
|
|
10
|
+
* Selected when no `siteOrigin` is supplied and `PUBLISH_MODE=deploy_hook`.
|
|
11
|
+
* Requires `VERCEL_DEPLOY_HOOK_URL` to be set.
|
|
12
|
+
*/
|
|
13
|
+
export class DeployHookPublishTarget {
|
|
14
|
+
name = "deploy-hook";
|
|
15
|
+
async publish(ctx) {
|
|
16
|
+
const { session, scopedSession, slugs } = ctx;
|
|
17
|
+
const deployHookUrl = process.env.VERCEL_DEPLOY_HOOK_URL?.trim();
|
|
18
|
+
if (!deployHookUrl) {
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
httpStatus: 400,
|
|
23
|
+
tracker: {
|
|
24
|
+
session,
|
|
25
|
+
status: "failed",
|
|
26
|
+
startedAt: now,
|
|
27
|
+
updatedAt: now,
|
|
28
|
+
slugs,
|
|
29
|
+
vercelState: "ERROR"
|
|
30
|
+
},
|
|
31
|
+
response: { error: "VERCEL_DEPLOY_HOOK_URL is not configured" }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const hookResponse = await fetch(deployHookUrl, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "content-type": "application/json" },
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
source: "orchestrator",
|
|
40
|
+
session: scopedSession,
|
|
41
|
+
slugs,
|
|
42
|
+
publishedAt: new Date().toISOString()
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
const responseText = await hookResponse.text();
|
|
46
|
+
const responseJson = parseJsonMaybe(responseText);
|
|
47
|
+
const inspectUrl = findStringByKeys(responseJson, new Set(["inspectorUrl", "inspectUrl", "url"])) ?? firstUrlFromText(responseText);
|
|
48
|
+
const deploymentId = findStringByKeys(responseJson, new Set(["deploymentId", "id"])) ??
|
|
49
|
+
(inspectUrl ? deploymentIdFromAny(inspectUrl) : undefined) ??
|
|
50
|
+
deploymentIdFromAny(responseText);
|
|
51
|
+
const vercelStateRaw = findStringByKeys(responseJson, new Set(["state", "readyState", "status"])) ??
|
|
52
|
+
(hookResponse.ok ? "TRIGGERED" : "FAILED");
|
|
53
|
+
const vercelState = typeof vercelStateRaw === "string" ? vercelStateRaw.toUpperCase() : undefined;
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
const tracker = {
|
|
56
|
+
session,
|
|
57
|
+
status: hookResponse.ok ? "triggered" : "failed",
|
|
58
|
+
startedAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
slugs,
|
|
61
|
+
deployStatus: hookResponse.status,
|
|
62
|
+
deployResponse: responseText.slice(0, 500),
|
|
63
|
+
inspectUrl,
|
|
64
|
+
deploymentId,
|
|
65
|
+
vercelState
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
ok: hookResponse.ok,
|
|
69
|
+
httpStatus: 200,
|
|
70
|
+
tracker,
|
|
71
|
+
response: {
|
|
72
|
+
status: hookResponse.ok ? "triggered" : "failed",
|
|
73
|
+
session,
|
|
74
|
+
slugs,
|
|
75
|
+
deployStatus: hookResponse.status,
|
|
76
|
+
deployResponse: responseText.slice(0, 500),
|
|
77
|
+
inspectUrl,
|
|
78
|
+
deploymentId,
|
|
79
|
+
vercelState
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const now = new Date().toISOString();
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
httpStatus: 502,
|
|
88
|
+
tracker: {
|
|
89
|
+
session,
|
|
90
|
+
status: "failed",
|
|
91
|
+
startedAt: now,
|
|
92
|
+
updatedAt: now,
|
|
93
|
+
slugs,
|
|
94
|
+
vercelState: "ERROR"
|
|
95
|
+
},
|
|
96
|
+
response: { error: toErrorDetail(error) }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function findStringByKeys(root, wanted) {
|
|
102
|
+
if (!root || typeof root !== "object")
|
|
103
|
+
return undefined;
|
|
104
|
+
if (Array.isArray(root)) {
|
|
105
|
+
for (const item of root) {
|
|
106
|
+
const found = findStringByKeys(item, wanted);
|
|
107
|
+
if (found)
|
|
108
|
+
return found;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const obj = root;
|
|
113
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
114
|
+
if (typeof value === "string" && wanted.has(key))
|
|
115
|
+
return value;
|
|
116
|
+
if (value && typeof value === "object") {
|
|
117
|
+
const found = findStringByKeys(value, wanted);
|
|
118
|
+
if (found)
|
|
119
|
+
return found;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PublishContext, PublishOutcome, PublishTarget } from "../publish-target.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Publish by committing `apps/site/lib/published-content.json` and pushing
|
|
4
|
+
* to the configured remote branch. Works standalone or with a Vercel project
|
|
5
|
+
* wired to that branch for auto-deploy.
|
|
6
|
+
*
|
|
7
|
+
* Selected when no `siteOrigin` is supplied and `PUBLISH_MODE=git` (the
|
|
8
|
+
* default). Honors `PUBLISH_GIT_BRANCH` and `PUBLISH_GIT_STRICT` env vars.
|
|
9
|
+
*/
|
|
10
|
+
export declare class GitPublishTarget implements PublishTarget {
|
|
11
|
+
readonly name = "git";
|
|
12
|
+
publish(ctx: PublishContext): Promise<PublishOutcome>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { publishViaGit } from "../publish-helpers.js";
|
|
2
|
+
/**
|
|
3
|
+
* Publish by committing `apps/site/lib/published-content.json` and pushing
|
|
4
|
+
* to the configured remote branch. Works standalone or with a Vercel project
|
|
5
|
+
* wired to that branch for auto-deploy.
|
|
6
|
+
*
|
|
7
|
+
* Selected when no `siteOrigin` is supplied and `PUBLISH_MODE=git` (the
|
|
8
|
+
* default). Honors `PUBLISH_GIT_BRANCH` and `PUBLISH_GIT_STRICT` env vars.
|
|
9
|
+
*/
|
|
10
|
+
export class GitPublishTarget {
|
|
11
|
+
name = "git";
|
|
12
|
+
async publish(ctx) {
|
|
13
|
+
const { session, scopedSession, slugs } = ctx;
|
|
14
|
+
const result = await publishViaGit(scopedSession);
|
|
15
|
+
const now = new Date().toISOString();
|
|
16
|
+
const tracker = {
|
|
17
|
+
session,
|
|
18
|
+
status: result.status === "failed" ? "failed" : "triggered",
|
|
19
|
+
startedAt: now,
|
|
20
|
+
updatedAt: now,
|
|
21
|
+
slugs: result.slugs,
|
|
22
|
+
vercelState: result.status === "failed" ? "ERROR" : "READY",
|
|
23
|
+
deployResponse: "git_publish",
|
|
24
|
+
deployStatus: result.status === "failed" ? 500 : 200
|
|
25
|
+
};
|
|
26
|
+
if (result.status === "failed") {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
httpStatus: 400,
|
|
30
|
+
tracker,
|
|
31
|
+
response: {
|
|
32
|
+
status: "failed",
|
|
33
|
+
session,
|
|
34
|
+
slugs: result.slugs,
|
|
35
|
+
reason: result.reason,
|
|
36
|
+
details: result.details
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
httpStatus: 200,
|
|
43
|
+
tracker,
|
|
44
|
+
response: {
|
|
45
|
+
status: result.status,
|
|
46
|
+
session,
|
|
47
|
+
slugs: result.slugs,
|
|
48
|
+
branch: result.branch,
|
|
49
|
+
commitSha: "commitSha" in result ? result.commitSha : undefined,
|
|
50
|
+
message: "message" in result ? result.message : undefined,
|
|
51
|
+
vercelState: "vercelState" in result ? result.vercelState ?? "READY" : "READY"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PublishContext, PublishOutcome, PublishTarget } from "../publish-target.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Publish by POSTing to the site's `/api/editor/publish` contract endpoint.
|
|
4
|
+
*
|
|
5
|
+
* This is the primary production flow: the remote site implements the publish
|
|
6
|
+
* contract from `@avocadostudio-ai/site-sdk`, receives pages + siteConfig +
|
|
7
|
+
* inline image assets, and writes them wherever it stores content (CMS, json
|
|
8
|
+
* file, database, etc.). The orchestrator does not touch the site's storage.
|
|
9
|
+
*
|
|
10
|
+
* For the legacy `avocado-stories` siteId this target also records a git
|
|
11
|
+
* snapshot into `apps/site/lib/published-content.json` so version history can
|
|
12
|
+
* find it. Non-legacy sites skip the snapshot — their publish handler owns
|
|
13
|
+
* durability.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SiteContractPublishTarget implements PublishTarget {
|
|
16
|
+
readonly name = "site-contract";
|
|
17
|
+
canHandle(ctx: PublishContext): boolean;
|
|
18
|
+
publish(ctx: PublishContext): Promise<PublishOutcome>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { normalizeSiteId, isLegacySiteId } from "../../state/session-state.js";
|
|
2
|
+
import { collectInlineAssets, recordPublishSnapshot } from "../publish-helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Publish by POSTing to the site's `/api/editor/publish` contract endpoint.
|
|
5
|
+
*
|
|
6
|
+
* This is the primary production flow: the remote site implements the publish
|
|
7
|
+
* contract from `@avocadostudio-ai/site-sdk`, receives pages + siteConfig +
|
|
8
|
+
* inline image assets, and writes them wherever it stores content (CMS, json
|
|
9
|
+
* file, database, etc.). The orchestrator does not touch the site's storage.
|
|
10
|
+
*
|
|
11
|
+
* For the legacy `avocado-stories` siteId this target also records a git
|
|
12
|
+
* snapshot into `apps/site/lib/published-content.json` so version history can
|
|
13
|
+
* find it. Non-legacy sites skip the snapshot — their publish handler owns
|
|
14
|
+
* durability.
|
|
15
|
+
*/
|
|
16
|
+
export class SiteContractPublishTarget {
|
|
17
|
+
name = "site-contract";
|
|
18
|
+
canHandle(ctx) {
|
|
19
|
+
return typeof ctx.siteOrigin === "string" && ctx.siteOrigin.length > 0;
|
|
20
|
+
}
|
|
21
|
+
async publish(ctx) {
|
|
22
|
+
const { session, scopedSession, siteId, siteOrigin, pages, slugs, siteConfig, generatedImageDir, logger } = ctx;
|
|
23
|
+
if (!siteOrigin) {
|
|
24
|
+
return failed(session, slugs, 400, "siteOrigin is required for site-contract target");
|
|
25
|
+
}
|
|
26
|
+
const assets = await collectInlineAssets(pages, generatedImageDir);
|
|
27
|
+
const publishTokenValue = process.env.PUBLISH_TOKEN?.trim();
|
|
28
|
+
let siteRes;
|
|
29
|
+
try {
|
|
30
|
+
siteRes = await fetch(`${siteOrigin}/api/editor/publish`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"content-type": "application/json",
|
|
34
|
+
...(publishTokenValue ? { "x-publish-token": publishTokenValue } : {})
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
pages,
|
|
38
|
+
siteConfig,
|
|
39
|
+
session: scopedSession,
|
|
40
|
+
publishedAt: new Date().toISOString(),
|
|
41
|
+
...(Object.keys(assets).length > 0 ? { assets } : {})
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const detail = err instanceof Error ? err.message : "fetch failed";
|
|
47
|
+
return failed(session, slugs, 502, `Site unreachable at ${siteOrigin}/api/editor/publish: ${detail}`);
|
|
48
|
+
}
|
|
49
|
+
if (siteRes.status === 404) {
|
|
50
|
+
return failed(session, slugs, 400, `Site does not implement the publish contract. POST ${siteOrigin}/api/editor/publish returned 404.`);
|
|
51
|
+
}
|
|
52
|
+
const siteResult = (await siteRes.json());
|
|
53
|
+
const ok = siteRes.ok && siteResult.ok !== false;
|
|
54
|
+
const now = new Date().toISOString();
|
|
55
|
+
const tracker = {
|
|
56
|
+
session,
|
|
57
|
+
status: ok ? "triggered" : "failed",
|
|
58
|
+
startedAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
slugs,
|
|
61
|
+
vercelState: ok ? "READY" : "ERROR",
|
|
62
|
+
deployResponse: "site_contract",
|
|
63
|
+
deployStatus: siteRes.status
|
|
64
|
+
};
|
|
65
|
+
if (!ok) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
httpStatus: 400,
|
|
69
|
+
tracker,
|
|
70
|
+
response: {
|
|
71
|
+
status: "failed",
|
|
72
|
+
session,
|
|
73
|
+
slugs,
|
|
74
|
+
reason: siteResult.error ?? "site publish failed"
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Record snapshot in git only for the default site (json-file based).
|
|
79
|
+
// CMS sites (sanity, contentful, strapi) publish to their CMS directly;
|
|
80
|
+
// writing their pages to apps/site/lib/published-content.json would
|
|
81
|
+
// overwrite the avocado-stories content.
|
|
82
|
+
const snapshotCommit = isLegacySiteId(normalizeSiteId(siteId))
|
|
83
|
+
? await recordPublishSnapshot(scopedSession, pages, logger, siteConfig)
|
|
84
|
+
: undefined;
|
|
85
|
+
const publishedSlugs = siteResult.slugs ?? slugs;
|
|
86
|
+
const pageNames = publishedSlugs.map((s) => (s === "/" ? "Home" : s.replace(/^\//, "")));
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
httpStatus: 200,
|
|
90
|
+
tracker,
|
|
91
|
+
response: {
|
|
92
|
+
status: "ready",
|
|
93
|
+
session,
|
|
94
|
+
slugs: publishedSlugs,
|
|
95
|
+
vercelState: "READY",
|
|
96
|
+
commitSha: snapshotCommit?.slice(0, 12),
|
|
97
|
+
message: `Published ${pageNames.join(", ")} to site.`
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function failed(session, slugs, httpStatus, reason) {
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
httpStatus,
|
|
107
|
+
tracker: {
|
|
108
|
+
session,
|
|
109
|
+
status: "failed",
|
|
110
|
+
startedAt: now,
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
slugs,
|
|
113
|
+
vercelState: "ERROR",
|
|
114
|
+
deployResponse: "site_contract",
|
|
115
|
+
deployStatus: httpStatus
|
|
116
|
+
},
|
|
117
|
+
response: {
|
|
118
|
+
status: "failed",
|
|
119
|
+
session,
|
|
120
|
+
slugs,
|
|
121
|
+
reason
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PageDoc, SiteConfig } from "@avocadostudio-ai/shared";
|
|
2
|
+
export interface ContentSource {
|
|
3
|
+
getPage(session: string, slug: string): Promise<PageDoc | null>;
|
|
4
|
+
getSessionPages(session: string): Promise<PageDoc[]>;
|
|
5
|
+
getSlugs(session: string): Promise<string[]>;
|
|
6
|
+
getSiteConfig(session: string): Promise<SiteConfig>;
|
|
7
|
+
setPage(session: string, page: PageDoc): Promise<void>;
|
|
8
|
+
removePage(session: string, slug: string): Promise<void>;
|
|
9
|
+
setSiteConfig(session: string, config: SiteConfig): Promise<void>;
|
|
10
|
+
getVersion(session: string): Promise<number>;
|
|
11
|
+
bumpVersion(session: string): Promise<number>;
|
|
12
|
+
pushUndo(session: string, slug: string, snapshot: PageDoc | null): Promise<void>;
|
|
13
|
+
popUndo(session: string, slug: string): Promise<PageDoc | null | undefined>;
|
|
14
|
+
pushRedo(session: string, slug: string, snapshot: PageDoc | null): Promise<void>;
|
|
15
|
+
popRedo(session: string, slug: string): Promise<PageDoc | null | undefined>;
|
|
16
|
+
clearRedo(session: string, slug: string): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PageDoc, SiteConfig } from "@avocadostudio-ai/shared";
|
|
2
|
+
import type { ContentSource } from "./content-source.ts";
|
|
3
|
+
export declare class InMemoryContentSource implements ContentSource {
|
|
4
|
+
getPage(session: string, slug: string): Promise<PageDoc | null>;
|
|
5
|
+
getSessionPages(session: string): Promise<PageDoc[]>;
|
|
6
|
+
getSlugs(session: string): Promise<string[]>;
|
|
7
|
+
getSiteConfig(session: string): Promise<{
|
|
8
|
+
name?: string | undefined;
|
|
9
|
+
logo?: string | undefined;
|
|
10
|
+
purpose?: string | undefined;
|
|
11
|
+
tone?: string | undefined;
|
|
12
|
+
constraints?: string[] | undefined;
|
|
13
|
+
navLabels?: Record<string, string> | undefined;
|
|
14
|
+
navGroups?: Record<string, string[]> | undefined;
|
|
15
|
+
themeOverrides?: Record<string, string> | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
setPage(session: string, page: PageDoc): Promise<void>;
|
|
18
|
+
removePage(session: string, slug: string): Promise<void>;
|
|
19
|
+
setSiteConfig(session: string, config: SiteConfig): Promise<void>;
|
|
20
|
+
getVersion(session: string): Promise<number>;
|
|
21
|
+
bumpVersion(session: string): Promise<number>;
|
|
22
|
+
pushUndo(session: string, slug: string, snapshot: PageDoc | null): Promise<void>;
|
|
23
|
+
popUndo(session: string, slug: string): Promise<PageDoc | null | undefined>;
|
|
24
|
+
pushRedo(session: string, slug: string, snapshot: PageDoc | null): Promise<void>;
|
|
25
|
+
popRedo(session: string, slug: string): Promise<PageDoc | null | undefined>;
|
|
26
|
+
clearRedo(session: string, slug: string): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { getSessionDraft, getPage as _getPage, getSessionPages as _getSessionPages, setPage as _setPage, removePage as _removePage, getSiteConfig as _getSiteConfig, setSiteConfig as _setSiteConfig, bumpVersion as _bumpVersion, pushUndo as _pushUndo, pushCappedHistory, orderSlugsHomeFirst, versions, historyUndo, historyRedo, getHistoryMap } from "./session-state.js";
|
|
2
|
+
export class InMemoryContentSource {
|
|
3
|
+
async getPage(session, slug) {
|
|
4
|
+
return _getPage(session, slug);
|
|
5
|
+
}
|
|
6
|
+
async getSessionPages(session) {
|
|
7
|
+
return _getSessionPages(session);
|
|
8
|
+
}
|
|
9
|
+
async getSlugs(session) {
|
|
10
|
+
return orderSlugsHomeFirst(Array.from(getSessionDraft(session).keys()));
|
|
11
|
+
}
|
|
12
|
+
async getSiteConfig(session) {
|
|
13
|
+
return _getSiteConfig(session);
|
|
14
|
+
}
|
|
15
|
+
async setPage(session, page) {
|
|
16
|
+
_setPage(session, page);
|
|
17
|
+
}
|
|
18
|
+
async removePage(session, slug) {
|
|
19
|
+
_removePage(session, slug);
|
|
20
|
+
}
|
|
21
|
+
async setSiteConfig(session, config) {
|
|
22
|
+
_setSiteConfig(session, config);
|
|
23
|
+
}
|
|
24
|
+
async getVersion(session) {
|
|
25
|
+
return versions.get(session) ?? 0;
|
|
26
|
+
}
|
|
27
|
+
async bumpVersion(session) {
|
|
28
|
+
return _bumpVersion(session);
|
|
29
|
+
}
|
|
30
|
+
async pushUndo(session, slug, snapshot) {
|
|
31
|
+
_pushUndo(session, slug, snapshot);
|
|
32
|
+
}
|
|
33
|
+
async popUndo(session, slug) {
|
|
34
|
+
const undoMap = getHistoryMap(historyUndo, session);
|
|
35
|
+
return undoMap.get(slug)?.pop();
|
|
36
|
+
}
|
|
37
|
+
async pushRedo(session, slug, snapshot) {
|
|
38
|
+
const redoMap = getHistoryMap(historyRedo, session);
|
|
39
|
+
const list = redoMap.get(slug) ?? [];
|
|
40
|
+
pushCappedHistory(list, snapshot);
|
|
41
|
+
redoMap.set(slug, list);
|
|
42
|
+
}
|
|
43
|
+
async popRedo(session, slug) {
|
|
44
|
+
const redoMap = getHistoryMap(historyRedo, session);
|
|
45
|
+
return redoMap.get(slug)?.pop();
|
|
46
|
+
}
|
|
47
|
+
async clearRedo(session, slug) {
|
|
48
|
+
const redoMap = getHistoryMap(historyRedo, session);
|
|
49
|
+
redoMap.set(slug, []);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire an exclusive lock for `session`. The returned release function
|
|
3
|
+
* MUST be called when done — wrap the critical section in try/finally.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const release = await acquireSessionLock(session)
|
|
7
|
+
* try { … } finally { release() }
|
|
8
|
+
*/
|
|
9
|
+
export declare function acquireSessionLock(session: string): Promise<() => void>;
|
|
10
|
+
/**
|
|
11
|
+
* Remove stale lock entries (e.g. on session cleanup).
|
|
12
|
+
*/
|
|
13
|
+
export declare function clearSessionLock(session: string): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Lightweight per-session async mutex using promise chains.
|
|
3
|
+
// Serializes applyOpsAtomically() calls within a session to prevent
|
|
4
|
+
// concurrent read-modify-write races (e.g. multi-tab, fast typing).
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const locks = new Map();
|
|
7
|
+
/**
|
|
8
|
+
* Acquire an exclusive lock for `session`. The returned release function
|
|
9
|
+
* MUST be called when done — wrap the critical section in try/finally.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const release = await acquireSessionLock(session)
|
|
13
|
+
* try { … } finally { release() }
|
|
14
|
+
*/
|
|
15
|
+
export function acquireSessionLock(session) {
|
|
16
|
+
const prev = locks.get(session) ?? Promise.resolve();
|
|
17
|
+
let release;
|
|
18
|
+
const next = new Promise((resolve) => {
|
|
19
|
+
release = resolve;
|
|
20
|
+
});
|
|
21
|
+
locks.set(session, next);
|
|
22
|
+
return prev.then(() => release);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Remove stale lock entries (e.g. on session cleanup).
|
|
26
|
+
*/
|
|
27
|
+
export function clearSessionLock(session) {
|
|
28
|
+
locks.delete(session);
|
|
29
|
+
}
|