@evandu/super-pi 0.1.14 → 0.1.15
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.
- package/dist/client/assets/index-B-nErgB-.js +43 -0
- package/dist/client/assets/{index-CIxRkYxD.css → index-CJgtcDBr.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/application/catalog-service.d.ts +2 -1
- package/dist/server/application/catalog-service.js +31 -3
- package/dist/server/application/conversation-service.d.ts +1 -1
- package/dist/server/application/conversation-service.js +13 -4
- package/dist/server/application/workspace-service.d.ts +1 -1
- package/dist/server/application/workspace-service.js +9 -1
- package/dist/server/command-handler.d.ts +1 -1
- package/dist/server/command-handler.js +18 -8
- package/dist/server/index.js +4 -0
- package/dist/server/model-filter.d.ts +13 -0
- package/dist/server/model-filter.js +40 -0
- package/dist/server/runtime.d.ts +3 -0
- package/dist/server/runtime.js +14 -3
- package/dist/server/store.d.ts +1 -0
- package/dist/server/store.js +13 -0
- package/dist/server/subagents.d.ts +2 -0
- package/dist/server/subagents.js +5 -0
- package/dist/server/types.d.ts +21 -1
- package/dist/server/ws-server.js +9 -2
- package/package.json +5 -3
- package/dist/client/assets/index-C1bVWsCs.js +0 -43
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertModelAllowed } from "../model-filter.js";
|
|
1
2
|
import { deleteConversationUploads } from "../upload.js";
|
|
2
3
|
/**
|
|
3
4
|
* Service managing workspaces: creation, rename, cascade deletion, and defaults configuration.
|
|
@@ -28,8 +29,9 @@ export class WorkspaceService {
|
|
|
28
29
|
* Creates a new workspace.
|
|
29
30
|
*/
|
|
30
31
|
async createWorkspace(name, path) {
|
|
31
|
-
await this.store.createWorkspace(name, path);
|
|
32
|
+
const created = await this.store.createWorkspace(name, path);
|
|
32
33
|
this.broadcaster.broadcastState();
|
|
34
|
+
return created.id;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Renames an existing workspace.
|
|
@@ -97,9 +99,15 @@ export class WorkspaceService {
|
|
|
97
99
|
const dirsLabel = Array.isArray(this.agentsDir) ? this.agentsDir.join(", ") : this.agentsDir;
|
|
98
100
|
throw new Error(`Agent ${JSON.stringify(settings.agentId)} 不存在或不是可用的 primary agent(目录:${dirsLabel})`);
|
|
99
101
|
}
|
|
102
|
+
if (agent.model) {
|
|
103
|
+
const catalog = await this.runtime.catalog();
|
|
104
|
+
const model = catalog.models.find(item => item.provider === agent.model?.provider && item.modelId === agent.model?.modelId);
|
|
105
|
+
assertModelAllowed(this.store.snapshot().modelFilterRules, workspace, model ?? { ...agent.model, label: agent.model.modelId });
|
|
106
|
+
}
|
|
100
107
|
agentToStore = agent.id;
|
|
101
108
|
}
|
|
102
109
|
}
|
|
110
|
+
assertModelAllowed(this.store.snapshot().modelFilterRules, workspace, selection ?? (await this.runtime.catalog()).defaultModel);
|
|
103
111
|
await this.store.setWorkspaceDefaults(workspaceId, {
|
|
104
112
|
model: selection,
|
|
105
113
|
thinkingLevel: settings.thinkingLevel ?? undefined,
|
|
@@ -18,5 +18,5 @@ export declare class CommandHandler {
|
|
|
18
18
|
/**
|
|
19
19
|
* Processes a client command.
|
|
20
20
|
*/
|
|
21
|
-
handleCommand(command: ClientCommand, sendToSocket?: SocketSender): Promise<void>;
|
|
21
|
+
handleCommand(command: ClientCommand, sendToSocket?: SocketSender): Promise<string | void>;
|
|
22
22
|
}
|
|
@@ -18,10 +18,13 @@ export class CommandHandler {
|
|
|
18
18
|
* Processes a client command.
|
|
19
19
|
*/
|
|
20
20
|
async handleCommand(command, sendToSocket) {
|
|
21
|
-
if (command.type === "
|
|
22
|
-
await this.
|
|
21
|
+
if (command.type === "model.rules.set") {
|
|
22
|
+
await this.catalogService.setModelRules(command.rules);
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
+
if (command.type === "workspace.create") {
|
|
26
|
+
return await this.workspaceService.createWorkspace(command.name, command.path);
|
|
27
|
+
}
|
|
25
28
|
if (command.type === "workspace.rename") {
|
|
26
29
|
await this.workspaceService.renameWorkspace(command.workspaceId, command.name);
|
|
27
30
|
return;
|
|
@@ -39,12 +42,19 @@ export class CommandHandler {
|
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
if (command.type === "conversation.create") {
|
|
42
|
-
await this.conversationService.createConversation(command.workspaceId);
|
|
43
|
-
return;
|
|
45
|
+
return await this.conversationService.createConversation(command.workspaceId);
|
|
44
46
|
}
|
|
45
47
|
if (command.type === "conversation.select") {
|
|
46
|
-
if (sendToSocket)
|
|
47
|
-
|
|
48
|
+
if (sendToSocket) {
|
|
49
|
+
try {
|
|
50
|
+
sendToSocket({ type: "model.catalog", catalog: await this.conversationService.getConversationCatalog(command.conversationId), conversationId: command.conversationId });
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Keep the permitted choices available so an existing invalid session can be corrected.
|
|
54
|
+
sendToSocket({ type: "model.catalog", catalog: await this.catalogService.getCatalog(undefined, command.conversationId), conversationId: command.conversationId });
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
48
58
|
else
|
|
49
59
|
await this.conversationService.selectConversation(command.conversationId);
|
|
50
60
|
return;
|
|
@@ -143,9 +153,9 @@ export class CommandHandler {
|
|
|
143
153
|
}
|
|
144
154
|
if (command.type === "catalog.fetch") {
|
|
145
155
|
if (sendToSocket)
|
|
146
|
-
sendToSocket({ type: "model.catalog", catalog: await this.catalogService.getCatalog() });
|
|
156
|
+
sendToSocket({ type: "model.catalog", conversationId: command.conversationId, catalog: await this.catalogService.getCatalog(undefined, command.conversationId) });
|
|
147
157
|
else
|
|
148
|
-
await this.catalogService.broadcastCatalog();
|
|
158
|
+
await this.catalogService.broadcastCatalog(undefined, command.conversationId);
|
|
149
159
|
return;
|
|
150
160
|
}
|
|
151
161
|
if (command.type === "agent.catalog.fetch") {
|
package/dist/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateConversationModel } from "./model-filter.js";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import { mkdir } from "node:fs/promises";
|
|
3
4
|
import { homedir } from "node:os";
|
|
@@ -60,6 +61,9 @@ const supervisor = new SubagentSupervisor(join(dataDir, "pi-sessions"), undefine
|
|
|
60
61
|
dir: initialAgentDirs,
|
|
61
62
|
promptDir: agentPromptDir,
|
|
62
63
|
});
|
|
64
|
+
const validateModel = (conversationId, selection) => validateConversationModel(store, runtime, conversationId, selection);
|
|
65
|
+
runtime.setModelValidator?.(validateModel);
|
|
66
|
+
supervisor.setModelValidator(validateModel);
|
|
63
67
|
const subagents = new SubagentRunRecorder(supervisor, {
|
|
64
68
|
store,
|
|
65
69
|
broadcast: (event) => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AppState, ModelFilterRule, ModelSelection, Workspace } from "./types.js";
|
|
2
|
+
export declare function applicableRules(rules: ModelFilterRule[] | undefined, workspace: Workspace): {
|
|
3
|
+
rule: ModelFilterRule;
|
|
4
|
+
modelRegex: RegExp;
|
|
5
|
+
}[];
|
|
6
|
+
export declare function assertModelAllowed(rules: ModelFilterRule[] | undefined, workspace: Workspace, model: ModelSelection | undefined): void;
|
|
7
|
+
export declare function conversationWorkspace(state: AppState, conversationId: string): Workspace;
|
|
8
|
+
/** Resolve Pi's actual global default when no model was selected; never guess a permitted replacement. */
|
|
9
|
+
export declare function validateConversationModel(store: {
|
|
10
|
+
snapshot(): AppState;
|
|
11
|
+
}, runtime: {
|
|
12
|
+
catalog(selection?: ModelSelection): Promise<import("./types.js").ModelCatalog>;
|
|
13
|
+
}, conversationId: string, selection?: ModelSelection): Promise<void>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Workspace names match case-insensitively. A model is allowed when either its label
|
|
2
|
+
// or provider/modelId matches. All matching rules must allow it (intersection).
|
|
3
|
+
// Invalid regexes are skipped with a diagnostic, never interpreted as deny-all.
|
|
4
|
+
export function applicableRules(rules, workspace) {
|
|
5
|
+
return (rules ?? []).flatMap(rule => {
|
|
6
|
+
try {
|
|
7
|
+
const workspaceRegex = new RegExp(rule.workspacePattern, "i");
|
|
8
|
+
const modelRegex = new RegExp(rule.modelPattern, "i");
|
|
9
|
+
return workspaceRegex.test(workspace.name) ? [{ rule, modelRegex }] : [];
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
console.warn(`模型过滤规则 ${rule.id} 无效,已跳过:${rule.workspacePattern} → ${rule.modelPattern}:${String(error)}`);
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function assertModelAllowed(rules, workspace, model) {
|
|
18
|
+
for (const { rule, modelRegex } of applicableRules(rules, workspace)) {
|
|
19
|
+
if (!model || (!modelRegex.test(model.label ?? "") && !modelRegex.test(`${model.provider}/${model.modelId}`))) {
|
|
20
|
+
throw new Error(`工作区「${workspace.name}」命中模型规则「${rule.label ?? rule.id}」:/${rule.workspacePattern}/i → /${rule.modelPattern}/i;当前模型「${model ? `${model.label} (${model.provider}/${model.modelId})` : "Pi 默认模型无法解析"}」不匹配允许的模型正则,拒绝使用。`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function conversationWorkspace(state, conversationId) {
|
|
25
|
+
const conversation = state.conversations.find(item => item.id === conversationId);
|
|
26
|
+
const workspace = state.workspaces.find(item => item.id === conversation?.workspaceId);
|
|
27
|
+
if (!workspace)
|
|
28
|
+
throw new Error("会话所属工作区不存在");
|
|
29
|
+
return workspace;
|
|
30
|
+
}
|
|
31
|
+
/** Resolve Pi's actual global default when no model was selected; never guess a permitted replacement. */
|
|
32
|
+
export async function validateConversationModel(store, runtime, conversationId, selection) {
|
|
33
|
+
const state = store.snapshot();
|
|
34
|
+
const workspace = conversationWorkspace(state, conversationId);
|
|
35
|
+
if (!applicableRules(state.modelFilterRules, workspace).length)
|
|
36
|
+
return;
|
|
37
|
+
const catalog = await runtime.catalog();
|
|
38
|
+
const model = selection ? catalog.models.find(item => item.provider === selection.provider && item.modelId === selection.modelId) ?? selection : catalog.defaultModel;
|
|
39
|
+
assertModelAllowed(store.snapshot().modelFilterRules, conversationWorkspace(store.snapshot(), conversationId), model);
|
|
40
|
+
}
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -201,6 +201,7 @@ export interface AgentRuntime {
|
|
|
201
201
|
*/
|
|
202
202
|
fork?(request: ForkRequest): Promise<ForkOutcome>;
|
|
203
203
|
/** Updates the active agent directory or directories to search. */
|
|
204
|
+
setModelValidator?(validator: (conversationId: string, model: ModelSelection | undefined) => Promise<void>): void;
|
|
204
205
|
setAgentsDir?(dir: string | string[]): void;
|
|
205
206
|
}
|
|
206
207
|
/** Deterministic offline runtime used by tests and local UI development. */
|
|
@@ -625,6 +626,8 @@ export declare class PiRpcRuntime implements AgentRuntime {
|
|
|
625
626
|
* spawning Pi so their prompt and tool restrictions cannot silently disappear.
|
|
626
627
|
*/
|
|
627
628
|
agentFor(conversation: Pick<Conversation, "id" | "agentId">): Promise<AgentDefinition | undefined>;
|
|
629
|
+
private modelValidator?;
|
|
630
|
+
setModelValidator(validator: (conversationId: string, model: ModelSelection | undefined) => Promise<void>): void;
|
|
628
631
|
setAgentsDir(dir: string | string[]): void;
|
|
629
632
|
/**
|
|
630
633
|
* The `--extension` argv for this turn, or `[]` when the task tool is not available.
|
package/dist/server/runtime.js
CHANGED
|
@@ -635,6 +635,8 @@ export class PiRpcRuntime {
|
|
|
635
635
|
const dirLabel = Array.isArray(this.agents.dir) ? this.agents.dir.join(", ") : this.agents.dir;
|
|
636
636
|
throw new Error(`Agent ${JSON.stringify(conversation.agentId)} 已失效或不是可用的 primary agent(目录:${dirLabel})。请重新选择 Agent,或选择“跟随 Pi 默认”后重试。`);
|
|
637
637
|
}
|
|
638
|
+
modelValidator;
|
|
639
|
+
setModelValidator(validator) { this.modelValidator = validator; }
|
|
638
640
|
setAgentsDir(dir) {
|
|
639
641
|
this.agents.dir = dir;
|
|
640
642
|
}
|
|
@@ -710,6 +712,9 @@ export class PiRpcRuntime {
|
|
|
710
712
|
// nothing to send after the process is already running. Fetched before the spawn so a definition
|
|
711
713
|
// error fails the turn loudly instead of starting a Pi that silently ignores it.
|
|
712
714
|
const agent = await this.agentFor(conversation);
|
|
715
|
+
if (agent?.model)
|
|
716
|
+
await this.modelValidator?.(conversation.id, { ...agent.model, label: agent.model.modelId });
|
|
717
|
+
await this.modelValidator?.(conversation.id, conversation.model);
|
|
713
718
|
// Whether the `task` tool EXISTS is decided first, because that is what decides whether it belongs
|
|
714
719
|
// on the allowlist. Naming a tool Pi has not registered is meaningless, and doing it anyway would
|
|
715
720
|
// change the spawn line of a runtime that has no task tool - which is what
|
|
@@ -784,6 +789,13 @@ export class PiRpcRuntime {
|
|
|
784
789
|
if (response.success === false)
|
|
785
790
|
throw new Error(`Pi rejected ${command.type}: ${response.error ?? "no reason given"}`);
|
|
786
791
|
}
|
|
792
|
+
// Resumed Pi sessions can carry a different model than the global default.
|
|
793
|
+
// Check the real get_state result as well, before any prompt reaches the model.
|
|
794
|
+
if (this.modelValidator) {
|
|
795
|
+
const actual = await channel.request({ id: "policy_state", type: "get_state" }, SESSION_COMMAND_TIMEOUT_MS);
|
|
796
|
+
const model = actual.data?.model;
|
|
797
|
+
await this.modelValidator(conversation.id, model?.provider && model.id ? { provider: model.provider, modelId: model.id, label: model.name ?? model.id } : undefined);
|
|
798
|
+
}
|
|
787
799
|
const imagePayloads = this.options.uploadsDir && images?.length
|
|
788
800
|
? await loadConversationImages(this.options.uploadsDir, conversation.id, images)
|
|
789
801
|
: undefined;
|
|
@@ -1405,9 +1417,8 @@ export class PiRpcRuntime {
|
|
|
1405
1417
|
source: "pi",
|
|
1406
1418
|
models: options,
|
|
1407
1419
|
levels: levelNames,
|
|
1408
|
-
// Pi's
|
|
1409
|
-
|
|
1410
|
-
defaultModel: pick(current) ?? pick(options[0]),
|
|
1420
|
+
// Only get_state identifies Pi's default. Catalog order must never invent a default.
|
|
1421
|
+
defaultModel: pick(current),
|
|
1411
1422
|
defaultThinkingLevel: text(state.data?.thinkingLevel) ?? levelNames[0],
|
|
1412
1423
|
fetchedAt: new Date().toISOString(),
|
|
1413
1424
|
};
|
package/dist/server/store.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export declare class Store {
|
|
|
73
73
|
private state;
|
|
74
74
|
private constructor();
|
|
75
75
|
static open(filePath: string): Promise<Store>;
|
|
76
|
+
setModelFilterRules(rules: import("./types.js").ModelFilterRule[]): Promise<void>;
|
|
76
77
|
snapshot(): AppState;
|
|
77
78
|
createWorkspace(name: string, path?: string): Promise<Workspace>;
|
|
78
79
|
renameWorkspace(workspaceId: string, name: string): Promise<Workspace>;
|
package/dist/server/store.js
CHANGED
|
@@ -473,6 +473,19 @@ export class Store {
|
|
|
473
473
|
return new Store(filePath, { workspaces: [{ id: "workspace_default", name: "Super Pi", path: process.cwd(), updatedAt: createdAt }], conversations: [], subagentRuns: [], queuedMessages: [], backgroundTasks: [] });
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
|
+
async setModelFilterRules(rules) {
|
|
477
|
+
if (!Array.isArray(rules) || rules.some(rule => !rule || typeof rule.id !== "string" || typeof rule.workspacePattern !== "string" || typeof rule.modelPattern !== "string" || (rule.label !== undefined && typeof rule.label !== "string")))
|
|
478
|
+
throw new Error("模型过滤规则格式不合法");
|
|
479
|
+
const previous = this.state.modelFilterRules;
|
|
480
|
+
this.state.modelFilterRules = structuredClone(rules);
|
|
481
|
+
try {
|
|
482
|
+
await this.persist();
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
this.state.modelFilterRules = previous;
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
476
489
|
snapshot() { return structuredClone(this.state); }
|
|
477
490
|
async createWorkspace(name, path) {
|
|
478
491
|
const cleanName = name.trim();
|
|
@@ -146,6 +146,8 @@ export declare class SubagentSupervisor {
|
|
|
146
146
|
* reachable. `Pi` is resolved once, because a resolution that could change mid-run would make one
|
|
147
147
|
* child's spawn line depend on when it was started.
|
|
148
148
|
*/
|
|
149
|
+
private modelValidator?;
|
|
150
|
+
setModelValidator(validator: (conversationId: string, model: import("./types.js").ModelSelection | undefined) => Promise<void>): void;
|
|
149
151
|
constructor(sessionDir: string, command?: string, agents?: {
|
|
150
152
|
dir: string | string[];
|
|
151
153
|
promptDir: string;
|
package/dist/server/subagents.js
CHANGED
|
@@ -177,6 +177,8 @@ export class SubagentSupervisor {
|
|
|
177
177
|
* reachable. `Pi` is resolved once, because a resolution that could change mid-run would make one
|
|
178
178
|
* child's spawn line depend on when it was started.
|
|
179
179
|
*/
|
|
180
|
+
modelValidator;
|
|
181
|
+
setModelValidator(validator) { this.modelValidator = validator; }
|
|
180
182
|
constructor(sessionDir, command, agents = { dir: join(sessionDir, "agents"), promptDir: join(sessionDir, "agent-prompts") }) {
|
|
181
183
|
this.sessionDir = sessionDir;
|
|
182
184
|
this.agents = agents;
|
|
@@ -250,6 +252,9 @@ export class SubagentSupervisor {
|
|
|
250
252
|
// default. "Omit the model and get what the parent is running on" is the default worth having, and it
|
|
251
253
|
// is a spawn-time flag because there is no RPC command to change it later.
|
|
252
254
|
const chosen = agent.model ?? (model?.provider && model.modelId ? { provider: model.provider, modelId: model.modelId } : undefined);
|
|
255
|
+
if (model)
|
|
256
|
+
await this.modelValidator?.(conversationId, model);
|
|
257
|
+
await this.modelValidator?.(conversationId, chosen ? { ...chosen, label: chosen.modelId } : undefined);
|
|
253
258
|
if (chosen)
|
|
254
259
|
args.push("--provider", chosen.provider, "--model", chosen.modelId);
|
|
255
260
|
if (agent.thinkingLevel)
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export type ModelFilterRule = {
|
|
2
|
+
id: string;
|
|
3
|
+
workspacePattern: string;
|
|
4
|
+
modelPattern: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
};
|
|
1
7
|
import type { WorkflowRun } from "./workflow.js";
|
|
2
8
|
export type RunStatus = "idle" | "running" | "completed" | "stopped" | "failed";
|
|
3
9
|
export type MessageRole = "user" | "assistant" | "tool" | "system";
|
|
@@ -499,6 +505,7 @@ export type BackgroundTaskSummary = {
|
|
|
499
505
|
* construct them - the same relationship `SubagentRun` already has with `subagent-runs.ts`.
|
|
500
506
|
*/
|
|
501
507
|
export type AppState = {
|
|
508
|
+
modelFilterRules?: ModelFilterRule[];
|
|
502
509
|
workspaces: Workspace[];
|
|
503
510
|
conversations: Conversation[];
|
|
504
511
|
subagentRuns: SubagentRun[];
|
|
@@ -614,7 +621,13 @@ export type ExtensionUiAnswer = {
|
|
|
614
621
|
confirmed?: boolean;
|
|
615
622
|
cancelled?: true;
|
|
616
623
|
};
|
|
617
|
-
export type ClientCommand = {
|
|
624
|
+
export type ClientCommand = ClientCommandBody & {
|
|
625
|
+
operationRequestId?: string;
|
|
626
|
+
};
|
|
627
|
+
export type ClientCommandBody = {
|
|
628
|
+
type: "model.rules.set";
|
|
629
|
+
rules: ModelFilterRule[];
|
|
630
|
+
} | {
|
|
618
631
|
type: "workspace.create";
|
|
619
632
|
name: string;
|
|
620
633
|
path?: string;
|
|
@@ -713,6 +726,7 @@ export type ClientCommand = {
|
|
|
713
726
|
/** Asks for the model/thinking catalog. Answered by `model.catalog`, never by an `error`. */
|
|
714
727
|
| {
|
|
715
728
|
type: "catalog.fetch";
|
|
729
|
+
conversationId?: string;
|
|
716
730
|
}
|
|
717
731
|
/** Asks for the agent catalog read from disk. Answered by `agent.catalog`. */
|
|
718
732
|
| {
|
|
@@ -839,6 +853,12 @@ export type ServerVersionInfo = {
|
|
|
839
853
|
needsRestart: boolean;
|
|
840
854
|
};
|
|
841
855
|
export type ServerEvent = {
|
|
856
|
+
type: "operation.result";
|
|
857
|
+
requestId: string;
|
|
858
|
+
ok: boolean;
|
|
859
|
+
message?: string;
|
|
860
|
+
entityId?: string;
|
|
861
|
+
} | {
|
|
842
862
|
type: "server.version";
|
|
843
863
|
version: ServerVersionInfo;
|
|
844
864
|
} | {
|
package/dist/server/ws-server.js
CHANGED
|
@@ -99,15 +99,22 @@ export class WsServer {
|
|
|
99
99
|
this.broadcaster.unregister(socket);
|
|
100
100
|
});
|
|
101
101
|
socket.on("message", async (raw) => {
|
|
102
|
+
let command;
|
|
102
103
|
try {
|
|
103
|
-
|
|
104
|
-
await this.commandHandler.handleCommand(command, (event) => {
|
|
104
|
+
command = JSON.parse(raw.toString());
|
|
105
|
+
const entityId = await this.commandHandler.handleCommand(command, (event) => {
|
|
105
106
|
if (socket.readyState === WebSocket.OPEN) {
|
|
106
107
|
socket.send(JSON.stringify(event));
|
|
107
108
|
}
|
|
108
109
|
});
|
|
110
|
+
if (command.operationRequestId)
|
|
111
|
+
socket.send(JSON.stringify({ type: "operation.result", requestId: command.operationRequestId, ok: true, entityId }));
|
|
109
112
|
}
|
|
110
113
|
catch (error) {
|
|
114
|
+
if (command?.operationRequestId) {
|
|
115
|
+
socket.send(JSON.stringify({ type: "operation.result", requestId: command.operationRequestId, ok: false, message: error instanceof Error ? error.message : String(error) }));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
111
118
|
socket.send(JSON.stringify({
|
|
112
119
|
type: "error",
|
|
113
120
|
message: error instanceof Error ? error.message : "command failed",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"scripts/fork-server.test.mjs",
|
|
75
75
|
"scripts/styles-no-dangling.test.mjs",
|
|
76
76
|
"scripts/theme-no-hardcoded-colours.test.mjs",
|
|
77
|
-
"scripts/agent-config-regression.test.mjs"
|
|
77
|
+
"scripts/agent-config-regression.test.mjs",
|
|
78
|
+
"scripts/model-filter.test.mjs"
|
|
78
79
|
],
|
|
79
80
|
"check:browser": [
|
|
80
81
|
"scripts/extension-ui-browser.test.mjs",
|
|
@@ -106,7 +107,8 @@
|
|
|
106
107
|
"scripts/file-tab-selected-contrast.test.mjs",
|
|
107
108
|
"scripts/help-window.test.mjs",
|
|
108
109
|
"scripts/composer-regression-browser.test.mjs",
|
|
109
|
-
"scripts/workspace-picker-browser.test.mjs"
|
|
110
|
+
"scripts/workspace-picker-browser.test.mjs",
|
|
111
|
+
"scripts/optimistic-browser.test.mjs"
|
|
110
112
|
],
|
|
111
113
|
"dependencies": {
|
|
112
114
|
"ws": "latest"
|