linkshell-cli 0.3.21 → 0.3.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linkshell-cli",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
4
4
  "description": "Remote terminal bridge for Claude Code / Codex / Gemini / Copilot — control from your phone",
5
5
  "type": "module",
6
6
  "main": "dist/cli/src/index.js",
@@ -35,8 +35,8 @@
35
35
  "qrcode-terminal": "^0.12.0",
36
36
  "ws": "^8.18.1",
37
37
  "zod": "^4.4.3",
38
- "@linkshell/protocol": "0.3.9",
39
- "@linkshell/gateway": "0.3.7"
38
+ "@linkshell/gateway": "0.3.8",
39
+ "@linkshell/protocol": "0.3.10"
40
40
  },
41
41
  "optionalDependencies": {
42
42
  "@anthropic-ai/claude-agent-sdk": "^0.2.128",
@@ -114,6 +114,37 @@ function permissionsForMode(
114
114
  };
115
115
  }
116
116
 
117
+ function sandboxPolicyForMode(
118
+ mode: AgentPermissionMode | undefined,
119
+ cwd: string,
120
+ ): unknown | undefined {
121
+ if (mode === "full_access") return { type: "dangerFullAccess" };
122
+ if (mode === "read_only") return { type: "readOnly", networkAccess: false };
123
+ if (mode === "workspace_write") {
124
+ return {
125
+ type: "workspaceWrite",
126
+ writableRoots: [cwd],
127
+ networkAccess: false,
128
+ excludeTmpdirEnvVar: false,
129
+ excludeSlashTmp: false,
130
+ };
131
+ }
132
+ return undefined;
133
+ }
134
+
135
+ function approvalPolicyForMode(mode: AgentPermissionMode | undefined): unknown | undefined {
136
+ if (mode === "full_access") return "never";
137
+ if (mode === "read_only" || mode === "workspace_write") return "on-request";
138
+ return undefined;
139
+ }
140
+
141
+ function sandboxModeForMode(mode: AgentPermissionMode | undefined): unknown | undefined {
142
+ if (mode === "full_access") return "danger-full-access";
143
+ if (mode === "read_only") return "read-only";
144
+ if (mode === "workspace_write") return "workspace-write";
145
+ return undefined;
146
+ }
147
+
117
148
  export class AcpClient {
118
149
  private readonly transport: JsonRpcStdioTransport;
119
150
  private readonly protocol: AgentProtocol;
@@ -229,33 +260,33 @@ export class AcpClient {
229
260
  cwd: string;
230
261
  }): Promise<unknown> {
231
262
  if (this.protocol === "codex-app-server") {
232
- const collaborationSettings = {
233
- ...(input.model ? { model: input.model } : {}),
234
- ...(input.reasoningEffort ? { reasoning_effort: input.reasoningEffort } : {}),
235
- ...(input.serviceTier ? { service_tier: input.serviceTier } : {}),
236
- };
237
- const collaborationMode = input.collaborationMode && input.collaborationMode !== "default"
263
+ const collaborationMode = input.collaborationMode && input.model
238
264
  ? {
239
265
  mode: input.collaborationMode,
240
- settings: collaborationSettings,
266
+ settings: {
267
+ model: input.model,
268
+ reasoning_effort: input.reasoningEffort ?? null,
269
+ developer_instructions: null,
270
+ },
241
271
  }
242
272
  : undefined;
243
273
  return this.transport.request("turn/start", {
244
274
  threadId: input.sessionId,
245
275
  model: input.model,
246
276
  effort: input.reasoningEffort,
247
- service_tier: input.serviceTier,
248
- permissions: permissionsForMode(input.permissionMode, input.cwd),
249
- collaborationMode,
277
+ ...(input.serviceTier === "fast" ? { serviceTier: input.serviceTier } : {}),
278
+ ...(approvalPolicyForMode(input.permissionMode) ? { approvalPolicy: approvalPolicyForMode(input.permissionMode) } : {}),
279
+ ...(sandboxPolicyForMode(input.permissionMode, input.cwd) ? { sandboxPolicy: sandboxPolicyForMode(input.permissionMode, input.cwd) } : {}),
280
+ ...(collaborationMode ? { collaborationMode } : {}),
250
281
  input: input.content.map((block) => {
251
282
  const raw = block as { type?: string; text?: string; data?: string; path?: string; name?: string };
252
283
  if (raw.type === "image" && raw.data) {
253
284
  return { type: "image", url: raw.data };
254
285
  }
255
286
  if (raw.type === "file" && raw.path) {
256
- return { type: "file", path: raw.path, name: raw.name };
287
+ return { type: "mention", path: raw.path, name: raw.name };
257
288
  }
258
- return { type: "text", text: raw.text ?? "" };
289
+ return { type: "text", text: raw.text ?? "", text_elements: [] };
259
290
  }),
260
291
  }, null);
261
292
  }
@@ -918,6 +918,7 @@ const LINKSHELL_NATIVE_COMMANDS: Array<{
918
918
  { name: "review", description: "Ask the Agent to review current local changes", category: "Agent", argsMode: "optional" },
919
919
  { name: "subagents", description: "Ask the Agent to split work across subagents when useful", category: "Agent", argsMode: "optional" },
920
920
  { name: "compact", description: "Compact the active Codex context", category: "Codex", argsMode: "none", providers: ["codex"] },
921
+ { name: "new", description: "Start a fresh Codex thread", category: "Codex", argsMode: "none", providers: ["codex"], destructive: true },
921
922
  { name: "clear", description: "Start a fresh Agent context for this conversation", category: "Agent", argsMode: "none", destructive: true },
922
923
  { name: "git-status", description: "Show branch and working tree status", category: "Git", argsMode: "none" },
923
924
  { name: "git-diff", description: "Show a compact diffstat for current changes", category: "Git", argsMode: "none" },
@@ -927,6 +928,7 @@ const LINKSHELL_NATIVE_COMMANDS: Array<{
927
928
  { name: "git-stash", description: "Stash current working tree changes", category: "Git", argsMode: "optional" },
928
929
  { name: "git-stash-pop", description: "Pop the latest stash", category: "Git", argsMode: "none" },
929
930
  ];
931
+ const CODEX_JSON_RPC_COMMANDS = new Set(["plan", "exit-plan", "review", "compact", "new"]);
930
932
  const CLAUDE_REMOTE_HIDDEN_COMMANDS = new Set([
931
933
  "add-dir",
932
934
  "agents",
@@ -1200,6 +1202,7 @@ function customClaudeCommands(cwd: string): AgentCommandDescriptor[] {
1200
1202
  function defaultProviderCommands(provider: AgentProvider, cwd: string, enabled: boolean): AgentCommandDescriptor[] {
1201
1203
  const disabledReason = enabled ? undefined : `${providerLabel(provider)} 未安装或启动失败`;
1202
1204
  const linkshellCommands = LINKSHELL_NATIVE_COMMANDS
1205
+ .filter((command) => provider !== "codex" || CODEX_JSON_RPC_COMMANDS.has(command.name))
1203
1206
  .filter((command) => !command.providers || command.providers.includes(provider))
1204
1207
  .map((command) => makeCommand({
1205
1208
  provider,
@@ -2384,7 +2387,7 @@ export class AgentWorkspaceProxy {
2384
2387
  return;
2385
2388
  }
2386
2389
 
2387
- if (command.name === "clear") {
2390
+ if (command.name === "clear" || command.name === "new") {
2388
2391
  if (!client) throw new Error("Agent provider 不在线。");
2389
2392
  const result = await client.newSession({ cwd: conversation.cwd });
2390
2393
  const nextAgentSessionId = this.extractSessionId(result) ?? id("agent-session");
@@ -130,7 +130,19 @@ export class CodexRpcBridge {
130
130
  }
131
131
 
132
132
  private failQueued(message: string): void {
133
- this.queuedMessages = [];
133
+ const queued = this.queuedMessages.splice(0);
134
+ for (const item of queued) {
135
+ if (!("id" in item) || item.id === undefined || item.id === null) continue;
136
+ this.input.send(createEnvelope({
137
+ type: "agent.codex.rpc",
138
+ hostDeviceId: this.input.hostDeviceId,
139
+ payload: {
140
+ jsonrpc: "2.0",
141
+ id: item.id,
142
+ error: { code: -32000, message },
143
+ },
144
+ }));
145
+ }
134
146
  if (this.input.verbose) {
135
147
  process.stderr.write(`[agent:codex-rpc] ${message}\n`);
136
148
  }