@treeseed/core 0.4.13 → 0.5.2

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 (81) hide show
  1. package/dist/agents/adapters/notification.d.ts +16 -1
  2. package/dist/agents/adapters/notification.js +31 -1
  3. package/dist/agents/adapters/research.d.ts +13 -1
  4. package/dist/agents/adapters/research.js +35 -1
  5. package/dist/agents/contracts/run.d.ts +1 -0
  6. package/dist/agents/kernel/agent-kernel.d.ts +2 -2
  7. package/dist/agents/kernel/agent-kernel.js +10 -3
  8. package/dist/agents/kernel/trigger-resolver.d.ts +1 -0
  9. package/dist/agents/kernel/trigger-resolver.js +5 -1
  10. package/dist/agents/runtime-types.d.ts +1 -0
  11. package/dist/api/app.js +10 -0
  12. package/dist/api/auth/d1-store.js +5 -0
  13. package/dist/api/auth/memory-provider.js +6 -1
  14. package/dist/api/auth/rbac.d.ts +2 -2
  15. package/dist/api/auth/rbac.js +2 -0
  16. package/dist/api/capabilities.d.ts +9 -0
  17. package/dist/api/capabilities.js +33 -0
  18. package/dist/api/operations-routes.d.ts +4 -0
  19. package/dist/api/operations-routes.js +49 -1
  20. package/dist/api/project-routes.d.ts +8 -0
  21. package/dist/api/project-routes.js +586 -0
  22. package/dist/api/types.d.ts +7 -0
  23. package/dist/components/site/NotesList.astro +13 -2
  24. package/dist/components/site/PublishedContentBody.astro +5 -0
  25. package/dist/content.js +77 -9
  26. package/dist/dev.d.ts +2 -2
  27. package/dist/dev.js +0 -15
  28. package/dist/env.yaml +39 -26
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +7 -1
  31. package/dist/launch.d.ts +3 -0
  32. package/dist/launch.js +8 -0
  33. package/dist/layouts/AuthoredEntryLayout.astro +76 -28
  34. package/dist/layouts/ProfileLayout.astro +9 -5
  35. package/dist/middleware.js +11 -0
  36. package/dist/pages/[slug].astro +10 -6
  37. package/dist/pages/agents/[slug].astro +17 -7
  38. package/dist/pages/agents/index.astro +2 -1
  39. package/dist/pages/books/[slug].astro +10 -5
  40. package/dist/pages/books/index.astro +4 -1
  41. package/dist/pages/decisions/[slug].astro +73 -0
  42. package/dist/pages/decisions/index.astro +47 -0
  43. package/dist/pages/docs-runtime/[...slug].astro +102 -0
  44. package/dist/pages/docs-runtime/index.astro +89 -0
  45. package/dist/pages/feed.xml.js +2 -1
  46. package/dist/pages/index.astro +160 -16
  47. package/dist/pages/notes/[slug].astro +10 -5
  48. package/dist/pages/notes/index.astro +6 -3
  49. package/dist/pages/objectives/[slug].astro +27 -9
  50. package/dist/pages/objectives/index.astro +19 -2
  51. package/dist/pages/people/[slug].astro +17 -7
  52. package/dist/pages/people/index.astro +2 -1
  53. package/dist/pages/proposals/[slug].astro +72 -0
  54. package/dist/pages/proposals/index.astro +47 -0
  55. package/dist/pages/questions/[slug].astro +27 -9
  56. package/dist/pages/questions/index.astro +19 -2
  57. package/dist/scripts/dev-platform.js +0 -1
  58. package/dist/scripts/release-verify.js +29 -2
  59. package/dist/scripts/tenant-build.js +4 -1
  60. package/dist/scripts/tenant-check.js +4 -1
  61. package/dist/services/agents.d.ts +1 -12
  62. package/dist/services/agents.js +28 -9
  63. package/dist/services/index.d.ts +0 -2
  64. package/dist/services/index.js +0 -6
  65. package/dist/services/manager.d.ts +4 -4
  66. package/dist/services/manager.js +123 -50
  67. package/dist/services/workday-report.d.ts +3 -3
  68. package/dist/services/workday-start.d.ts +3 -3
  69. package/dist/services/worker-capacity.d.ts +58 -0
  70. package/dist/services/worker-capacity.js +208 -0
  71. package/dist/services/worker.js +70 -13
  72. package/dist/site.js +18 -5
  73. package/dist/tenant/runtime-config.js +8 -1
  74. package/dist/utils/hub-content.js +14 -0
  75. package/dist/utils/published-content.js +13 -0
  76. package/dist/utils/site-config.js +20 -0
  77. package/dist/utils/site-content-runtime.js +185 -0
  78. package/dist/utils/web-cache.js +149 -0
  79. package/package.json +11 -6
  80. package/scripts/verify-driver.mjs +34 -0
  81. package/templates/github/deploy.workflow.yml +11 -1
@@ -0,0 +1,208 @@
1
+ import { createWorkerPoolScaler } from "./worker-pool-scaler.js";
2
+ function integerFromEnv(name, fallback) {
3
+ const value = process.env[name];
4
+ if (!value) return fallback;
5
+ const parsed = Number.parseInt(value, 10);
6
+ return Number.isFinite(parsed) ? parsed : fallback;
7
+ }
8
+ function envValue(name) {
9
+ const value = process.env[name]?.trim();
10
+ return value ? value : "";
11
+ }
12
+ function parseJson(value, fallback) {
13
+ if (typeof value !== "string" || !value.trim()) {
14
+ return fallback;
15
+ }
16
+ try {
17
+ return JSON.parse(value);
18
+ } catch {
19
+ return fallback;
20
+ }
21
+ }
22
+ function readNumber(record, ...keys) {
23
+ for (const key of keys) {
24
+ const value = record[key];
25
+ if (typeof value === "number" && Number.isFinite(value)) {
26
+ return value;
27
+ }
28
+ if (typeof value === "string" && value.trim()) {
29
+ const parsed = Number.parseFloat(value);
30
+ if (Number.isFinite(parsed)) {
31
+ return parsed;
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ function asRecords(value) {
38
+ return Array.isArray(value) ? value : [];
39
+ }
40
+ function resolveAutoscalePolicyFromEnv() {
41
+ return {
42
+ minWorkers: integerFromEnv("TREESEED_AGENT_POOL_MIN_WORKERS", 0),
43
+ maxWorkers: integerFromEnv("TREESEED_AGENT_POOL_MAX_WORKERS", 1),
44
+ targetQueueDepth: Math.max(1, integerFromEnv("TREESEED_AGENT_POOL_TARGET_QUEUE_DEPTH", 1)),
45
+ cooldownSeconds: Math.max(0, integerFromEnv("TREESEED_AGENT_POOL_COOLDOWN_SECONDS", 60))
46
+ };
47
+ }
48
+ function resolveWorkerPoolIdentityFromEnv(projectId) {
49
+ const environment = envValue("TREESEED_DEPLOY_ENVIRONMENT") || (process.env.NODE_ENV === "production" ? "prod" : "local");
50
+ const resolvedProjectId = projectId?.trim() || envValue("TREESEED_PROJECT_ID") || "treeseed-market";
51
+ return {
52
+ projectId: resolvedProjectId,
53
+ environment,
54
+ poolName: envValue("TREESEED_AGENT_POOL_NAME") || `${resolvedProjectId}-${environment}`
55
+ };
56
+ }
57
+ function computeDesiredWorkerCount(autoscale, metrics) {
58
+ const { minWorkers, maxWorkers, targetQueueDepth } = autoscale;
59
+ if (metrics.queuedCount <= 0 && metrics.activeLeases <= 0) {
60
+ return minWorkers;
61
+ }
62
+ const requiredByQueue = Math.ceil(metrics.queuedCount / Math.max(1, targetQueueDepth));
63
+ const minimumActive = metrics.activeLeases > 0 ? 1 : 0;
64
+ return Math.max(minWorkers, Math.min(maxWorkers, Math.max(requiredByQueue, minimumActive)));
65
+ }
66
+ function applyScaleCooldown(autoscale, latestDecision, nextDesired, now) {
67
+ if (!latestDecision) {
68
+ return nextDesired;
69
+ }
70
+ if (nextDesired >= latestDecision.desiredWorkers) {
71
+ return nextDesired;
72
+ }
73
+ const cooldownMs = Math.max(0, autoscale.cooldownSeconds) * 1e3;
74
+ if (cooldownMs === 0) {
75
+ return nextDesired;
76
+ }
77
+ const lastChangedAt = new Date(latestDecision.createdAt);
78
+ if (!Number.isFinite(lastChangedAt.valueOf())) {
79
+ return nextDesired;
80
+ }
81
+ return now.valueOf() - lastChangedAt.valueOf() < cooldownMs ? latestDecision.desiredWorkers : nextDesired;
82
+ }
83
+ function applyInteractiveWakeUpOverride(options) {
84
+ if (options.priorityClass !== "interactive") {
85
+ return options.desiredWorkers;
86
+ }
87
+ if (options.queuedCount <= 0 || options.currentWorkers > 0 || options.desiredWorkers > 0) {
88
+ return options.desiredWorkers;
89
+ }
90
+ return 1;
91
+ }
92
+ async function collectTaskMetrics(sdk, workDayId) {
93
+ const [queuedEnvelope, activeEnvelope] = await Promise.all([
94
+ sdk.searchTasks({
95
+ workDayId: workDayId ?? void 0,
96
+ limit: 500,
97
+ state: ["pending", "queued"]
98
+ }),
99
+ sdk.searchTasks({
100
+ workDayId: workDayId ?? void 0,
101
+ limit: 500,
102
+ state: ["claimed", "running"]
103
+ })
104
+ ]);
105
+ const queuedTasks = asRecords(queuedEnvelope.payload);
106
+ const activeTasks = asRecords(activeEnvelope.payload);
107
+ const queuedCredits = queuedTasks.reduce((total, task) => {
108
+ const payload = parseJson(String(task.payloadJson ?? "{}"), {});
109
+ const credits = readNumber(payload, "estimatedCredits") ?? 1;
110
+ return total + credits;
111
+ }, 0);
112
+ return {
113
+ queuedTasks,
114
+ activeTasks,
115
+ queuedCount: queuedTasks.length,
116
+ activeLeases: activeTasks.length,
117
+ queuedCredits
118
+ };
119
+ }
120
+ async function enqueueTaskAndEnsureCapacity(sdk, request) {
121
+ await request.enqueueTask(sdk, {
122
+ taskId: request.taskId,
123
+ queueName: request.queueName,
124
+ deliveryDelaySeconds: request.deliveryDelaySeconds,
125
+ actor: request.actor
126
+ });
127
+ const now = request.now ?? /* @__PURE__ */ new Date();
128
+ const identity = request.identity ?? resolveWorkerPoolIdentityFromEnv(request.projectId);
129
+ const autoscale = request.autoscale ?? resolveAutoscalePolicyFromEnv();
130
+ const scaler = request.scaler ?? createWorkerPoolScaler();
131
+ const metrics = await collectTaskMetrics(sdk);
132
+ const latestScaleDecision = await sdk.getLatestScaleDecision(identity.projectId, identity.environment, identity.poolName);
133
+ const currentWorkers = Math.max(0, Number(latestScaleDecision.payload?.desiredWorkers ?? 0));
134
+ const rawDesiredWorkers = computeDesiredWorkerCount(autoscale, metrics);
135
+ const desiredAfterCooldown = applyScaleCooldown(autoscale, latestScaleDecision.payload, rawDesiredWorkers, now);
136
+ const desiredWorkers = applyInteractiveWakeUpOverride({
137
+ priorityClass: request.priorityClass ?? "background",
138
+ queuedCount: metrics.queuedCount,
139
+ currentWorkers,
140
+ desiredWorkers: desiredAfterCooldown
141
+ });
142
+ const coldStartTriggered = (request.priorityClass ?? "background") === "interactive" && currentWorkers <= 0 && desiredWorkers > 0;
143
+ const scaleReason = desiredWorkers !== desiredAfterCooldown ? "interactive_cold_start" : coldStartTriggered ? "interactive_cold_start" : desiredWorkers !== rawDesiredWorkers ? "cooldown_hold" : request.priorityClass === "interactive" ? "interactive_enqueue" : "enqueue";
144
+ const recordedScaleDecision = await sdk.recordScaleDecision({
145
+ projectId: identity.projectId,
146
+ environment: identity.environment,
147
+ poolName: identity.poolName,
148
+ workDayId: null,
149
+ desiredWorkers,
150
+ observedQueueDepth: metrics.queuedCount,
151
+ observedActiveLeases: metrics.activeLeases,
152
+ reason: scaleReason,
153
+ metadata: {
154
+ priorityClass: request.priorityClass ?? "background",
155
+ taskId: request.taskId
156
+ }
157
+ });
158
+ const scaleDecision = recordedScaleDecision.payload ?? {
159
+ projectId: identity.projectId,
160
+ environment: identity.environment,
161
+ poolName: identity.poolName,
162
+ workDayId: null,
163
+ desiredWorkers,
164
+ observedQueueDepth: metrics.queuedCount,
165
+ observedActiveLeases: metrics.activeLeases,
166
+ reason: scaleReason,
167
+ metadata: {
168
+ priorityClass: request.priorityClass ?? "background",
169
+ taskId: request.taskId
170
+ },
171
+ createdAt: now.toISOString()
172
+ };
173
+ let scaleResult;
174
+ try {
175
+ scaleResult = await scaler.scale(scaleDecision);
176
+ } catch (error) {
177
+ scaleResult = {
178
+ applied: false,
179
+ provider: "error",
180
+ desiredWorkers,
181
+ metadata: {
182
+ reason: "scale_failed",
183
+ error: error instanceof Error ? error.message : String(error)
184
+ }
185
+ };
186
+ }
187
+ return {
188
+ ok: true,
189
+ taskId: request.taskId,
190
+ queued: true,
191
+ workerState: currentWorkers > 0 ? "warm" : desiredWorkers > 0 ? "cold_starting" : "warm",
192
+ desiredWorkers,
193
+ scaleApplied: scaleResult.applied,
194
+ scaleReason,
195
+ scaleDecision,
196
+ scaleResult,
197
+ metrics
198
+ };
199
+ }
200
+ export {
201
+ applyInteractiveWakeUpOverride,
202
+ applyScaleCooldown,
203
+ collectTaskMetrics,
204
+ computeDesiredWorkerCount,
205
+ enqueueTaskAndEnsureCapacity,
206
+ resolveAutoscalePolicyFromEnv,
207
+ resolveWorkerPoolIdentityFromEnv
208
+ };
@@ -1,10 +1,70 @@
1
1
  #!/usr/bin/env node
2
2
  import { fileURLToPath } from "node:url";
3
- import { buildTaskContext, createQueueClient, createServiceSdk, resolveWorkerConfig } from "./common.js";
3
+ import { AgentKernel } from "../agents/kernel/agent-kernel.js";
4
+ import { buildTaskContext, createQueueClient, createServiceSdk, resolveServiceRepoRoot, resolveWorkerConfig } from "./common.js";
5
+ function parseTaskPayload(task) {
6
+ const raw = typeof task?.payloadJson === "string" ? task.payloadJson : "{}";
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ async function executeQueuedTask(options) {
14
+ const context = await buildTaskContext(options.sdk, options.taskId);
15
+ const task = context.task;
16
+ const payload = parseTaskPayload(task);
17
+ const executionKind = typeof payload.executionKind === "string" ? payload.executionKind : null;
18
+ if (executionKind === "workflow_dispatch" || executionKind === "sdk_dispatch") {
19
+ const namespace = typeof payload.namespace === "string" ? payload.namespace : "workflow";
20
+ const operation = typeof payload.operation === "string" ? payload.operation : "";
21
+ if (!operation) {
22
+ throw new Error(`Task ${options.taskId} does not define a dispatch operation.`);
23
+ }
24
+ const input = payload.input && typeof payload.input === "object" ? payload.input : {};
25
+ const result = await options.sdk.dispatch({
26
+ namespace,
27
+ operation,
28
+ input,
29
+ preferredMode: "prefer_local"
30
+ });
31
+ return {
32
+ workerId: options.workerId,
33
+ queueAttempt: options.queueAttempt,
34
+ executionKind,
35
+ namespace,
36
+ operation,
37
+ result,
38
+ summary: {
39
+ status: "completed",
40
+ workerId: options.workerId,
41
+ summary: `Executed ${namespace}:${operation}`
42
+ }
43
+ };
44
+ }
45
+ const agentSlug = typeof payload.agentSlug === "string" && payload.agentSlug ? payload.agentSlug : typeof context.agent?.slug === "string" && context.agent.slug ? context.agent.slug : typeof task?.agentId === "string" && task.agentId ? task.agentId : typeof task?.agent_id === "string" && task.agent_id ? task.agent_id : "";
46
+ if (!agentSlug) {
47
+ throw new Error(`Task ${options.taskId} does not resolve to an agent slug.`);
48
+ }
49
+ const invocation = payload.invocation && typeof payload.invocation === "object" ? payload.invocation : null;
50
+ const agentResult = await options.kernel.runAgent(agentSlug, invocation ? "manual" : "auto", invocation);
51
+ return {
52
+ workerId: options.workerId,
53
+ queueAttempt: options.queueAttempt,
54
+ agentSlug,
55
+ result: agentResult,
56
+ summary: {
57
+ status: agentResult.status,
58
+ workerId: options.workerId,
59
+ summary: agentResult.summary
60
+ }
61
+ };
62
+ }
4
63
  async function runWorkerCycle() {
5
64
  const sdk = createServiceSdk();
6
65
  const queue = createQueueClient();
7
66
  const config = resolveWorkerConfig();
67
+ const kernel = new AgentKernel(sdk, resolveServiceRepoRoot());
8
68
  if (!queue) {
9
69
  if (process.env.TREESEED_LOCAL_DEV_MODE?.trim()) {
10
70
  return { ok: true, processed: 0, idle: true, reason: "queue_unconfigured" };
@@ -27,9 +87,6 @@ async function runWorkerCycle() {
27
87
  leaseSeconds: config.leaseSeconds,
28
88
  actor: "worker"
29
89
  });
30
- const context = await buildTaskContext(sdk, message.body.taskId);
31
- const task = context.task;
32
- const payload = task && typeof task.payloadJson === "string" ? JSON.parse(task.payloadJson) : {};
33
90
  await sdk.recordTaskProgress({
34
91
  id: message.body.taskId,
35
92
  workerId: config.workerId,
@@ -40,17 +97,17 @@ async function runWorkerCycle() {
40
97
  },
41
98
  actor: "worker"
42
99
  });
100
+ const output = await executeQueuedTask({
101
+ sdk,
102
+ kernel,
103
+ taskId: message.body.taskId,
104
+ workerId: config.workerId,
105
+ queueAttempt: message.attempts
106
+ });
43
107
  await sdk.completeTask({
44
108
  id: message.body.taskId,
45
- output: {
46
- workerId: config.workerId,
47
- queueAttempt: message.attempts,
48
- payload
49
- },
50
- summary: {
51
- status: "completed",
52
- workerId: config.workerId
53
- },
109
+ output,
110
+ summary: output.summary,
54
111
  actor: "worker"
55
112
  });
56
113
  await queue.ack([message.leaseId]);
package/dist/site.js CHANGED
@@ -11,6 +11,7 @@ import { buildTenantBookRuntime } from "@treeseed/sdk/platform/books-data";
11
11
  import { getStarlightSidebarConfigFromRuntime } from "./utils/starlight-nav.js";
12
12
  import { buildTenantThemeCss } from "./utils/theme.js";
13
13
  import { loadTreeseedDeployConfig } from "@treeseed/sdk/platform/deploy-config";
14
+ import { getTreeseedContentServingMode } from "@treeseed/sdk/platform/deploy-runtime";
14
15
  import { loadTreeseedPluginRuntime } from "@treeseed/sdk/platform/plugins";
15
16
  import {
16
17
  buildTreeseedSiteLayers,
@@ -48,8 +49,12 @@ const PACKAGE_ROUTE_ENTRIES = [
48
49
  { pattern: "/notes/[slug]", resourcePath: "pages/notes/[slug].astro", model: "notes" },
49
50
  { pattern: "/objectives", resourcePath: "pages/objectives/index.astro", model: "objectives" },
50
51
  { pattern: "/objectives/[slug]", resourcePath: "pages/objectives/[slug].astro", model: "objectives" },
52
+ { pattern: "/proposals", resourcePath: "pages/proposals/index.astro", model: "proposals" },
53
+ { pattern: "/proposals/[slug]", resourcePath: "pages/proposals/[slug].astro", model: "proposals" },
51
54
  { pattern: "/people", resourcePath: "pages/people/index.astro", model: "people" },
52
55
  { pattern: "/people/[slug]", resourcePath: "pages/people/[slug].astro", model: "people" },
56
+ { pattern: "/decisions", resourcePath: "pages/decisions/index.astro", model: "decisions" },
57
+ { pattern: "/decisions/[slug]", resourcePath: "pages/decisions/[slug].astro", model: "decisions" },
53
58
  { pattern: "/questions", resourcePath: "pages/questions/index.astro", model: "questions" },
54
59
  { pattern: "/questions/[slug]", resourcePath: "pages/questions/[slug].astro", model: "questions" }
55
60
  ];
@@ -223,15 +228,20 @@ function createTreeseedSite(tenantConfig, { starlight }) {
223
228
  siteConfig,
224
229
  deployConfig
225
230
  });
226
- const resolvedRoutes = [...PACKAGE_ROUTE_ENTRIES, ...siteExtensions.routes].map(
227
- (route) => resolveRouteEntry(route, siteLayers)
228
- );
229
231
  const injectedTenantConfig = JSON.stringify(tenantConfig);
230
232
  const injectedProjectRoot = JSON.stringify(projectRoot);
231
233
  const injectedSiteConfig = JSON.stringify(siteConfig);
232
234
  const injectedDeployConfig = JSON.stringify(deployConfig);
233
235
  const resolvedGlobalCss = resolveTreeseedStyleEntrypoint(siteLayers, "styles/global.css");
234
236
  const serverRendered = deployConfig.surfaces?.web?.provider === "cloudflare" || deployConfig.providers.deploy === "cloudflare";
237
+ const publishedRuntime = getTreeseedContentServingMode() === "published_runtime";
238
+ const packageRoutes = [
239
+ ...PACKAGE_ROUTE_ENTRIES,
240
+ ...docsRendered && publishedRuntime ? [
241
+ { pattern: "/knowledge", resourcePath: "pages/docs-runtime/index.astro", model: "docs" },
242
+ { pattern: "/knowledge/[...slug]", resourcePath: "pages/docs-runtime/[...slug].astro", model: "docs" }
243
+ ] : []
244
+ ];
235
245
  return defineConfig({
236
246
  adapter: serverRendered ? cloudflare({ imageService: "compile" }) : void 0,
237
247
  output: serverRendered ? "server" : "static",
@@ -292,8 +302,11 @@ function createTreeseedSite(tenantConfig, { starlight }) {
292
302
  }
293
303
  },
294
304
  integrations: [
295
- createTreeseedRoutesIntegration(tenantConfig, resolvedRoutes),
296
- ...docsRendered ? [starlight({
305
+ createTreeseedRoutesIntegration(tenantConfig, [
306
+ ...packageRoutes.map((route) => resolveRouteEntry(route, siteLayers)),
307
+ ...siteExtensions.routes.map((route) => resolveRouteEntry(route, siteLayers))
308
+ ]),
309
+ ...docsRendered && !publishedRuntime ? [starlight({
297
310
  prerender: !serverRendered,
298
311
  pagefind: !serverRendered,
299
312
  disable404Route: true,
@@ -15,6 +15,8 @@ function fallbackTenantConfig(projectRoot) {
15
15
  notes: resolve(projectRoot, "src/content/notes"),
16
16
  questions: resolve(projectRoot, "src/content/questions"),
17
17
  objectives: resolve(projectRoot, "src/content/objectives"),
18
+ proposals: resolve(projectRoot, "src/content/proposals"),
19
+ decisions: resolve(projectRoot, "src/content/decisions"),
18
20
  people: resolve(projectRoot, "src/content/people"),
19
21
  agents: resolve(projectRoot, "src/content/agents"),
20
22
  books: resolve(projectRoot, "src/content/books"),
@@ -25,7 +27,12 @@ function fallbackTenantConfig(projectRoot) {
25
27
  },
26
28
  features: {
27
29
  docs: true,
28
- books: true
30
+ books: true,
31
+ notes: true,
32
+ questions: true,
33
+ objectives: true,
34
+ proposals: true,
35
+ decisions: true
29
36
  }
30
37
  };
31
38
  }
@@ -25,6 +25,18 @@ async function getPublishedObjectives() {
25
25
  }
26
26
  return sortEntriesByDateDescending(await getCollection("objectives", ({ data }) => !data.draft));
27
27
  }
28
+ async function getPublishedProposals() {
29
+ if (!siteModelRendered("proposals")) {
30
+ return [];
31
+ }
32
+ return sortEntriesByDateDescending(await getCollection("proposals", ({ data }) => !data.draft));
33
+ }
34
+ async function getPublishedDecisions() {
35
+ if (!siteModelRendered("decisions")) {
36
+ return [];
37
+ }
38
+ return sortEntriesByDateDescending(await getCollection("decisions", ({ data }) => !data.draft));
39
+ }
28
40
  async function getPublishedNotes() {
29
41
  if (!siteModelRendered("notes")) {
30
42
  return [];
@@ -52,9 +64,11 @@ async function getPublishedBooks() {
52
64
  export {
53
65
  getPublishedAgents,
54
66
  getPublishedBooks,
67
+ getPublishedDecisions,
55
68
  getPublishedNotes,
56
69
  getPublishedObjectives,
57
70
  getPublishedPeople,
71
+ getPublishedProposals,
58
72
  getPublishedQuestions,
59
73
  resolveContributor,
60
74
  resolveContributorsForEntries,
@@ -54,7 +54,20 @@ async function loadHostedBookRuntime(locals) {
54
54
  }
55
55
  return provider.getObject(pointer);
56
56
  }
57
+ async function loadHostedDocsTree(locals) {
58
+ const provider = resolveHostedContentRuntimeProvider(locals);
59
+ if (!provider) {
60
+ return null;
61
+ }
62
+ const manifest = await provider.getManifest();
63
+ const pointer = manifest.runtime?.docsTree;
64
+ if (!pointer) {
65
+ return null;
66
+ }
67
+ return provider.getObject(pointer);
68
+ }
57
69
  export {
58
70
  loadHostedBookRuntime,
71
+ loadHostedDocsTree,
59
72
  resolveHostedContentRuntimeProvider
60
73
  };
@@ -35,6 +35,8 @@ const PAGE_MODEL_DEFAULTS = SITE_CONFIG.models.pages.defaults;
35
35
  const NOTE_MODEL_DEFAULTS = SITE_CONFIG.models.notes.defaults;
36
36
  const QUESTION_MODEL_DEFAULTS = SITE_CONFIG.models.questions.defaults;
37
37
  const OBJECTIVE_MODEL_DEFAULTS = SITE_CONFIG.models.objectives.defaults;
38
+ const PROPOSAL_MODEL_DEFAULTS = SITE_CONFIG.models.proposals.defaults;
39
+ const DECISION_MODEL_DEFAULTS = SITE_CONFIG.models.decisions.defaults;
38
40
  const PEOPLE_MODEL_DEFAULTS = SITE_CONFIG.models.people.defaults;
39
41
  const AGENT_MODEL_DEFAULTS = SITE_CONFIG.models.agents.defaults;
40
42
  const BOOK_MODEL_DEFAULTS = SITE_CONFIG.models.books.defaults;
@@ -67,6 +69,20 @@ function applyObjectiveModelDefaults(value) {
67
69
  tags: value.tags ?? OBJECTIVE_MODEL_DEFAULTS.tags ?? []
68
70
  };
69
71
  }
72
+ function applyProposalModelDefaults(value) {
73
+ return {
74
+ ...PROPOSAL_MODEL_DEFAULTS,
75
+ ...value,
76
+ tags: value.tags ?? PROPOSAL_MODEL_DEFAULTS.tags ?? []
77
+ };
78
+ }
79
+ function applyDecisionModelDefaults(value) {
80
+ return {
81
+ ...DECISION_MODEL_DEFAULTS,
82
+ ...value,
83
+ tags: value.tags ?? DECISION_MODEL_DEFAULTS.tags ?? []
84
+ };
85
+ }
70
86
  function applyPeopleModelDefaults(value) {
71
87
  return {
72
88
  ...PEOPLE_MODEL_DEFAULTS,
@@ -98,10 +114,12 @@ function applyDocsModelDefaults(value) {
98
114
  export {
99
115
  AGENT_MODEL_DEFAULTS,
100
116
  BOOK_MODEL_DEFAULTS,
117
+ DECISION_MODEL_DEFAULTS,
101
118
  NOTE_MODEL_DEFAULTS,
102
119
  OBJECTIVE_MODEL_DEFAULTS,
103
120
  PAGE_MODEL_DEFAULTS,
104
121
  PEOPLE_MODEL_DEFAULTS,
122
+ PROPOSAL_MODEL_DEFAULTS,
105
123
  QUESTION_MODEL_DEFAULTS,
106
124
  SITE,
107
125
  SITE_CONFIG,
@@ -113,10 +131,12 @@ export {
113
131
  TREESEED_MODEL_DEFAULTS,
114
132
  applyAgentModelDefaults,
115
133
  applyBookModelDefaults,
134
+ applyDecisionModelDefaults,
116
135
  applyDocsModelDefaults,
117
136
  applyNoteModelDefaults,
118
137
  applyObjectiveModelDefaults,
119
138
  applyPageModelDefaults,
120
139
  applyPeopleModelDefaults,
140
+ applyProposalModelDefaults,
121
141
  applyQuestionModelDefaults
122
142
  };
@@ -0,0 +1,185 @@
1
+ import { createMarkdownProcessor } from "@astrojs/markdown-remark";
2
+ import rehypeKatex from "rehype-katex";
3
+ import remarkMath from "remark-math";
4
+ import { getTreeseedContentServingMode } from "@treeseed/sdk/platform/deploy-runtime";
5
+ import { resolveHostedContentRuntimeProvider } from "./published-content.js";
6
+ let markdownProcessorPromise = null;
7
+ function markdownProcessor() {
8
+ if (!markdownProcessorPromise) {
9
+ markdownProcessorPromise = createMarkdownProcessor({
10
+ remarkPlugins: [remarkMath],
11
+ rehypePlugins: [[rehypeKatex, { strict: "ignore" }]]
12
+ });
13
+ }
14
+ return markdownProcessorPromise;
15
+ }
16
+ function providerForLocals(locals) {
17
+ return resolveHostedContentRuntimeProvider(locals);
18
+ }
19
+ function record(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
21
+ }
22
+ function strings(value) {
23
+ if (Array.isArray(value)) {
24
+ return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
25
+ }
26
+ if (typeof value === "string" && value.trim()) {
27
+ return [value.trim()];
28
+ }
29
+ return [];
30
+ }
31
+ function dateValue(value) {
32
+ if (value instanceof Date && Number.isFinite(value.valueOf())) {
33
+ return value;
34
+ }
35
+ if (typeof value === "string" && value.trim()) {
36
+ const parsed = new Date(value);
37
+ if (Number.isFinite(parsed.valueOf())) {
38
+ return parsed;
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ function normalizeTags(value) {
44
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
45
+ }
46
+ function normalizeLinks(value) {
47
+ if (!Array.isArray(value)) {
48
+ return [];
49
+ }
50
+ return value.map((entry) => record(entry)).map((entry) => ({
51
+ label: typeof entry.label === "string" ? entry.label : "",
52
+ href: typeof entry.href === "string" ? entry.href : ""
53
+ })).filter((entry) => entry.label && entry.href);
54
+ }
55
+ function normalizePublishedEntry(entry, content) {
56
+ const frontmatter = record(content?.frontmatter);
57
+ const data = {
58
+ ...frontmatter,
59
+ title: typeof frontmatter.title === "string" ? frontmatter.title : entry.title,
60
+ summary: typeof frontmatter.summary === "string" ? frontmatter.summary : entry.summary,
61
+ status: typeof frontmatter.status === "string" ? frontmatter.status : entry.status,
62
+ slug: typeof frontmatter.slug === "string" ? frontmatter.slug : entry.slug,
63
+ tags: normalizeTags(frontmatter.tags),
64
+ links: normalizeLinks(frontmatter.links)
65
+ };
66
+ const date = dateValue(frontmatter.date ?? entry.publishedAt);
67
+ if (date) {
68
+ data.date = date;
69
+ }
70
+ const updated = dateValue(content?.updatedAt ?? frontmatter.updated ?? entry.updatedAt);
71
+ if (updated) {
72
+ data.updated = updated;
73
+ }
74
+ return {
75
+ id: entry.id,
76
+ model: entry.model,
77
+ slug: entry.slug,
78
+ data
79
+ };
80
+ }
81
+ function isPublishedRuntimeContentMode() {
82
+ return getTreeseedContentServingMode() === "published_runtime";
83
+ }
84
+ async function renderPublishedMarkdown(body) {
85
+ const rendered = await (await markdownProcessor()).render(body ?? "", {
86
+ frontmatter: {}
87
+ });
88
+ return rendered.code;
89
+ }
90
+ async function loadPublishedCollection(locals, model) {
91
+ const provider = providerForLocals(locals);
92
+ if (!provider) {
93
+ return [];
94
+ }
95
+ const entries = await provider.listCollection(model);
96
+ const resolved = await Promise.all(entries.map(async (entry) => {
97
+ const content = await provider.getObject(entry.content);
98
+ return normalizePublishedEntry(entry, content);
99
+ }));
100
+ return resolved;
101
+ }
102
+ async function loadPublishedEntry(locals, model, slugOrId) {
103
+ const provider = providerForLocals(locals);
104
+ if (!provider) {
105
+ return null;
106
+ }
107
+ const entry = await provider.getEntry(model, slugOrId);
108
+ if (!entry) {
109
+ return null;
110
+ }
111
+ const content = await provider.getObject(entry.content);
112
+ if (!content) {
113
+ return null;
114
+ }
115
+ return {
116
+ publishedEntry: entry,
117
+ entry: normalizePublishedEntry(entry, content),
118
+ content,
119
+ html: await renderPublishedMarkdown(content.body ?? "")
120
+ };
121
+ }
122
+ async function resolvePublishedReferences(locals, model, references) {
123
+ const provider = providerForLocals(locals);
124
+ if (!provider) {
125
+ return [];
126
+ }
127
+ const resolved = await Promise.all(strings(references).map(async (slugOrId) => {
128
+ const entry = await provider.getEntry(model, slugOrId);
129
+ if (!entry) {
130
+ return null;
131
+ }
132
+ const content = await provider.getObject(entry.content);
133
+ return normalizePublishedEntry(entry, content);
134
+ }));
135
+ return resolved.filter(Boolean);
136
+ }
137
+ async function resolvePublishedContributor(locals, reference) {
138
+ const provider = providerForLocals(locals);
139
+ if (!provider) {
140
+ return null;
141
+ }
142
+ for (const model of ["people", "agents"]) {
143
+ for (const slugOrId of strings(reference)) {
144
+ const entry = await provider.getEntry(model, slugOrId);
145
+ if (entry) {
146
+ const content = await provider.getObject(entry.content);
147
+ return normalizePublishedEntry(entry, content);
148
+ }
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ function metadataFromPublishedContent(content) {
154
+ return record(content?.frontmatter);
155
+ }
156
+ function publishedDate(content, ...fallbacks) {
157
+ for (const candidate of [content?.frontmatter?.date, ...fallbacks]) {
158
+ const parsed = dateValue(candidate);
159
+ if (parsed) {
160
+ return parsed;
161
+ }
162
+ }
163
+ return /* @__PURE__ */ new Date(0);
164
+ }
165
+ function publishedUpdated(content, ...fallbacks) {
166
+ for (const candidate of [content?.updatedAt, content?.frontmatter?.updated, ...fallbacks]) {
167
+ const parsed = dateValue(candidate);
168
+ if (parsed) {
169
+ return parsed;
170
+ }
171
+ }
172
+ return /* @__PURE__ */ new Date(0);
173
+ }
174
+ export {
175
+ isPublishedRuntimeContentMode,
176
+ loadPublishedCollection,
177
+ loadPublishedEntry,
178
+ metadataFromPublishedContent,
179
+ normalizePublishedEntry,
180
+ publishedDate,
181
+ publishedUpdated,
182
+ renderPublishedMarkdown,
183
+ resolvePublishedContributor,
184
+ resolvePublishedReferences
185
+ };