@cjhyy/code-shell-core 0.6.0-rc.2 → 0.6.0-rc.3

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/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export declare const VERSION = "0.5.0-rc.2";
6
+ export declare const VERSION = "0.6.0-rc.3";
7
7
  export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
8
8
  export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
9
9
  export { Engine, loadAgentDefinitionsForCwd } from "./engine/engine.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Public API exports.
5
5
  */
6
- export const VERSION = "0.5.0-rc.2";
6
+ export const VERSION = "0.6.0-rc.3";
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) ────────────────────────────────────────
@@ -461,7 +461,18 @@ export class AgentServer {
461
461
  }
462
462
  const resolve = s.pendingApprovals.get(params.requestId);
463
463
  if (!resolve) {
464
- this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, `No pending approval: ${params.requestId}`));
464
+ // Tool approvals still resolve through the interactive backend's
465
+ // legacy pending map; the sessionId on their envelope is UI routing
466
+ // metadata. Accept a session-tagged response for those requests too.
467
+ const legacyResolve = this.pendingApprovals.get(params.requestId);
468
+ if (!legacyResolve) {
469
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, `No pending approval: ${params.requestId}`));
470
+ return;
471
+ }
472
+ this.pendingApprovals.delete(params.requestId);
473
+ this.clearApprovalTimer(params.requestId);
474
+ legacyResolve(params.decision);
475
+ this.transport.send(createResponse(req.id, { ok: true }));
465
476
  return;
466
477
  }
467
478
  s.pendingApprovals.delete(params.requestId);
@@ -1291,6 +1302,7 @@ export class AgentServer {
1291
1302
  requestApprovalFromClient(request) {
1292
1303
  return new Promise((resolve) => {
1293
1304
  const requestId = nanoid(12);
1305
+ const sessionId = typeof request.sessionId === "string" ? request.sessionId : undefined;
1294
1306
  this.pendingApprovals.set(requestId, resolve);
1295
1307
  const timer = setTimeout(() => {
1296
1308
  if (this.pendingApprovals.has(requestId)) {
@@ -1300,7 +1312,11 @@ export class AgentServer {
1300
1312
  }
1301
1313
  }, AgentServer.APPROVAL_TIMEOUT_MS);
1302
1314
  this.approvalTimers.set(requestId, timer);
1303
- this.notify(Methods.ApprovalRequest, { requestId, request });
1315
+ this.notify(Methods.ApprovalRequest, {
1316
+ ...(sessionId ? { sessionId } : {}),
1317
+ requestId,
1318
+ request,
1319
+ });
1304
1320
  });
1305
1321
  }
1306
1322
  /**
@@ -243,6 +243,8 @@ export interface AgentStreamEventNotification {
243
243
  }
244
244
  /** Server requests approval from the client (UI). */
245
245
  export interface ApprovalRequestNotification {
246
+ /** Originating engine session when known. */
247
+ sessionId?: string;
246
248
  requestId: string;
247
249
  request: ApprovalRequest;
248
250
  }
@@ -143,8 +143,10 @@ export function resolveSpawnTarget(command, opts) {
143
143
  const wrapped = opts.sandbox.wrap(command, { cwd: opts.cwd, shell: opts.shell });
144
144
  return { file: wrapped.file, args: wrapped.args, cleanup: wrapped.cleanup };
145
145
  }
146
- // No sandbox (the `off` backend / Windows): pick the shell + command-flag
147
- // form for the platform instead of assuming POSIX `-c`.
146
+ // No sandbox configured: pick the shell + command-flag form for the
147
+ // platform instead of assuming POSIX `-c`. Note Bash always passes a
148
+ // backend (off at minimum), so it never reaches this line — the off
149
+ // backend's wrap() delegates to resolveShellInvocation itself.
148
150
  return resolveShellInvocation(command, opts.shell);
149
151
  }
150
152
  /**
@@ -27,7 +27,14 @@ export const cronCreateToolDef = {
27
27
  "'30 8 * * 1' = 8:30am every Monday. Day-of-week: 0=Sunday..6=Saturday.\n\n" +
28
28
  "For calendar schedules, set `timezone` to the user's IANA zone (e.g. 'Asia/Shanghai', " +
29
29
  "'America/New_York'); ask the user if unknown. Set `cwd` to the project the job operates on. " +
30
- "Leave `permissionLevel` as 'read-only' unless the user explicitly wants the job to modify code.",
30
+ "Leave `permissionLevel` as 'read-only' unless the user explicitly wants the job to modify code.\n\n" +
31
+ "SELF-WAKEUP / background-task safety net: you can also schedule THIS job for yourself, with no user " +
32
+ "request, to re-check on a long-running background task (a download, a build, a background shell/agent) " +
33
+ "that might hang and never signal completion. Use a short interval + `once: true` + `continueInSession: true` " +
34
+ "and a `prompt` that reminds you what to check, e.g. schedule '5m', once true, continueInSession true, " +
35
+ "prompt 'check whether the yt-dlp download finished (BashOutput/ListShells); if still running, wait again'. " +
36
+ "You wake back in THIS conversation with full context, inspect the task, and either finish or reschedule. " +
37
+ "This is the right pattern for a simple poll-until-done loop or a hang safety net — prefer it over looping Sleep.",
31
38
  inputSchema: {
32
39
  type: "object",
33
40
  properties: {
@@ -52,7 +59,8 @@ export const cronCreateToolDef = {
52
59
  once: {
53
60
  type: "boolean",
54
61
  description: "true = one-shot: run once at the scheduled time, then auto-delete (for 'in N minutes / " +
55
- "at <time>, do X once' reminders or tasks). Default false = recurring per `schedule`. " +
62
+ "at <time>, do X once' reminders, tasks, or a self-wakeup to re-check a background task). " +
63
+ "Default false = recurring per `schedule`. " +
56
64
  "A one-shot still uses `schedule` for its time: interval '10m' = 10 minutes from now; " +
57
65
  "cron '0 7 25 6 *' = once at 07:00 on June 25.",
58
66
  },
@@ -6,6 +6,11 @@ export const sleepToolDef = {
6
6
  description: "Pause execution for a brief, deterministic wait (e.g. letting a just-started service settle for a few seconds). " +
7
7
  "Do NOT use Sleep to poll for or wait on background work (background shells, async sub-agents, video generation): " +
8
8
  "the system wakes you automatically when that work completes — just end your turn instead of looping Sleep. " +
9
+ "If you want a safety net in case a background task hangs and never signals completion, do NOT loop Sleep either — " +
10
+ "instead end your turn and schedule a one-shot self-wakeup with CronCreate " +
11
+ "({ schedule: '5m', once: true, continueInSession: true, permissionLevel: 'read-only', " +
12
+ "prompt: 'check whether <that task> finished; if still running, wait again' }). " +
13
+ "That returns control to you at the interval without burning a turn spinning. " +
9
14
  "Maximum duration is 300 seconds (5 minutes).",
10
15
  inputSchema: {
11
16
  type: "object",
@@ -274,7 +274,7 @@ export class ToolExecutor {
274
274
  // the hook and the user together have decided.
275
275
  if (hookResult.decision === "ask") {
276
276
  const reason = hookResult.messages?.join("\n") ?? undefined;
277
- const approved = await this.permission.handleAsk(call.toolName, call.args, reason);
277
+ const approved = await this.permission.handleAsk(call.toolName, call.args, reason, { sessionId: this.toolCtx?.sessionId });
278
278
  if (!approved) {
279
279
  return {
280
280
  id: call.id,
@@ -347,7 +347,9 @@ export class ToolExecutor {
347
347
  }
348
348
  if (decision === "ask") {
349
349
  const reason = permHook.messages?.join("\n");
350
- const approved = await this.permission.handleAsk(call.toolName, call.args, reason);
350
+ const approved = await this.permission.handleAsk(call.toolName, call.args, reason, {
351
+ sessionId: this.toolCtx?.sessionId,
352
+ });
351
353
  if (!approved) {
352
354
  return {
353
355
  id: call.id,
@@ -130,7 +130,9 @@ export declare class PermissionClassifier {
130
130
  reconfigure(mode: PermissionMode, approvalBackend: ApprovalBackend, rules?: PermissionRule[]): void;
131
131
  getMode(): PermissionMode;
132
132
  classify(toolName: string, args: Record<string, unknown>): PermissionDecision;
133
- handleAsk(toolName: string, args: Record<string, unknown>, reason?: string): Promise<boolean>;
133
+ handleAsk(toolName: string, args: Record<string, unknown>, reason?: string, opts?: {
134
+ sessionId?: string;
135
+ }): Promise<boolean>;
134
136
  /** Get denial warning message if the model keeps getting denied. */
135
137
  getDenialWarning(toolName: string): string | undefined;
136
138
  private matchesRule;
@@ -861,7 +861,7 @@ export class PermissionClassifier {
861
861
  return "ask";
862
862
  }
863
863
  }
864
- async handleAsk(toolName, args, reason) {
864
+ async handleAsk(toolName, args, reason, opts) {
865
865
  if (this.defaultMode === "dontAsk") {
866
866
  this.log.info("permission.auto_deny", {
867
867
  cat: "permission",
@@ -900,6 +900,7 @@ export class PermissionClassifier {
900
900
  ? `${baseDescription}\n\nReason (from pre_tool_use hook): ${reason}`
901
901
  : baseDescription;
902
902
  result = await this.approvalBackend.requestApproval({
903
+ ...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
903
904
  toolName,
904
905
  args,
905
906
  description,
@@ -1,8 +1,12 @@
1
+ import { resolveShellInvocation } from "../../runtime/spawn-common.js";
1
2
  export function createOffBackend() {
2
3
  return {
3
4
  name: "off",
4
5
  wrap(command, opts) {
5
- return { file: opts.shell, args: ["-c", command] };
6
+ // Platform-aware flag: cmd.exe takes /c, PowerShell -Command, POSIX -c.
7
+ // Bash always passes a backend (off at minimum), so resolveSpawnTarget's
8
+ // no-sandbox fallback never covers this path — wrap() must handle it.
9
+ return resolveShellInvocation(command, opts.shell);
6
10
  },
7
11
  };
8
12
  }
package/dist/types.d.ts CHANGED
@@ -229,6 +229,8 @@ export interface PermissionRule {
229
229
  reason?: string;
230
230
  }
231
231
  export interface ApprovalRequest {
232
+ /** Originating engine session. Hosts use this only to route the prompt UI. */
233
+ sessionId?: string;
232
234
  toolName: string;
233
235
  args: Record<string, unknown>;
234
236
  description: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-core",
3
- "version": "0.6.0-rc.2",
3
+ "version": "0.6.0-rc.3",
4
4
  "description": "Core engine for code-shell — agent orchestration, tool execution, hooks, protocol. UI-agnostic.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",