@cjhyy/code-shell-core 0.6.0-rc.16 → 0.6.0-rc.18
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/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/cc-orchestrator/agent-adapter.d.ts +2 -0
- package/dist/cc-orchestrator/agent-adapter.js +7 -1
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +102 -51
- package/dist/cli/agent-server-stdio.js +2 -0
- package/dist/credentials/access.d.ts +56 -0
- package/dist/credentials/access.js +183 -0
- package/dist/credentials/index.d.ts +1 -0
- package/dist/credentials/index.js +1 -0
- package/dist/credentials/inject-credential-tool.js +5 -5
- package/dist/credentials/use-credential-tool.d.ts +8 -1
- package/dist/credentials/use-credential-tool.js +55 -45
- package/dist/engine/engine.d.ts +3 -0
- package/dist/engine/engine.js +40 -13
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +255 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +4 -0
- package/dist/engine/turn-loop.js +106 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/preset/index.js +10 -9
- package/dist/protocol/chat-session-manager.d.ts +1 -0
- package/dist/protocol/chat-session-manager.js +18 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +12 -0
- package/dist/protocol/server.js +116 -38
- package/dist/protocol/types.d.ts +37 -0
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/tool-system/builtin/drive-claude-code.d.ts +1 -0
- package/dist/tool-system/builtin/drive-claude-code.js +137 -18
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +15 -13
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +114 -5
- package/dist/tool-system/builtin/view-image.js +9 -0
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +2 -0
- package/dist/tool-system/mcp-manager.js +23 -8
- package/dist/tool-system/path-policy.js +13 -0
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/types.d.ts +23 -4
- package/package.json +4 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Permission system — classifier + approval backend.
|
|
3
3
|
*/
|
|
4
4
|
import { resolve as resolvePath, dirname } from "node:path";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { logger as rootPermLogger } from "../logging/logger.js";
|
|
8
8
|
import { READ_ONLY_TOOLS } from "./plan-mode-allowlist.js";
|
|
@@ -93,6 +93,17 @@ export class AutoApprovalBackend {
|
|
|
93
93
|
return false;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Closed-session tombstones are a bounded replay guard. A prompt that started
|
|
98
|
+
* before close is still protected by the state-object identity check in
|
|
99
|
+
* isActiveSessionState(): clearSession deletes the captured state, so the late
|
|
100
|
+
* result cannot write into a new bucket even if an old tombstone has aged out.
|
|
101
|
+
* The tombstone only blocks fresh post-close calls carrying a recently closed
|
|
102
|
+
* sessionId from creating an empty bucket. 4096 is 256x the default
|
|
103
|
+
* ChatSessionManager.maxSessions (16), which covers normal late-result bursts
|
|
104
|
+
* while keeping long-lived processes from retaining every historical id.
|
|
105
|
+
*/
|
|
106
|
+
export const CLOSED_SESSION_TOMBSTONE_LIMIT = 4096;
|
|
96
107
|
/**
|
|
97
108
|
* Interactive approval backend — prompts the user via a callback.
|
|
98
109
|
*
|
|
@@ -106,29 +117,21 @@ export class AutoApprovalBackend {
|
|
|
106
117
|
* the next time the user opens the project.
|
|
107
118
|
*/
|
|
108
119
|
export class InteractiveApprovalBackend {
|
|
109
|
-
// Session-scoped grants
|
|
110
|
-
//
|
|
111
|
-
// the
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
// Session-scoped grants are bucketed by ApprovalRequest.sessionId and then
|
|
121
|
+
// keyed on the OPERATION (tool + narrowed argsPattern via buildProjectRule),
|
|
122
|
+
// NOT just the tool name. Approving `git status` for one session must not
|
|
123
|
+
// auto-allow either `rm -rf /` in that same session or `git ...` in another.
|
|
124
|
+
// Allow and deny are tracked separately so a one-off deny of `curl evil`
|
|
125
|
+
// never blocks an unrelated `git status`.
|
|
126
|
+
sessionStateById = new Map();
|
|
127
|
+
closedSessionIds = new Set();
|
|
116
128
|
promptFn = null;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
savedProjectRules = [];
|
|
124
|
-
onProjectRules = null;
|
|
125
|
-
// Serialize prompts so a burst of parallel tool calls doesn't queue N
|
|
126
|
-
// identical cards: while one ask is outstanding, later requests wait here
|
|
127
|
-
// and RE-CHECK the session rules when their turn comes — the first
|
|
128
|
-
// "本会话一直允许" answer then silently absorbs the queued duplicates.
|
|
129
|
-
// (All callers were already serialized at the UI anyway — the renderer
|
|
130
|
-
// shows one approval card at a time.)
|
|
131
|
-
promptTurn = Promise.resolve();
|
|
129
|
+
legacyPromptTurn = Promise.resolve();
|
|
130
|
+
legacyContext = {
|
|
131
|
+
cwd: null,
|
|
132
|
+
savedProjectRules: [],
|
|
133
|
+
onProjectRules: null,
|
|
134
|
+
};
|
|
132
135
|
setPromptFn(fn) {
|
|
133
136
|
this.promptFn = fn;
|
|
134
137
|
}
|
|
@@ -144,7 +147,7 @@ export class InteractiveApprovalBackend {
|
|
|
144
147
|
}
|
|
145
148
|
/** Inject the project root so persistence writes to the right settings file. */
|
|
146
149
|
setCwd(cwd) {
|
|
147
|
-
this.cwd = cwd;
|
|
150
|
+
this.legacyContext.cwd = cwd;
|
|
148
151
|
}
|
|
149
152
|
/**
|
|
150
153
|
* Inject a callback fired when the user approves "for this project". The
|
|
@@ -153,11 +156,68 @@ export class InteractiveApprovalBackend {
|
|
|
153
156
|
* earlier approvals.
|
|
154
157
|
*/
|
|
155
158
|
setOnProjectRules(fn) {
|
|
156
|
-
this.onProjectRules = fn;
|
|
159
|
+
this.legacyContext.onProjectRules = fn;
|
|
160
|
+
}
|
|
161
|
+
setSessionContext(sessionId, context) {
|
|
162
|
+
const state = this.getSessionState(sessionId, true);
|
|
163
|
+
if (!state)
|
|
164
|
+
return;
|
|
165
|
+
state.cwd = context.cwd;
|
|
166
|
+
state.onProjectRules = context.onProjectRules;
|
|
167
|
+
}
|
|
168
|
+
openSession(sessionId) {
|
|
169
|
+
if (!sessionId)
|
|
170
|
+
return;
|
|
171
|
+
this.closedSessionIds.delete(sessionId);
|
|
172
|
+
}
|
|
173
|
+
clearSession(sessionId) {
|
|
174
|
+
if (!sessionId)
|
|
175
|
+
return;
|
|
176
|
+
this.sessionStateById.delete(sessionId);
|
|
177
|
+
this.rememberClosedSession(sessionId);
|
|
178
|
+
}
|
|
179
|
+
rememberClosedSession(sessionId) {
|
|
180
|
+
this.closedSessionIds.delete(sessionId);
|
|
181
|
+
this.closedSessionIds.add(sessionId);
|
|
182
|
+
while (this.closedSessionIds.size > CLOSED_SESSION_TOMBSTONE_LIMIT) {
|
|
183
|
+
const oldest = this.closedSessionIds.values().next().value;
|
|
184
|
+
if (oldest === undefined)
|
|
185
|
+
break;
|
|
186
|
+
this.closedSessionIds.delete(oldest);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
makeSessionState() {
|
|
190
|
+
return {
|
|
191
|
+
allowRules: [],
|
|
192
|
+
denyRules: [],
|
|
193
|
+
promptTurn: Promise.resolve(),
|
|
194
|
+
cwd: null,
|
|
195
|
+
savedProjectRules: [],
|
|
196
|
+
onProjectRules: null,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
getSessionState(sessionId, create) {
|
|
200
|
+
if (!sessionId)
|
|
201
|
+
return null;
|
|
202
|
+
if (this.closedSessionIds.has(sessionId))
|
|
203
|
+
return null;
|
|
204
|
+
const existing = this.sessionStateById.get(sessionId);
|
|
205
|
+
if (existing || !create)
|
|
206
|
+
return existing ?? null;
|
|
207
|
+
const next = this.makeSessionState();
|
|
208
|
+
this.sessionStateById.set(sessionId, next);
|
|
209
|
+
return next;
|
|
210
|
+
}
|
|
211
|
+
isActiveSessionState(sessionId, state) {
|
|
212
|
+
return (!!sessionId &&
|
|
213
|
+
!!state &&
|
|
214
|
+
!this.closedSessionIds.has(sessionId) &&
|
|
215
|
+
this.sessionStateById.get(sessionId) === state);
|
|
157
216
|
}
|
|
158
217
|
async requestApproval(req) {
|
|
218
|
+
const state = this.getSessionState(req.sessionId, true);
|
|
159
219
|
// Fast path: the operation may already be covered by a session rule.
|
|
160
|
-
const cached = this.checkSessionRules(req);
|
|
220
|
+
const cached = state ? this.checkSessionRules(req, state) : null;
|
|
161
221
|
if (cached)
|
|
162
222
|
return cached;
|
|
163
223
|
if (!this.promptFn) {
|
|
@@ -167,51 +227,68 @@ export class InteractiveApprovalBackend {
|
|
|
167
227
|
// field doc): a burst of parallel tool calls all passes the fast path
|
|
168
228
|
// before the first decision lands; without the re-check the user had to
|
|
169
229
|
// answer one card per duplicate.
|
|
170
|
-
const prevTurn = this.
|
|
230
|
+
const prevTurn = state ? state.promptTurn : this.legacyPromptTurn;
|
|
171
231
|
let release;
|
|
172
|
-
|
|
232
|
+
const currentTurn = new Promise((r) => (release = r));
|
|
233
|
+
if (state) {
|
|
234
|
+
state.promptTurn = currentTurn;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
this.legacyPromptTurn = currentTurn;
|
|
238
|
+
}
|
|
173
239
|
try {
|
|
174
240
|
await prevTurn;
|
|
175
|
-
const
|
|
241
|
+
const activeState = this.isActiveSessionState(req.sessionId, state) ? state : null;
|
|
242
|
+
const nowCached = activeState ? this.checkSessionRules(req, activeState) : null;
|
|
176
243
|
if (nowCached)
|
|
177
244
|
return nowCached;
|
|
178
|
-
return await this.promptAndRecord(req);
|
|
245
|
+
return await this.promptAndRecord(req, activeState);
|
|
179
246
|
}
|
|
180
247
|
finally {
|
|
181
248
|
release();
|
|
182
249
|
}
|
|
183
250
|
}
|
|
184
|
-
/** Session-rule lookup — operation-scoped (see
|
|
251
|
+
/** Session-rule lookup — operation-scoped (see sessionStateById doc). Deny
|
|
185
252
|
* wins over allow if both somehow match (conservative). Null = no rule. */
|
|
186
|
-
checkSessionRules(req) {
|
|
187
|
-
if (
|
|
253
|
+
checkSessionRules(req, state) {
|
|
254
|
+
if (state.denyRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
|
|
188
255
|
return { approved: false };
|
|
189
256
|
}
|
|
190
|
-
if (
|
|
257
|
+
if (state.allowRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
|
|
191
258
|
return { approved: true };
|
|
192
259
|
}
|
|
193
260
|
return null;
|
|
194
261
|
}
|
|
195
262
|
/** The actual interactive ask + rule recording (runs inside the prompt turn). */
|
|
196
|
-
async promptAndRecord(req) {
|
|
263
|
+
async promptAndRecord(req, state) {
|
|
197
264
|
if (!this.promptFn) {
|
|
198
265
|
return { approved: false, reason: "interactive approval backend has no prompt function" };
|
|
199
266
|
}
|
|
200
267
|
const result = await this.promptFn(req);
|
|
201
268
|
const scope = result.scope ?? (result.always ? "session" : "once");
|
|
269
|
+
const activeState = this.isActiveSessionState(req.sessionId, state) ? state : null;
|
|
270
|
+
const context = activeState ?? (!req.sessionId ? this.legacyContext : null);
|
|
202
271
|
// Path narrowing only rides on an APPROVE: a path-scoped deny is confusing
|
|
203
272
|
// (deny stays tool-wide). pathScope is ignored by buildProjectRule for
|
|
204
273
|
// non-file tools / when absent.
|
|
205
274
|
const ruleOpts = result.approved
|
|
206
|
-
? { pathScope: result.pathScope, cwd:
|
|
275
|
+
? { pathScope: result.pathScope, cwd: context?.cwd ?? undefined }
|
|
207
276
|
: undefined;
|
|
208
277
|
if (scope === "session" && result.always) {
|
|
278
|
+
if (!activeState) {
|
|
279
|
+
rootPermLogger.warn("permission.session_remember_ignored", {
|
|
280
|
+
cat: "permission",
|
|
281
|
+
tool: req.toolName,
|
|
282
|
+
reason: req.sessionId ? "session_closed" : "missing_session_id",
|
|
283
|
+
});
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
209
286
|
// Narrow to the operation (Bash → head command, file tools → path scope)
|
|
210
287
|
// so the session grant is scoped, not tool-wide. A rule with no
|
|
211
288
|
// argsPattern keeps the prior tool-granularity behavior.
|
|
212
289
|
const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
|
|
213
290
|
if (rule) {
|
|
214
|
-
const target = result.approved ?
|
|
291
|
+
const target = result.approved ? activeState.allowRules : activeState.denyRules;
|
|
215
292
|
const dup = target.some((r) => r.tool === rule.tool &&
|
|
216
293
|
JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
|
|
217
294
|
if (!dup) {
|
|
@@ -225,23 +302,23 @@ export class InteractiveApprovalBackend {
|
|
|
225
302
|
// We only persist allow rules — denies stay session-only because a
|
|
226
303
|
// persisted deny is harder to recover from than a session deny.
|
|
227
304
|
const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
|
|
228
|
-
if (rule &&
|
|
305
|
+
if (rule && context?.cwd) {
|
|
229
306
|
try {
|
|
230
|
-
persistProjectRule(
|
|
307
|
+
persistProjectRule(context.cwd, rule);
|
|
231
308
|
// Dedup against the in-memory list using the same equality used by
|
|
232
309
|
// persistProjectRule so re-prompts on the same tool/argsPattern
|
|
233
310
|
// don't bloat the live rule set.
|
|
234
|
-
const dup =
|
|
311
|
+
const dup = context.savedProjectRules.some((r) => r.tool === rule.tool &&
|
|
235
312
|
r.decision === rule.decision &&
|
|
236
313
|
JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
|
|
237
314
|
if (!dup)
|
|
238
|
-
|
|
239
|
-
|
|
315
|
+
context.savedProjectRules.push(rule);
|
|
316
|
+
context.onProjectRules?.(context.savedProjectRules);
|
|
240
317
|
rootPermLogger.info("permission.persist", {
|
|
241
318
|
cat: "permission",
|
|
242
319
|
tool: rule.tool,
|
|
243
320
|
decision: rule.decision,
|
|
244
|
-
totalProjectRules:
|
|
321
|
+
totalProjectRules: context.savedProjectRules.length,
|
|
245
322
|
duplicate: dup,
|
|
246
323
|
});
|
|
247
324
|
}
|
|
@@ -258,11 +335,11 @@ export class InteractiveApprovalBackend {
|
|
|
258
335
|
// Also seed the session allow list (operation-scoped) so the rest of
|
|
259
336
|
// this REPL session benefits even if the classifier path doesn't pick
|
|
260
337
|
// the rule up immediately.
|
|
261
|
-
if (rule) {
|
|
262
|
-
const dup =
|
|
338
|
+
if (rule && activeState) {
|
|
339
|
+
const dup = activeState.allowRules.some((r) => r.tool === rule.tool &&
|
|
263
340
|
JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
|
|
264
341
|
if (!dup)
|
|
265
|
-
|
|
342
|
+
activeState.allowRules.push(rule);
|
|
266
343
|
}
|
|
267
344
|
}
|
|
268
345
|
return result;
|
|
@@ -452,6 +529,12 @@ export function getInteractiveApprovalBackend() {
|
|
|
452
529
|
}
|
|
453
530
|
return _interactiveBackend;
|
|
454
531
|
}
|
|
532
|
+
export function openInteractiveApprovalSession(sessionId) {
|
|
533
|
+
getInteractiveApprovalBackend().openSession(sessionId);
|
|
534
|
+
}
|
|
535
|
+
export function clearInteractiveApprovalSession(sessionId) {
|
|
536
|
+
getInteractiveApprovalBackend().clearSession(sessionId);
|
|
537
|
+
}
|
|
455
538
|
export function setInteractiveApprovalFn(fn) {
|
|
456
539
|
getInteractiveApprovalBackend().setPromptFn(fn);
|
|
457
540
|
}
|
|
@@ -896,9 +979,7 @@ export class PermissionClassifier {
|
|
|
896
979
|
let result;
|
|
897
980
|
try {
|
|
898
981
|
const baseDescription = this.describeToolCall(toolName, args);
|
|
899
|
-
const description = reason
|
|
900
|
-
? `${baseDescription}\n\nReason (from pre_tool_use hook): ${reason}`
|
|
901
|
-
: baseDescription;
|
|
982
|
+
const description = reason ? `${baseDescription}\n\nReason:\n${reason}` : baseDescription;
|
|
902
983
|
result = await this.approvalBackend.requestApproval({
|
|
903
984
|
...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
904
985
|
toolName,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified tool registry — built-in tools + MCP tools.
|
|
3
3
|
*/
|
|
4
|
-
import { ConfigError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError } from "../exceptions.js";
|
|
4
|
+
import { ConfigError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, } from "../exceptions.js";
|
|
5
5
|
import { BUILTIN_TOOLS } from "./builtin/index.js";
|
|
6
6
|
import { validateToolMetadata } from "./validate-tool-metadata.js";
|
|
7
7
|
/**
|
|
@@ -22,7 +22,9 @@ export class ToolRegistry {
|
|
|
22
22
|
if (selectedNames) {
|
|
23
23
|
const unknown = [...selectedNames].filter((name) => !availableNames.has(name));
|
|
24
24
|
if (unknown.length > 0) {
|
|
25
|
-
throw new ConfigError(`Unknown built-in tool(s): ${unknown.join(", ")}`, {
|
|
25
|
+
throw new ConfigError(`Unknown built-in tool(s): ${unknown.join(", ")}`, {
|
|
26
|
+
unknownBuiltinTools: unknown,
|
|
27
|
+
});
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
for (const tool of BUILTIN_TOOLS) {
|
|
@@ -120,12 +122,18 @@ export class ToolRegistry {
|
|
|
120
122
|
// 各取所有,缺的字段为 undefined。
|
|
121
123
|
const contentBlocks = "contentBlocks" in result ? result.contentBlocks : undefined;
|
|
122
124
|
const sandbox = "sandbox" in result ? result.sandbox : undefined;
|
|
125
|
+
const sensitive = "sensitive" in result ? result.sensitive : undefined;
|
|
126
|
+
const displayResult = "displayResult" in result ? result.displayResult : undefined;
|
|
127
|
+
const transcriptResult = "transcriptResult" in result ? result.transcriptResult : undefined;
|
|
123
128
|
return {
|
|
124
129
|
id,
|
|
125
130
|
toolName: name,
|
|
126
131
|
result: result.result ?? (contentBlocks ? "(image)" : ""),
|
|
127
132
|
contentBlocks,
|
|
128
133
|
sandbox,
|
|
134
|
+
sensitive,
|
|
135
|
+
displayResult,
|
|
136
|
+
transcriptResult,
|
|
129
137
|
};
|
|
130
138
|
}
|
|
131
139
|
catch (err) {
|
|
@@ -133,8 +141,7 @@ export class ToolRegistry {
|
|
|
133
141
|
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
134
142
|
// Always return error as ToolResult, never throw
|
|
135
143
|
let errorMsg;
|
|
136
|
-
const isAbort = err?.name === "AbortError" ||
|
|
137
|
-
parentSignal?.aborted === true;
|
|
144
|
+
const isAbort = err?.name === "AbortError" || parentSignal?.aborted === true;
|
|
138
145
|
if (err instanceof ToolTimeoutError) {
|
|
139
146
|
errorMsg = `Tool timed out after ${timeout}ms: ${name}`;
|
|
140
147
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Message, ToolResult } from "../types.js";
|
|
2
|
+
export declare const SENSITIVE_TOOL_RESULT_PLACEHOLDER = "[credential value withheld]";
|
|
3
|
+
export declare function toolResultDisplayText(result: ToolResult): string | undefined;
|
|
4
|
+
export declare function toolResultTranscriptText(result: ToolResult): string | undefined;
|
|
5
|
+
export declare function toolResultForDisplay(result: ToolResult): ToolResult;
|
|
6
|
+
export declare function toolResultsForDisplay(results: ToolResult[]): ToolResult[];
|
|
7
|
+
export declare function redactSensitiveToolResultsInMessages(messages: Message[], redactions: ReadonlyMap<string, string>): Message[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const SENSITIVE_TOOL_RESULT_PLACEHOLDER = "[credential value withheld]";
|
|
2
|
+
export function toolResultDisplayText(result) {
|
|
3
|
+
if (!result.sensitive)
|
|
4
|
+
return result.result;
|
|
5
|
+
return result.displayResult ?? result.transcriptResult ?? SENSITIVE_TOOL_RESULT_PLACEHOLDER;
|
|
6
|
+
}
|
|
7
|
+
export function toolResultTranscriptText(result) {
|
|
8
|
+
if (!result.sensitive)
|
|
9
|
+
return result.result;
|
|
10
|
+
return result.transcriptResult ?? result.displayResult ?? SENSITIVE_TOOL_RESULT_PLACEHOLDER;
|
|
11
|
+
}
|
|
12
|
+
export function toolResultForDisplay(result) {
|
|
13
|
+
if (!result.sensitive)
|
|
14
|
+
return result;
|
|
15
|
+
const { displayResult: _displayResult, transcriptResult: _transcriptResult, ...rest } = result;
|
|
16
|
+
return {
|
|
17
|
+
...rest,
|
|
18
|
+
result: toolResultDisplayText(result),
|
|
19
|
+
contentBlocks: undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function toolResultsForDisplay(results) {
|
|
23
|
+
return results.map((result) => toolResultForDisplay(result));
|
|
24
|
+
}
|
|
25
|
+
export function redactSensitiveToolResultsInMessages(messages, redactions) {
|
|
26
|
+
if (redactions.size === 0)
|
|
27
|
+
return messages;
|
|
28
|
+
let changed = false;
|
|
29
|
+
const out = messages.map((message) => {
|
|
30
|
+
if (!Array.isArray(message.content))
|
|
31
|
+
return message;
|
|
32
|
+
let contentChanged = false;
|
|
33
|
+
const content = message.content.map((block) => {
|
|
34
|
+
if (block.type !== "tool_result")
|
|
35
|
+
return block;
|
|
36
|
+
const replacement = typeof block.tool_use_id === "string" ? redactions.get(block.tool_use_id) : undefined;
|
|
37
|
+
if (replacement === undefined)
|
|
38
|
+
return block;
|
|
39
|
+
contentChanged = true;
|
|
40
|
+
return { ...block, content: replacement };
|
|
41
|
+
});
|
|
42
|
+
if (!contentChanged)
|
|
43
|
+
return message;
|
|
44
|
+
changed = true;
|
|
45
|
+
return { ...message, content };
|
|
46
|
+
});
|
|
47
|
+
return changed ? out : messages;
|
|
48
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -43,7 +43,15 @@ export interface ToolCall {
|
|
|
43
43
|
export interface ToolResult {
|
|
44
44
|
id: string;
|
|
45
45
|
toolName: string;
|
|
46
|
+
/**
|
|
47
|
+
* Sensitive tool results keep `result` as the model-facing value for the
|
|
48
|
+
* current model round only. Persisted/displayed/streamed observers must use
|
|
49
|
+
* `transcriptResult`/`displayResult` (or the standard placeholder) instead.
|
|
50
|
+
*/
|
|
51
|
+
sensitive?: boolean;
|
|
46
52
|
result?: string;
|
|
53
|
+
displayResult?: string;
|
|
54
|
+
transcriptResult?: string;
|
|
47
55
|
/**
|
|
48
56
|
* 结构化结果块(目前仅图片)。存在时优先于 `result` 用作发给 LLM 的
|
|
49
57
|
* tool_result content —— view_image 用它把本地图片以 image ContentBlock
|
|
@@ -54,13 +62,13 @@ export interface ToolResult {
|
|
|
54
62
|
error?: string;
|
|
55
63
|
isError?: boolean;
|
|
56
64
|
/**
|
|
57
|
-
* Set by tools
|
|
58
|
-
* worktree) so the UI can show whether THIS call was isolated. `backend`
|
|
65
|
+
* Set by command-executing tools with sandbox visibility (Bash / background
|
|
66
|
+
* shell / worktree / PowerShell) so the UI can show whether THIS call was isolated. `backend`
|
|
59
67
|
* "off" means the command ran un-sandboxed (we surface it explicitly so the
|
|
60
68
|
* user sees "未隔离" rather than guessing from an absent badge); "seatbelt"
|
|
61
69
|
* / "bwrap" mean OS-level isolation applied. `network` is the policy that
|
|
62
|
-
* was in force (absent when off). Tools
|
|
63
|
-
*
|
|
70
|
+
* was in force (absent when off). Tools with no sandbox visibility leave this
|
|
71
|
+
* undefined and the UI renders no badge.
|
|
64
72
|
*/
|
|
65
73
|
sandbox?: {
|
|
66
74
|
backend: "off" | "seatbelt" | "bwrap";
|
|
@@ -97,6 +105,12 @@ export interface RegisteredTool {
|
|
|
97
105
|
inputSchema: Record<string, unknown>;
|
|
98
106
|
source: ToolSource;
|
|
99
107
|
serverName?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Declarative UI/metadata hint for hosts, docs, and capability listings.
|
|
110
|
+
* It is not an execution-policy input: PermissionClassifier does not read
|
|
111
|
+
* RegisteredTool and runtime decisions come from explicit rules, permission
|
|
112
|
+
* mode, approval backend, and tool-specific gates.
|
|
113
|
+
*/
|
|
100
114
|
permissionDefault: PermissionDecision;
|
|
101
115
|
isConcurrencySafe?: boolean;
|
|
102
116
|
isReadOnly?: boolean;
|
|
@@ -334,6 +348,7 @@ export type StreamEvent = {
|
|
|
334
348
|
} | {
|
|
335
349
|
type: "stream_request_start";
|
|
336
350
|
turnNumber: number;
|
|
351
|
+
messageId?: string;
|
|
337
352
|
agentId?: string;
|
|
338
353
|
} | {
|
|
339
354
|
type: "steer_injected";
|
|
@@ -360,6 +375,7 @@ export type StreamEvent = {
|
|
|
360
375
|
} | {
|
|
361
376
|
type: "assistant_message";
|
|
362
377
|
message: Message;
|
|
378
|
+
messageId?: string;
|
|
363
379
|
agentId?: string;
|
|
364
380
|
} | {
|
|
365
381
|
type: "turn_complete";
|
|
@@ -390,6 +406,7 @@ export type StreamEvent = {
|
|
|
390
406
|
} | {
|
|
391
407
|
type: "tombstone";
|
|
392
408
|
messageId: string;
|
|
409
|
+
agentId?: string;
|
|
393
410
|
} | {
|
|
394
411
|
type: "task_update";
|
|
395
412
|
tasks: TaskInfo[];
|
|
@@ -431,6 +448,8 @@ export type StreamEvent = {
|
|
|
431
448
|
} | {
|
|
432
449
|
type: "tool_summary";
|
|
433
450
|
summary: string;
|
|
451
|
+
toolCallIds?: string[];
|
|
452
|
+
agentId?: string;
|
|
434
453
|
} | {
|
|
435
454
|
type: "context_compact";
|
|
436
455
|
strategy: "micro" | "summary" | "window" | "snip" | "emergency" | "compacted";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-core",
|
|
3
|
-
"version": "0.6.0-rc.
|
|
3
|
+
"version": "0.6.0-rc.18",
|
|
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",
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
"./dist/data/*.json",
|
|
17
17
|
"./dist/prompt/sections/*.md"
|
|
18
18
|
],
|
|
19
|
-
"//files": "LICENSE/README intentionally omitted: npm/bun always include them in a publish regardless of this list, so the published tarball is unchanged — but listing them makes electron-builder (which follows the workspace symlink when packaging the desktop app) try to copy them from packages/core, tripping its 'file must be under <app>' guard (electron-builder#3238).
|
|
19
|
+
"//files": "LICENSE/README intentionally omitted: npm/bun always include them in a publish regardless of this list, so the published tarball is unchanged — but listing them makes electron-builder (which follows the workspace symlink when packaging the desktop app) try to copy them from packages/core, tripping its 'file must be under <app>' guard (electron-builder#3238). THIRD_PARTY_NOTICES.md is listed explicitly because third-party attribution must ship with npm tarballs.",
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
21
|
+
"dist",
|
|
22
|
+
"THIRD_PARTY_NOTICES.md"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
25
|
"build": "tsc -p tsconfig.json && bun run copy-assets",
|