vibe-coding-master 0.0.1

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 (52) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +226 -0
  3. package/dist/backend/adapters/claude-adapter.js +38 -0
  4. package/dist/backend/adapters/command-runner.js +33 -0
  5. package/dist/backend/adapters/filesystem.js +60 -0
  6. package/dist/backend/adapters/git-adapter.js +33 -0
  7. package/dist/backend/api/artifact-routes.js +109 -0
  8. package/dist/backend/api/message-routes.js +90 -0
  9. package/dist/backend/api/project-routes.js +17 -0
  10. package/dist/backend/api/session-routes.js +64 -0
  11. package/dist/backend/api/task-routes.js +30 -0
  12. package/dist/backend/errors.js +29 -0
  13. package/dist/backend/runtime/node-pty-runtime.js +162 -0
  14. package/dist/backend/runtime/session-registry.js +36 -0
  15. package/dist/backend/runtime/terminal-runtime.js +1 -0
  16. package/dist/backend/server.js +159 -0
  17. package/dist/backend/services/artifact-service.js +170 -0
  18. package/dist/backend/services/command-dispatcher.js +37 -0
  19. package/dist/backend/services/message-service.js +217 -0
  20. package/dist/backend/services/project-service.js +71 -0
  21. package/dist/backend/services/session-service.js +221 -0
  22. package/dist/backend/services/status-service.js +21 -0
  23. package/dist/backend/services/task-service.js +88 -0
  24. package/dist/backend/templates/handoff.js +76 -0
  25. package/dist/backend/templates/message-envelope.js +27 -0
  26. package/dist/backend/templates/role-command.js +21 -0
  27. package/dist/backend/templates/role-messaging-context.js +44 -0
  28. package/dist/backend/ws/terminal-ws.js +60 -0
  29. package/dist/cli/vcmctl.js +141 -0
  30. package/dist/main.js +63 -0
  31. package/dist/shared/constants.js +45 -0
  32. package/dist/shared/types/api.js +1 -0
  33. package/dist/shared/types/artifact.js +1 -0
  34. package/dist/shared/types/message.js +1 -0
  35. package/dist/shared/types/project.js +1 -0
  36. package/dist/shared/types/role.js +1 -0
  37. package/dist/shared/types/session.js +1 -0
  38. package/dist/shared/types/task.js +1 -0
  39. package/dist/shared/types/terminal.js +1 -0
  40. package/dist/shared/validation/artifact-check.js +64 -0
  41. package/dist/shared/validation/slug-check.js +22 -0
  42. package/dist-frontend/assets/index-Bah6k-Ix.css +32 -0
  43. package/dist-frontend/assets/index-EMaQuIB6.js +58 -0
  44. package/dist-frontend/index.html +13 -0
  45. package/docs/cc-best-practices.md +2142 -0
  46. package/docs/product-design.md +1597 -0
  47. package/docs/v1-architecture-design.md +1431 -0
  48. package/docs/v1-implementation-plan.md +1949 -0
  49. package/docs/v1-message-bus-orchestration-design.md +534 -0
  50. package/package.json +60 -0
  51. package/scripts/clean-build.mjs +12 -0
  52. package/scripts/fix-node-pty-spawn-helper.mjs +31 -0
@@ -0,0 +1,109 @@
1
+ import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
2
+ import { VcmError } from "../errors.js";
3
+ export function registerArtifactRoutes(app, deps) {
4
+ app.get("/api/tasks/:taskSlug/artifacts", async (request) => {
5
+ const project = await requireCurrentProject(deps.projectService);
6
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
7
+ return deps.artifactService.listArtifacts({
8
+ repoRoot: project.repoRoot,
9
+ handoffDir: task.handoffDir
10
+ });
11
+ });
12
+ app.get("/api/tasks/:taskSlug/artifacts/:artifactName", async (request) => {
13
+ const project = await requireCurrentProject(deps.projectService);
14
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
15
+ const paths = deps.artifactService.getHandoffPaths(project.repoRoot, task.handoffDir);
16
+ const artifactPath = artifactNameToPath(paths, request.params.artifactName);
17
+ return {
18
+ path: artifactPath,
19
+ content: await deps.artifactService.readArtifact({
20
+ repoRoot: project.repoRoot,
21
+ artifactPath
22
+ })
23
+ };
24
+ });
25
+ app.get("/api/tasks/:taskSlug/role-commands/:role", async (request) => {
26
+ const project = await requireCurrentProject(deps.projectService);
27
+ const role = parseDispatchableRole(request.params.role);
28
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
29
+ return {
30
+ role,
31
+ content: await deps.artifactService.readRoleCommand({
32
+ repoRoot: project.repoRoot,
33
+ handoffDir: task.handoffDir,
34
+ role
35
+ })
36
+ };
37
+ });
38
+ app.put("/api/tasks/:taskSlug/role-commands/:role", async (request) => {
39
+ const project = await requireCurrentProject(deps.projectService);
40
+ const role = parseDispatchableRole(request.params.role);
41
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
42
+ await deps.artifactService.saveRoleCommand({
43
+ repoRoot: project.repoRoot,
44
+ handoffDir: task.handoffDir,
45
+ role,
46
+ content: request.body.content
47
+ });
48
+ return { ok: true };
49
+ });
50
+ app.get("/api/tasks/:taskSlug/logs/:role", async (request) => {
51
+ const project = await requireCurrentProject(deps.projectService);
52
+ if (!isRoleName(request.params.role)) {
53
+ throw new VcmError({
54
+ code: "UNKNOWN_ROLE",
55
+ message: `Unknown role: ${request.params.role}`,
56
+ statusCode: 400
57
+ });
58
+ }
59
+ const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
60
+ const paths = deps.artifactService.getHandoffPaths(project.repoRoot, task.handoffDir);
61
+ return {
62
+ role: request.params.role,
63
+ content: await deps.artifactService.readArtifact({
64
+ repoRoot: project.repoRoot,
65
+ artifactPath: paths.roleLogPaths[request.params.role]
66
+ })
67
+ };
68
+ });
69
+ }
70
+ function parseDispatchableRole(role) {
71
+ if (!isDispatchableRole(role)) {
72
+ throw new VcmError({
73
+ code: "ROLE_NOT_DISPATCHABLE",
74
+ message: `${role} cannot receive role commands.`,
75
+ statusCode: 400
76
+ });
77
+ }
78
+ return role;
79
+ }
80
+ function artifactNameToPath(paths, artifactName) {
81
+ if (artifactName === "architecture-plan.md") {
82
+ return paths.architecturePlanPath;
83
+ }
84
+ if (artifactName === "implementation-log.md") {
85
+ return paths.implementationLogPath;
86
+ }
87
+ if (artifactName === "validation-log.md") {
88
+ return paths.validationLogPath;
89
+ }
90
+ if (artifactName === "review-report.md") {
91
+ return paths.reviewReportPath;
92
+ }
93
+ throw new VcmError({
94
+ code: "ARTIFACT_UNKNOWN",
95
+ message: `Unknown artifact: ${artifactName}`,
96
+ statusCode: 404
97
+ });
98
+ }
99
+ async function requireCurrentProject(projectService) {
100
+ const project = await projectService.getCurrentProject();
101
+ if (!project) {
102
+ throw new VcmError({
103
+ code: "PROJECT_NOT_CONNECTED",
104
+ message: "Connect a repository first.",
105
+ statusCode: 409
106
+ });
107
+ }
108
+ return project;
109
+ }
@@ -0,0 +1,90 @@
1
+ import { VcmError } from "../errors.js";
2
+ export function registerMessageRoutes(app, deps) {
3
+ app.get("/api/tasks/:taskSlug/messages", async (request) => {
4
+ const context = await getRouteContext(deps, request.params.taskSlug);
5
+ return deps.messageService.listMessages(context);
6
+ });
7
+ app.post("/api/tasks/:taskSlug/messages", async (request) => {
8
+ const context = await getRouteContext(deps, request.params.taskSlug);
9
+ return deps.messageService.sendMessage({
10
+ ...context,
11
+ ...request.body
12
+ });
13
+ });
14
+ app.post("/api/tasks/:taskSlug/messages/:messageId/stage", async (request) => {
15
+ const context = await getRouteContext(deps, request.params.taskSlug);
16
+ return deps.messageService.stageMessage({
17
+ ...context,
18
+ messageId: request.params.messageId
19
+ });
20
+ });
21
+ app.post("/api/tasks/:taskSlug/messages/:messageId/approve", async (request) => {
22
+ const context = await getRouteContext(deps, request.params.taskSlug);
23
+ return deps.messageService.approveMessage({
24
+ ...context,
25
+ messageId: request.params.messageId
26
+ });
27
+ });
28
+ app.post("/api/tasks/:taskSlug/messages/:messageId/reject", async (request) => {
29
+ const context = await getRouteContext(deps, request.params.taskSlug);
30
+ return deps.messageService.rejectMessage({
31
+ ...context,
32
+ messageId: request.params.messageId
33
+ });
34
+ });
35
+ app.get("/api/tasks/:taskSlug/orchestration", async (request) => {
36
+ const context = await getRouteContext(deps, request.params.taskSlug);
37
+ return deps.messageService.getOrchestrationState(context);
38
+ });
39
+ app.put("/api/tasks/:taskSlug/orchestration", async (request) => {
40
+ const context = await getRouteContext(deps, request.params.taskSlug);
41
+ if (request.body.mode && request.body.mode !== "manual" && request.body.mode !== "auto") {
42
+ throw new VcmError({
43
+ code: "ORCHESTRATION_MODE_INVALID",
44
+ message: `Invalid orchestration mode: ${request.body.mode}`,
45
+ statusCode: 400
46
+ });
47
+ }
48
+ return deps.messageService.updateOrchestrationState({
49
+ ...context,
50
+ mode: request.body.mode,
51
+ paused: request.body.paused
52
+ });
53
+ });
54
+ app.post("/api/tasks/:taskSlug/orchestration/pause", async (request) => {
55
+ const context = await getRouteContext(deps, request.params.taskSlug);
56
+ return deps.messageService.updateOrchestrationState({
57
+ ...context,
58
+ paused: true
59
+ });
60
+ });
61
+ app.post("/api/tasks/:taskSlug/orchestration/resume", async (request) => {
62
+ const context = await getRouteContext(deps, request.params.taskSlug);
63
+ return deps.messageService.updateOrchestrationState({
64
+ ...context,
65
+ paused: false
66
+ });
67
+ });
68
+ }
69
+ async function getRouteContext(deps, taskSlug) {
70
+ const project = await requireCurrentProject(deps.projectService);
71
+ const config = await deps.projectService.loadConfig(project.repoRoot);
72
+ const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
73
+ return {
74
+ repoRoot: project.repoRoot,
75
+ stateRoot: config.stateRoot,
76
+ handoffDir: task.handoffDir,
77
+ taskSlug
78
+ };
79
+ }
80
+ async function requireCurrentProject(projectService) {
81
+ const project = await projectService.getCurrentProject();
82
+ if (!project) {
83
+ throw new VcmError({
84
+ code: "PROJECT_NOT_CONNECTED",
85
+ message: "Connect a repository first.",
86
+ statusCode: 409
87
+ });
88
+ }
89
+ return project;
90
+ }
@@ -0,0 +1,17 @@
1
+ export function registerProjectRoutes(app, deps) {
2
+ app.get("/api/health", async () => ({ ok: true }));
3
+ app.post("/api/projects/connect", async (request) => {
4
+ return deps.projectService.connectProject(request.body);
5
+ });
6
+ app.get("/api/projects/current", async () => {
7
+ return deps.projectService.getCurrentProject();
8
+ });
9
+ app.get("/api/projects/harness", async () => ({
10
+ checks: [
11
+ {
12
+ name: "local-gui",
13
+ status: "ok"
14
+ }
15
+ ]
16
+ }));
17
+ }
@@ -0,0 +1,64 @@
1
+ import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
2
+ import { VcmError } from "../errors.js";
3
+ export function registerSessionRoutes(app, deps) {
4
+ app.get("/api/tasks/:taskSlug/sessions", async (request) => {
5
+ const project = await requireCurrentProject(deps.projectService);
6
+ return deps.sessionService.listRoleSessions(project.repoRoot, request.params.taskSlug);
7
+ });
8
+ app.post("/api/tasks/:taskSlug/sessions/:role/start", async (request) => {
9
+ const project = await requireCurrentProject(deps.projectService);
10
+ const role = parseRole(request.params.role);
11
+ return deps.sessionService.startRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
12
+ });
13
+ app.post("/api/tasks/:taskSlug/sessions/:role/stop", async (request) => {
14
+ const project = await requireCurrentProject(deps.projectService);
15
+ const role = parseRole(request.params.role);
16
+ return deps.sessionService.stopRoleSession(project.repoRoot, request.params.taskSlug, role);
17
+ });
18
+ app.post("/api/tasks/:taskSlug/sessions/:role/restart", async (request) => {
19
+ const project = await requireCurrentProject(deps.projectService);
20
+ const role = parseRole(request.params.role);
21
+ return deps.sessionService.restartRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
22
+ });
23
+ app.post("/api/tasks/:taskSlug/sessions/:role/resume", async (request) => {
24
+ const project = await requireCurrentProject(deps.projectService);
25
+ const role = parseRole(request.params.role);
26
+ return deps.sessionService.resumeRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
27
+ });
28
+ app.post("/api/tasks/:taskSlug/sessions/:role/dispatch", async (request) => {
29
+ const project = await requireCurrentProject(deps.projectService);
30
+ if (!isDispatchableRole(request.params.role)) {
31
+ throw new VcmError({
32
+ code: "ROLE_NOT_DISPATCHABLE",
33
+ message: `${request.params.role} cannot receive role commands.`,
34
+ statusCode: 400
35
+ });
36
+ }
37
+ return deps.commandDispatcher.dispatchRoleCommand({
38
+ repoRoot: project.repoRoot,
39
+ taskSlug: request.params.taskSlug,
40
+ role: request.params.role
41
+ });
42
+ });
43
+ }
44
+ function parseRole(role) {
45
+ if (!isRoleName(role)) {
46
+ throw new VcmError({
47
+ code: "UNKNOWN_ROLE",
48
+ message: `Unknown role: ${role}`,
49
+ statusCode: 400
50
+ });
51
+ }
52
+ return role;
53
+ }
54
+ async function requireCurrentProject(projectService) {
55
+ const project = await projectService.getCurrentProject();
56
+ if (!project) {
57
+ throw new VcmError({
58
+ code: "PROJECT_NOT_CONNECTED",
59
+ message: "Connect a repository first.",
60
+ statusCode: 409
61
+ });
62
+ }
63
+ return project;
64
+ }
@@ -0,0 +1,30 @@
1
+ import { VcmError } from "../errors.js";
2
+ export function registerTaskRoutes(app, deps) {
3
+ app.get("/api/tasks", async () => {
4
+ const project = await requireCurrentProject(deps.projectService);
5
+ return deps.taskService.listTasks(project.repoRoot);
6
+ });
7
+ app.post("/api/tasks", async (request) => {
8
+ const project = await requireCurrentProject(deps.projectService);
9
+ return deps.taskService.createTask(project.repoRoot, request.body);
10
+ });
11
+ app.get("/api/tasks/:taskSlug", async (request) => {
12
+ const project = await requireCurrentProject(deps.projectService);
13
+ return deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
14
+ });
15
+ app.get("/api/tasks/:taskSlug/status", async (request) => {
16
+ const project = await requireCurrentProject(deps.projectService);
17
+ return deps.statusService.getTaskStatus(project.repoRoot, request.params.taskSlug);
18
+ });
19
+ }
20
+ async function requireCurrentProject(projectService) {
21
+ const project = await projectService.getCurrentProject();
22
+ if (!project) {
23
+ throw new VcmError({
24
+ code: "PROJECT_NOT_CONNECTED",
25
+ message: "Connect a repository first.",
26
+ statusCode: 409
27
+ });
28
+ }
29
+ return project;
30
+ }
@@ -0,0 +1,29 @@
1
+ export class VcmError extends Error {
2
+ code;
3
+ statusCode;
4
+ hint;
5
+ constructor(input) {
6
+ super(input.message);
7
+ this.name = "VcmError";
8
+ this.code = input.code;
9
+ this.statusCode = input.statusCode ?? 400;
10
+ this.hint = input.hint;
11
+ }
12
+ }
13
+ export function toVcmError(error) {
14
+ if (error instanceof VcmError) {
15
+ return error;
16
+ }
17
+ if (error instanceof Error) {
18
+ return new VcmError({
19
+ code: "INTERNAL_ERROR",
20
+ message: error.message,
21
+ statusCode: 500
22
+ });
23
+ }
24
+ return new VcmError({
25
+ code: "INTERNAL_ERROR",
26
+ message: "Unknown error",
27
+ statusCode: 500
28
+ });
29
+ }
@@ -0,0 +1,162 @@
1
+ import * as pty from "node-pty";
2
+ import { VcmError } from "../errors.js";
3
+ export function createNodePtyTerminalRuntime(deps) {
4
+ const entries = new Map();
5
+ const now = deps.now ?? (() => new Date().toISOString());
6
+ const id = deps.id ?? (() => `session_${Date.now()}_${Math.random().toString(16).slice(2)}`);
7
+ const emit = (entry, event) => {
8
+ const terminalEvent = {
9
+ id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
10
+ timestamp: now(),
11
+ ...event
12
+ };
13
+ for (const listener of entry.listeners) {
14
+ listener(terminalEvent);
15
+ }
16
+ };
17
+ const create = async (input, sessionId = id()) => {
18
+ await deps.fs.ensureDir(input.logPath.replace(/[/\\][^/\\]+$/, ""));
19
+ const child = pty.spawn(input.command, input.args, {
20
+ cwd: input.cwd,
21
+ env: buildPtyEnvironment(process.env, input.env),
22
+ cols: input.cols ?? 100,
23
+ rows: input.rows ?? 28,
24
+ name: "xterm-256color"
25
+ });
26
+ const session = {
27
+ id: sessionId,
28
+ taskSlug: input.taskSlug,
29
+ role: input.role,
30
+ status: "running",
31
+ pid: child.pid,
32
+ startedAt: now(),
33
+ exitCode: null
34
+ };
35
+ const entry = {
36
+ input,
37
+ session,
38
+ process: child,
39
+ listeners: new Set()
40
+ };
41
+ entries.set(session.id, entry);
42
+ child.onData((data) => {
43
+ entry.session.lastOutputAt = now();
44
+ void deps.fs.appendText(input.logPath, data);
45
+ emit(entry, {
46
+ sessionId: session.id,
47
+ taskSlug: input.taskSlug,
48
+ role: input.role,
49
+ type: "output",
50
+ data
51
+ });
52
+ });
53
+ child.onExit(({ exitCode }) => {
54
+ entry.session.status = exitCode === 0 ? "exited" : "crashed";
55
+ entry.session.exitCode = exitCode;
56
+ emit(entry, {
57
+ sessionId: session.id,
58
+ taskSlug: input.taskSlug,
59
+ role: input.role,
60
+ type: "exit",
61
+ exitCode
62
+ });
63
+ });
64
+ return { ...session };
65
+ };
66
+ return {
67
+ createSession(input) {
68
+ return create(input);
69
+ },
70
+ getSession(sessionId) {
71
+ const entry = entries.get(sessionId);
72
+ return entry ? { ...entry.session } : undefined;
73
+ },
74
+ getSessionByRole(taskSlug, role) {
75
+ for (const entry of entries.values()) {
76
+ if (entry.session.taskSlug === taskSlug && entry.session.role === role) {
77
+ return { ...entry.session };
78
+ }
79
+ }
80
+ return undefined;
81
+ },
82
+ listSessions(taskSlug) {
83
+ return [...entries.values()]
84
+ .filter((entry) => !taskSlug || entry.session.taskSlug === taskSlug)
85
+ .map((entry) => ({ ...entry.session }));
86
+ },
87
+ write(sessionId, data) {
88
+ const entry = getEntry(entries, sessionId);
89
+ entry.process.write(data);
90
+ emit(entry, {
91
+ sessionId,
92
+ taskSlug: entry.session.taskSlug,
93
+ role: entry.session.role,
94
+ type: "input",
95
+ data
96
+ });
97
+ },
98
+ resize(sessionId, cols, rows) {
99
+ const entry = getEntry(entries, sessionId);
100
+ entry.process.resize(cols, rows);
101
+ },
102
+ async stop(sessionId) {
103
+ const entry = getEntry(entries, sessionId);
104
+ entry.session.status = "exited";
105
+ entry.process.kill();
106
+ },
107
+ async restart(sessionId) {
108
+ const entry = getEntry(entries, sessionId);
109
+ entry.process.kill();
110
+ return create(entry.input, sessionId);
111
+ },
112
+ subscribe(sessionId, listener) {
113
+ const entry = getEntry(entries, sessionId);
114
+ entry.listeners.add(listener);
115
+ void deps.fs.readText(entry.input.logPath)
116
+ .then((data) => {
117
+ if (!data || !entry.listeners.has(listener)) {
118
+ return;
119
+ }
120
+ listener({
121
+ id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
122
+ sessionId,
123
+ taskSlug: entry.session.taskSlug,
124
+ role: entry.session.role,
125
+ type: "output",
126
+ timestamp: now(),
127
+ data
128
+ });
129
+ })
130
+ .catch(() => {
131
+ // The log file may not exist yet for a brand-new session.
132
+ });
133
+ return () => {
134
+ entry.listeners.delete(listener);
135
+ };
136
+ }
137
+ };
138
+ }
139
+ export function buildPtyEnvironment(baseEnv, inputEnv = {}) {
140
+ const env = {
141
+ ...baseEnv,
142
+ ...inputEnv,
143
+ TERM: inputEnv.TERM ?? "xterm-256color",
144
+ COLORTERM: inputEnv.COLORTERM ?? baseEnv.COLORTERM ?? "truecolor",
145
+ FORCE_COLOR: inputEnv.FORCE_COLOR ?? baseEnv.FORCE_COLOR ?? "3",
146
+ CLICOLOR: inputEnv.CLICOLOR ?? baseEnv.CLICOLOR ?? "1",
147
+ TERM_PROGRAM: inputEnv.TERM_PROGRAM ?? "VibeCodingMaster"
148
+ };
149
+ delete env.NO_COLOR;
150
+ return env;
151
+ }
152
+ function getEntry(entries, sessionId) {
153
+ const entry = entries.get(sessionId);
154
+ if (!entry) {
155
+ throw new VcmError({
156
+ code: "SESSION_MISSING",
157
+ message: `Terminal session does not exist: ${sessionId}`,
158
+ statusCode: 404
159
+ });
160
+ }
161
+ return entry;
162
+ }
@@ -0,0 +1,36 @@
1
+ export function createSessionRegistry() {
2
+ const sessions = new Map();
3
+ return {
4
+ upsert(session) {
5
+ sessions.set(session.id, session);
6
+ },
7
+ get(sessionId) {
8
+ const session = sessions.get(sessionId);
9
+ return session ? { ...session } : undefined;
10
+ },
11
+ getByRole(taskSlug, role) {
12
+ const session = [...sessions.values()].find((candidate) => (candidate.taskSlug === taskSlug && candidate.role === role));
13
+ return session ? { ...session } : undefined;
14
+ },
15
+ list(taskSlug) {
16
+ return [...sessions.values()]
17
+ .filter((session) => !taskSlug || session.taskSlug === taskSlug)
18
+ .map((session) => ({ ...session }));
19
+ },
20
+ updateStatus(sessionId, status, patch = {}) {
21
+ const current = sessions.get(sessionId);
22
+ if (!current) {
23
+ return;
24
+ }
25
+ sessions.set(sessionId, {
26
+ ...current,
27
+ ...patch,
28
+ status,
29
+ updatedAt: patch.updatedAt ?? new Date().toISOString()
30
+ });
31
+ },
32
+ remove(sessionId) {
33
+ sessions.delete(sessionId);
34
+ }
35
+ };
36
+ }
@@ -0,0 +1 @@
1
+ export {};