Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ module.exports = {
// --- pi-ai boundary (audit gap): only spark-ai may import pi-ai directly ---
{
name: "no-direct-pi-ai",
comment:
"Direct @earendil-works/pi-ai imports must go through @zendev-lab/spark-ai. " +
"apps/spark-tui/src/cli/pi-parity-commands.ts is WIP — exempt until the user commits cleanup.",
comment: "Direct @earendil-works/pi-ai imports must go through @zendev-lab/spark-ai.",
severity: "error",
from: {
pathNot: "^(packages/spark-ai/|apps/spark-tui/src/cli/pi-parity-commands\\.ts$)",
pathNot: "^packages/spark-ai/",
},
to: {
path: "node_modules/.*/@earendil-works/pi-ai|/node_modules/@earendil-works/pi-ai|^@earendil-works/pi-ai",
Expand Down Expand Up @@ -172,11 +170,11 @@ module.exports = {
},
},
{
name: "fusion-repro-no-circular",
comment: "Fusion and reproduction capability packages must remain acyclic.",
name: "production-no-circular",
comment: "Production application and package modules must remain acyclic.",
severity: "error",
from: {
path: "^packages/spark-(fusion|repro)/",
path: "^(apps|packages)/",
},
to: {
circular: true,
Expand Down Expand Up @@ -272,11 +270,12 @@ module.exports = {
],
options: {
doNotFollow: {
path: ["node_modules", "dist", "\\.svelte-kit", "reports", "coverage"],
path: ["node_modules", "build", "dist", "\\.svelte-kit", "reports", "coverage"],
},
exclude: {
path: [
"node_modules",
"build",
"dist",
"\\.svelte-kit",
"reports",
Expand Down
14 changes: 2 additions & 12 deletions apps/spark-cockpit/src/cli/coordination-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,11 @@ import type { ProjectRef } from "@zendev-lab/spark-core";
import type {
SparkCockpitArtifactSummary,
SparkCockpitCliOptions,
SparkCockpitCoordinationState,
SparkCockpitGoalSummary,
SparkCockpitReviewSummary,
SparkCockpitWorkflowSummary,
} from "./coordination.ts";

export interface SparkCockpitCoordinationState {
cwd: string;
graph: TaskGraph | null;
currentProjectRef: ProjectRef | null;
currentSessionKey: string | null;
goal: SparkCockpitGoalSummary | null;
artifacts: SparkCockpitArtifactSummary[];
reviews: SparkCockpitReviewSummary[];
workflows: SparkCockpitWorkflowSummary[];
}
} from "./coordination-contract.ts";

export async function loadSparkCockpitCoordinationState(
options: SparkCockpitCliOptions,
Expand Down
66 changes: 66 additions & 0 deletions apps/spark-cockpit/src/cli/coordination-contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { ArtifactRef } from "@zendev-lab/spark-artifacts";
import type { ProjectRef } from "@zendev-lab/spark-core";
import type { TaskGraph } from "@zendev-lab/spark-tasks";

import type { CockpitCoordinationDaemonClientOptions } from "./coordination-daemon.ts";
import type { CockpitInstanceCliOptions } from "./instance.ts";

export interface SparkCockpitCliOptions {
cwd?: string;
daemonClient?: CockpitCoordinationDaemonClientOptions;
graph?: TaskGraph | null;
currentProjectRef?: ProjectRef;
currentSessionKey?: string | null;
goal?: SparkCockpitGoalSummary | null;
artifacts?: SparkCockpitArtifactSummary[];
reviews?: SparkCockpitReviewSummary[];
workflows?: SparkCockpitWorkflowSummary[];
instance?: CockpitInstanceCliOptions;
}

export type SparkCockpitGoalSource =
| "none"
| "current-project"
| "unrelated-project"
| "legacy-unscoped";

export interface SparkCockpitGoalSummary {
status: string;
objective?: string;
goalId?: string;
sessionKey?: string;
projectRef?: ProjectRef;
source?: SparkCockpitGoalSource;
current?: boolean;
}

export interface SparkCockpitArtifactSummary {
artifactRef: ArtifactRef;
title?: string;
kind?: string;
status?: string;
}

export interface SparkCockpitReviewSummary {
reviewRef: string;
status?: string;
targetRef?: string;
outcome?: string;
}

export interface SparkCockpitWorkflowSummary {
runRef: string;
status?: string;
name?: string;
}

export interface SparkCockpitCoordinationState {
cwd: string;
graph: TaskGraph | null;
currentProjectRef: ProjectRef | null;
currentSessionKey: string | null;
goal: SparkCockpitGoalSummary | null;
artifacts: SparkCockpitArtifactSummary[];
reviews: SparkCockpitReviewSummary[];
workflows: SparkCockpitWorkflowSummary[];
}
81 changes: 19 additions & 62 deletions apps/spark-cockpit/src/cli/coordination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,26 @@ import {
} from "./shared.ts";
import type { CockpitAccessCliResult } from "./access.ts";
import type { WorkspaceAccessCliResult } from "./workspace-access.ts";
import type { CockpitInstanceCliFailure, CockpitInstanceCliResult } from "./instance.ts";
import { loadSparkCockpitCoordinationState } from "./coordination-adapter.ts";
import type {
CockpitInstanceCliFailure,
CockpitInstanceCliOptions,
CockpitInstanceCliResult,
} from "./instance.ts";
import {
loadSparkCockpitCoordinationState,
type SparkCockpitCoordinationState,
} from "./coordination-adapter.ts";
import {
getManagedSession,
submitAssignment,
type CockpitCoordinationDaemonClientOptions,
} from "./coordination-daemon.ts";
SparkCockpitArtifactSummary,
SparkCockpitCliOptions,
SparkCockpitCoordinationState,
SparkCockpitGoalSource,
SparkCockpitGoalSummary,
SparkCockpitReviewSummary,
SparkCockpitWorkflowSummary,
} from "./coordination-contract.ts";
export type {
SparkCockpitArtifactSummary,
SparkCockpitCliOptions,
SparkCockpitGoalSource,
SparkCockpitGoalSummary,
SparkCockpitReviewSummary,
SparkCockpitWorkflowSummary,
} from "./coordination-contract.ts";
import { getManagedSession, submitAssignment } from "./coordination-daemon.ts";

export type SparkCockpitCliResource =
| "help"
Expand Down Expand Up @@ -64,55 +70,6 @@ export interface SparkCockpitCliCommand {
tokenId?: string;
}

export interface SparkCockpitCliOptions {
cwd?: string;
daemonClient?: CockpitCoordinationDaemonClientOptions;
graph?: TaskGraph | null;
currentProjectRef?: ProjectRef;
currentSessionKey?: string | null;
goal?: SparkCockpitGoalSummary | null;
artifacts?: SparkCockpitArtifactSummary[];
reviews?: SparkCockpitReviewSummary[];
workflows?: SparkCockpitWorkflowSummary[];
instance?: CockpitInstanceCliOptions;
}

export type SparkCockpitGoalSource =
| "none"
| "current-project"
| "unrelated-project"
| "legacy-unscoped";

export interface SparkCockpitGoalSummary {
status: string;
objective?: string;
goalId?: string;
sessionKey?: string;
projectRef?: ProjectRef;
source?: SparkCockpitGoalSource;
current?: boolean;
}

export interface SparkCockpitArtifactSummary {
artifactRef: ArtifactRef;
title?: string;
kind?: string;
status?: string;
}

export interface SparkCockpitReviewSummary {
reviewRef: string;
status?: string;
targetRef?: string;
outcome?: string;
}

export interface SparkCockpitWorkflowSummary {
runRef: string;
status?: string;
name?: string;
}

export type SparkCockpitCliResult =
| { action: "help"; text: string }
| { action: "status"; result: SparkCockpitStatusResult }
Expand Down
5 changes: 2 additions & 3 deletions apps/spark-daemon/src/claimed-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import {
daemonWorkspaceRouteMatches,
sendJson,
workspaceSnapshotPayloadForDaemon,
type MessageContext,
type ServerSocket,
} from "./daemon.ts";
} from "./daemon-command-runtime.ts";
import type { MessageContext, ServerSocket } from "./daemon-runtime-contract.ts";
import {
executeSparkDaemonModelChannelPublicControl,
isSparkDaemonModelChannelPublicKind,
Expand Down
3 changes: 2 additions & 1 deletion apps/spark-daemon/src/cli-daemon-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
readSparkDaemonConfig,
writeSparkDaemonConfig,
} from "./config.js";
import { createSparkDaemonUplinkControl, startSparkDaemon } from "./daemon.js";
import { createSparkDaemonUplinkControl } from "./daemon.js";
import { startSparkDaemon } from "./daemon-start.js";
import { getSparkDaemonServerProfile } from "./server-profiles.js";
import { createSparkDaemonModelControl } from "./model-control.ts";
import { unifyDaemonSessionTranscripts } from "./session-transcript-unification.ts";
Expand Down
95 changes: 95 additions & 0 deletions apps/spark-daemon/src/daemon-command-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { DatabaseSync } from "node:sqlite";

import {
serverCommandEnvelopeSchema,
sparkProtocolJsonObjectSchema,
} from "@zendev-lab/spark-protocol";

import { workspaceSnapshot, type RouteContext } from "./protocol/outbound.ts";
import { SparkChannelDeliveryStore } from "./store/channel-deliveries.ts";
import { SparkInvocationStore } from "./store/invocations.ts";
import {
getWorkspaceById,
isMutationBlockingBorrowedWorkspace,
listWorkspaces,
sparkDaemonServerStatusSummaries,
} from "./store/workspaces.ts";
import type { MessageContext, ServerSocket } from "./daemon-runtime-contract.ts";

export function commandRoute(
runtimeId: string,
command: ReturnType<typeof serverCommandEnvelopeSchema.parse>,
): RouteContext {
return {
runtimeId,
workspaceBindingId: command.workspaceBindingId,
workspaceId: command.workspaceId,
projectId: command.projectId,
commandId: command.commandId,
sessionId: command.sessionId,
ackOf: command.messageId,
};
}

export function daemonWorkspaceRouteMatches(
db: DatabaseSync,
localWorkspaceId: string,
serverWorkspaceId: string | undefined,
serverBindingId: string | undefined,
): boolean {
if (!serverWorkspaceId || !serverBindingId) return false;
return Boolean(
db
.prepare(
`SELECT 1
FROM daemon_workspaces
WHERE (id = ? OR server_binding_id = ?)
AND server_workspace_id = ?
AND server_binding_id = ?
LIMIT 1`,
)
.get(localWorkspaceId, localWorkspaceId, serverWorkspaceId, serverBindingId),
);
}

export function workspaceSnapshotPayloadForDaemon(
db: DatabaseSync,
workspace: NonNullable<ReturnType<typeof getWorkspaceById>>,
): Parameters<typeof workspaceSnapshot>[0] {
const mutationBlocked = isMutationBlockingBorrowedWorkspace(db, workspace.id);
return {
displayName: workspace.displayName,
status: workspace.status,
projects: [],
unresolvedInboxCount: 0,
activeInvocationCount: workspace.executor?.activeInvocationCount ?? 0,
activeAgentCount: workspace.executor?.activeAgentCount ?? 0,
...(workspace.borrowed ? { borrowed: workspace.borrowed } : {}),
workspaceClients: workspace.workspaceClients ?? [],
...(workspace.executor ? { executor: workspace.executor } : {}),
control: {
mode: mutationBlocked ? "snapshot_only" : "full",
...(mutationBlocked ? { reason: "borrowed" } : {}),
serverMutationAllowed: !mutationBlocked,
},
latestArtifactIds: [],
resources: [],
};
}

export function daemonStatusProjection(context: MessageContext) {
const store = new SparkInvocationStore(context.db);
return sparkProtocolJsonObjectSchema.parse({
runtimeId: context.runtimeId,
servers: sparkDaemonServerStatusSummaries(context.db),
invocations: store.counts(),
invocationHealth: store.oldestActive(),
channelDeliveries: new SparkChannelDeliveryStore(context.db).summary(),
workspaceCount: listWorkspaces(context.db).length,
observedAt: new Date().toISOString(),
});
}

export function sendJson(ws: ServerSocket, value: unknown): void {
ws.send(JSON.stringify(value));
}
Loading
Loading