@cjhyy/code-shell-core 0.8.13 → 0.9.0
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/automation/desktop-authority-client.d.ts +9 -0
- package/dist/automation/desktop-authority-client.js +47 -0
- package/dist/automation/scheduler.d.ts +8 -0
- package/dist/automation/scheduler.js +37 -1
- package/dist/automation/store.js +14 -1
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/cli/agent-server-stdio.js +8 -4
- package/dist/engine/engine-workspace-authority.d.ts +35 -0
- package/dist/engine/engine-workspace-authority.js +136 -0
- package/dist/engine/engine.d.ts +5 -10
- package/dist/engine/engine.js +108 -112
- package/dist/engine/input-attachments.d.ts +1 -0
- package/dist/engine/input-attachments.js +6 -2
- package/dist/engine/run-environment.d.ts +8 -3
- package/dist/engine/run-environment.js +33 -8
- package/dist/engine/run-image-input.d.ts +1 -0
- package/dist/engine/run-image-input.js +1 -0
- package/dist/engine/run-session-open.d.ts +2 -1
- package/dist/engine/run-session-open.js +7 -1
- package/dist/engine/run-setup.d.ts +1 -0
- package/dist/engine/run-setup.js +2 -1
- package/dist/engine/run-tooling.d.ts +2 -0
- package/dist/engine/run-tooling.js +3 -0
- package/dist/engine/run-types.d.ts +4 -0
- package/dist/engine/run-workspace.d.ts +6 -1
- package/dist/engine/run-workspace.js +47 -0
- package/dist/engine/subagent-spawner.d.ts +1 -0
- package/dist/engine/subagent-spawner.js +1 -0
- package/dist/engine/turn-loop.js +17 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.extension.d.ts +1 -1
- package/dist/index.internal.d.ts +3 -2
- package/dist/index.internal.js +2 -0
- package/dist/index.js +2 -2
- package/dist/llm/client-base.d.ts +2 -1
- package/dist/llm/client-base.js +3 -1
- package/dist/llm/providers/anthropic.js +24 -25
- package/dist/llm/providers/openai.d.ts +4 -1
- package/dist/llm/providers/openai.js +76 -13
- package/dist/panel-apps/index.d.ts +1 -1
- package/dist/panel-apps/index.js +1 -1
- package/dist/panel-apps/installer.d.ts +29 -0
- package/dist/panel-apps/installer.js +124 -15
- package/dist/plugins/pluginAutomationTemplates.d.ts +2 -0
- package/dist/plugins/pluginAutomationTemplates.js +4 -0
- package/dist/plugins/pluginCatalog.d.ts +6 -0
- package/dist/plugins/pluginCatalog.js +7 -2
- package/dist/plugins/pluginContent.d.ts +1 -1
- package/dist/plugins/pluginContent.js +2 -10
- package/dist/prompt/composer.d.ts +4 -1
- package/dist/prompt/composer.js +11 -3
- package/dist/protocol/chat-session-manager.d.ts +42 -1
- package/dist/protocol/chat-session-manager.js +167 -4
- package/dist/protocol/chat-session.d.ts +11 -1
- package/dist/protocol/chat-session.js +20 -2
- package/dist/protocol/mobile-remote-types.d.ts +15 -0
- package/dist/protocol/server.d.ts +1 -2
- package/dist/protocol/server.js +28 -51
- package/dist/protocol/session-workspace-rpc.d.ts +23 -0
- package/dist/protocol/session-workspace-rpc.js +171 -0
- package/dist/protocol/types.d.ts +47 -1
- package/dist/protocol/types.js +4 -0
- package/dist/session/session-manager.d.ts +25 -0
- package/dist/session/session-manager.js +106 -6
- package/dist/session/transcript.d.ts +9 -0
- package/dist/session/transcript.js +81 -0
- package/dist/settings/manager.d.ts +12 -0
- package/dist/settings/manager.js +31 -0
- package/dist/tool-system/builtin/configure-model-connection.d.ts +36 -0
- package/dist/tool-system/builtin/configure-model-connection.js +396 -0
- package/dist/tool-system/builtin/cron.d.ts +11 -0
- package/dist/tool-system/builtin/cron.js +27 -2
- package/dist/tool-system/builtin/edit-model-catalog.d.ts +4 -6
- package/dist/tool-system/builtin/edit-model-catalog.js +5 -8
- package/dist/tool-system/builtin/edit.js +5 -3
- package/dist/tool-system/builtin/index.js +14 -0
- package/dist/tool-system/builtin/install-capability.js +22 -8
- package/dist/tool-system/builtin/settings-changed.d.ts +9 -0
- package/dist/tool-system/builtin/settings-changed.js +18 -0
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +12 -3
- package/dist/tool-system/executor.js +1 -1
- package/dist/tool-system/path-policy.d.ts +11 -3
- package/dist/tool-system/path-policy.js +56 -36
- package/dist/types.d.ts +11 -0
- package/dist/workspace/canonical-key.d.ts +7 -0
- package/dist/workspace/canonical-key.js +39 -0
- package/dist/workspace/workspace-context.d.ts +26 -0
- package/dist/workspace/workspace-context.js +112 -0
- package/package.json +1 -1
|
@@ -41,6 +41,24 @@ function cachedTokensOf(usage) {
|
|
|
41
41
|
out.cacheCreationTokens = details.cache_write_tokens;
|
|
42
42
|
return out;
|
|
43
43
|
}
|
|
44
|
+
/** Read the effective output-token ceiling from a built Chat Completions request. */
|
|
45
|
+
function outputTokenLimitOf(requestBody) {
|
|
46
|
+
const value = requestBody.max_completion_tokens ?? requestBody.max_tokens;
|
|
47
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Some OpenAI-compatible gateways return finish_reason `tool_calls` when the
|
|
51
|
+
* output ceiling cuts a function-argument JSON string off. The malformed JSON
|
|
52
|
+
* plus exact cap usage is the evidence that distinguishes this from an ordinary
|
|
53
|
+
* completed tool call.
|
|
54
|
+
*/
|
|
55
|
+
function didHitToolArgumentOutputLimit(input) {
|
|
56
|
+
return (input.finishReason === "tool_calls" &&
|
|
57
|
+
input.malformedToolArguments &&
|
|
58
|
+
input.outputTokenLimit !== undefined &&
|
|
59
|
+
input.completionTokens !== undefined &&
|
|
60
|
+
input.completionTokens >= input.outputTokenLimit);
|
|
61
|
+
}
|
|
44
62
|
/**
|
|
45
63
|
* Consume an async iterable of stream chunks with an idle watchdog.
|
|
46
64
|
* Returns the accumulated text — either from onChunk return values, or
|
|
@@ -193,6 +211,7 @@ function normalizeOpenAIToolMessagePairs(messages) {
|
|
|
193
211
|
}
|
|
194
212
|
export class OpenAIClient extends LLMClientBase {
|
|
195
213
|
_client = null;
|
|
214
|
+
dangerouslyAllowBrowser;
|
|
196
215
|
// Sticky override: once the endpoint tells us `max_tokens` is rejected for
|
|
197
216
|
// this model, switch to `max_completion_tokens` for the lifetime of the
|
|
198
217
|
// client. Cheaper and more reliable than re-deriving from the model id when
|
|
@@ -205,8 +224,9 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
205
224
|
// succeed. Omitting the field just means "model default reasoning", which is
|
|
206
225
|
// fine for our background/aux calls.
|
|
207
226
|
_dropReasoningEffort = false;
|
|
208
|
-
constructor(config, defaults) {
|
|
227
|
+
constructor(config, defaults, runtimeOptions = {}) {
|
|
209
228
|
super(config, defaults);
|
|
229
|
+
this.dangerouslyAllowBrowser = runtimeOptions.dangerouslyAllowBrowser === true;
|
|
210
230
|
}
|
|
211
231
|
initClient() {
|
|
212
232
|
// Lazy init — client created on first use
|
|
@@ -222,6 +242,8 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
222
242
|
(Object.keys(headers).length > 0 ? "x-headers-auth" : undefined),
|
|
223
243
|
...(this.config.baseUrl ? { baseURL: this.config.baseUrl } : {}),
|
|
224
244
|
...(Object.keys(headers).length > 0 ? { defaultHeaders: headers } : {}),
|
|
245
|
+
...(this.fetch ? { fetch: this.fetch } : {}),
|
|
246
|
+
...(this.dangerouslyAllowBrowser ? { dangerouslyAllowBrowser: true } : {}),
|
|
225
247
|
timeout: this.timeout,
|
|
226
248
|
});
|
|
227
249
|
}
|
|
@@ -473,7 +495,8 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
473
495
|
}
|
|
474
496
|
async nonStreamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
475
497
|
try {
|
|
476
|
-
const
|
|
498
|
+
const requestBody = this.buildRequestBody(options, messages, tools, reasoning, false);
|
|
499
|
+
const response = await this.client.chat.completions.create(requestBody, { signal: requestSignal ?? options.signal });
|
|
477
500
|
const choice = response.choices[0];
|
|
478
501
|
if (!choice)
|
|
479
502
|
throw new LLMError("No response from OpenAI", "openai");
|
|
@@ -486,7 +509,7 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
486
509
|
...cachedTokensOf(response.usage),
|
|
487
510
|
};
|
|
488
511
|
this.recordUsage(usage, options);
|
|
489
|
-
return this.processChoice(choice, usage);
|
|
512
|
+
return this.processChoice(choice, usage, outputTokenLimitOf(requestBody));
|
|
490
513
|
}
|
|
491
514
|
catch (err) {
|
|
492
515
|
this.handleApiError(err);
|
|
@@ -496,7 +519,9 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
496
519
|
async streamMessage(options, messages, tools, reasoning, requestSignal) {
|
|
497
520
|
const sdkSignal = requestSignal ?? options.signal;
|
|
498
521
|
try {
|
|
499
|
-
const
|
|
522
|
+
const requestBody = this.buildRequestBody(options, messages, tools, reasoning, true);
|
|
523
|
+
const stream = await this.client.chat.completions.create(requestBody, { signal: sdkSignal });
|
|
524
|
+
const outputTokenLimit = outputTokenLimitOf(requestBody);
|
|
500
525
|
let text = "";
|
|
501
526
|
let reasoningContent = "";
|
|
502
527
|
const toolCallsMap = new Map();
|
|
@@ -605,6 +630,7 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
605
630
|
signal: sdkSignal,
|
|
606
631
|
});
|
|
607
632
|
const toolCalls = [];
|
|
633
|
+
let malformedToolArguments = false;
|
|
608
634
|
for (const [, tc] of toolCallsMap) {
|
|
609
635
|
// Drop incomplete tool calls: an empty id or name (the fallbacks set
|
|
610
636
|
// when a delta never delivered them) would become a malformed call
|
|
@@ -621,9 +647,16 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
621
647
|
args = JSON.parse(tc.args || "{}");
|
|
622
648
|
}
|
|
623
649
|
catch {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
650
|
+
malformedToolArguments = true;
|
|
651
|
+
// Keep the historical empty-args fallback for genuinely malformed
|
|
652
|
+
// model output. If usage also proves the configured output ceiling was
|
|
653
|
+
// reached, the stop reason is normalized to "length" below so the turn
|
|
654
|
+
// loop retries instead of executing this misleading empty call.
|
|
655
|
+
logger.warn("openai.malformed_tool_arguments", {
|
|
656
|
+
id: tc.id,
|
|
657
|
+
name: tc.name,
|
|
658
|
+
argumentChars: tc.args.length,
|
|
659
|
+
});
|
|
627
660
|
}
|
|
628
661
|
toolCalls.push({ id: tc.id, toolName: tc.name, args });
|
|
629
662
|
}
|
|
@@ -634,11 +667,31 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
634
667
|
...cachedTokensOf(streamUsage),
|
|
635
668
|
};
|
|
636
669
|
this.recordUsage(usage, options);
|
|
670
|
+
const inferredToolArgumentTruncation = didHitToolArgumentOutputLimit({
|
|
671
|
+
finishReason,
|
|
672
|
+
malformedToolArguments,
|
|
673
|
+
completionTokens: usage.completionTokens,
|
|
674
|
+
outputTokenLimit,
|
|
675
|
+
});
|
|
676
|
+
if (inferredToolArgumentTruncation) {
|
|
677
|
+
logger.warn("openai.tool_arguments_truncation_inferred", {
|
|
678
|
+
provider: this.provider,
|
|
679
|
+
model: this.model,
|
|
680
|
+
finishReason,
|
|
681
|
+
completionTokens: usage.completionTokens,
|
|
682
|
+
outputTokenLimit,
|
|
683
|
+
toolCount: toolCalls.length,
|
|
684
|
+
});
|
|
685
|
+
}
|
|
637
686
|
return {
|
|
638
687
|
text,
|
|
639
688
|
toolCalls,
|
|
640
689
|
usage,
|
|
641
|
-
|
|
690
|
+
// OpenRouter can report `tool_calls` even when max_tokens cuts a tool's
|
|
691
|
+
// argument JSON off. Normalize that proven cap hit to the standard
|
|
692
|
+
// OpenAI `length` spelling so the shared continuation guard can skip
|
|
693
|
+
// execution and ask the model to retry with a smaller call.
|
|
694
|
+
stopReason: inferredToolArgumentTruncation ? "length" : (finishReason ?? "stop"),
|
|
642
695
|
...(reasoningContent ? { reasoningContent } : {}),
|
|
643
696
|
};
|
|
644
697
|
}
|
|
@@ -647,9 +700,10 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
647
700
|
throw err;
|
|
648
701
|
}
|
|
649
702
|
}
|
|
650
|
-
processChoice(choice, usage) {
|
|
703
|
+
processChoice(choice, usage, outputTokenLimit) {
|
|
651
704
|
const text = choice.message.content ?? "";
|
|
652
705
|
const toolCalls = [];
|
|
706
|
+
let malformedToolArguments = false;
|
|
653
707
|
if (choice.message.tool_calls) {
|
|
654
708
|
for (const tc of choice.message.tool_calls) {
|
|
655
709
|
let args = {};
|
|
@@ -657,9 +711,12 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
657
711
|
args = JSON.parse(tc.function.arguments || "{}");
|
|
658
712
|
}
|
|
659
713
|
catch {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
714
|
+
malformedToolArguments = true;
|
|
715
|
+
logger.warn("openai.malformed_tool_arguments", {
|
|
716
|
+
id: tc.id,
|
|
717
|
+
name: tc.function.name,
|
|
718
|
+
argumentChars: tc.function.arguments?.length ?? 0,
|
|
719
|
+
});
|
|
663
720
|
}
|
|
664
721
|
toolCalls.push({
|
|
665
722
|
id: tc.id,
|
|
@@ -669,11 +726,17 @@ export class OpenAIClient extends LLMClientBase {
|
|
|
669
726
|
}
|
|
670
727
|
}
|
|
671
728
|
const reasoningContent = extractReasoningContent(choice.message);
|
|
729
|
+
const inferredToolArgumentTruncation = didHitToolArgumentOutputLimit({
|
|
730
|
+
finishReason: choice.finish_reason ?? undefined,
|
|
731
|
+
malformedToolArguments,
|
|
732
|
+
completionTokens: usage.completionTokens,
|
|
733
|
+
outputTokenLimit,
|
|
734
|
+
});
|
|
672
735
|
return {
|
|
673
736
|
text,
|
|
674
737
|
toolCalls,
|
|
675
738
|
usage,
|
|
676
|
-
stopReason: choice.finish_reason ?? undefined,
|
|
739
|
+
stopReason: inferredToolArgumentTruncation ? "length" : (choice.finish_reason ?? undefined),
|
|
677
740
|
...(reasoningContent ? { reasoningContent } : {}),
|
|
678
741
|
};
|
|
679
742
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { PANEL_APP_ICONS, PANEL_APP_MANIFEST_FILE, PANEL_APP_PERMISSIONS, PanelAppAgentContribution, PanelAppAgentTool, PanelAppManifest, type PanelAppAgentContribution as PanelAppAgentContributionData, type PanelAppAgentTool as PanelAppAgentToolData, type PanelAppManifest as PanelAppManifestData, } from "./manifest.js";
|
|
2
2
|
export { PanelAppAlreadyInstalledError, PanelAppInstallError, PanelAppReviewChangedError, assertSafePanelAppId, panelAppInstallDir, panelAppsRegistryPath, panelAppsRoot, } from "./paths.js";
|
|
3
|
-
export { installReviewedLocalPanelApp, installReviewedPanelAppUpdate, listInstalledPanelApps, previewInstalledPanelAppUpdate, previewLocalPanelApp, uninstallPanelApp, type InstalledPanelApp, type InstalledPanelAppSource, type GitPanelAppSourceInput, type LocalPanelAppSourceInput, type PanelAppSourceInput, type PanelAppPreview, } from "./installer.js";
|
|
3
|
+
export { discoverGitPanelApps, installReviewedLocalPanelApp, installReviewedPanelAppUpdate, listInstalledPanelApps, previewInstalledPanelAppUpdate, previewLocalPanelApp, uninstallPanelApp, type InstalledPanelApp, type InstalledPanelAppSource, type GitPanelAppSourceInput, type GitPanelAppDiscovery, type GitPanelAppDiscoveryCandidate, type GitPanelAppDiscoveryIssue, type LocalPanelAppSourceInput, type PanelAppSourceInput, type PanelAppPreview, } from "./installer.js";
|
|
4
4
|
export { isPanelAppBound, resolvePanelAppBindingPolicy, resolvePanelAppBindingProjectPath, type PanelAppBindingPolicy, } from "./bindings.js";
|
package/dist/panel-apps/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { PANEL_APP_ICONS, PANEL_APP_MANIFEST_FILE, PANEL_APP_PERMISSIONS, PanelAppAgentContribution, PanelAppAgentTool, PanelAppManifest, } from "./manifest.js";
|
|
2
2
|
export { PanelAppAlreadyInstalledError, PanelAppInstallError, PanelAppReviewChangedError, assertSafePanelAppId, panelAppInstallDir, panelAppsRegistryPath, panelAppsRoot, } from "./paths.js";
|
|
3
|
-
export { installReviewedLocalPanelApp, installReviewedPanelAppUpdate, listInstalledPanelApps, previewInstalledPanelAppUpdate, previewLocalPanelApp, uninstallPanelApp, } from "./installer.js";
|
|
3
|
+
export { discoverGitPanelApps, installReviewedLocalPanelApp, installReviewedPanelAppUpdate, listInstalledPanelApps, previewInstalledPanelAppUpdate, previewLocalPanelApp, uninstallPanelApp, } from "./installer.js";
|
|
4
4
|
export { isPanelAppBound, resolvePanelAppBindingPolicy, resolvePanelAppBindingProjectPath, } from "./bindings.js";
|
|
@@ -33,6 +33,29 @@ export interface PanelAppPreview {
|
|
|
33
33
|
};
|
|
34
34
|
warnings: string[];
|
|
35
35
|
}
|
|
36
|
+
export interface GitPanelAppDiscoveryCandidate {
|
|
37
|
+
/** Repository-relative path, or "." when the repository root is the app. */
|
|
38
|
+
subdir: string;
|
|
39
|
+
source: GitPanelAppSourceInput;
|
|
40
|
+
id: string;
|
|
41
|
+
version: string;
|
|
42
|
+
title: {
|
|
43
|
+
default: string;
|
|
44
|
+
en?: string;
|
|
45
|
+
"zh-CN"?: string;
|
|
46
|
+
};
|
|
47
|
+
description?: string;
|
|
48
|
+
icon: PanelAppManifest["icon"];
|
|
49
|
+
}
|
|
50
|
+
export interface GitPanelAppDiscoveryIssue {
|
|
51
|
+
subdir: string;
|
|
52
|
+
error: string;
|
|
53
|
+
}
|
|
54
|
+
export interface GitPanelAppDiscovery {
|
|
55
|
+
source: GitPanelAppSourceInput;
|
|
56
|
+
panels: GitPanelAppDiscoveryCandidate[];
|
|
57
|
+
issues: GitPanelAppDiscoveryIssue[];
|
|
58
|
+
}
|
|
36
59
|
export interface InstalledPanelApp {
|
|
37
60
|
id: string;
|
|
38
61
|
version: string;
|
|
@@ -52,6 +75,12 @@ export interface InstalledPanelApp {
|
|
|
52
75
|
installedAt: string;
|
|
53
76
|
lastUpdated: string;
|
|
54
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Download a public GitHub repository once and enumerate every independently
|
|
80
|
+
* installable Panel App beneath it. Discovery is read-only: selecting a result
|
|
81
|
+
* still runs the normal full review and digest-bound install flow.
|
|
82
|
+
*/
|
|
83
|
+
export declare function discoverGitPanelApps(input: GitPanelAppSourceInput): Promise<GitPanelAppDiscovery>;
|
|
55
84
|
export declare function previewLocalPanelApp(input: PanelAppSourceInput): Promise<PanelAppPreview>;
|
|
56
85
|
/**
|
|
57
86
|
* Re-open the original folder, archive, or GitHub source through the same
|
|
@@ -16,6 +16,9 @@ const MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
|
16
16
|
const MAX_DEPTH = 16;
|
|
17
17
|
const MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
18
18
|
const MAX_AGENT_SKILL_BYTES = 256 * 1024;
|
|
19
|
+
const MAX_PANEL_DISCOVERY_DEPTH = 4;
|
|
20
|
+
const MAX_PANEL_DISCOVERY_DIRECTORIES = 512;
|
|
21
|
+
const MAX_PANEL_DISCOVERY_RESULTS = 16;
|
|
19
22
|
const REVIEWED_GIT_SNAPSHOT_TTL_MS = 10 * 60 * 1000;
|
|
20
23
|
const MAX_REVIEWED_GIT_SNAPSHOTS = 8;
|
|
21
24
|
const ALLOWED_ASSET_EXTENSIONS = new Set([
|
|
@@ -61,9 +64,14 @@ function normalizeGitPanelAppSource(input) {
|
|
|
61
64
|
if (!raw || raw.length > MAX_SOURCE_PATH || raw.includes("\0")) {
|
|
62
65
|
throw new PanelAppInstallError("GitHub repository URL is invalid");
|
|
63
66
|
}
|
|
67
|
+
const urlText = /^[A-Za-z0-9_.-]{1,100}\/[A-Za-z0-9_.-]{1,100}(?:\.git)?\/?$/.test(raw)
|
|
68
|
+
? `https://github.com/${raw}`
|
|
69
|
+
: /^github\.com\//i.test(raw)
|
|
70
|
+
? `https://${raw}`
|
|
71
|
+
: raw;
|
|
64
72
|
let parsed;
|
|
65
73
|
try {
|
|
66
|
-
parsed = new URL(
|
|
74
|
+
parsed = new URL(urlText);
|
|
67
75
|
}
|
|
68
76
|
catch {
|
|
69
77
|
throw new PanelAppInstallError("GitHub repository URL is invalid");
|
|
@@ -135,19 +143,52 @@ function normalizedGitSourceKey(input) {
|
|
|
135
143
|
function looksLikePanelAppRoot(directory) {
|
|
136
144
|
return existsSync(join(directory, PANEL_APP_MANIFEST_FILE));
|
|
137
145
|
}
|
|
146
|
+
async function discoverPanelAppRoots(directory) {
|
|
147
|
+
const found = [];
|
|
148
|
+
const pending = [{ directory, depth: 0 }];
|
|
149
|
+
let visited = 0;
|
|
150
|
+
while (pending.length > 0 &&
|
|
151
|
+
visited < MAX_PANEL_DISCOVERY_DIRECTORIES &&
|
|
152
|
+
found.length < MAX_PANEL_DISCOVERY_RESULTS) {
|
|
153
|
+
const current = pending.shift();
|
|
154
|
+
visited += 1;
|
|
155
|
+
if (looksLikePanelAppRoot(current.directory)) {
|
|
156
|
+
found.push(current.directory);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (current.depth >= MAX_PANEL_DISCOVERY_DEPTH)
|
|
160
|
+
continue;
|
|
161
|
+
const entries = await readdir(current.directory, { withFileTypes: true });
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
pending.push({ directory: join(current.directory, entry.name), depth: current.depth + 1 });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return found;
|
|
170
|
+
}
|
|
138
171
|
async function findPanelAppRoot(directory) {
|
|
139
172
|
if (looksLikePanelAppRoot(directory))
|
|
140
173
|
return directory;
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
174
|
+
const found = await discoverPanelAppRoots(directory);
|
|
175
|
+
if (found.length === 1)
|
|
176
|
+
return found[0];
|
|
177
|
+
if (found.length > 1) {
|
|
178
|
+
const candidates = found
|
|
179
|
+
.map((root) => relative(directory, root).split(sep).join(posix.sep))
|
|
180
|
+
.sort()
|
|
181
|
+
.join(", ");
|
|
182
|
+
throw new PanelAppInstallError(`multiple Panel Apps found; choose an app subdirectory: ${candidates}`);
|
|
183
|
+
}
|
|
184
|
+
throw new PanelAppInstallError(`no Panel App found (expected ${PANEL_APP_MANIFEST_FILE}); ` +
|
|
185
|
+
"for a monorepo, provide the app subdirectory or a GitHub /tree/<ref>/<path> URL");
|
|
149
186
|
}
|
|
150
|
-
|
|
187
|
+
function joinedPanelAppSubdirectory(base, nested) {
|
|
188
|
+
const parts = [base, nested].filter((part) => Boolean(part));
|
|
189
|
+
return parts.length > 0 ? parts.join("/") : undefined;
|
|
190
|
+
}
|
|
191
|
+
async function openGitPanelAppArchive(input) {
|
|
151
192
|
const source = normalizeGitPanelAppSource(input);
|
|
152
193
|
const temporaryRoot = await mkdtemp(join(tmpdir(), "cs-panel-app-git-"));
|
|
153
194
|
try {
|
|
@@ -157,18 +198,86 @@ async function openGitPanelAppSource(input) {
|
|
|
157
198
|
await downloadGitHubPanelAppArchive(source, archivePath);
|
|
158
199
|
await extractZipSubdirectory(archivePath, extractedRoot, source.subdir);
|
|
159
200
|
await rm(archivePath, { force: true });
|
|
201
|
+
return { source, extractedRoot, temporaryRoot };
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
await rm(temporaryRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function openGitPanelAppSource(input) {
|
|
209
|
+
const opened = await openGitPanelAppArchive(input);
|
|
210
|
+
try {
|
|
160
211
|
return {
|
|
161
|
-
source,
|
|
162
|
-
sourceKey: JSON.stringify(source),
|
|
163
|
-
sourceRoot: await findPanelAppRoot(extractedRoot),
|
|
164
|
-
temporaryRoot,
|
|
212
|
+
source: opened.source,
|
|
213
|
+
sourceKey: JSON.stringify(opened.source),
|
|
214
|
+
sourceRoot: await findPanelAppRoot(opened.extractedRoot),
|
|
215
|
+
temporaryRoot: opened.temporaryRoot,
|
|
165
216
|
};
|
|
166
217
|
}
|
|
167
218
|
catch (error) {
|
|
168
|
-
await rm(temporaryRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
219
|
+
await rm(opened.temporaryRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
169
220
|
throw error;
|
|
170
221
|
}
|
|
171
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Download a public GitHub repository once and enumerate every independently
|
|
225
|
+
* installable Panel App beneath it. Discovery is read-only: selecting a result
|
|
226
|
+
* still runs the normal full review and digest-bound install flow.
|
|
227
|
+
*/
|
|
228
|
+
export async function discoverGitPanelApps(input) {
|
|
229
|
+
const opened = await openGitPanelAppArchive(input);
|
|
230
|
+
try {
|
|
231
|
+
const roots = await discoverPanelAppRoots(opened.extractedRoot);
|
|
232
|
+
if (roots.length === 0) {
|
|
233
|
+
throw new PanelAppInstallError(`no Panel App found (expected ${PANEL_APP_MANIFEST_FILE}); ` +
|
|
234
|
+
"check the repository, branch, or optional search subdirectory");
|
|
235
|
+
}
|
|
236
|
+
const panels = [];
|
|
237
|
+
const issues = [];
|
|
238
|
+
for (const root of roots) {
|
|
239
|
+
const nested = relative(opened.extractedRoot, root).split(sep).join(posix.sep);
|
|
240
|
+
const subdir = joinedPanelAppSubdirectory(opened.source.subdir, nested);
|
|
241
|
+
const label = subdir ?? ".";
|
|
242
|
+
try {
|
|
243
|
+
const inspected = await inspectPanelAppSource(root);
|
|
244
|
+
panels.push({
|
|
245
|
+
subdir: label,
|
|
246
|
+
source: {
|
|
247
|
+
kind: "git",
|
|
248
|
+
url: opened.source.url,
|
|
249
|
+
...(opened.source.ref ? { ref: opened.source.ref } : {}),
|
|
250
|
+
...(subdir ? { subdir } : {}),
|
|
251
|
+
},
|
|
252
|
+
id: inspected.manifest.id,
|
|
253
|
+
version: inspected.manifest.version,
|
|
254
|
+
title: inspected.manifest.title,
|
|
255
|
+
...(inspected.manifest.description
|
|
256
|
+
? { description: inspected.manifest.description }
|
|
257
|
+
: {}),
|
|
258
|
+
icon: inspected.manifest.icon,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
issues.push({
|
|
263
|
+
subdir: label,
|
|
264
|
+
error: error instanceof Error ? error.message : String(error),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
panels.sort((left, right) => left.subdir.localeCompare(right.subdir));
|
|
269
|
+
issues.sort((left, right) => left.subdir.localeCompare(right.subdir));
|
|
270
|
+
if (panels.length === 0) {
|
|
271
|
+
throw new PanelAppInstallError(`found ${issues.length} Panel App manifest(s), but none passed validation: ${issues
|
|
272
|
+
.map((issue) => `${issue.subdir}: ${issue.error}`)
|
|
273
|
+
.join("; ")}`);
|
|
274
|
+
}
|
|
275
|
+
return { source: opened.source, panels, issues };
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
await rm(opened.temporaryRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
172
281
|
async function disposeReviewedGitSnapshot(snapshot) {
|
|
173
282
|
clearTimeout(snapshot.expiryTimer);
|
|
174
283
|
await rm(snapshot.temporaryRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
@@ -6,6 +6,8 @@ export interface InstantiatePluginAutomationTemplateOptions {
|
|
|
6
6
|
templateId: string;
|
|
7
7
|
expectedRevision: string;
|
|
8
8
|
cwd?: string;
|
|
9
|
+
projectId?: string;
|
|
10
|
+
rootId?: string;
|
|
9
11
|
/** Effective project/global disable list computed by the host for this cwd. */
|
|
10
12
|
disabledPluginNames: ReadonlySet<string>;
|
|
11
13
|
maxJobs?: number;
|
|
@@ -38,6 +38,10 @@ export function instantiatePluginAutomationTemplate(options) {
|
|
|
38
38
|
const template = contribution.template;
|
|
39
39
|
return options.scheduler.create(template.title.default, template.schedule, template.prompt, {
|
|
40
40
|
...(template.workspace === "current" && options.cwd ? { cwd: options.cwd } : {}),
|
|
41
|
+
...(template.workspace === "current" && options.projectId
|
|
42
|
+
? { projectId: options.projectId }
|
|
43
|
+
: {}),
|
|
44
|
+
...(template.workspace === "current" && options.rootId ? { rootId: options.rootId } : {}),
|
|
41
45
|
...(template.timezone ? { timezone: template.timezone } : {}),
|
|
42
46
|
permissionLevel: template.permissionLevel,
|
|
43
47
|
templateSource: {
|
|
@@ -33,6 +33,12 @@ export interface PluginAutomationTemplateContribution {
|
|
|
33
33
|
* instantiated later. Install paths and timestamps are intentionally excluded.
|
|
34
34
|
*/
|
|
35
35
|
export declare function pluginAutomationTemplateRevision(installKey: string, template: PluginAutomationTemplate): string;
|
|
36
|
+
/**
|
|
37
|
+
* Read an installed plugin manifest without following links or accepting an
|
|
38
|
+
* unbounded/non-regular file. Shared by the runtime catalog and UI inventory so
|
|
39
|
+
* those views cannot disagree about which manifest bytes are trusted.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readCatalogPluginManifest(installPath: string): CanonicalPluginManifestData | null;
|
|
36
42
|
/**
|
|
37
43
|
* Load the installed plugin catalog at the core boundary.
|
|
38
44
|
*
|
|
@@ -19,7 +19,12 @@ export function pluginAutomationTemplateRevision(installKey, template) {
|
|
|
19
19
|
.update(JSON.stringify(template))
|
|
20
20
|
.digest("hex");
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Read an installed plugin manifest without following links or accepting an
|
|
24
|
+
* unbounded/non-regular file. Shared by the runtime catalog and UI inventory so
|
|
25
|
+
* those views cannot disagree about which manifest bytes are trusted.
|
|
26
|
+
*/
|
|
27
|
+
export function readCatalogPluginManifest(installPath) {
|
|
23
28
|
const file = join(installPath, CANONICAL_PLUGIN_MANIFEST_FILE);
|
|
24
29
|
let descriptor;
|
|
25
30
|
try {
|
|
@@ -69,7 +74,7 @@ export function loadPluginCatalog(options = {}) {
|
|
|
69
74
|
const installPath = resolveSafePluginPath(entry.installPath, root);
|
|
70
75
|
if (!installPath)
|
|
71
76
|
continue;
|
|
72
|
-
const manifest =
|
|
77
|
+
const manifest = readCatalogPluginManifest(installPath);
|
|
73
78
|
const identity = identityFromInstallKey(installKey, manifest);
|
|
74
79
|
catalog.push({
|
|
75
80
|
installKey,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type PluginHookEntry } from "./loadPluginHooks.js";
|
|
9
9
|
import { type PluginHookReview } from "./pluginHookApproval.js";
|
|
10
|
-
import {
|
|
10
|
+
import type { PluginAutomationTemplate } from "./installer/types.js";
|
|
11
11
|
export type PluginAutomationTemplateDescriptor = PluginAutomationTemplate & {
|
|
12
12
|
revision: string;
|
|
13
13
|
};
|
|
@@ -11,8 +11,7 @@ import { parseFrontmatter } from "../skills/frontmatter.js";
|
|
|
11
11
|
import { listPluginHooks } from "./loadPluginHooks.js";
|
|
12
12
|
import { reviewPluginHooks } from "./pluginHookApproval.js";
|
|
13
13
|
import { readPluginMcp } from "./installer/loadPluginMcp.js";
|
|
14
|
-
import {
|
|
15
|
-
import { pluginAutomationTemplateRevision } from "./pluginCatalog.js";
|
|
14
|
+
import { pluginAutomationTemplateRevision, readCatalogPluginManifest } from "./pluginCatalog.js";
|
|
16
15
|
function resolveContainedPath(root, candidate) {
|
|
17
16
|
try {
|
|
18
17
|
const realRoot = realpathSync(root);
|
|
@@ -117,14 +116,7 @@ export function describePluginContent(pluginName, installPath, installKey) {
|
|
|
117
116
|
return undefined;
|
|
118
117
|
}
|
|
119
118
|
})();
|
|
120
|
-
const manifest = (
|
|
121
|
-
try {
|
|
122
|
-
return CanonicalPluginManifest.parse(JSON.parse(readFileSync(join(installPath, CANONICAL_PLUGIN_MANIFEST_FILE), "utf-8")));
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
})();
|
|
119
|
+
const manifest = readCatalogPluginManifest(installPath);
|
|
128
120
|
return {
|
|
129
121
|
skills: listSkills(installPath),
|
|
130
122
|
commands: listMdNames(installPath, "commands"),
|
|
@@ -10,8 +10,11 @@ import { type ScanOptions } from "./instruction-scanner.js";
|
|
|
10
10
|
import { type AgentPreset } from "../preset/index.js";
|
|
11
11
|
import type { BuiltinTool } from "../tool-system/builtin/index.js";
|
|
12
12
|
import type { CapabilityDynamicContextProvider } from "../capabilities/index.js";
|
|
13
|
+
import { type WorkspaceContext } from "../workspace/workspace-context.js";
|
|
13
14
|
export interface ComposerOptions {
|
|
14
15
|
cwd: string;
|
|
16
|
+
/** Run workspace; omitted by legacy callers that only provide cwd. */
|
|
17
|
+
workspace?: WorkspaceContext;
|
|
15
18
|
model: string;
|
|
16
19
|
instructionOptions?: ScanOptions;
|
|
17
20
|
/** Resolved preset — used to load section-based prompt. */
|
|
@@ -106,9 +109,9 @@ export interface ComposerOptions {
|
|
|
106
109
|
disableSourcesContext?: boolean;
|
|
107
110
|
}
|
|
108
111
|
export declare class PromptComposer {
|
|
109
|
-
private readonly options;
|
|
110
112
|
private sectionCache;
|
|
111
113
|
private cachedInstructions;
|
|
114
|
+
private readonly options;
|
|
112
115
|
constructor(options: ComposerOptions);
|
|
113
116
|
/**
|
|
114
117
|
* Build the system prompt from sections.
|
package/dist/prompt/composer.js
CHANGED
|
@@ -11,12 +11,18 @@ import { MemoryManager } from "../session/memory.js";
|
|
|
11
11
|
import { scanSkills } from "../skills/index.js";
|
|
12
12
|
import { buildSkillListing } from "../tool-system/builtin/skill-prompt.js";
|
|
13
13
|
import { resolveAgentPreset, buildPresetSystemPrompt } from "../preset/index.js";
|
|
14
|
+
import { legacySingleRootWorkspace, validateWorkspaceContext, } from "../workspace/workspace-context.js";
|
|
14
15
|
export class PromptComposer {
|
|
15
|
-
options;
|
|
16
16
|
sectionCache = new SectionCache();
|
|
17
17
|
cachedInstructions = null;
|
|
18
|
+
options;
|
|
18
19
|
constructor(options) {
|
|
19
|
-
this.options =
|
|
20
|
+
this.options = {
|
|
21
|
+
...options,
|
|
22
|
+
workspace: options.workspace === undefined
|
|
23
|
+
? legacySingleRootWorkspace(options.cwd)
|
|
24
|
+
: validateWorkspaceContext(options.workspace),
|
|
25
|
+
};
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* Build the system prompt from sections.
|
|
@@ -58,7 +64,7 @@ export class PromptComposer {
|
|
|
58
64
|
const preset = this.options.preset ?? resolveAgentPreset();
|
|
59
65
|
const parts = await Promise.all((this.options.dynamicContextProviders ?? []).map(async (provider) => {
|
|
60
66
|
try {
|
|
61
|
-
return await provider({ cwd: this.options.cwd, preset });
|
|
67
|
+
return await provider({ cwd: this.options.cwd, workspace: this.options.workspace, preset });
|
|
62
68
|
}
|
|
63
69
|
catch {
|
|
64
70
|
// A capability's optional context must not make a turn fail.
|
|
@@ -164,6 +170,8 @@ export class PromptComposer {
|
|
|
164
170
|
const lines = [
|
|
165
171
|
`You are an AI agent powered by ${this.options.model}.`,
|
|
166
172
|
`Working directory: ${this.options.cwd}`,
|
|
173
|
+
`Workspace roots (${this.options.workspace.roots.length}):`,
|
|
174
|
+
...this.options.workspace.roots.map((root) => `- [${root.role}] ${root.path} (rootId: ${root.id})`),
|
|
167
175
|
`Platform: ${process.platform}`,
|
|
168
176
|
`Shell: ${process.env.SHELL ?? "unknown"}`,
|
|
169
177
|
];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ChatSession } from "./chat-session.js";
|
|
2
2
|
import type { SessionKind } from "../types.js";
|
|
3
|
+
import type { SessionProjectBinding, SessionWorkspace } from "../types.js";
|
|
3
4
|
import type { Engine } from "../engine/engine.js";
|
|
4
5
|
import type { EngineRuntime } from "../engine/runtime.js";
|
|
5
6
|
import type { EngineConfig } from "../engine/types.js";
|
|
6
|
-
export type EngineConfigSlice = Pick<EngineConfig, "permissionMode" | "preset" | "customSystemPrompt" | "appendSystemPrompt" | "goal" | "maxTurns" | "maxContextTokens" | "cwd" | "projectTrusted" | "sessionStorageDir">;
|
|
7
|
+
export type EngineConfigSlice = Pick<EngineConfig, "permissionMode" | "preset" | "customSystemPrompt" | "appendSystemPrompt" | "goal" | "maxTurns" | "maxContextTokens" | "cwd" | "workspaceContext" | "projectTrusted" | "sessionStorageDir">;
|
|
7
8
|
/** Identity scope every ChatSessionManager belongs to when none is injected. */
|
|
8
9
|
export declare const LOCAL_CHAT_IDENTITY = "local";
|
|
9
10
|
export interface ChatSessionManagerOptions {
|
|
@@ -48,11 +49,30 @@ export interface LiveChatSessionSnapshot {
|
|
|
48
49
|
}>;
|
|
49
50
|
}
|
|
50
51
|
export declare const CLOSED_CHAT_SESSION_TOMBSTONE_LIMIT = 4096;
|
|
52
|
+
export interface ResidentSessionMainRootMigration {
|
|
53
|
+
project: SessionProjectBinding;
|
|
54
|
+
mainRoot: string;
|
|
55
|
+
workspaceContext: import("../workspace/workspace-context.js").WorkspaceContext;
|
|
56
|
+
projectTrusted: boolean;
|
|
57
|
+
}
|
|
58
|
+
export type SessionMigrationOwnership = {
|
|
59
|
+
status: "resident";
|
|
60
|
+
session: ChatSession;
|
|
61
|
+
} | {
|
|
62
|
+
status: "not-resident";
|
|
63
|
+
ownershipToken: string;
|
|
64
|
+
} | {
|
|
65
|
+
status: "failed";
|
|
66
|
+
error: string;
|
|
67
|
+
};
|
|
51
68
|
export declare class ChatSessionManager {
|
|
52
69
|
private readonly sessions;
|
|
53
70
|
private readonly closingSessions;
|
|
54
71
|
private readonly closedSessions;
|
|
55
72
|
private readonly sessionGeneration;
|
|
73
|
+
private readonly sessionSlices;
|
|
74
|
+
private readonly migrationClaims;
|
|
75
|
+
private readonly residentMigrations;
|
|
56
76
|
readonly runtime: EngineRuntime;
|
|
57
77
|
/** Identity scope this manager serves ("local" unless injected). */
|
|
58
78
|
readonly identity: string;
|
|
@@ -76,6 +96,26 @@ export declare class ChatSessionManager {
|
|
|
76
96
|
*/
|
|
77
97
|
forIdentity(identity: string): ChatSessionManager;
|
|
78
98
|
getOrCreate(sessionId: string, slice: EngineConfigSlice): Promise<ChatSession>;
|
|
99
|
+
/**
|
|
100
|
+
* Prove whether this worker currently owns a resident Engine, or fence the
|
|
101
|
+
* Session so Main can perform one durable migration without a re-resume race.
|
|
102
|
+
*/
|
|
103
|
+
beginSessionMigration(sessionId: string, ownershipToken: string): SessionMigrationOwnership;
|
|
104
|
+
/** Release only the exact claim minted for this Session. */
|
|
105
|
+
completeSessionMigration(sessionId: string, ownershipToken: string): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Rebuild an idle resident Engine against a new authoritative main root.
|
|
108
|
+
*
|
|
109
|
+
* Transaction order is intentional:
|
|
110
|
+
* 1. construct and restore the candidate while durable state still points at
|
|
111
|
+
* the old owner (factory/restore failure is therefore a no-op),
|
|
112
|
+
* 2. atomically commit durable state through the old owning Engine,
|
|
113
|
+
* 3. synchronously swap the Engine inside the existing ChatSession,
|
|
114
|
+
* 4. dispose the unreachable old Engine before reporting success.
|
|
115
|
+
*
|
|
116
|
+
* No await occurs between the idle check, durable commit and owner swap.
|
|
117
|
+
*/
|
|
118
|
+
migrateResidentSessionMainRoot(sessionId: string, target: ResidentSessionMainRootMigration): Promise<SessionWorkspace>;
|
|
79
119
|
/**
|
|
80
120
|
* Cold-resume a persisted session using its own cwd. The first detached
|
|
81
121
|
* Engine is only a storage probe; all actual work runs on the second Engine
|
|
@@ -121,6 +161,7 @@ export declare class ChatSessionManager {
|
|
|
121
161
|
startIdleSweeper(intervalMs?: number): void;
|
|
122
162
|
stopIdleSweeper(): void;
|
|
123
163
|
private unregisterMcpOwner;
|
|
164
|
+
private disposeEngine;
|
|
124
165
|
private engineSessionManager;
|
|
125
166
|
}
|
|
126
167
|
/**
|