@cjhyy/code-shell-core 0.8.5 → 0.8.7
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/scheduler.d.ts +9 -5
- package/dist/automation/scheduler.js +29 -6
- package/dist/engine/engine.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/panel-apps/manifest.d.ts +11 -11
- package/dist/panel-apps/manifest.js +12 -1
- package/dist/prompt/composer.js +2 -2
- package/dist/prompt/sections/browser.md +2 -2
- package/dist/protocol/server.js +1 -0
- package/dist/tool-system/browser-bridge.d.ts +3 -0
- package/dist/tool-system/builtin/browser-tools.js +13 -0
- package/dist/tool-system/path-policy.js +84 -5
- package/package.json +1 -1
|
@@ -62,11 +62,15 @@ export interface CronJob {
|
|
|
62
62
|
* job_error carries the message `fire()` would otherwise swallow.
|
|
63
63
|
*/
|
|
64
64
|
export interface CronJobLifecycleEvent {
|
|
65
|
-
type: "job_start" | "job_end" | "job_stopped" | "job_cancelled" | "job_error";
|
|
65
|
+
type: "job_start" | "job_end" | "job_stopped" | "job_cancelled" | "job_error" | "job_missed";
|
|
66
66
|
job: CronJob;
|
|
67
67
|
durationMs?: number;
|
|
68
68
|
reason?: string;
|
|
69
69
|
error?: string;
|
|
70
|
+
/** Planned wall-clock instant that was skipped after the host woke too late. */
|
|
71
|
+
scheduledFor?: number;
|
|
72
|
+
/** Time the scheduler observed the skipped occurrence. */
|
|
73
|
+
observedAt?: number;
|
|
70
74
|
}
|
|
71
75
|
/** Optional semantic outcome returned by an executor after a non-throwing run. */
|
|
72
76
|
export interface CronExecutionOutcome {
|
|
@@ -128,10 +132,10 @@ export declare class CronScheduler {
|
|
|
128
132
|
setStore(store: CronStore): void;
|
|
129
133
|
setExecutor(fn: (job: CronJob, signal: AbortSignal) => Promise<void | CronExecutionOutcome>): void;
|
|
130
134
|
/**
|
|
131
|
-
* Observe job execution lifecycle
|
|
132
|
-
*
|
|
133
|
-
* wire this to their notification surface so
|
|
134
|
-
*
|
|
135
|
+
* Observe job execution lifecycle. A real execution emits job_start followed
|
|
136
|
+
* by one terminal event; a sleep/wake skip emits job_missed without pretending
|
|
137
|
+
* the job started. Hosts wire this to their notification surface so failures
|
|
138
|
+
* and intentionally skipped occurrences are no longer silent.
|
|
135
139
|
* Listener errors are swallowed; observation never affects scheduling.
|
|
136
140
|
*/
|
|
137
141
|
setJobEventListener(listener: ((event: CronJobLifecycleEvent) => void) | undefined): void;
|
|
@@ -88,10 +88,10 @@ export class CronScheduler {
|
|
|
88
88
|
this.onExecute = fn;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Observe job execution lifecycle
|
|
92
|
-
*
|
|
93
|
-
* wire this to their notification surface so
|
|
94
|
-
*
|
|
91
|
+
* Observe job execution lifecycle. A real execution emits job_start followed
|
|
92
|
+
* by one terminal event; a sleep/wake skip emits job_missed without pretending
|
|
93
|
+
* the job started. Hosts wire this to their notification surface so failures
|
|
94
|
+
* and intentionally skipped occurrences are no longer silent.
|
|
95
95
|
* Listener errors are swallowed; observation never affects scheduling.
|
|
96
96
|
*/
|
|
97
97
|
setJobEventListener(listener) {
|
|
@@ -167,6 +167,14 @@ export class CronScheduler {
|
|
|
167
167
|
if (Number.isFinite(n) && n > maxId)
|
|
168
168
|
maxId = n;
|
|
169
169
|
if (!prev) {
|
|
170
|
+
const observedAt = Date.now();
|
|
171
|
+
const missedWhileStopped = arm &&
|
|
172
|
+
this.executionEnabled &&
|
|
173
|
+
job.enabled &&
|
|
174
|
+
typeof job.nextRun === "number" &&
|
|
175
|
+
isCronMisfire(job.nextRun, observedAt)
|
|
176
|
+
? job.nextRun
|
|
177
|
+
: undefined;
|
|
170
178
|
this.jobs.set(job.id, job);
|
|
171
179
|
if (arm && job.enabled) {
|
|
172
180
|
this.arm(job);
|
|
@@ -174,6 +182,15 @@ export class CronScheduler {
|
|
|
174
182
|
else {
|
|
175
183
|
this.refreshNextRunForDisplay(job);
|
|
176
184
|
}
|
|
185
|
+
if (missedWhileStopped !== undefined) {
|
|
186
|
+
this.persistRunStats(job);
|
|
187
|
+
this.emitJobEvent({
|
|
188
|
+
type: "job_missed",
|
|
189
|
+
job,
|
|
190
|
+
scheduledFor: missedWhileStopped,
|
|
191
|
+
observedAt,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
177
194
|
continue;
|
|
178
195
|
}
|
|
179
196
|
const definitionChanged = this.jobDefinitionChanged(prev, job);
|
|
@@ -600,8 +617,11 @@ export class CronScheduler {
|
|
|
600
617
|
// Interval jobs get the same misfire semantics cron already had: if we
|
|
601
618
|
// wake far PAST the target (host slept through it), skip this occurrence
|
|
602
619
|
// and re-arm on the next absolute slot instead of firing late.
|
|
603
|
-
|
|
620
|
+
const observedAt = Date.now();
|
|
621
|
+
if (isCronMisfire(scheduledFor, observedAt)) {
|
|
604
622
|
rearm();
|
|
623
|
+
this.persistRunStats(job);
|
|
624
|
+
this.emitJobEvent({ type: "job_missed", job, scheduledFor, observedAt });
|
|
605
625
|
return;
|
|
606
626
|
}
|
|
607
627
|
void this.fire(job, rearm).then((ran) => {
|
|
@@ -636,9 +656,12 @@ export class CronScheduler {
|
|
|
636
656
|
// resume). If we wake too far PAST the scheduled instant, this is a
|
|
637
657
|
// misfire: skip running and re-arm to the next correct occurrence rather
|
|
638
658
|
// than running at the wrong time.
|
|
639
|
-
|
|
659
|
+
const observedAt = Date.now();
|
|
660
|
+
if (isCronMisfire(scheduledFor, observedAt)) {
|
|
640
661
|
if (job.enabled)
|
|
641
662
|
this.armCron(job);
|
|
663
|
+
this.persistRunStats(job);
|
|
664
|
+
this.emitJobEvent({ type: "job_missed", job, scheduledFor, observedAt });
|
|
642
665
|
return;
|
|
643
666
|
}
|
|
644
667
|
const rearm = () => {
|
package/dist/engine/engine.js
CHANGED
|
@@ -1956,7 +1956,11 @@ export class Engine {
|
|
|
1956
1956
|
disabledPlugins,
|
|
1957
1957
|
skillAllowlist: this.config.skillAllowlist,
|
|
1958
1958
|
memoriesMaxAgeDays: this.readMemoriesConfig()?.maxAge,
|
|
1959
|
-
goalToolState:
|
|
1959
|
+
goalToolState: !profile?.allowedToolNames ||
|
|
1960
|
+
profile.allowedToolNames.has("complete_goal") ||
|
|
1961
|
+
profile.allowedToolNames.has("cancel_goal")
|
|
1962
|
+
? { hasGoal: hasRunnableGoal }
|
|
1963
|
+
: undefined,
|
|
1960
1964
|
capabilityPromptSections: this.capabilityPromptSections,
|
|
1961
1965
|
dynamicContextProviders: this.capabilityDynamicContextProviders,
|
|
1962
1966
|
getSettingsManager: () => this.getSettingsManager(),
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export declare const VERSION = "0.8.
|
|
6
|
+
export declare const VERSION = "0.8.7";
|
|
7
7
|
export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
|
|
8
8
|
export type { GoalConfig, GoalLifecycleConfig, GoalLifecyclePhase, GoalLifecycleTerminalReason, GoalLifecycleV1, } from "./goal/lifecycle.js";
|
|
9
9
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export const VERSION = "0.8.
|
|
6
|
+
export const VERSION = "0.8.7";
|
|
7
7
|
// ─── Exceptions ──────────────────────────────────────────────────
|
|
8
8
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
|
9
9
|
// ─── Engine (primary API) ────────────────────────────────────────
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const PANEL_APP_MANIFEST_FILE = ".codeshell-panel/panel.json";
|
|
3
|
-
export declare const PANEL_APP_PERMISSIONS: readonly ["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"];
|
|
3
|
+
export declare const PANEL_APP_PERMISSIONS: readonly ["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"];
|
|
4
4
|
export declare const PANEL_APP_ICONS: readonly ["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"];
|
|
5
5
|
export declare const PanelAppAgentTool: z.ZodEffects<z.ZodObject<{
|
|
6
6
|
name: z.ZodString;
|
|
@@ -116,12 +116,12 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
116
116
|
icon: z.ZodDefault<z.ZodEnum<["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"]>>;
|
|
117
117
|
placement: z.ZodDefault<z.ZodLiteral<"right-dock">>;
|
|
118
118
|
singleton: z.ZodDefault<z.ZodBoolean>;
|
|
119
|
-
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"]>, "many">>;
|
|
119
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"]>, "many">>;
|
|
120
120
|
schemaVersion: z.ZodLiteral<1>;
|
|
121
121
|
}, "strict", z.ZodTypeAny, {
|
|
122
122
|
id: string;
|
|
123
123
|
version: string;
|
|
124
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
124
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
125
125
|
entry: string;
|
|
126
126
|
title: {
|
|
127
127
|
default: string;
|
|
@@ -144,7 +144,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
144
144
|
};
|
|
145
145
|
schemaVersion: 1;
|
|
146
146
|
description?: string | undefined;
|
|
147
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
147
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
148
148
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
149
149
|
placement?: "right-dock" | undefined;
|
|
150
150
|
singleton?: boolean | undefined;
|
|
@@ -230,12 +230,12 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
230
230
|
icon: z.ZodDefault<z.ZodEnum<["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"]>>;
|
|
231
231
|
placement: z.ZodDefault<z.ZodLiteral<"right-dock">>;
|
|
232
232
|
singleton: z.ZodDefault<z.ZodBoolean>;
|
|
233
|
-
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"]>, "many">>;
|
|
233
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"]>, "many">>;
|
|
234
234
|
schemaVersion: z.ZodLiteral<2>;
|
|
235
235
|
}, "strict", z.ZodTypeAny, {
|
|
236
236
|
id: string;
|
|
237
237
|
version: string;
|
|
238
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
238
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
239
239
|
entry: string;
|
|
240
240
|
title: {
|
|
241
241
|
default: string;
|
|
@@ -276,14 +276,14 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
276
276
|
}[] | undefined;
|
|
277
277
|
} | undefined;
|
|
278
278
|
description?: string | undefined;
|
|
279
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
279
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
280
280
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
281
281
|
placement?: "right-dock" | undefined;
|
|
282
282
|
singleton?: boolean | undefined;
|
|
283
283
|
}>]>, {
|
|
284
284
|
id: string;
|
|
285
285
|
version: string;
|
|
286
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
286
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
287
287
|
entry: string;
|
|
288
288
|
title: {
|
|
289
289
|
default: string;
|
|
@@ -298,7 +298,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
298
298
|
} | {
|
|
299
299
|
id: string;
|
|
300
300
|
version: string;
|
|
301
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
301
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
302
302
|
entry: string;
|
|
303
303
|
title: {
|
|
304
304
|
default: string;
|
|
@@ -330,7 +330,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
330
330
|
};
|
|
331
331
|
schemaVersion: 1;
|
|
332
332
|
description?: string | undefined;
|
|
333
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
333
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
334
334
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
335
335
|
placement?: "right-dock" | undefined;
|
|
336
336
|
singleton?: boolean | undefined;
|
|
@@ -354,7 +354,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
354
354
|
}[] | undefined;
|
|
355
355
|
} | undefined;
|
|
356
356
|
description?: string | undefined;
|
|
357
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
357
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
358
358
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
359
359
|
placement?: "right-dock" | undefined;
|
|
360
360
|
singleton?: boolean | undefined;
|
|
@@ -11,6 +11,8 @@ export const PANEL_APP_PERMISSIONS = [
|
|
|
11
11
|
"workspace.read",
|
|
12
12
|
"workspace.write",
|
|
13
13
|
"notifications.send",
|
|
14
|
+
"credentials.cookies",
|
|
15
|
+
"automations.manage",
|
|
14
16
|
];
|
|
15
17
|
export const PANEL_APP_ICONS = [
|
|
16
18
|
"panel",
|
|
@@ -136,7 +138,7 @@ const PanelAppManifestFields = {
|
|
|
136
138
|
icon: z.enum(PANEL_APP_ICONS).default("panel"),
|
|
137
139
|
placement: z.literal("right-dock").default("right-dock"),
|
|
138
140
|
singleton: z.boolean().default(true),
|
|
139
|
-
permissions: z.array(z.enum(PANEL_APP_PERMISSIONS)).max(
|
|
141
|
+
permissions: z.array(z.enum(PANEL_APP_PERMISSIONS)).max(12).default([]),
|
|
140
142
|
};
|
|
141
143
|
/**
|
|
142
144
|
* Schema v2 keeps one installable Panel App identity while allowing it to
|
|
@@ -186,4 +188,13 @@ export const PanelAppManifest = z
|
|
|
186
188
|
message: "workspace.read and workspace.write require context.workspace",
|
|
187
189
|
});
|
|
188
190
|
}
|
|
191
|
+
if (value.permissions.includes("automations.manage") &&
|
|
192
|
+
(!value.permissions.includes("context.session") ||
|
|
193
|
+
!value.permissions.includes("context.workspace"))) {
|
|
194
|
+
ctx.addIssue({
|
|
195
|
+
code: z.ZodIssueCode.custom,
|
|
196
|
+
path: ["permissions"],
|
|
197
|
+
message: "automations.manage requires context.session and context.workspace",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
189
200
|
});
|
package/dist/prompt/composer.js
CHANGED
|
@@ -134,8 +134,8 @@ export class PromptComposer {
|
|
|
134
134
|
if (!this.options.goalToolState)
|
|
135
135
|
return "";
|
|
136
136
|
return this.options.goalToolState.hasGoal
|
|
137
|
-
? "
|
|
138
|
-
: "
|
|
137
|
+
? "Goal 工具状态:本会话存在一个持久 Goal。只有在该 Goal 完全完成时才可调用 complete_goal;只有用户明确要求取消/停止/放弃该 Goal 时才可调用 cancel_goal。此状态不代表后台任务或其他 Session 的运行状态,不要把它改写成系统是否空闲。"
|
|
138
|
+
: "Goal 工具状态:本会话没有持久 Goal,因此不要调用 complete_goal/cancel_goal;如果误调用,系统会拒绝。这只描述 Goal 工具的可用状态,不代表没有后台任务、没有运行中的 Session 或系统正在待命,也不要向用户复述为任务状态。";
|
|
139
139
|
}
|
|
140
140
|
invalidateCache(sectionName) {
|
|
141
141
|
this.sectionCache.invalidate(sectionName);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
## Browser automation (browser_observe / browser_act / browser_navigate)
|
|
2
2
|
|
|
3
3
|
- When a task needs you to operate a real web page — search a site, open a result, read/extract an article, fill a form — use the three browser tools. They drive a task-owned background tab in CodeShell's in-app browser profile. The tab can share that profile's login state, but it never controls a user-opened tab unless the user explicitly grants that exact tab. The user's regular Chrome profile is available only through an explicit Chrome extension grant.
|
|
4
|
-
- The Browser Runtime starts lazily in the background. Call `browser_navigate{url}`, then `browser_act{action:"wait"}` and `browser_observe`. If login, 2FA, or a high-consequence action needs the user
|
|
4
|
+
- The Browser Runtime starts lazily in the background. Call `browser_navigate{url}`, then `browser_act{action:"wait"}` and `browser_observe`. If login, 2FA, CAPTCHA, or a high-consequence action needs the user—or the user explicitly asks to see the page—call `browser_act{action:"request_takeover"}`. CodeShell reveals the exact same runtime-owned in-app target; do not send a second link and claim it is the page you operated. Scheduled and explicitly isolated work may use a separate Dedicated Playwright profile instead.
|
|
5
5
|
- The loop is observe → act → re-observe: ALWAYS `browser_observe` first (default mode `snapshot`) to see the page's interactive elements (each tagged `[ref=eN]`), then `browser_act` on elements BY THAT ref: `{action:"click",ref}`, `{action:"type",ref,text}`, `{action:"select",ref,value}` (native `<select>`), `{action:"hover",ref}`. Refs are only valid for the most recent snapshot — after any navigation, content-loading click, or `{action:"scroll"}`, run `{action:"wait"}` then `browser_observe` again. If an action says a ref is stale, re-observe.
|
|
6
6
|
- To submit a search: `browser_act{action:"type",ref,text}` into the search box, then `browser_act{action:"press_key",key:"Enter"}`, then `{action:"wait"}` + `browser_observe`. `press_key` also does Tab / Escape / arrows / combos like `Control+a`.
|
|
7
7
|
- To extract/summarize page content, navigate/click to it, `browser_act{action:"wait"}`, then `browser_observe{mode:"read"}`. If it returns `nextCursor`, continue with `browser_observe{mode:"read",cursor:"..."}` until `Read: complete`. Do NOT scroll merely to obtain the next text chunk. Use scroll only to load lazy/infinite content; stop when it reports `NO_PROGRESS` or `(end)`. For real link/image/video URLs, use `browser_observe{mode:"extract"}` (media is tagged [ref=imgN/vidN]).
|
|
8
8
|
- To SEE an actual image's content (e.g. a 小红书 笔记配图 or product photo), `browser_observe{mode:"extract"}` to get image refs, then `browser_observe{mode:"image", refs:["img3"]}` — it loads the real pixels (works behind hotlink protection). A vidN ref grabs the video's current frame. To see the rendered layout/canvas/chart, `browser_observe{mode:"vision"}` (optionally `ref` for one region). Use images sparingly — they cost tokens; prefer snapshot/read. (Both need a vision-capable model; on a non-vision model they're skipped.) Do NOT repeat `vision` on the same page hoping for a clearer view — one screenshot is all you get. If snapshot/screenshot don't reveal what you need (infinite-scroll/canvas feeds), scroll + re-observe or extract the URLs and act on them — don't loop screenshots.
|
|
9
9
|
- Multiple tabs: `browser_act{action:"list_tabs"}` shows open tabs (tabId/url/title/active); `browser_act{action:"switch_tab", tabId}` makes another the active one (then re-observe). Any action also accepts `tabId` to target a specific tab (it switches first). Refs are per-tab — re-observe after switching.
|
|
10
|
-
- If `browser_observe` reports a sign-in is required (or you hit a login wall / 2FA), STOP and ask the user to
|
|
10
|
+
- If `browser_observe` reports a sign-in is required (or you hit a login wall / 2FA / CAPTCHA), call `browser_act{action:"request_takeover"}`, STOP, and ask the user to act in the revealed Browser Runtime window. Continue only after the user finishes; do not attempt to enter credentials yourself. Sensitive actions (payment, delete, entering card/password values) require user approval.
|
|
11
11
|
- If the tools report that no Browser Runtime is available, the current host does not provide browser automation — say so rather than retrying.
|
package/dist/protocol/server.js
CHANGED
|
@@ -2790,6 +2790,7 @@ export class AgentServer {
|
|
|
2790
2790
|
makeBrowserBridge(session, sessionId) {
|
|
2791
2791
|
const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload);
|
|
2792
2792
|
return {
|
|
2793
|
+
requestHumanTakeover: () => call("requestTakeover", {}),
|
|
2793
2794
|
snapshot: () => call("snapshot", {}),
|
|
2794
2795
|
click: (ref) => call("click", { ref }),
|
|
2795
2796
|
type: (ref, text) => call("type", { ref, text }),
|
|
@@ -145,6 +145,9 @@ export interface BrowserImageData {
|
|
|
145
145
|
detail?: string;
|
|
146
146
|
}
|
|
147
147
|
export interface BrowserBridge {
|
|
148
|
+
/** Reveal the exact task-owned browser target so the user can complete a
|
|
149
|
+
* login, 2FA, CAPTCHA, or another interaction that requires human control. */
|
|
150
|
+
requestHumanTakeover?(): Promise<BrowserResult>;
|
|
148
151
|
snapshot(): Promise<BrowserSnapshot>;
|
|
149
152
|
click(ref: string): Promise<BrowserResult>;
|
|
150
153
|
type(ref: string, text: string): Promise<BrowserResult>;
|
|
@@ -209,6 +209,9 @@ export const browserActToolDef = {
|
|
|
209
209
|
"- hover {ref}: hover to reveal menus/tooltips.\n" +
|
|
210
210
|
"- scroll {direction: up|down, amount?}: scroll the page, then re-observe.\n" +
|
|
211
211
|
"- wait {timeout_ms?}: wait for the page to finish loading before observing.\n" +
|
|
212
|
+
"- request_takeover: reveal the exact task-owned Browser Runtime page so the " +
|
|
213
|
+
"user can see it and complete login, 2FA, CAPTCHA, or another required manual step. " +
|
|
214
|
+
"Use only when the user asks to see the page or human interaction is required.\n" +
|
|
212
215
|
"- list_tabs: list open browser tabs (tabId, url, title, which is active).\n" +
|
|
213
216
|
"- switch_tab {tabId}: make another tab the active one that actions drive.\n" +
|
|
214
217
|
"Pass tabId on any action to target a specific tab (switches to it first). " +
|
|
@@ -226,6 +229,7 @@ export const browserActToolDef = {
|
|
|
226
229
|
"hover",
|
|
227
230
|
"scroll",
|
|
228
231
|
"wait",
|
|
232
|
+
"request_takeover",
|
|
229
233
|
"list_tabs",
|
|
230
234
|
"switch_tab",
|
|
231
235
|
],
|
|
@@ -260,6 +264,15 @@ export async function browserActTool(args, ctx) {
|
|
|
260
264
|
return `Error: could not switch to tab ${tabId} — ${sw.detail ?? "not found"}`;
|
|
261
265
|
}
|
|
262
266
|
switch (action) {
|
|
267
|
+
case "request_takeover": {
|
|
268
|
+
if (!b.requestHumanTakeover) {
|
|
269
|
+
return "Error: this Browser Runtime cannot reveal its page for user takeover";
|
|
270
|
+
}
|
|
271
|
+
const r = await b.requestHumanTakeover();
|
|
272
|
+
return r.ok
|
|
273
|
+
? `Browser Runtime is visible for user takeover${r.detail ? ` — ${r.detail}` : ""}`
|
|
274
|
+
: `Error: ${r.detail ?? "could not reveal Browser Runtime"}`;
|
|
275
|
+
}
|
|
263
276
|
case "list_tabs": {
|
|
264
277
|
const tabs = await b.listTabs();
|
|
265
278
|
if (tabs.length === 0)
|
|
@@ -432,9 +432,10 @@ function isSkillTreeResource(resolved, skillsRoot) {
|
|
|
432
432
|
*
|
|
433
433
|
* Keep the exception narrow:
|
|
434
434
|
* - read-only (the caller checks the operation);
|
|
435
|
-
* - user Skills,
|
|
435
|
+
* - user Skills, an installed plugin recorded in the V2 registry, or a
|
|
436
|
+
* declared Skill in the installed Panel App registry;
|
|
436
437
|
* - plugin installs must realpath beneath the managed plugin cache;
|
|
437
|
-
* - the target must remain inside
|
|
438
|
+
* - the target must remain inside the registered Skill directory.
|
|
438
439
|
*/
|
|
439
440
|
function isRegisteredSkillResourceRead(resolved) {
|
|
440
441
|
let codeShellRoot;
|
|
@@ -448,6 +449,8 @@ function isRegisteredSkillResourceRead(resolved) {
|
|
|
448
449
|
return false;
|
|
449
450
|
if (isSkillTreeResource(resolved, join(codeShellRoot, "skills")))
|
|
450
451
|
return true;
|
|
452
|
+
if (isInstalledPanelAppSkillResourceRead(resolved, codeShellRoot))
|
|
453
|
+
return true;
|
|
451
454
|
let cacheRoot;
|
|
452
455
|
try {
|
|
453
456
|
cacheRoot = realpathSync(join(codeShellRoot, "plugins", "cache"));
|
|
@@ -475,6 +478,84 @@ function isRegisteredSkillResourceRead(resolved) {
|
|
|
475
478
|
}
|
|
476
479
|
return false;
|
|
477
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Panel App Skills live under the otherwise-sensitive
|
|
483
|
+
* `~/.code-shell/panel-apps` tree. Installation already reviews and copies the
|
|
484
|
+
* package, and the Skill scanner exposes only entries declared by the app
|
|
485
|
+
* manifest. Mirror that exact boundary here so reading a Skill reference does
|
|
486
|
+
* not trigger a second approval prompt.
|
|
487
|
+
*
|
|
488
|
+
* Registry, app root, manifest, declared SKILL.md and target are all
|
|
489
|
+
* realpathed and containment-checked. This deliberately does not trust an
|
|
490
|
+
* undeclared Skill directory or a symlink escaping the installed app.
|
|
491
|
+
*/
|
|
492
|
+
function isInstalledPanelAppSkillResourceRead(resolved, codeShellRoot) {
|
|
493
|
+
let appsRoot;
|
|
494
|
+
let registryPath;
|
|
495
|
+
try {
|
|
496
|
+
appsRoot = realpathSync(join(codeShellRoot, "panel-apps"));
|
|
497
|
+
if (!isInsideDir(appsRoot, codeShellRoot))
|
|
498
|
+
return false;
|
|
499
|
+
registryPath = realpathSync(join(appsRoot, "installed.json"));
|
|
500
|
+
if (!isInsideDir(registryPath, appsRoot))
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
let registry;
|
|
507
|
+
try {
|
|
508
|
+
registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
if (registry.version !== 1 || !Array.isArray(registry.apps))
|
|
514
|
+
return false;
|
|
515
|
+
for (const rawEntry of registry.apps) {
|
|
516
|
+
if (!rawEntry || typeof rawEntry !== "object")
|
|
517
|
+
continue;
|
|
518
|
+
const id = rawEntry.id;
|
|
519
|
+
if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(id))
|
|
520
|
+
continue;
|
|
521
|
+
try {
|
|
522
|
+
const appRoot = realpathSync(join(appsRoot, id));
|
|
523
|
+
if (appRoot === appsRoot || !isInsideDir(appRoot, appsRoot))
|
|
524
|
+
continue;
|
|
525
|
+
const manifestPath = realpathSync(join(appRoot, ".codeshell-panel", "panel.json"));
|
|
526
|
+
if (!isInsideDir(manifestPath, appRoot) || !statSync(manifestPath).isFile())
|
|
527
|
+
continue;
|
|
528
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
529
|
+
if (manifest.schemaVersion !== 2 ||
|
|
530
|
+
manifest.id !== id ||
|
|
531
|
+
!Array.isArray(manifest.agent?.skills)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
for (const skillEntry of manifest.agent.skills) {
|
|
535
|
+
if (typeof skillEntry !== "string")
|
|
536
|
+
continue;
|
|
537
|
+
const segments = skillEntry.split("/");
|
|
538
|
+
if (segments.length !== 4 ||
|
|
539
|
+
segments[0] !== "agent" ||
|
|
540
|
+
segments[1] !== "skills" ||
|
|
541
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(segments[2] ?? "") ||
|
|
542
|
+
segments[3] !== "SKILL.md") {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const skillManifest = realpathSync(join(appRoot, ...segments));
|
|
546
|
+
if (!isInsideDir(skillManifest, appRoot) || !statSync(skillManifest).isFile())
|
|
547
|
+
continue;
|
|
548
|
+
const skillRoot = dirname(skillManifest);
|
|
549
|
+
if (isInsideDir(resolved, skillRoot))
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
// A stale, malformed, or tampered Panel App entry grants no read access.
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
478
559
|
/**
|
|
479
560
|
* Returns the matching sensitive-dir entry (with the user's home prefix) if
|
|
480
561
|
* `resolved` lives underneath any sensitive directory, else undefined.
|
|
@@ -564,9 +645,7 @@ export function classifyPath(rawPath, opts) {
|
|
|
564
645
|
// files through the ordinary Read tool as well. A credential-shaped basename
|
|
565
646
|
// (.env, token.txt, key files, ...) deliberately keeps the sensitive-file
|
|
566
647
|
// gate even inside a Skill tree.
|
|
567
|
-
if (opts.operation === "read" &&
|
|
568
|
-
!sensitiveFile &&
|
|
569
|
-
isRegisteredSkillResourceRead(resolved)) {
|
|
648
|
+
if (opts.operation === "read" && !sensitiveFile && isRegisteredSkillResourceRead(resolved)) {
|
|
570
649
|
return {
|
|
571
650
|
decision: "allow",
|
|
572
651
|
reason: "registered Skill resource read",
|
package/package.json
CHANGED