Skip to content
Open
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
1 change: 1 addition & 0 deletions .agents/notes/contracts/command-planes.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ second CLI catalog.
| `spark-daemon` | `spark daemon` | daemon execution plane | persistent sessions, channel listeners, SQLite invocations, autonomous Loop timing/retry/recovery, events, logs, process state | domain goal/review/task definitions |
| `spark-hub` | `spark hub` | global control plane and management host | user/daemon authentication, workspace registry, cross-workspace delegation state, delivery outbox, idempotency, audit, bounded receipts, and embedded Web lifecycle | target execution state, local repositories, or internal evidence bodies |
| `spark-tui` | `spark tui` | local terminal control surface | interactive terminal UI, attach/resume, visible transcript, theme, export | canonical business-state ownership |
| `spark-web` | `spark web` | DSH web profile boot | Spark-owned boot of the installed DeepSeek Harness web profile, Cue/LLM plugins, and provider onboarding | Hub UI, daemon execution, or the `web_search`/`fetch_content` tool family |
| `spark-acp` | `spark acp` | ACP stdio adapter | protocol translation for new/prompt/cancel/permission | durable sessions, invocations, provider policy, or execution truth |
| `spark-mcp` | `spark mcp` | read-only MCP stdio adapter | bounded projection of canonical workspace Memory | memory writes, daemon execution, or another memory store |
| `spark-update` | `spark update` | installation and update surface | build-info inspection, install/update policy, release transition | daemon or Hub state |
Expand Down
11 changes: 4 additions & 7 deletions .agents/notes/contracts/package-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,14 @@ operation. Most tools are adapters over an owning domain:
- `spark-files`, `spark-memory`, `spark-tasks`, and `spark-artifacts` remain
domain packages because they own vocabulary, policy, and state semantics;
- a package whose primary reusable contract is one stateless tool family uses
the singular form `spark-tool-<family>`;
the singular form `spark-tool-<family>`; `spark-tool-web` is the search and
fetch adapter (persisted `responseId` values keep the `spark-web:` prefix and
`.spark/web/content.json`). The `spark web` executable lives in
`apps/spark-web` and must not own those tools;
- `spark-tools-*` is avoided because the plural prefix does not identify an
owner or boundary. The bare `spark-tools` name is reserved for a future
composition-only aggregator and must not own behavior.

A rename to `spark-tool-*` therefore requires evidence that the package is a
tool adapter rather than a domain owner. The current `spark-web` search/fetch
capability is the first candidate for a separate `spark-tool-web` migration;
that migration is intentionally outside the Hub/executable rename because it
changes extension specifiers and user configuration compatibility.

## Layer meanings

| Layer | Responsibility | Must not own |
Expand Down
42 changes: 42 additions & 0 deletions .agents/notes/decisions/2026-08-20-spark-web-app-and-tool-web.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: "Place search/fetch in spark-tool-web and make spark-web the DSH web application"
owner: zrr1999
created: 2026-08-20
---

# spark-tool-web capability and spark-web application

## Decision

- Rename the search/fetch capability from `@zendev-lab/spark-web` to
`@zendev-lab/spark-tool-web` at `packages/spark-tool-web`.
- Make `@zendev-lab/spark-web` the `apps/spark-web` application that owns
`spark web` / `spark-web`. Absorb the former `spark-web-dsh` DSH client
plugin and the CLI in-process boot into that app.
- Keep the package budget closed at 44: this is a rename plus a
capability-to-application reclassify, not a new workspace.

## Why

`spark-tool-<family>` is the inventory name for a stateless tool adapter.
Search and fetch do not own a Spark process. `spark web` does: it boots the
DSH web profile, writes managed plugins into that profile, and is a dispatcher
plane. Keeping the DSH client in `packages/` implied a capability while the
real process still lived inside `spark-cli`.

## Compatibility

- Persisted `responseId` prefix remains `spark-web:`; the store remains
`.spark/web/content.json`.
- Persisted extension specifiers `@zendev-lab/spark-web/extension` rewrite to
`@zendev-lab/spark-tool-web/extension`.
- The DSH overlay id stays `spark-web-dsh` so existing profiles skip a second
insert. The profile still resolves the client plugin as
`@zendev-lab/spark-web-dsh`.
- `spark web` dispatches to the `spark-web` companion the same way `spark hub`
and `spark tui` dispatch, instead of importing the boot path in-process.

## Not in this change

Native-izing the web surface beyond DSH profile boot, `dsh-scope`, or
`dsh-tools` guard/pre-execute wiring.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ supported packages.
| `apps/spark-tui` | Native terminal host and interaction adapters |
| `apps/spark-daemon` | Durable sessions, invocations, channels, and execution |
| `apps/spark-hub` | Browser presentation and control |
| `apps/spark-web` | DSH web profile boot with Spark-owned Cue, LLM, and onboarding plugins |
| `apps/spark-docs` | Public bilingual user documentation |
| `packages/spark-*` | Shared contracts, capabilities, runtimes, clients, and adapters |
| `architecture/packages.json` | Machine-readable layer, state-writer, exception, Pi ownership, composition-root, and package-budget inventory |
Expand Down
1 change: 1 addition & 0 deletions apps/spark-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The dispatcher does not own terminal rendering, daemon execution, provider/model
- `spark daemon ...` to daemon execution, authentication, model, session, and
administration surfaces.
- `spark version`, `spark install`, and `spark update` to the update executable.
- `spark web ...` to the `spark-web` companion, which boots the DSH web profile.

Spark 0.2 rejects the former Pi-style `--print`, `--mode`, `--list-models`,
root session aliases, and resource-management commands. Provider authentication
Expand Down
6 changes: 1 addition & 5 deletions apps/spark-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@
},
"dependencies": {
"@optique/core": "catalog:",
"@zendev-lab/dsh-tool-cue": "workspace:^",
"@zendev-lab/spark-i18n": "workspace:^",
"@zendev-lab/spark-llm": "workspace:^",
"@zendev-lab/spark-system": "workspace:^",
"@zendev-lab/spark-web-dsh": "workspace:^",
"esbuild": "^0.28.0"
"@zendev-lab/spark-system": "workspace:^"
},
"devDependencies": {
"typescript": "catalog:",
Expand Down
12 changes: 1 addition & 11 deletions apps/spark-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { command, constant, option, passThrough } from "@optique/core/primitives
import { sparkCliDispatcherStrings } from "@zendev-lab/spark-i18n/cli";
import { resolveSparkPaths, resolveSparkUserPaths } from "@zendev-lab/spark-system";

import { parseSparkWebArgs, runSparkWeb } from "./web.ts";

const dispatcherStrings = sparkCliDispatcherStrings();

export type SparkDispatcherTarget = "tui" | "daemon" | "hub" | "acp" | "mcp" | "update" | "web";
Expand Down Expand Up @@ -190,14 +188,6 @@ export async function runSparkDispatcher(
stderr.write(`${dispatcherStrings.tuiRequiresTty}\n`);
return 2;
}
if (command.target === "web") {
try {
return await runSparkWeb(parseSparkWebArgs(dispatchArgv));
} catch (error) {
stderr.write(`spark web: ${error instanceof Error ? error.message : String(error)}\n`);
return 1;
}
}
return await launcher.run(
command.target,
dispatchArgv,
Expand Down Expand Up @@ -489,13 +479,13 @@ function sourceCheckoutTargetCommand(target: SparkDispatcherTarget): string | un
acp: "../../packages/spark-acp/bin/spark-acp.ts",
mcp: "../../packages/spark-mcp/bin/spark-mcp.ts",
update: "../../packages/spark-update/bin/spark-update",
web: "../spark-web/bin/spark-web",
};
const entry = entryByTarget[target];
return entry ? resolve(cliRoot, entry) : undefined;
}

function targetExecutable(target: SparkDispatcherTarget): string {
if (target === "web") return "dsh";
return `spark-${target}`;
}

Expand Down
27 changes: 27 additions & 0 deletions apps/spark-cli/src/spark-dispatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ test("parseSparkDispatcherArgs routes canonical planes and rejects removed alias
target: "mcp",
argv: ["--help"],
});
assert.deepEqual(parseSparkDispatcherArgs(["web", "--host", "0.0.0.0"]), {
kind: "dispatch",
target: "web",
argv: ["--host", "0.0.0.0"],
});
for (const removed of [
["sessions", "list", "--all-workspaces"],
["session", "replay", "--session", "s1"],
Expand Down Expand Up @@ -194,6 +199,9 @@ test("dispatcher resolves source companion executables without importing app CLI
const update = resolveTargetCommand("update");
assert.match(update.command, /packages\/spark-update\/bin\/spark-update$/u);
assert.deepEqual(update.args, []);
const web = resolveTargetCommand("web");
assert.match(web.command, /apps\/spark-web\/bin\/spark-web$/u);
assert.deepEqual(web.args, []);
});

test("dispatcher honors an explicit packaged updater command", () => {
Expand All @@ -210,6 +218,25 @@ test("dispatcher honors an explicit packaged updater command", () => {
}
});

test("runSparkDispatcher dispatches web to the spark-web companion", async () => {
const calls: Array<{ target: string; argv: string[]; options: unknown }> = [];
const code = await runSparkDispatcher(
["web", "--host", "0.0.0.0"],
{},
{
run: async (target, argv, options) => {
calls.push({ target, argv, options });
return 0;
},
},
);

assert.equal(code, 0);
assert.deepEqual(calls, [
{ target: "web", argv: ["--host", "0.0.0.0"], options: { stdio: "inherit" } },
]);
});

test("runSparkDispatcher invokes injected launcher with the selected target", async () => {
const calls: Array<{ target: string; argv: string[]; options: unknown }> = [];
const code = await runSparkDispatcher(
Expand Down
5 changes: 3 additions & 2 deletions apps/spark-docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ spark hub --help
## Cue-first DSH web

`spark web` boots the installed DeepSeek Harness web profile and currently
supports exactly `@deepseek-ai/dsh@0.1.0-rc.7`. Initialize the profile once with
`dsh web`, then start the Spark surface:
supports exactly `@deepseek-ai/dsh@0.1.0-rc.7`. The companion executable is
`spark-web`; `spark web` is the dispatcher alias. Initialize the profile once
with `dsh web`, then start the Spark surface:

```bash
spark web
Expand Down
3 changes: 2 additions & 1 deletion apps/spark-docs/src/content/docs/zh/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ spark hub --help
## Cue-first DSH Web

`spark web` 启动已安装的 DeepSeek Harness Web profile,目前只支持
`@deepseek-ai/dsh@0.1.0-rc.7`。先用 `dsh web` 初始化一次 profile,再启动:
`@deepseek-ai/dsh@0.1.0-rc.7`。配套可执行程序是 `spark-web`,`spark web`
是分发器别名。先用 `dsh web` 初始化一次 profile,再启动:

```bash
spark web
Expand Down
2 changes: 1 addition & 1 deletion apps/spark-tui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@zendev-lab/spark-tasks": "workspace:^",
"@zendev-lab/spark-tui-adapter": "workspace:^",
"@zendev-lab/spark-turn": "workspace:^",
"@zendev-lab/spark-web": "workspace:^",
"@zendev-lab/spark-tool-web": "workspace:^",
"@zendev-lab/spark-workflows": "workspace:^"
},
"devDependencies": {
Expand Down
17 changes: 17 additions & 0 deletions apps/spark-tui/src/__tests__/spark-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,23 @@ test("persisted v2 default profile drops standalone workflow after composition o
assert.equal(migrated.extensionProfileVersion, CURRENT_SPARK_EXTENSION_PROFILE_VERSION);
});

test("current profiles rewrite the retired spark-web search-tool specifier", () => {
const migrated = mergeSparkConfigWithDefault({
extensionProfileVersion: CURRENT_SPARK_EXTENSION_PROFILE_VERSION,
extensions: [
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-extension/extension",
"my-extension",
],
});

assert.deepEqual(migrated.extensions, [
"@zendev-lab/spark-tool-web/extension",
"@zendev-lab/spark-extension/extension",
"my-extension",
]);
});

test("current profiles remove a conflicting standalone workflow entry", () => {
const migrated = mergeSparkConfigWithDefault({
extensionProfileVersion: CURRENT_SPARK_EXTENSION_PROFILE_VERSION,
Expand Down
4 changes: 2 additions & 2 deletions apps/spark-tui/src/__tests__/spark-extension-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ test("loadBuiltinExtensionFactories exposes the retained Spark CLI builtin exten
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-roles/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-tool-web/extension",
"@zendev-lab/spark-workflows/extension",
"@zendev-lab/spark-graft/extension",
"@zendev-lab/spark-extension/extension",
Expand Down Expand Up @@ -523,7 +523,7 @@ test("SparkExtensionLoader loads builtin factories through explicit imports", as
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-roles/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-tool-web/extension",
"@zendev-lab/spark-graft/extension",
"@zendev-lab/spark-extension/extension",
],
Expand Down
21 changes: 14 additions & 7 deletions apps/spark-tui/src/host/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ const CURRENT_SPARK_EXTENSION_FACADE = "@zendev-lab/spark-extension/extension";
const STANDALONE_WORKFLOW_EXTENSION = "@zendev-lab/spark-workflows/extension";
/** Pi product / prior Spark-native facade; rewrite to the Spark-native boundary. */
const LEGACY_PI_EXTENSION_FACADE = "@zendev-lab/pi-extension/extension";
/** Search/fetch tools moved from spark-web to spark-tool-web. */
const LEGACY_WEB_EXTENSION = "@zendev-lab/spark-web/extension";
const CURRENT_WEB_EXTENSION = "@zendev-lab/spark-tool-web/extension";
const LEGACY_DEFAULT_EXTENSION_CORE = [
"@zendev-lab/spark-ask/extension",
"@zendev-lab/spark-cue/extension",
Expand All @@ -84,7 +87,7 @@ const SPARK_EXTENSION_PROFILE_V1 = [
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-roles/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-tool-web/extension",
CURRENT_SPARK_EXTENSION_FACADE,
] as const;
const SPARK_EXTENSION_PROFILE_V2 = [
Expand All @@ -95,7 +98,7 @@ const SPARK_EXTENSION_PROFILE_V2 = [
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-roles/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-tool-web/extension",
"@zendev-lab/spark-workflows/extension",
CURRENT_SPARK_EXTENSION_FACADE,
] as const;
Expand Down Expand Up @@ -313,9 +316,11 @@ export function migrateSparkExtensionProfile(
): string[] {
const version = typeof rawVersion === "number" && Number.isInteger(rawVersion) ? rawVersion : 0;
const normalized = dedupeStrings(
extensions.map((specifier) =>
specifier === LEGACY_PI_EXTENSION_FACADE ? CURRENT_SPARK_EXTENSION_FACADE : specifier,
),
extensions.map((specifier) => {
if (specifier === LEGACY_PI_EXTENSION_FACADE) return CURRENT_SPARK_EXTENSION_FACADE;
if (specifier === LEGACY_WEB_EXTENSION) return CURRENT_WEB_EXTENSION;
return specifier;
}),
);
const conflictFree = removeWorkflowCompositionConflict(normalized);
if (version >= CURRENT_SPARK_EXTENSION_PROFILE_VERSION) return conflictFree;
Expand All @@ -331,7 +336,8 @@ export function migrateSparkExtensionProfile(
...LEGACY_DEFAULT_EXTENSION_CORE,
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
LEGACY_WEB_EXTENSION,
CURRENT_WEB_EXTENSION,
STANDALONE_WORKFLOW_EXTENSION,
"@zendev-lab/spark-graft/extension",
CURRENT_SPARK_EXTENSION_FACADE,
Expand All @@ -348,7 +354,8 @@ export function migrateSparkExtensionProfile(
...LEGACY_DEFAULT_EXTENSION_CORE,
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
LEGACY_WEB_EXTENSION,
CURRENT_WEB_EXTENSION,
STANDALONE_WORKFLOW_EXTENSION,
"@zendev-lab/spark-graft/extension",
CURRENT_SPARK_EXTENSION_FACADE,
Expand Down
8 changes: 4 additions & 4 deletions apps/spark-tui/src/host/extension-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import sparkMemoryExtension, {
} from "@zendev-lab/spark-memory/extension";
import sparkRolesExtension from "@zendev-lab/spark-roles/extension";
import sparkSessionExtension from "@zendev-lab/spark-session/extension";
import sparkWebExtension from "@zendev-lab/spark-web/extension";
import sparkWebExtension from "@zendev-lab/spark-tool-web/extension";
import sparkWorkflowsExtension from "@zendev-lab/spark-workflows/extension";

import { DEFAULT_SPARK_EXTENSION_SPECS } from "./extension-specs.ts";
Expand All @@ -36,7 +36,7 @@ export type SparkBuiltinExtensionName =
| "@zendev-lab/spark-memory"
| "@zendev-lab/spark-roles"
| "@zendev-lab/spark-session"
| "@zendev-lab/spark-web"
| "@zendev-lab/spark-tool-web"
| "@zendev-lab/spark-workflows"
| "@zendev-lab/spark-llm"
| "spark";
Expand Down Expand Up @@ -117,8 +117,8 @@ const BUILTIN_EXTENSION_FACTORIES: readonly SparkBuiltinCapabilityFactory[] = [
factory: sparkSessionExtension as SparkCapabilityFactory,
},
{
name: "@zendev-lab/spark-web",
specifier: "@zendev-lab/spark-web/extension",
name: "@zendev-lab/spark-tool-web",
specifier: "@zendev-lab/spark-tool-web/extension",
factory: sparkWebExtension as SparkCapabilityFactory,
},
{
Expand Down
2 changes: 1 addition & 1 deletion apps/spark-tui/src/host/extension-specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export const DEFAULT_SPARK_EXTENSION_SPECS = [
"@zendev-lab/spark-memory/extension",
"@zendev-lab/spark-roles/extension",
"@zendev-lab/spark-session/extension",
"@zendev-lab/spark-web/extension",
"@zendev-lab/spark-tool-web/extension",
"@zendev-lab/spark-extension/extension",
] as const;
Loading
Loading