@atolis-hq/wake 0.4.48 → 0.4.50

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 (76) hide show
  1. package/README.md +3 -0
  2. package/dist/src/activities/agent/agent-activity.js +9 -3
  3. package/dist/src/activities/contracts/activity.d.ts +14 -0
  4. package/dist/src/artifacts/application/artifact-projection.d.ts +3 -0
  5. package/dist/src/artifacts/application/artifact-projection.js +15 -0
  6. package/dist/src/artifacts/application/artifact-repository.d.ts +13 -0
  7. package/dist/src/artifacts/application/artifact-repository.js +19 -0
  8. package/dist/src/artifacts/application/artifact-service.d.ts +40 -0
  9. package/dist/src/artifacts/application/artifact-service.js +128 -0
  10. package/dist/src/artifacts/contracts/config.d.ts +6 -0
  11. package/dist/src/artifacts/contracts/config.js +19 -0
  12. package/dist/src/artifacts/contracts/event-factory.d.ts +6 -0
  13. package/dist/src/artifacts/contracts/event-factory.js +10 -0
  14. package/dist/src/artifacts/contracts/events.d.ts +33 -0
  15. package/dist/src/artifacts/contracts/events.js +59 -0
  16. package/dist/src/artifacts/contracts/identifiers.d.ts +5 -0
  17. package/dist/src/artifacts/contracts/identifiers.js +8 -0
  18. package/dist/src/artifacts/contracts/paths.d.ts +5 -0
  19. package/dist/src/artifacts/contracts/paths.js +9 -0
  20. package/dist/src/artifacts/contracts/streams.d.ts +7 -0
  21. package/dist/src/artifacts/contracts/streams.js +4 -0
  22. package/dist/src/artifacts/contracts/views.d.ts +20 -0
  23. package/dist/src/artifacts/contracts/views.js +1 -0
  24. package/dist/src/artifacts/domain/artifact-work-item.d.ts +4 -0
  25. package/dist/src/artifacts/domain/artifact-work-item.js +42 -0
  26. package/dist/src/artifacts/index.d.ts +14 -0
  27. package/dist/src/artifacts/index.js +14 -0
  28. package/dist/src/artifacts/infrastructure/file-artifact-store.d.ts +22 -0
  29. package/dist/src/artifacts/infrastructure/file-artifact-store.js +51 -0
  30. package/dist/src/artifacts/infrastructure/mcp-server.d.ts +18 -0
  31. package/dist/src/artifacts/infrastructure/mcp-server.js +135 -0
  32. package/dist/src/artifacts/infrastructure/mcp-session-store.d.ts +25 -0
  33. package/dist/src/artifacts/infrastructure/mcp-session-store.js +61 -0
  34. package/dist/src/bootstrap/artifact-mcp.d.ts +23 -0
  35. package/dist/src/bootstrap/artifact-mcp.js +119 -0
  36. package/dist/src/bootstrap/composition-root.d.ts +2 -0
  37. package/dist/src/bootstrap/composition-root.js +19 -0
  38. package/dist/src/bootstrap/config/root-schema.d.ts +6 -0
  39. package/dist/src/bootstrap/config/root-schema.js +5 -0
  40. package/dist/src/bootstrap/paths.d.ts +1 -0
  41. package/dist/src/bootstrap/paths.js +1 -0
  42. package/dist/src/bootstrap/projection-runtime.js +2 -0
  43. package/dist/src/bootstrap/runner-registry.js +22 -4
  44. package/dist/src/bootstrap/surface-api-work-applications.js +39 -1
  45. package/dist/src/bootstrap/surface-cli-applications.js +12 -1
  46. package/dist/src/bootstrap/version.js +1 -1
  47. package/dist/src/execution/application/execution-activity.d.ts +11 -0
  48. package/dist/src/execution/application/execution-activity.js +26 -10
  49. package/dist/src/execution/application/execution-service.js +1 -1
  50. package/dist/src/execution/application/execution-validation.js +17 -3
  51. package/dist/src/execution/contracts/runner.d.ts +10 -0
  52. package/dist/src/execution/contracts/workspace.d.ts +11 -1
  53. package/dist/src/execution/infrastructure/runners/claude.d.ts +1 -0
  54. package/dist/src/execution/infrastructure/runners/claude.js +21 -0
  55. package/dist/src/execution/infrastructure/runners/codex.js +12 -0
  56. package/dist/src/execution/infrastructure/runners/fake.d.ts +1 -0
  57. package/dist/src/execution/infrastructure/runners/fake.js +1 -0
  58. package/dist/src/execution/infrastructure/runners/registry.d.ts +3 -1
  59. package/dist/src/execution/infrastructure/runners/registry.js +5 -3
  60. package/dist/src/execution/infrastructure/workspace/git-workspace.d.ts +10 -5
  61. package/dist/src/execution/infrastructure/workspace/git-workspace.js +270 -38
  62. package/dist/src/surfaces/api/contracts/work.d.ts +10 -0
  63. package/dist/src/surfaces/api/routes/applications.d.ts +5 -0
  64. package/dist/src/surfaces/api/routes/read.js +34 -3
  65. package/dist/src/surfaces/api/routes/work.d.ts +1 -1
  66. package/dist/src/surfaces/api/routes/work.js +1 -0
  67. package/dist/src/surfaces/cli/main.d.ts +8 -0
  68. package/dist/src/surfaces/cli/main.js +31 -0
  69. package/dist/src/surfaces/cli/usage.js +1 -0
  70. package/dist/src/surfaces/index.d.ts +1 -0
  71. package/dist/src/surfaces/index.js +1 -0
  72. package/dist/src/surfaces/mcp/stdio.d.ts +3 -0
  73. package/dist/src/surfaces/mcp/stdio.js +5 -0
  74. package/dist/src/surfaces/web-assets/assets/{index-CuAZYm7r.js → index-CuIiUKuh.js} +2 -2
  75. package/dist/src/surfaces/web-assets/index.html +1 -1
  76. package/package.json +3 -1
package/README.md CHANGED
@@ -109,6 +109,9 @@ For more detail, see [docs/vision.md](docs/vision.md) and
109
109
  `workspaces/` at the top level for
110
110
  what you edit or browse day-to-day, with durable/internal state (events,
111
111
  projections, runs, logs, sandbox auth) nested under a hidden `.wake/`.
112
+ - **Durable handoffs.** Agent stages can publish work-item artifacts outside
113
+ Git workspaces. Wake passes only a scoped manifest to later agents, which
114
+ fetch content on demand through a run-bound MCP capability.
112
115
  - **Sandbox-oriented execution.** Wake can run from a persistent Docker sandbox
113
116
  with durable auth state and mounted Wake home data.
114
117
  - **Runner agnostic.** Claude Code, Codex, Cursor, and fake runners sit behind
@@ -12,6 +12,8 @@ export function createAgentActivity(templates, contextReader) {
12
12
  resumeStartedAt: context.resumeStartedAt,
13
13
  usageBaseline: context.usageBaseline,
14
14
  workspace: context.workspace,
15
+ mcpServers: context.mcpServers,
16
+ mcpPrompt: context.mcpPrompt,
15
17
  reportRunnerTimeout: context.reportRunnerTimeout,
16
18
  });
17
19
  await capturePrompt(context, invocation.workItemId, request);
@@ -77,7 +79,7 @@ async function recordTranscript(context, write) {
77
79
  async function agentRequest(invocation, templates, contextReader, context) {
78
80
  const input = invocation.input;
79
81
  const template = await resolveTemplate(input.template, invocation.workItemId, templates, contextReader, context.resumeSessionId !== undefined, context.resumeStartedAt);
80
- return requestFrom(input, context.runId ?? invocation.activationId, template, context.runnerContext, context.resumeSessionId, context.usageBaseline, context.workspace, context.reportRunnerTimeout);
82
+ return requestFrom(input, context.runId ?? invocation.activationId, template, context.runnerContext, context.resumeSessionId, context.usageBaseline, context.workspace, context.mcpServers, context.mcpPrompt, context.reportRunnerTimeout);
81
83
  }
82
84
  async function resolveTemplate(name, workItemId, templates, contextReader, isResume, observedSince) {
83
85
  if (name === undefined)
@@ -133,10 +135,10 @@ function escapeUntrustedJson(json) {
133
135
  }
134
136
  // The runner request maps each independently optional execution concern.
135
137
  // eslint-disable-next-line max-params, complexity
136
- function requestFrom(input, runId, template, runnerContext, resumeSessionId, usageBaseline, workspace, reportRunnerTimeout) {
138
+ function requestFrom(input, runId, template, runnerContext, resumeSessionId, usageBaseline, workspace, mcpServers, mcpPrompt, reportRunnerTimeout) {
137
139
  return {
138
140
  runId,
139
- prompt: input.prompt ?? template.prompt,
141
+ prompt: appendMcpPrompt(input.prompt ?? template.prompt, mcpPrompt),
140
142
  ...modelField(input.model ?? template?.model ?? runnerContext?.model),
141
143
  ...effortField(runnerContext?.effort),
142
144
  allowedTools: input.allowedTools ?? template?.allowedTools ?? [],
@@ -145,9 +147,13 @@ function requestFrom(input, runId, template, runnerContext, resumeSessionId, usa
145
147
  ...(resumeSessionId === undefined ? {} : { resumeSessionId }),
146
148
  ...(usageBaseline === undefined ? {} : { usageBaseline }),
147
149
  ...workspaceFields(workspace),
150
+ ...(mcpServers === undefined ? {} : { mcpServers }),
148
151
  ...(reportRunnerTimeout === undefined ? {} : { onTimeout: reportRunnerTimeout }),
149
152
  };
150
153
  }
154
+ function appendMcpPrompt(prompt, mcpPrompt) {
155
+ return mcpPrompt === undefined ? prompt : `${prompt}\n\n${mcpPrompt}`;
156
+ }
151
157
  function workspaceFields(workspace) {
152
158
  return workspace === undefined
153
159
  ? {}
@@ -54,6 +54,12 @@ export interface AgentRunnerPort {
54
54
  readonly cacheRead?: number;
55
55
  readonly cacheWrite?: number;
56
56
  };
57
+ readonly mcpServers?: readonly {
58
+ readonly name: string;
59
+ readonly command: string;
60
+ readonly args?: readonly string[];
61
+ readonly env?: Readonly<Record<string, string>>;
62
+ }[];
57
63
  readonly context?: {
58
64
  readonly runnerName: string;
59
65
  readonly action: string;
@@ -107,6 +113,14 @@ export interface ActivityExecutionContext {
107
113
  readonly mode: ActivityWorkspaceMode;
108
114
  };
109
115
  readonly runner?: AgentRunnerPort;
116
+ readonly mcpServers?: readonly {
117
+ readonly name: string;
118
+ readonly command: string;
119
+ readonly args?: readonly string[];
120
+ readonly env?: Readonly<Record<string, string>>;
121
+ }[];
122
+ /** Wake-owned capability inventory appended after the operator prompt. */
123
+ readonly mcpPrompt?: string;
110
124
  readonly runnerContext?: {
111
125
  readonly runnerName: string;
112
126
  /** CLI identity configured for the resolved runner. */
@@ -0,0 +1,3 @@
1
+ import type { ProjectionDefinition } from '@atolis-hq/eventing';
2
+ import type { ArtifactWorkItemView } from '../contracts/views.js';
3
+ export declare const artifactProjection: ProjectionDefinition<ArtifactWorkItemView | null>;
@@ -0,0 +1,15 @@
1
+ import { selectArtifactEvent } from '../contracts/events.js';
2
+ import { ArtifactStreamKind } from '../contracts/streams.js';
3
+ import { applyArtifactEvent } from '../domain/artifact-work-item.js';
4
+ export const artifactProjection = {
5
+ name: ArtifactStreamKind.WorkItem,
6
+ select(event) {
7
+ const owned = selectArtifactEvent(event);
8
+ return owned === null ? null : { key: owned.stream.id };
9
+ },
10
+ initial: () => null,
11
+ project(previous, event) {
12
+ const owned = selectArtifactEvent(event);
13
+ return owned === null ? previous : applyArtifactEvent(previous, owned);
14
+ },
15
+ };
@@ -0,0 +1,13 @@
1
+ import type { EventJournal } from '@atolis-hq/eventing';
2
+ import { type ArtifactEvent, type ArtifactEventData } from '../contracts/events.js';
3
+ import type { ArtifactWorkItemId } from '../contracts/identifiers.js';
4
+ import type { ArtifactWorkItemView } from '../contracts/views.js';
5
+ export declare class ArtifactRepository {
6
+ private readonly journal;
7
+ constructor(journal: EventJournal);
8
+ load(id: ArtifactWorkItemId): Promise<{
9
+ sequence: number;
10
+ view: ArtifactWorkItemView | null;
11
+ }>;
12
+ append(id: ArtifactWorkItemId, sequence: number, drafts: readonly ArtifactEventData[]): Promise<ArtifactEvent[]>;
13
+ }
@@ -0,0 +1,19 @@
1
+ import { decodeArtifactEvent, selectArtifactEvent, } from '../contracts/events.js';
2
+ import { artifactWorkItemStream } from '../contracts/streams.js';
3
+ import { foldArtifactWorkItem } from '../domain/artifact-work-item.js';
4
+ export class ArtifactRepository {
5
+ journal;
6
+ constructor(journal) {
7
+ this.journal = journal;
8
+ }
9
+ async load(id) {
10
+ const events = await this.journal.readStream(artifactWorkItemStream(id));
11
+ return {
12
+ sequence: events.length,
13
+ view: foldArtifactWorkItem(events.map(selectArtifactEvent).filter((event) => event !== null)),
14
+ };
15
+ }
16
+ async append(id, sequence, drafts) {
17
+ return (await this.journal.appendToStream(artifactWorkItemStream(id), sequence, drafts)).map(decodeArtifactEvent);
18
+ }
19
+ }
@@ -0,0 +1,40 @@
1
+ import { type CommandContext } from '@atolis-hq/eventing';
2
+ import type { WorkItemId } from '../../work/index.js';
3
+ import type { ArtifactsConfig } from '../contracts/config.js';
4
+ import { type ArtifactPath } from '../contracts/paths.js';
5
+ import type { ArtifactWorkItemView } from '../contracts/views.js';
6
+ import type { FileArtifactStore } from '../infrastructure/file-artifact-store.js';
7
+ import { ArtifactRepository } from './artifact-repository.js';
8
+ export interface ArtifactService {
9
+ stageRevision(command: StageArtifactRevision, scope: ArtifactWriteScope, context: CommandContext): Promise<ArtifactWorkItemView>;
10
+ stageTombstone(command: StageArtifactTombstone, scope: ArtifactWriteScope, context: CommandContext): Promise<ArtifactWorkItemView>;
11
+ get(workItemId: string): Promise<ArtifactWorkItemView | null>;
12
+ latestPublished(workItemId: string, acceptedActivation: (activationId: string) => Promise<boolean>): Promise<ReadonlyArray<ArtifactWorkItemView['revisions'][number]>>;
13
+ readAcceptedRevision(workItemId: string, revisionId: string, acceptedActivation: (activationId: string) => Promise<boolean>): Promise<{
14
+ readonly revision: ArtifactWorkItemView['revisions'][number];
15
+ readonly bytes: Uint8Array;
16
+ } | null>;
17
+ visibleTo(scope: ArtifactReadScope, acceptedActivation: (activationId: string) => Promise<boolean>): Promise<ReadonlyArray<ArtifactWorkItemView['revisions'][number]>>;
18
+ read(location: string): Promise<Uint8Array>;
19
+ }
20
+ export interface StageArtifactRevision {
21
+ readonly revisionId: string;
22
+ readonly path: ArtifactPath | string;
23
+ readonly bytes: Uint8Array;
24
+ readonly mediaType?: string;
25
+ }
26
+ export interface StageArtifactTombstone {
27
+ readonly revisionId: string;
28
+ readonly path: ArtifactPath | string;
29
+ }
30
+ /** Trusted execution context, never supplied by an MCP tool request. */
31
+ export interface ArtifactWriteScope {
32
+ readonly workItemId: WorkItemId;
33
+ readonly producer: string;
34
+ readonly runId: string;
35
+ readonly activationId: string;
36
+ }
37
+ export interface ArtifactReadScope extends ArtifactWriteScope {
38
+ readonly readableProducers: ReadonlySet<string>;
39
+ }
40
+ export declare function createArtifactService(journal: ConstructorParameters<typeof ArtifactRepository>[0], store: FileArtifactStore, config: ArtifactsConfig): ArtifactService;
@@ -0,0 +1,128 @@
1
+ import { EventSourceKind } from '@atolis-hq/eventing';
2
+ import { createArtifactEventData } from '../contracts/event-factory.js';
3
+ import { ArtifactEventType } from '../contracts/events.js';
4
+ import { artifactWorkItemIdForWorkItem } from '../contracts/identifiers.js';
5
+ import { artifactPath } from '../contracts/paths.js';
6
+ import { ArtifactRepository } from './artifact-repository.js';
7
+ // The public service keeps the tightly coupled idempotent staging operations adjacent.
8
+ // eslint-disable-next-line max-lines-per-function
9
+ export function createArtifactService(journal, store, config) {
10
+ const repository = new ArtifactRepository(journal);
11
+ return {
12
+ async stageRevision(command, scope, context) {
13
+ const id = artifactWorkItemIdForWorkItem(scope.workItemId);
14
+ const loaded = await repository.load(id);
15
+ if (loaded.view?.revisions.some((revision) => revision.revisionId === command.revisionId))
16
+ return loaded.view;
17
+ if (command.bytes.byteLength > config.maxWriteBytes)
18
+ throw new Error(`Artifact write exceeds ${config.maxWriteBytes} byte limit`);
19
+ const retained = loaded.view?.revisions.reduce((total, revision) => total + (revision.byteLength ?? 0), 0) ??
20
+ 0;
21
+ if (retained + command.bytes.byteLength > config.maxWorkItemBytes)
22
+ throw new Error(`Artifact work item exceeds ${config.maxWorkItemBytes} byte limit`);
23
+ const path = artifactPath(String(command.path));
24
+ const stored = await store.write({
25
+ workItemId: scope.workItemId,
26
+ revisionId: command.revisionId,
27
+ path,
28
+ bytes: command.bytes,
29
+ });
30
+ const [event] = await repository.append(id, loaded.sequence, [
31
+ createArtifactEventData({
32
+ eventId: `${context.commandId}:${ArtifactEventType.RevisionStaged}:${command.revisionId}`,
33
+ eventType: ArtifactEventType.RevisionStaged,
34
+ occurredAt: context.occurredAt,
35
+ correlationId: context.correlationId,
36
+ causationId: context.commandId,
37
+ actor: context.actor,
38
+ source: { kind: EventSourceKind.Internal, id: 'artifact-service' },
39
+ payload: {
40
+ workItemId: scope.workItemId,
41
+ revisionId: command.revisionId,
42
+ producer: scope.producer,
43
+ path,
44
+ runId: scope.runId,
45
+ activationId: scope.activationId,
46
+ ...stored,
47
+ ...(command.mediaType === undefined ? {} : { mediaType: command.mediaType }),
48
+ },
49
+ }),
50
+ ]);
51
+ const view = event === undefined ? null : (await repository.load(id)).view;
52
+ if (view === null)
53
+ throw new Error(`Artifact work item ${scope.workItemId} was not created`);
54
+ return view;
55
+ },
56
+ async stageTombstone(command, scope, context) {
57
+ const id = artifactWorkItemIdForWorkItem(scope.workItemId);
58
+ const loaded = await repository.load(id);
59
+ if (loaded.view?.revisions.some((revision) => revision.revisionId === command.revisionId))
60
+ return loaded.view;
61
+ const [event] = await repository.append(id, loaded.sequence, [
62
+ createArtifactEventData({
63
+ eventId: `${context.commandId}:${ArtifactEventType.TombstoneStaged}:${command.revisionId}`,
64
+ eventType: ArtifactEventType.TombstoneStaged,
65
+ occurredAt: context.occurredAt,
66
+ correlationId: context.correlationId,
67
+ causationId: context.commandId,
68
+ actor: context.actor,
69
+ source: { kind: EventSourceKind.Internal, id: 'artifact-service' },
70
+ payload: {
71
+ workItemId: scope.workItemId,
72
+ revisionId: command.revisionId,
73
+ producer: scope.producer,
74
+ path: artifactPath(String(command.path)),
75
+ runId: scope.runId,
76
+ activationId: scope.activationId,
77
+ },
78
+ }),
79
+ ]);
80
+ const view = event === undefined ? null : (await repository.load(id)).view;
81
+ if (view === null)
82
+ throw new Error(`Artifact work item ${scope.workItemId} was not created`);
83
+ return view;
84
+ },
85
+ async get(workItemId) {
86
+ return (await repository.load(artifactWorkItemIdForWorkItem(workItemId))).view;
87
+ },
88
+ async latestPublished(workItemId, acceptedActivation) {
89
+ const view = await repository.load(artifactWorkItemIdForWorkItem(workItemId));
90
+ const visible = [];
91
+ const latest = new Map();
92
+ for (const revision of view.view?.revisions ?? []) {
93
+ if (!(await acceptedActivation(revision.activationId)))
94
+ continue;
95
+ latest.set(`${revision.producer}\u0000${revision.path}`, revision);
96
+ }
97
+ for (const revision of latest.values())
98
+ if (!revision.deleted)
99
+ visible.push(revision);
100
+ return visible;
101
+ },
102
+ async visibleTo(scope, acceptedActivation) {
103
+ const view = await repository.load(artifactWorkItemIdForWorkItem(scope.workItemId));
104
+ const latest = new Map();
105
+ for (const revision of view.view?.revisions ?? []) {
106
+ const published = scope.readableProducers.has(revision.producer) &&
107
+ (await acceptedActivation(revision.activationId));
108
+ const ownStaged = revision.producer === scope.producer &&
109
+ revision.runId === scope.runId &&
110
+ revision.activationId === scope.activationId;
111
+ if (published || ownStaged)
112
+ latest.set(`${revision.producer}\u0000${revision.path}`, revision);
113
+ }
114
+ return [...latest.values()].filter((revision) => !revision.deleted);
115
+ },
116
+ async readAcceptedRevision(workItemId, revisionId, acceptedActivation) {
117
+ const view = await repository.load(artifactWorkItemIdForWorkItem(workItemId));
118
+ const revision = (view.view?.revisions ?? []).find((candidate) => candidate.revisionId === revisionId);
119
+ if (revision === undefined ||
120
+ revision.deleted ||
121
+ revision.location === undefined ||
122
+ !(await acceptedActivation(revision.activationId)))
123
+ return null;
124
+ return { revision, bytes: await store.read(revision.location) };
125
+ },
126
+ read: (location) => store.read(location),
127
+ };
128
+ }
@@ -0,0 +1,6 @@
1
+ import { z } from 'zod';
2
+ export declare const artifactsConfigSchema: z.ZodObject<{
3
+ maxWriteBytes: z.ZodDefault<z.ZodNumber>;
4
+ maxWorkItemBytes: z.ZodDefault<z.ZodNumber>;
5
+ }, z.core.$strict>;
6
+ export type ArtifactsConfig = z.infer<typeof artifactsConfigSchema>;
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export const artifactsConfigSchema = z
3
+ .object({
4
+ maxWriteBytes: z
5
+ .number()
6
+ .int()
7
+ .positive()
8
+ .default(25 * 1024 * 1024),
9
+ maxWorkItemBytes: z
10
+ .number()
11
+ .int()
12
+ .positive()
13
+ .default(250 * 1024 * 1024),
14
+ })
15
+ .strict()
16
+ .refine((value) => value.maxWorkItemBytes >= value.maxWriteBytes, {
17
+ message: 'artifacts.maxWorkItemBytes must be at least artifacts.maxWriteBytes',
18
+ path: ['maxWorkItemBytes'],
19
+ });
@@ -0,0 +1,6 @@
1
+ import { type EventDataInput } from '@atolis-hq/eventing';
2
+ import { type ArtifactEventData, type ArtifactEventPayloads } from './events.js';
3
+ export type ArtifactEventDataInput = {
4
+ [Type in keyof ArtifactEventPayloads]: EventDataInput<Type, ArtifactEventPayloads[Type]>;
5
+ }[keyof ArtifactEventPayloads];
6
+ export declare function createArtifactEventData(input: ArtifactEventDataInput): ArtifactEventData;
@@ -0,0 +1,10 @@
1
+ import { createEventData } from '@atolis-hq/eventing';
2
+ import { ArtifactEventType } from './events.js';
3
+ export function createArtifactEventData(input) {
4
+ switch (input.eventType) {
5
+ case ArtifactEventType.RevisionStaged:
6
+ return createEventData(input);
7
+ case ArtifactEventType.TombstoneStaged:
8
+ return createEventData(input);
9
+ }
10
+ }
@@ -0,0 +1,33 @@
1
+ import { type EventDataUnion, type EventEnvelope, type EventUnion } from '@atolis-hq/eventing';
2
+ import { type WorkItemId } from '../../work/index.js';
3
+ import { type ArtifactWorkItemStreamRef } from './streams.js';
4
+ export declare const ArtifactEventType: {
5
+ readonly RevisionStaged: "artifact.revision-staged";
6
+ readonly TombstoneStaged: "artifact.tombstone-staged";
7
+ };
8
+ export interface ArtifactEventPayloads {
9
+ readonly [ArtifactEventType.RevisionStaged]: {
10
+ readonly workItemId: WorkItemId;
11
+ readonly revisionId: string;
12
+ readonly producer: string;
13
+ readonly path: string;
14
+ readonly runId: string;
15
+ readonly activationId: string;
16
+ readonly location: string;
17
+ readonly digest: string;
18
+ readonly byteLength: number;
19
+ readonly mediaType?: string | undefined;
20
+ };
21
+ readonly [ArtifactEventType.TombstoneStaged]: {
22
+ readonly workItemId: WorkItemId;
23
+ readonly revisionId: string;
24
+ readonly producer: string;
25
+ readonly path: string;
26
+ readonly runId: string;
27
+ readonly activationId: string;
28
+ };
29
+ }
30
+ export type ArtifactEvent = EventUnion<ArtifactEventPayloads, ArtifactWorkItemStreamRef>;
31
+ export type ArtifactEventData = EventDataUnion<ArtifactEventPayloads>;
32
+ export declare function decodeArtifactEvent(event: EventEnvelope): ArtifactEvent;
33
+ export declare function selectArtifactEvent(event: EventEnvelope): ArtifactEvent | null;
@@ -0,0 +1,59 @@
1
+ import { eventDataSchema, eventEnvelopeSchema, } from '@atolis-hq/eventing';
2
+ import { z } from 'zod';
3
+ import { brandedStringSchema } from '../../kernel/index.js';
4
+ import { workItemId } from '../../work/index.js';
5
+ import { artifactWorkItemId, artifactWorkItemIdForWorkItem } from './identifiers.js';
6
+ import { artifactPath } from './paths.js';
7
+ import { ArtifactStreamKind } from './streams.js';
8
+ export const ArtifactEventType = {
9
+ RevisionStaged: 'artifact.revision-staged',
10
+ TombstoneStaged: 'artifact.tombstone-staged',
11
+ };
12
+ const stream = z
13
+ .object({
14
+ kind: z.literal(ArtifactStreamKind.WorkItem),
15
+ id: brandedStringSchema(artifactWorkItemId),
16
+ })
17
+ .strict();
18
+ const staged = z
19
+ .object({
20
+ workItemId: brandedStringSchema(workItemId),
21
+ revisionId: z.string().min(1),
22
+ producer: z.string().min(1),
23
+ path: z.string().transform(artifactPath),
24
+ runId: z.string().min(1),
25
+ activationId: z.string().min(1),
26
+ })
27
+ .strict();
28
+ const schema = z.union([
29
+ eventEnvelopeSchema.extend({
30
+ event: eventDataSchema.extend({
31
+ eventType: z.literal(ArtifactEventType.RevisionStaged),
32
+ payload: staged.extend({
33
+ location: z.string().min(1),
34
+ digest: z.string().regex(/^[a-f0-9]{64}$/),
35
+ byteLength: z.number().int().nonnegative(),
36
+ mediaType: z.string().min(1).optional(),
37
+ }),
38
+ }),
39
+ stream,
40
+ }),
41
+ eventEnvelopeSchema.extend({
42
+ event: eventDataSchema.extend({
43
+ eventType: z.literal(ArtifactEventType.TombstoneStaged),
44
+ payload: staged,
45
+ }),
46
+ stream,
47
+ }),
48
+ ]);
49
+ export function decodeArtifactEvent(event) {
50
+ const result = schema.safeParse(event);
51
+ if (!result.success)
52
+ throw new Error(`Invalid Artifact event ${event.event.eventId}: ${result.error.message}`);
53
+ if (result.data.stream.id !== artifactWorkItemIdForWorkItem(result.data.event.payload.workItemId))
54
+ throw new Error(`Artifact event ${event.event.eventId} belongs to another work item stream`);
55
+ return result.data;
56
+ }
57
+ export function selectArtifactEvent(event) {
58
+ return event.event.eventType.startsWith('artifact.') ? decodeArtifactEvent(event) : null;
59
+ }
@@ -0,0 +1,5 @@
1
+ import type { Brand } from '../../kernel/index.js';
2
+ import type { WorkItemId } from '../../work/index.js';
3
+ export type ArtifactWorkItemId = Brand<string, 'ArtifactWorkItemId'>;
4
+ export declare function artifactWorkItemId(value: string): ArtifactWorkItemId;
5
+ export declare function artifactWorkItemIdForWorkItem(workItemId: WorkItemId): ArtifactWorkItemId;
@@ -0,0 +1,8 @@
1
+ export function artifactWorkItemId(value) {
2
+ if (!/^artifact-work-[0-9a-hjkmnp-tv-z]{26}$/.test(value))
3
+ throw new Error('Invalid ArtifactWorkItemId');
4
+ return value;
5
+ }
6
+ export function artifactWorkItemIdForWorkItem(workItemId) {
7
+ return artifactWorkItemId(`artifact-work-${String(workItemId).slice('work-'.length)}`);
8
+ }
@@ -0,0 +1,5 @@
1
+ export type ArtifactPath = string & {
2
+ readonly __artifactPath: unique symbol;
3
+ };
4
+ /** A logical path, never a path that can escape an artifact owner namespace. */
5
+ export declare function artifactPath(value: string): ArtifactPath;
@@ -0,0 +1,9 @@
1
+ /** A logical path, never a path that can escape an artifact owner namespace. */
2
+ export function artifactPath(value) {
3
+ if (value.length === 0 ||
4
+ value.startsWith('/') ||
5
+ value.includes('\\') ||
6
+ value.split('/').some((segment) => segment.length === 0 || segment === '.' || segment === '..'))
7
+ throw new Error('Artifact path must be a normalized relative path');
8
+ return value;
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { EntityRef } from '../../kernel/index.js';
2
+ import type { ArtifactWorkItemId } from './identifiers.js';
3
+ export declare const ArtifactStreamKind: {
4
+ readonly WorkItem: "artifact-work-item";
5
+ };
6
+ export type ArtifactWorkItemStreamRef = EntityRef<typeof ArtifactStreamKind.WorkItem, ArtifactWorkItemId>;
7
+ export declare function artifactWorkItemStream(id: ArtifactWorkItemId): ArtifactWorkItemStreamRef;
@@ -0,0 +1,4 @@
1
+ export const ArtifactStreamKind = { WorkItem: 'artifact-work-item' };
2
+ export function artifactWorkItemStream(id) {
3
+ return { kind: ArtifactStreamKind.WorkItem, id };
4
+ }
@@ -0,0 +1,20 @@
1
+ import type { WorkItemId } from '../../work/index.js';
2
+ import type { ArtifactWorkItemId } from './identifiers.js';
3
+ export interface ArtifactRevisionView {
4
+ readonly revisionId: string;
5
+ readonly producer: string;
6
+ readonly path: string;
7
+ readonly runId: string;
8
+ readonly activationId: string;
9
+ readonly occurredAt: string;
10
+ readonly deleted: boolean;
11
+ readonly location?: string;
12
+ readonly digest?: string;
13
+ readonly byteLength?: number;
14
+ readonly mediaType?: string;
15
+ }
16
+ export interface ArtifactWorkItemView {
17
+ readonly artifactWorkItemId: ArtifactWorkItemId;
18
+ readonly workItemId: WorkItemId;
19
+ readonly revisions: readonly ArtifactRevisionView[];
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { type ArtifactEvent } from '../contracts/events.js';
2
+ import type { ArtifactWorkItemView } from '../contracts/views.js';
3
+ export declare function foldArtifactWorkItem(events: readonly ArtifactEvent[]): ArtifactWorkItemView | null;
4
+ export declare function applyArtifactEvent(view: ArtifactWorkItemView | null, event: ArtifactEvent): ArtifactWorkItemView;
@@ -0,0 +1,42 @@
1
+ import { ArtifactEventType, } from '../contracts/events.js';
2
+ export function foldArtifactWorkItem(events) {
3
+ let view = null;
4
+ for (const event of events)
5
+ view = applyArtifactEvent(view, event);
6
+ return view;
7
+ }
8
+ export function applyArtifactEvent(view, event) {
9
+ const payload = event.event.payload;
10
+ const current = view ?? {
11
+ artifactWorkItemId: event.stream.id,
12
+ workItemId: payload.workItemId,
13
+ revisions: [],
14
+ };
15
+ if (current.revisions.some((revision) => revision.revisionId === payload.revisionId))
16
+ return current;
17
+ const common = {
18
+ revisionId: payload.revisionId,
19
+ producer: payload.producer,
20
+ path: payload.path,
21
+ runId: payload.runId,
22
+ activationId: payload.activationId,
23
+ occurredAt: event.event.occurredAt,
24
+ };
25
+ if (event.event.eventType === ArtifactEventType.TombstoneStaged)
26
+ return { ...current, revisions: [...current.revisions, { ...common, deleted: true }] };
27
+ const revision = payload;
28
+ return {
29
+ ...current,
30
+ revisions: [
31
+ ...current.revisions,
32
+ {
33
+ ...common,
34
+ deleted: false,
35
+ location: revision.location,
36
+ digest: revision.digest,
37
+ byteLength: revision.byteLength,
38
+ ...(revision.mediaType === undefined ? {} : { mediaType: revision.mediaType }),
39
+ },
40
+ ],
41
+ };
42
+ }
@@ -0,0 +1,14 @@
1
+ export * from './application/artifact-projection.js';
2
+ export * from './application/artifact-repository.js';
3
+ export * from './application/artifact-service.js';
4
+ export * from './contracts/config.js';
5
+ export * from './contracts/event-factory.js';
6
+ export * from './contracts/events.js';
7
+ export * from './contracts/identifiers.js';
8
+ export * from './contracts/paths.js';
9
+ export * from './contracts/streams.js';
10
+ export * from './contracts/views.js';
11
+ export * from './domain/artifact-work-item.js';
12
+ export * from './infrastructure/file-artifact-store.js';
13
+ export * from './infrastructure/mcp-server.js';
14
+ export * from './infrastructure/mcp-session-store.js';
@@ -0,0 +1,14 @@
1
+ export * from './application/artifact-projection.js';
2
+ export * from './application/artifact-repository.js';
3
+ export * from './application/artifact-service.js';
4
+ export * from './contracts/config.js';
5
+ export * from './contracts/event-factory.js';
6
+ export * from './contracts/events.js';
7
+ export * from './contracts/identifiers.js';
8
+ export * from './contracts/paths.js';
9
+ export * from './contracts/streams.js';
10
+ export * from './contracts/views.js';
11
+ export * from './domain/artifact-work-item.js';
12
+ export * from './infrastructure/file-artifact-store.js';
13
+ export * from './infrastructure/mcp-server.js';
14
+ export * from './infrastructure/mcp-session-store.js';
@@ -0,0 +1,22 @@
1
+ import type { ArtifactPath } from '../contracts/paths.js';
2
+ export interface StoredArtifactBytes {
3
+ readonly location: string;
4
+ readonly byteLength: number;
5
+ readonly digest: string;
6
+ }
7
+ /**
8
+ * Private filesystem byte store. Its locations are opaque implementation
9
+ * details; durable artifact facts carry the location, digest, and length.
10
+ */
11
+ export declare class FileArtifactStore {
12
+ private readonly root;
13
+ constructor(root: string);
14
+ write(input: {
15
+ readonly workItemId: string;
16
+ readonly revisionId: string;
17
+ readonly path: ArtifactPath;
18
+ readonly bytes: Uint8Array;
19
+ }): Promise<StoredArtifactBytes>;
20
+ read(location: string): Promise<Uint8Array>;
21
+ private absolute;
22
+ }