@clawos-dev/clawd 0.2.70 → 0.2.71-beta.124.b09b0a0
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/cli.cjs +993 -280
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -114,6 +114,13 @@ var init_methods = __esm({
|
|
|
114
114
|
"attachment.groupList",
|
|
115
115
|
// v2:跨 session 聚合(本期 UI 不调,保留槽位用于 HTTP ACL 内部判定 / 未来 "All files" tab)
|
|
116
116
|
"attachment.groupListPersona",
|
|
117
|
+
// ---- capability:* (capability platform 鉴权底座) ----
|
|
118
|
+
// owner 颁发 / 列出 / 撤销给 guest 的 capability。三者均需 admin 权限(METHOD_GRANT_MAP
|
|
119
|
+
// 在 daemon 端固定为 `{ resource: '*', action: 'admin' }`,owner 自动满足)。
|
|
120
|
+
// 颁发后 daemon 推 'capability:tokenIssued' 帧;撤销推 'capability:revoked' 帧。
|
|
121
|
+
"capability:issue",
|
|
122
|
+
"capability:list",
|
|
123
|
+
"capability:revoke",
|
|
117
124
|
"info",
|
|
118
125
|
"ping"
|
|
119
126
|
];
|
|
@@ -605,8 +612,8 @@ var init_parseUtil = __esm({
|
|
|
605
612
|
init_errors2();
|
|
606
613
|
init_en();
|
|
607
614
|
makeIssue = (params) => {
|
|
608
|
-
const { data, path:
|
|
609
|
-
const fullPath = [...
|
|
615
|
+
const { data, path: path35, errorMaps, issueData } = params;
|
|
616
|
+
const fullPath = [...path35, ...issueData.path || []];
|
|
610
617
|
const fullIssue = {
|
|
611
618
|
...issueData,
|
|
612
619
|
path: fullPath
|
|
@@ -917,11 +924,11 @@ var init_types = __esm({
|
|
|
917
924
|
init_parseUtil();
|
|
918
925
|
init_util();
|
|
919
926
|
ParseInputLazyPath = class {
|
|
920
|
-
constructor(parent, value,
|
|
927
|
+
constructor(parent, value, path35, key) {
|
|
921
928
|
this._cachedPath = [];
|
|
922
929
|
this.parent = parent;
|
|
923
930
|
this.data = value;
|
|
924
|
-
this._path =
|
|
931
|
+
this._path = path35;
|
|
925
932
|
this._key = key;
|
|
926
933
|
}
|
|
927
934
|
get path() {
|
|
@@ -4331,11 +4338,9 @@ var init_attachment_schemas = __esm({
|
|
|
4331
4338
|
stale: external_exports.boolean().optional()
|
|
4332
4339
|
});
|
|
4333
4340
|
AttachmentSignUrlArgs = external_exports.object({
|
|
4334
|
-
/**
|
|
4335
|
-
|
|
4336
|
-
/**
|
|
4337
|
-
relPath: external_exports.string().min(1),
|
|
4338
|
-
/** TTL 秒数;缺省 24h;null 走永久 URL(不带 exp 字段) */
|
|
4341
|
+
/** 要分享的绝对路径;签名只关心 absPath,不区分 persona/session */
|
|
4342
|
+
absPath: external_exports.string().min(1),
|
|
4343
|
+
/** TTL 秒数;缺省 24h;'never' 走 null 不带 exp 字段(永久有效) */
|
|
4339
4344
|
ttlSeconds: external_exports.number().int().positive().nullable().optional()
|
|
4340
4345
|
});
|
|
4341
4346
|
AttachmentSignUrlResponseSchema = external_exports.object({
|
|
@@ -4470,7 +4475,7 @@ var init_persona_schemas = __esm({
|
|
|
4470
4475
|
});
|
|
4471
4476
|
|
|
4472
4477
|
// ../protocol/src/schemas.ts
|
|
4473
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema,
|
|
4478
|
+
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
|
|
4474
4479
|
var init_schemas = __esm({
|
|
4475
4480
|
"../protocol/src/schemas.ts"() {
|
|
4476
4481
|
"use strict";
|
|
@@ -4994,11 +4999,6 @@ var init_schemas = __esm({
|
|
|
4994
4999
|
subdomain: external_exports.string().nullable(),
|
|
4995
5000
|
url: external_exports.string().nullable()
|
|
4996
5001
|
});
|
|
4997
|
-
TunnelUnavailableEventSchema = external_exports.object({
|
|
4998
|
-
type: external_exports.literal("tunnel:unavailable"),
|
|
4999
|
-
reason: external_exports.string().min(1),
|
|
5000
|
-
failedAt: external_exports.string().min(1)
|
|
5001
|
-
});
|
|
5002
5002
|
InfoRunningSessionSchema = external_exports.object({
|
|
5003
5003
|
sessionId: external_exports.string().min(1),
|
|
5004
5004
|
status: SessionStatusSchema,
|
|
@@ -5049,6 +5049,69 @@ var init_persona_mode = __esm({
|
|
|
5049
5049
|
}
|
|
5050
5050
|
});
|
|
5051
5051
|
|
|
5052
|
+
// ../protocol/src/principal.ts
|
|
5053
|
+
var PrincipalKindSchema, PrincipalSchema, OWNER_PRINCIPAL;
|
|
5054
|
+
var init_principal = __esm({
|
|
5055
|
+
"../protocol/src/principal.ts"() {
|
|
5056
|
+
"use strict";
|
|
5057
|
+
init_zod();
|
|
5058
|
+
PrincipalKindSchema = external_exports.enum(["owner", "guest"]);
|
|
5059
|
+
PrincipalSchema = external_exports.object({
|
|
5060
|
+
id: external_exports.string().min(1),
|
|
5061
|
+
kind: PrincipalKindSchema,
|
|
5062
|
+
displayName: external_exports.string()
|
|
5063
|
+
}).strict();
|
|
5064
|
+
OWNER_PRINCIPAL = {
|
|
5065
|
+
id: "owner",
|
|
5066
|
+
kind: "owner",
|
|
5067
|
+
displayName: "owner"
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
});
|
|
5071
|
+
|
|
5072
|
+
// ../protocol/src/capability.ts
|
|
5073
|
+
function stripSecretHash(cap) {
|
|
5074
|
+
const { secretHash: _hash, ...wire } = cap;
|
|
5075
|
+
return wire;
|
|
5076
|
+
}
|
|
5077
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema;
|
|
5078
|
+
var init_capability = __esm({
|
|
5079
|
+
"../protocol/src/capability.ts"() {
|
|
5080
|
+
"use strict";
|
|
5081
|
+
init_zod();
|
|
5082
|
+
ResourceSchema = external_exports.discriminatedUnion("type", [
|
|
5083
|
+
external_exports.object({ type: external_exports.literal("persona"), id: external_exports.string().min(1) }).strict(),
|
|
5084
|
+
external_exports.object({ type: external_exports.literal("chat"), id: external_exports.string().min(1) }).strict(),
|
|
5085
|
+
external_exports.object({ type: external_exports.literal("*") }).strict()
|
|
5086
|
+
]);
|
|
5087
|
+
ActionSchema = external_exports.enum(["read", "send", "admin"]);
|
|
5088
|
+
GrantSchema = external_exports.object({
|
|
5089
|
+
resource: ResourceSchema,
|
|
5090
|
+
actions: external_exports.array(ActionSchema).min(1)
|
|
5091
|
+
}).strict();
|
|
5092
|
+
CapabilitySchema = external_exports.object({
|
|
5093
|
+
id: external_exports.string().min(1),
|
|
5094
|
+
// sha256(token) hex 64
|
|
5095
|
+
secretHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
5096
|
+
displayName: external_exports.string(),
|
|
5097
|
+
// 空数组合法 = 纯访客(只能 DM)
|
|
5098
|
+
grants: external_exports.array(GrantSchema),
|
|
5099
|
+
issuedAt: external_exports.number().int().nonnegative(),
|
|
5100
|
+
expiresAt: external_exports.number().int().positive().optional(),
|
|
5101
|
+
maxUses: external_exports.number().int().positive().optional(),
|
|
5102
|
+
usedCount: external_exports.number().int().nonnegative(),
|
|
5103
|
+
revokedAt: external_exports.number().int().positive().optional()
|
|
5104
|
+
}).strict();
|
|
5105
|
+
CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
|
|
5106
|
+
CapabilityErrorCodeSchema = external_exports.enum([
|
|
5107
|
+
"TOKEN_INVALID",
|
|
5108
|
+
"TOKEN_REVOKED",
|
|
5109
|
+
"TOKEN_EXPIRED",
|
|
5110
|
+
"TOKEN_EXHAUSTED"
|
|
5111
|
+
]);
|
|
5112
|
+
}
|
|
5113
|
+
});
|
|
5114
|
+
|
|
5052
5115
|
// ../protocol/src/runtime.ts
|
|
5053
5116
|
var init_runtime = __esm({
|
|
5054
5117
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5061,6 +5124,8 @@ var init_runtime = __esm({
|
|
|
5061
5124
|
init_persona_schemas();
|
|
5062
5125
|
init_persona_mode();
|
|
5063
5126
|
init_attachment_schemas();
|
|
5127
|
+
init_principal();
|
|
5128
|
+
init_capability();
|
|
5064
5129
|
}
|
|
5065
5130
|
});
|
|
5066
5131
|
|
|
@@ -5335,8 +5400,8 @@ var require_req = __commonJS({
|
|
|
5335
5400
|
if (req.originalUrl) {
|
|
5336
5401
|
_req.url = req.originalUrl;
|
|
5337
5402
|
} else {
|
|
5338
|
-
const
|
|
5339
|
-
_req.url = typeof
|
|
5403
|
+
const path35 = req.path;
|
|
5404
|
+
_req.url = typeof path35 === "string" ? path35 : req.url ? req.url.path || req.url : void 0;
|
|
5340
5405
|
}
|
|
5341
5406
|
if (req.query) {
|
|
5342
5407
|
_req.query = req.query;
|
|
@@ -5501,14 +5566,14 @@ var require_redact = __commonJS({
|
|
|
5501
5566
|
}
|
|
5502
5567
|
return obj;
|
|
5503
5568
|
}
|
|
5504
|
-
function parsePath(
|
|
5569
|
+
function parsePath(path35) {
|
|
5505
5570
|
const parts = [];
|
|
5506
5571
|
let current = "";
|
|
5507
5572
|
let inBrackets = false;
|
|
5508
5573
|
let inQuotes = false;
|
|
5509
5574
|
let quoteChar = "";
|
|
5510
|
-
for (let i = 0; i <
|
|
5511
|
-
const char =
|
|
5575
|
+
for (let i = 0; i < path35.length; i++) {
|
|
5576
|
+
const char = path35[i];
|
|
5512
5577
|
if (!inBrackets && char === ".") {
|
|
5513
5578
|
if (current) {
|
|
5514
5579
|
parts.push(current);
|
|
@@ -5639,10 +5704,10 @@ var require_redact = __commonJS({
|
|
|
5639
5704
|
return current;
|
|
5640
5705
|
}
|
|
5641
5706
|
function redactPaths(obj, paths, censor, remove = false) {
|
|
5642
|
-
for (const
|
|
5643
|
-
const parts = parsePath(
|
|
5707
|
+
for (const path35 of paths) {
|
|
5708
|
+
const parts = parsePath(path35);
|
|
5644
5709
|
if (parts.includes("*")) {
|
|
5645
|
-
redactWildcardPath(obj, parts, censor,
|
|
5710
|
+
redactWildcardPath(obj, parts, censor, path35, remove);
|
|
5646
5711
|
} else {
|
|
5647
5712
|
if (remove) {
|
|
5648
5713
|
removeKey(obj, parts);
|
|
@@ -5727,8 +5792,8 @@ var require_redact = __commonJS({
|
|
|
5727
5792
|
}
|
|
5728
5793
|
} else {
|
|
5729
5794
|
if (afterWildcard.includes("*")) {
|
|
5730
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
5731
|
-
const fullPath = [...pathArray.slice(0, pathLength), ...
|
|
5795
|
+
const wrappedCensor = typeof censor === "function" ? (value, path35) => {
|
|
5796
|
+
const fullPath = [...pathArray.slice(0, pathLength), ...path35];
|
|
5732
5797
|
return censor(value, fullPath);
|
|
5733
5798
|
} : censor;
|
|
5734
5799
|
redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
|
|
@@ -5763,8 +5828,8 @@ var require_redact = __commonJS({
|
|
|
5763
5828
|
return null;
|
|
5764
5829
|
}
|
|
5765
5830
|
const pathStructure = /* @__PURE__ */ new Map();
|
|
5766
|
-
for (const
|
|
5767
|
-
const parts = parsePath(
|
|
5831
|
+
for (const path35 of pathsToClone) {
|
|
5832
|
+
const parts = parsePath(path35);
|
|
5768
5833
|
let current = pathStructure;
|
|
5769
5834
|
for (let i = 0; i < parts.length; i++) {
|
|
5770
5835
|
const part = parts[i];
|
|
@@ -5816,24 +5881,24 @@ var require_redact = __commonJS({
|
|
|
5816
5881
|
}
|
|
5817
5882
|
return cloneSelectively(obj, pathStructure);
|
|
5818
5883
|
}
|
|
5819
|
-
function validatePath(
|
|
5820
|
-
if (typeof
|
|
5884
|
+
function validatePath(path35) {
|
|
5885
|
+
if (typeof path35 !== "string") {
|
|
5821
5886
|
throw new Error("Paths must be (non-empty) strings");
|
|
5822
5887
|
}
|
|
5823
|
-
if (
|
|
5888
|
+
if (path35 === "") {
|
|
5824
5889
|
throw new Error("Invalid redaction path ()");
|
|
5825
5890
|
}
|
|
5826
|
-
if (
|
|
5827
|
-
throw new Error(`Invalid redaction path (${
|
|
5891
|
+
if (path35.includes("..")) {
|
|
5892
|
+
throw new Error(`Invalid redaction path (${path35})`);
|
|
5828
5893
|
}
|
|
5829
|
-
if (
|
|
5830
|
-
throw new Error(`Invalid redaction path (${
|
|
5894
|
+
if (path35.includes(",")) {
|
|
5895
|
+
throw new Error(`Invalid redaction path (${path35})`);
|
|
5831
5896
|
}
|
|
5832
5897
|
let bracketCount = 0;
|
|
5833
5898
|
let inQuotes = false;
|
|
5834
5899
|
let quoteChar = "";
|
|
5835
|
-
for (let i = 0; i <
|
|
5836
|
-
const char =
|
|
5900
|
+
for (let i = 0; i < path35.length; i++) {
|
|
5901
|
+
const char = path35[i];
|
|
5837
5902
|
if ((char === '"' || char === "'") && bracketCount > 0) {
|
|
5838
5903
|
if (!inQuotes) {
|
|
5839
5904
|
inQuotes = true;
|
|
@@ -5847,20 +5912,20 @@ var require_redact = __commonJS({
|
|
|
5847
5912
|
} else if (char === "]" && !inQuotes) {
|
|
5848
5913
|
bracketCount--;
|
|
5849
5914
|
if (bracketCount < 0) {
|
|
5850
|
-
throw new Error(`Invalid redaction path (${
|
|
5915
|
+
throw new Error(`Invalid redaction path (${path35})`);
|
|
5851
5916
|
}
|
|
5852
5917
|
}
|
|
5853
5918
|
}
|
|
5854
5919
|
if (bracketCount !== 0) {
|
|
5855
|
-
throw new Error(`Invalid redaction path (${
|
|
5920
|
+
throw new Error(`Invalid redaction path (${path35})`);
|
|
5856
5921
|
}
|
|
5857
5922
|
}
|
|
5858
5923
|
function validatePaths(paths) {
|
|
5859
5924
|
if (!Array.isArray(paths)) {
|
|
5860
5925
|
throw new TypeError("paths must be an array");
|
|
5861
5926
|
}
|
|
5862
|
-
for (const
|
|
5863
|
-
validatePath(
|
|
5927
|
+
for (const path35 of paths) {
|
|
5928
|
+
validatePath(path35);
|
|
5864
5929
|
}
|
|
5865
5930
|
}
|
|
5866
5931
|
function slowRedact(options = {}) {
|
|
@@ -6028,8 +6093,8 @@ var require_redaction = __commonJS({
|
|
|
6028
6093
|
if (shape[k2] === null) {
|
|
6029
6094
|
o[k2] = (value) => topCensor(value, [k2]);
|
|
6030
6095
|
} else {
|
|
6031
|
-
const wrappedCensor = typeof censor === "function" ? (value,
|
|
6032
|
-
return censor(value, [k2, ...
|
|
6096
|
+
const wrappedCensor = typeof censor === "function" ? (value, path35) => {
|
|
6097
|
+
return censor(value, [k2, ...path35]);
|
|
6033
6098
|
} : censor;
|
|
6034
6099
|
o[k2] = Redact({
|
|
6035
6100
|
paths: shape[k2],
|
|
@@ -6247,10 +6312,10 @@ var require_atomic_sleep = __commonJS({
|
|
|
6247
6312
|
var require_sonic_boom = __commonJS({
|
|
6248
6313
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6249
6314
|
"use strict";
|
|
6250
|
-
var
|
|
6315
|
+
var fs31 = require("fs");
|
|
6251
6316
|
var EventEmitter2 = require("events");
|
|
6252
6317
|
var inherits = require("util").inherits;
|
|
6253
|
-
var
|
|
6318
|
+
var path35 = require("path");
|
|
6254
6319
|
var sleep = require_atomic_sleep();
|
|
6255
6320
|
var assert = require("assert");
|
|
6256
6321
|
var BUSY_WRITE_TIMEOUT = 100;
|
|
@@ -6304,20 +6369,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6304
6369
|
const mode = sonic.mode;
|
|
6305
6370
|
if (sonic.sync) {
|
|
6306
6371
|
try {
|
|
6307
|
-
if (sonic.mkdir)
|
|
6308
|
-
const fd =
|
|
6372
|
+
if (sonic.mkdir) fs31.mkdirSync(path35.dirname(file), { recursive: true });
|
|
6373
|
+
const fd = fs31.openSync(file, flags, mode);
|
|
6309
6374
|
fileOpened(null, fd);
|
|
6310
6375
|
} catch (err) {
|
|
6311
6376
|
fileOpened(err);
|
|
6312
6377
|
throw err;
|
|
6313
6378
|
}
|
|
6314
6379
|
} else if (sonic.mkdir) {
|
|
6315
|
-
|
|
6380
|
+
fs31.mkdir(path35.dirname(file), { recursive: true }, (err) => {
|
|
6316
6381
|
if (err) return fileOpened(err);
|
|
6317
|
-
|
|
6382
|
+
fs31.open(file, flags, mode, fileOpened);
|
|
6318
6383
|
});
|
|
6319
6384
|
} else {
|
|
6320
|
-
|
|
6385
|
+
fs31.open(file, flags, mode, fileOpened);
|
|
6321
6386
|
}
|
|
6322
6387
|
}
|
|
6323
6388
|
function SonicBoom(opts) {
|
|
@@ -6358,8 +6423,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6358
6423
|
this.flush = flushBuffer;
|
|
6359
6424
|
this.flushSync = flushBufferSync;
|
|
6360
6425
|
this._actualWrite = actualWriteBuffer;
|
|
6361
|
-
fsWriteSync = () =>
|
|
6362
|
-
fsWrite = () =>
|
|
6426
|
+
fsWriteSync = () => fs31.writeSync(this.fd, this._writingBuf);
|
|
6427
|
+
fsWrite = () => fs31.write(this.fd, this._writingBuf, this.release);
|
|
6363
6428
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6364
6429
|
this._writingBuf = "";
|
|
6365
6430
|
this.write = write;
|
|
@@ -6368,15 +6433,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6368
6433
|
this._actualWrite = actualWrite;
|
|
6369
6434
|
fsWriteSync = () => {
|
|
6370
6435
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6371
|
-
return
|
|
6436
|
+
return fs31.writeSync(this.fd, this._writingBuf);
|
|
6372
6437
|
}
|
|
6373
|
-
return
|
|
6438
|
+
return fs31.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6374
6439
|
};
|
|
6375
6440
|
fsWrite = () => {
|
|
6376
6441
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6377
|
-
return
|
|
6442
|
+
return fs31.write(this.fd, this._writingBuf, this.release);
|
|
6378
6443
|
}
|
|
6379
|
-
return
|
|
6444
|
+
return fs31.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6380
6445
|
};
|
|
6381
6446
|
} else {
|
|
6382
6447
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -6433,7 +6498,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6433
6498
|
}
|
|
6434
6499
|
}
|
|
6435
6500
|
if (this._fsync) {
|
|
6436
|
-
|
|
6501
|
+
fs31.fsyncSync(this.fd);
|
|
6437
6502
|
}
|
|
6438
6503
|
const len = this._len;
|
|
6439
6504
|
if (this._reopening) {
|
|
@@ -6547,7 +6612,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6547
6612
|
const onDrain = () => {
|
|
6548
6613
|
if (!this._fsync) {
|
|
6549
6614
|
try {
|
|
6550
|
-
|
|
6615
|
+
fs31.fsync(this.fd, (err) => {
|
|
6551
6616
|
this._flushPending = false;
|
|
6552
6617
|
cb(err);
|
|
6553
6618
|
});
|
|
@@ -6649,7 +6714,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6649
6714
|
const fd = this.fd;
|
|
6650
6715
|
this.once("ready", () => {
|
|
6651
6716
|
if (fd !== this.fd) {
|
|
6652
|
-
|
|
6717
|
+
fs31.close(fd, (err) => {
|
|
6653
6718
|
if (err) {
|
|
6654
6719
|
return this.emit("error", err);
|
|
6655
6720
|
}
|
|
@@ -6698,7 +6763,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6698
6763
|
buf = this._bufs[0];
|
|
6699
6764
|
}
|
|
6700
6765
|
try {
|
|
6701
|
-
const n = Buffer.isBuffer(buf) ?
|
|
6766
|
+
const n = Buffer.isBuffer(buf) ? fs31.writeSync(this.fd, buf) : fs31.writeSync(this.fd, buf, "utf8");
|
|
6702
6767
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
6703
6768
|
buf = releasedBufObj.writingBuf;
|
|
6704
6769
|
this._len = releasedBufObj.len;
|
|
@@ -6714,7 +6779,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6714
6779
|
}
|
|
6715
6780
|
}
|
|
6716
6781
|
try {
|
|
6717
|
-
|
|
6782
|
+
fs31.fsyncSync(this.fd);
|
|
6718
6783
|
} catch {
|
|
6719
6784
|
}
|
|
6720
6785
|
}
|
|
@@ -6735,7 +6800,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6735
6800
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
6736
6801
|
}
|
|
6737
6802
|
try {
|
|
6738
|
-
const n =
|
|
6803
|
+
const n = fs31.writeSync(this.fd, buf);
|
|
6739
6804
|
buf = buf.subarray(n);
|
|
6740
6805
|
this._len = Math.max(this._len - n, 0);
|
|
6741
6806
|
if (buf.length <= 0) {
|
|
@@ -6763,13 +6828,13 @@ var require_sonic_boom = __commonJS({
|
|
|
6763
6828
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
6764
6829
|
if (this.sync) {
|
|
6765
6830
|
try {
|
|
6766
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
6831
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs31.writeSync(this.fd, this._writingBuf) : fs31.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6767
6832
|
release(null, written);
|
|
6768
6833
|
} catch (err) {
|
|
6769
6834
|
release(err);
|
|
6770
6835
|
}
|
|
6771
6836
|
} else {
|
|
6772
|
-
|
|
6837
|
+
fs31.write(this.fd, this._writingBuf, release);
|
|
6773
6838
|
}
|
|
6774
6839
|
}
|
|
6775
6840
|
function actualWriteBuffer() {
|
|
@@ -6778,7 +6843,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6778
6843
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
6779
6844
|
if (this.sync) {
|
|
6780
6845
|
try {
|
|
6781
|
-
const written =
|
|
6846
|
+
const written = fs31.writeSync(this.fd, this._writingBuf);
|
|
6782
6847
|
release(null, written);
|
|
6783
6848
|
} catch (err) {
|
|
6784
6849
|
release(err);
|
|
@@ -6787,7 +6852,7 @@ var require_sonic_boom = __commonJS({
|
|
|
6787
6852
|
if (kCopyBuffer) {
|
|
6788
6853
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
6789
6854
|
}
|
|
6790
|
-
|
|
6855
|
+
fs31.write(this.fd, this._writingBuf, release);
|
|
6791
6856
|
}
|
|
6792
6857
|
}
|
|
6793
6858
|
function actualClose(sonic) {
|
|
@@ -6803,12 +6868,12 @@ var require_sonic_boom = __commonJS({
|
|
|
6803
6868
|
sonic._lens = [];
|
|
6804
6869
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
6805
6870
|
try {
|
|
6806
|
-
|
|
6871
|
+
fs31.fsync(sonic.fd, closeWrapped);
|
|
6807
6872
|
} catch {
|
|
6808
6873
|
}
|
|
6809
6874
|
function closeWrapped() {
|
|
6810
6875
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
6811
|
-
|
|
6876
|
+
fs31.close(sonic.fd, done);
|
|
6812
6877
|
} else {
|
|
6813
6878
|
done();
|
|
6814
6879
|
}
|
|
@@ -7065,7 +7130,7 @@ var require_thread_stream = __commonJS({
|
|
|
7065
7130
|
var { version: version2 } = require_package();
|
|
7066
7131
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7067
7132
|
var { Worker } = require("worker_threads");
|
|
7068
|
-
var { join:
|
|
7133
|
+
var { join: join8 } = require("path");
|
|
7069
7134
|
var { pathToFileURL } = require("url");
|
|
7070
7135
|
var { wait } = require_wait();
|
|
7071
7136
|
var {
|
|
@@ -7101,7 +7166,7 @@ var require_thread_stream = __commonJS({
|
|
|
7101
7166
|
function createWorker(stream, opts) {
|
|
7102
7167
|
const { filename, workerData } = opts;
|
|
7103
7168
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7104
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7169
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join8(__dirname, "lib", "worker.js");
|
|
7105
7170
|
const worker = new Worker(toExecute, {
|
|
7106
7171
|
...opts.workerOpts,
|
|
7107
7172
|
trackUnmanagedFds: false,
|
|
@@ -7487,7 +7552,7 @@ var require_transport = __commonJS({
|
|
|
7487
7552
|
"use strict";
|
|
7488
7553
|
var { createRequire } = require("module");
|
|
7489
7554
|
var getCallers = require_caller();
|
|
7490
|
-
var { join:
|
|
7555
|
+
var { join: join8, isAbsolute, sep: sep2 } = require("path");
|
|
7491
7556
|
var sleep = require_atomic_sleep();
|
|
7492
7557
|
var onExit = require_on_exit_leak_free();
|
|
7493
7558
|
var ThreadStream = require_thread_stream();
|
|
@@ -7550,7 +7615,7 @@ var require_transport = __commonJS({
|
|
|
7550
7615
|
throw new Error("only one of target or targets can be specified");
|
|
7551
7616
|
}
|
|
7552
7617
|
if (targets) {
|
|
7553
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7618
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
7554
7619
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
7555
7620
|
return {
|
|
7556
7621
|
...dest,
|
|
@@ -7568,7 +7633,7 @@ var require_transport = __commonJS({
|
|
|
7568
7633
|
});
|
|
7569
7634
|
});
|
|
7570
7635
|
} else if (pipeline2) {
|
|
7571
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
7636
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
7572
7637
|
options.pipelines = [pipeline2.map((dest) => {
|
|
7573
7638
|
return {
|
|
7574
7639
|
...dest,
|
|
@@ -7590,12 +7655,12 @@ var require_transport = __commonJS({
|
|
|
7590
7655
|
return origin;
|
|
7591
7656
|
}
|
|
7592
7657
|
if (origin === "pino/file") {
|
|
7593
|
-
return
|
|
7658
|
+
return join8(__dirname, "..", "file.js");
|
|
7594
7659
|
}
|
|
7595
7660
|
let fixTarget2;
|
|
7596
7661
|
for (const filePath of callers) {
|
|
7597
7662
|
try {
|
|
7598
|
-
const context = filePath === "node:repl" ? process.cwd() +
|
|
7663
|
+
const context = filePath === "node:repl" ? process.cwd() + sep2 : filePath;
|
|
7599
7664
|
fixTarget2 = createRequire(context).resolve(origin);
|
|
7600
7665
|
break;
|
|
7601
7666
|
} catch (err) {
|
|
@@ -8580,7 +8645,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8580
8645
|
return circularValue;
|
|
8581
8646
|
}
|
|
8582
8647
|
let res = "";
|
|
8583
|
-
let
|
|
8648
|
+
let join8 = ",";
|
|
8584
8649
|
const originalIndentation = indentation;
|
|
8585
8650
|
if (Array.isArray(value)) {
|
|
8586
8651
|
if (value.length === 0) {
|
|
@@ -8594,7 +8659,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8594
8659
|
indentation += spacer;
|
|
8595
8660
|
res += `
|
|
8596
8661
|
${indentation}`;
|
|
8597
|
-
|
|
8662
|
+
join8 = `,
|
|
8598
8663
|
${indentation}`;
|
|
8599
8664
|
}
|
|
8600
8665
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8602,13 +8667,13 @@ ${indentation}`;
|
|
|
8602
8667
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8603
8668
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8604
8669
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8605
|
-
res +=
|
|
8670
|
+
res += join8;
|
|
8606
8671
|
}
|
|
8607
8672
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
8608
8673
|
res += tmp !== void 0 ? tmp : "null";
|
|
8609
8674
|
if (value.length - 1 > maximumBreadth) {
|
|
8610
8675
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8611
|
-
res += `${
|
|
8676
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8612
8677
|
}
|
|
8613
8678
|
if (spacer !== "") {
|
|
8614
8679
|
res += `
|
|
@@ -8629,7 +8694,7 @@ ${originalIndentation}`;
|
|
|
8629
8694
|
let separator = "";
|
|
8630
8695
|
if (spacer !== "") {
|
|
8631
8696
|
indentation += spacer;
|
|
8632
|
-
|
|
8697
|
+
join8 = `,
|
|
8633
8698
|
${indentation}`;
|
|
8634
8699
|
whitespace = " ";
|
|
8635
8700
|
}
|
|
@@ -8643,13 +8708,13 @@ ${indentation}`;
|
|
|
8643
8708
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
8644
8709
|
if (tmp !== void 0) {
|
|
8645
8710
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8646
|
-
separator =
|
|
8711
|
+
separator = join8;
|
|
8647
8712
|
}
|
|
8648
8713
|
}
|
|
8649
8714
|
if (keyLength > maximumBreadth) {
|
|
8650
8715
|
const removedKeys = keyLength - maximumBreadth;
|
|
8651
8716
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
8652
|
-
separator =
|
|
8717
|
+
separator = join8;
|
|
8653
8718
|
}
|
|
8654
8719
|
if (spacer !== "" && separator.length > 1) {
|
|
8655
8720
|
res = `
|
|
@@ -8690,7 +8755,7 @@ ${originalIndentation}`;
|
|
|
8690
8755
|
}
|
|
8691
8756
|
const originalIndentation = indentation;
|
|
8692
8757
|
let res = "";
|
|
8693
|
-
let
|
|
8758
|
+
let join8 = ",";
|
|
8694
8759
|
if (Array.isArray(value)) {
|
|
8695
8760
|
if (value.length === 0) {
|
|
8696
8761
|
return "[]";
|
|
@@ -8703,7 +8768,7 @@ ${originalIndentation}`;
|
|
|
8703
8768
|
indentation += spacer;
|
|
8704
8769
|
res += `
|
|
8705
8770
|
${indentation}`;
|
|
8706
|
-
|
|
8771
|
+
join8 = `,
|
|
8707
8772
|
${indentation}`;
|
|
8708
8773
|
}
|
|
8709
8774
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -8711,13 +8776,13 @@ ${indentation}`;
|
|
|
8711
8776
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8712
8777
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8713
8778
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
8714
|
-
res +=
|
|
8779
|
+
res += join8;
|
|
8715
8780
|
}
|
|
8716
8781
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
8717
8782
|
res += tmp !== void 0 ? tmp : "null";
|
|
8718
8783
|
if (value.length - 1 > maximumBreadth) {
|
|
8719
8784
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8720
|
-
res += `${
|
|
8785
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8721
8786
|
}
|
|
8722
8787
|
if (spacer !== "") {
|
|
8723
8788
|
res += `
|
|
@@ -8730,7 +8795,7 @@ ${originalIndentation}`;
|
|
|
8730
8795
|
let whitespace = "";
|
|
8731
8796
|
if (spacer !== "") {
|
|
8732
8797
|
indentation += spacer;
|
|
8733
|
-
|
|
8798
|
+
join8 = `,
|
|
8734
8799
|
${indentation}`;
|
|
8735
8800
|
whitespace = " ";
|
|
8736
8801
|
}
|
|
@@ -8739,7 +8804,7 @@ ${indentation}`;
|
|
|
8739
8804
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
8740
8805
|
if (tmp !== void 0) {
|
|
8741
8806
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
8742
|
-
separator =
|
|
8807
|
+
separator = join8;
|
|
8743
8808
|
}
|
|
8744
8809
|
}
|
|
8745
8810
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -8797,20 +8862,20 @@ ${originalIndentation}`;
|
|
|
8797
8862
|
indentation += spacer;
|
|
8798
8863
|
let res2 = `
|
|
8799
8864
|
${indentation}`;
|
|
8800
|
-
const
|
|
8865
|
+
const join9 = `,
|
|
8801
8866
|
${indentation}`;
|
|
8802
8867
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
8803
8868
|
let i = 0;
|
|
8804
8869
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
8805
8870
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
8806
8871
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
8807
|
-
res2 +=
|
|
8872
|
+
res2 += join9;
|
|
8808
8873
|
}
|
|
8809
8874
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
8810
8875
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
8811
8876
|
if (value.length - 1 > maximumBreadth) {
|
|
8812
8877
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
8813
|
-
res2 += `${
|
|
8878
|
+
res2 += `${join9}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
8814
8879
|
}
|
|
8815
8880
|
res2 += `
|
|
8816
8881
|
${originalIndentation}`;
|
|
@@ -8826,16 +8891,16 @@ ${originalIndentation}`;
|
|
|
8826
8891
|
return '"[Object]"';
|
|
8827
8892
|
}
|
|
8828
8893
|
indentation += spacer;
|
|
8829
|
-
const
|
|
8894
|
+
const join8 = `,
|
|
8830
8895
|
${indentation}`;
|
|
8831
8896
|
let res = "";
|
|
8832
8897
|
let separator = "";
|
|
8833
8898
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
8834
8899
|
if (isTypedArrayWithEntries(value)) {
|
|
8835
|
-
res += stringifyTypedArray(value,
|
|
8900
|
+
res += stringifyTypedArray(value, join8, maximumBreadth);
|
|
8836
8901
|
keys = keys.slice(value.length);
|
|
8837
8902
|
maximumPropertiesToStringify -= value.length;
|
|
8838
|
-
separator =
|
|
8903
|
+
separator = join8;
|
|
8839
8904
|
}
|
|
8840
8905
|
if (deterministic) {
|
|
8841
8906
|
keys = sort(keys, comparator);
|
|
@@ -8846,13 +8911,13 @@ ${indentation}`;
|
|
|
8846
8911
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
8847
8912
|
if (tmp !== void 0) {
|
|
8848
8913
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
8849
|
-
separator =
|
|
8914
|
+
separator = join8;
|
|
8850
8915
|
}
|
|
8851
8916
|
}
|
|
8852
8917
|
if (keyLength > maximumBreadth) {
|
|
8853
8918
|
const removedKeys = keyLength - maximumBreadth;
|
|
8854
8919
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
8855
|
-
separator =
|
|
8920
|
+
separator = join8;
|
|
8856
8921
|
}
|
|
8857
8922
|
if (separator !== "") {
|
|
8858
8923
|
res = `
|
|
@@ -9943,11 +10008,11 @@ var init_lib = __esm({
|
|
|
9943
10008
|
}
|
|
9944
10009
|
}
|
|
9945
10010
|
},
|
|
9946
|
-
addToPath: function addToPath(
|
|
9947
|
-
var last =
|
|
10011
|
+
addToPath: function addToPath(path35, added, removed, oldPosInc, options) {
|
|
10012
|
+
var last = path35.lastComponent;
|
|
9948
10013
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
9949
10014
|
return {
|
|
9950
|
-
oldPos:
|
|
10015
|
+
oldPos: path35.oldPos + oldPosInc,
|
|
9951
10016
|
lastComponent: {
|
|
9952
10017
|
count: last.count + 1,
|
|
9953
10018
|
added,
|
|
@@ -9957,7 +10022,7 @@ var init_lib = __esm({
|
|
|
9957
10022
|
};
|
|
9958
10023
|
} else {
|
|
9959
10024
|
return {
|
|
9960
|
-
oldPos:
|
|
10025
|
+
oldPos: path35.oldPos + oldPosInc,
|
|
9961
10026
|
lastComponent: {
|
|
9962
10027
|
count: 1,
|
|
9963
10028
|
added,
|
|
@@ -10015,7 +10080,7 @@ var init_lib = __esm({
|
|
|
10015
10080
|
tokenize: function tokenize(value) {
|
|
10016
10081
|
return Array.from(value);
|
|
10017
10082
|
},
|
|
10018
|
-
join: function
|
|
10083
|
+
join: function join4(chars) {
|
|
10019
10084
|
return chars.join("");
|
|
10020
10085
|
},
|
|
10021
10086
|
postProcess: function postProcess(changeObjects) {
|
|
@@ -10388,10 +10453,10 @@ function attachmentToHistoryMessage(o, ts) {
|
|
|
10388
10453
|
const memories = raw.map((m2) => {
|
|
10389
10454
|
if (!m2 || typeof m2 !== "object") return null;
|
|
10390
10455
|
const rec = m2;
|
|
10391
|
-
const
|
|
10456
|
+
const path35 = typeof rec.path === "string" ? rec.path : null;
|
|
10392
10457
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
10393
|
-
if (!
|
|
10394
|
-
const entry = { path:
|
|
10458
|
+
if (!path35 || content == null) return null;
|
|
10459
|
+
const entry = { path: path35, content };
|
|
10395
10460
|
if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
|
|
10396
10461
|
return entry;
|
|
10397
10462
|
}).filter((m2) => m2 !== null);
|
|
@@ -10851,6 +10916,27 @@ var init_claude_history = __esm({
|
|
|
10851
10916
|
}
|
|
10852
10917
|
});
|
|
10853
10918
|
|
|
10919
|
+
// src/tools/sandbox.ts
|
|
10920
|
+
function shouldSandbox(cwd, personaRoot) {
|
|
10921
|
+
if (!personaRoot) return false;
|
|
10922
|
+
const sep2 = personaRoot.endsWith(path12.sep) ? "" : path12.sep;
|
|
10923
|
+
return cwd.startsWith(personaRoot + sep2) && cwd !== personaRoot;
|
|
10924
|
+
}
|
|
10925
|
+
function inferSandboxSettingsPath(cwd, personaRoot) {
|
|
10926
|
+
if (!shouldSandbox(cwd, personaRoot)) return null;
|
|
10927
|
+
const rel = path12.relative(personaRoot, cwd);
|
|
10928
|
+
const personaId = rel.split(path12.sep)[0];
|
|
10929
|
+
if (!personaId) return null;
|
|
10930
|
+
return path12.join(personaRoot, personaId, ".clawd", "sandbox-settings.json");
|
|
10931
|
+
}
|
|
10932
|
+
var path12;
|
|
10933
|
+
var init_sandbox = __esm({
|
|
10934
|
+
"src/tools/sandbox.ts"() {
|
|
10935
|
+
"use strict";
|
|
10936
|
+
path12 = __toESM(require("path"), 1);
|
|
10937
|
+
}
|
|
10938
|
+
});
|
|
10939
|
+
|
|
10854
10940
|
// src/tools/claude.ts
|
|
10855
10941
|
function macOSDesktopCandidates(home) {
|
|
10856
10942
|
return [
|
|
@@ -10915,7 +11001,8 @@ function buildSpawnArgs(ctx) {
|
|
|
10915
11001
|
throw new Error(`unexpected personaMode: ${String(_exhaustive)}`);
|
|
10916
11002
|
}
|
|
10917
11003
|
}
|
|
10918
|
-
|
|
11004
|
+
const sandboxSettings = ctx.extraSettings ?? inferSandboxSettingsPath(ctx.cwd, ctx.personaRoot);
|
|
11005
|
+
if (sandboxSettings) args.push("--settings", sandboxSettings);
|
|
10919
11006
|
if (ctx.extraSystemPrompt) args.push("--append-system-prompt", ctx.extraSystemPrompt);
|
|
10920
11007
|
if (ctx.effort) args.push("--effort", ctx.effort);
|
|
10921
11008
|
if (ctx.toolSessionId) args.push("--resume", ctx.toolSessionId);
|
|
@@ -11195,10 +11282,10 @@ function parseAttachment(obj) {
|
|
|
11195
11282
|
const memories = raw.map((m2) => {
|
|
11196
11283
|
if (!m2 || typeof m2 !== "object") return null;
|
|
11197
11284
|
const rec = m2;
|
|
11198
|
-
const
|
|
11285
|
+
const path35 = typeof rec.path === "string" ? rec.path : null;
|
|
11199
11286
|
const content = typeof rec.content === "string" ? rec.content : null;
|
|
11200
|
-
if (!
|
|
11201
|
-
const out = { path:
|
|
11287
|
+
if (!path35 || content == null) return null;
|
|
11288
|
+
const out = { path: path35, content };
|
|
11202
11289
|
if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
|
|
11203
11290
|
return out;
|
|
11204
11291
|
}).filter((m2) => m2 !== null);
|
|
@@ -11314,6 +11401,7 @@ var init_claude = __esm({
|
|
|
11314
11401
|
init_protocol();
|
|
11315
11402
|
init_claude_history();
|
|
11316
11403
|
init_tool_result_extra();
|
|
11404
|
+
init_sandbox();
|
|
11317
11405
|
ATTACHMENT_SILENT_SUBTYPES2 = /* @__PURE__ */ new Set([
|
|
11318
11406
|
"hook_additional_context",
|
|
11319
11407
|
"hook_success",
|
|
@@ -18696,7 +18784,7 @@ var require_websocket = __commonJS({
|
|
|
18696
18784
|
var http2 = require("http");
|
|
18697
18785
|
var net = require("net");
|
|
18698
18786
|
var tls = require("tls");
|
|
18699
|
-
var { randomBytes, createHash } = require("crypto");
|
|
18787
|
+
var { randomBytes: randomBytes2, createHash: createHash3 } = require("crypto");
|
|
18700
18788
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
18701
18789
|
var { URL: URL2 } = require("url");
|
|
18702
18790
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -19226,7 +19314,7 @@ var require_websocket = __commonJS({
|
|
|
19226
19314
|
}
|
|
19227
19315
|
}
|
|
19228
19316
|
const defaultPort = isSecure ? 443 : 80;
|
|
19229
|
-
const key =
|
|
19317
|
+
const key = randomBytes2(16).toString("base64");
|
|
19230
19318
|
const request = isSecure ? https.request : http2.request;
|
|
19231
19319
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19232
19320
|
let perMessageDeflate;
|
|
@@ -19356,7 +19444,7 @@ var require_websocket = __commonJS({
|
|
|
19356
19444
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
19357
19445
|
return;
|
|
19358
19446
|
}
|
|
19359
|
-
const digest =
|
|
19447
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
19360
19448
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
19361
19449
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
19362
19450
|
return;
|
|
@@ -19723,7 +19811,7 @@ var require_websocket_server = __commonJS({
|
|
|
19723
19811
|
var EventEmitter2 = require("events");
|
|
19724
19812
|
var http2 = require("http");
|
|
19725
19813
|
var { Duplex } = require("stream");
|
|
19726
|
-
var { createHash } = require("crypto");
|
|
19814
|
+
var { createHash: createHash3 } = require("crypto");
|
|
19727
19815
|
var extension2 = require_extension();
|
|
19728
19816
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
19729
19817
|
var subprotocol2 = require_subprotocol();
|
|
@@ -20024,7 +20112,7 @@ var require_websocket_server = __commonJS({
|
|
|
20024
20112
|
);
|
|
20025
20113
|
}
|
|
20026
20114
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
20027
|
-
const digest =
|
|
20115
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
20028
20116
|
const headers = [
|
|
20029
20117
|
"HTTP/1.1 101 Switching Protocols",
|
|
20030
20118
|
"Upgrade: websocket",
|
|
@@ -20112,7 +20200,7 @@ var require_websocket_server = __commonJS({
|
|
|
20112
20200
|
// src/run-case/recorder.ts
|
|
20113
20201
|
function startRunCaseRecorder(opts) {
|
|
20114
20202
|
const now = opts.now ?? Date.now;
|
|
20115
|
-
const dir =
|
|
20203
|
+
const dir = import_node_path27.default.dirname(opts.recordPath);
|
|
20116
20204
|
let stream = null;
|
|
20117
20205
|
let closing = false;
|
|
20118
20206
|
let closedSettled = false;
|
|
@@ -20152,12 +20240,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20152
20240
|
};
|
|
20153
20241
|
return { tap, close, closed };
|
|
20154
20242
|
}
|
|
20155
|
-
var import_node_fs25,
|
|
20243
|
+
var import_node_fs25, import_node_path27;
|
|
20156
20244
|
var init_recorder = __esm({
|
|
20157
20245
|
"src/run-case/recorder.ts"() {
|
|
20158
20246
|
"use strict";
|
|
20159
20247
|
import_node_fs25 = __toESM(require("fs"), 1);
|
|
20160
|
-
|
|
20248
|
+
import_node_path27 = __toESM(require("path"), 1);
|
|
20161
20249
|
}
|
|
20162
20250
|
});
|
|
20163
20251
|
|
|
@@ -20200,7 +20288,7 @@ var init_wire = __esm({
|
|
|
20200
20288
|
// src/run-case/controller.ts
|
|
20201
20289
|
async function runController(opts) {
|
|
20202
20290
|
const now = opts.now ?? Date.now;
|
|
20203
|
-
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(
|
|
20291
|
+
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path28.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
|
|
20204
20292
|
const ownsCwd = opts.cwd === void 0;
|
|
20205
20293
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20206
20294
|
const spawnCtx = { cwd };
|
|
@@ -20367,13 +20455,13 @@ async function runController(opts) {
|
|
|
20367
20455
|
}
|
|
20368
20456
|
return exitCode ?? 0;
|
|
20369
20457
|
}
|
|
20370
|
-
var import_node_fs26, import_node_os14,
|
|
20458
|
+
var import_node_fs26, import_node_os14, import_node_path28;
|
|
20371
20459
|
var init_controller = __esm({
|
|
20372
20460
|
"src/run-case/controller.ts"() {
|
|
20373
20461
|
"use strict";
|
|
20374
20462
|
import_node_fs26 = require("fs");
|
|
20375
20463
|
import_node_os14 = __toESM(require("os"), 1);
|
|
20376
|
-
|
|
20464
|
+
import_node_path28 = __toESM(require("path"), 1);
|
|
20377
20465
|
init_claude();
|
|
20378
20466
|
init_stdout_splitter();
|
|
20379
20467
|
init_permission_stdio();
|
|
@@ -20605,7 +20693,7 @@ Env (advanced):
|
|
|
20605
20693
|
`;
|
|
20606
20694
|
|
|
20607
20695
|
// src/index.ts
|
|
20608
|
-
var
|
|
20696
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
20609
20697
|
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
20610
20698
|
|
|
20611
20699
|
// src/logger.ts
|
|
@@ -20643,6 +20731,9 @@ function createLogger(opts = {}) {
|
|
|
20643
20731
|
return wrap(base);
|
|
20644
20732
|
}
|
|
20645
20733
|
|
|
20734
|
+
// src/session/store-factory.ts
|
|
20735
|
+
var path5 = __toESM(require("path"), 1);
|
|
20736
|
+
|
|
20646
20737
|
// src/session/store.ts
|
|
20647
20738
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
20648
20739
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -20680,12 +20771,16 @@ function safeFileName(sessionId) {
|
|
|
20680
20771
|
var SessionStore = class {
|
|
20681
20772
|
root;
|
|
20682
20773
|
constructor(opts) {
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
"
|
|
20687
|
-
|
|
20688
|
-
|
|
20774
|
+
if ("root" in opts) {
|
|
20775
|
+
this.root = opts.root;
|
|
20776
|
+
} else {
|
|
20777
|
+
const scope = opts.scope ?? { kind: "default" };
|
|
20778
|
+
this.root = import_node_path4.default.join(
|
|
20779
|
+
opts.dataDir,
|
|
20780
|
+
"sessions",
|
|
20781
|
+
...scopeSubPath(scope).map(safeFileName)
|
|
20782
|
+
);
|
|
20783
|
+
}
|
|
20689
20784
|
}
|
|
20690
20785
|
filePath(sessionId) {
|
|
20691
20786
|
return import_node_path4.default.join(this.root, `${safeFileName(sessionId)}.json`);
|
|
@@ -20750,6 +20845,66 @@ var SessionStore = class {
|
|
|
20750
20845
|
}
|
|
20751
20846
|
};
|
|
20752
20847
|
|
|
20848
|
+
// src/session/store-factory.ts
|
|
20849
|
+
var SessionStoreFactory = class {
|
|
20850
|
+
dataDir;
|
|
20851
|
+
bareStore = null;
|
|
20852
|
+
vmOwnerStores = /* @__PURE__ */ new Map();
|
|
20853
|
+
// vmGuest 索引 key = `${pid}::${capId}`
|
|
20854
|
+
vmGuestStores = /* @__PURE__ */ new Map();
|
|
20855
|
+
constructor(opts) {
|
|
20856
|
+
this.dataDir = opts.dataDir;
|
|
20857
|
+
}
|
|
20858
|
+
// ---- root path 派生(暴露给 migration / revoke cascade 等外部消费方) ----
|
|
20859
|
+
bareRoot() {
|
|
20860
|
+
return path5.join(this.dataDir, "sessions");
|
|
20861
|
+
}
|
|
20862
|
+
vmOwnerRoot(personaId) {
|
|
20863
|
+
return path5.join(
|
|
20864
|
+
this.dataDir,
|
|
20865
|
+
"personas",
|
|
20866
|
+
safeFileName(personaId),
|
|
20867
|
+
".clawd",
|
|
20868
|
+
"sessions",
|
|
20869
|
+
"owner"
|
|
20870
|
+
);
|
|
20871
|
+
}
|
|
20872
|
+
vmGuestRoot(personaId, capabilityId) {
|
|
20873
|
+
return path5.join(
|
|
20874
|
+
this.dataDir,
|
|
20875
|
+
"personas",
|
|
20876
|
+
safeFileName(personaId),
|
|
20877
|
+
".clawd",
|
|
20878
|
+
"sessions",
|
|
20879
|
+
"guests",
|
|
20880
|
+
safeFileName(capabilityId)
|
|
20881
|
+
);
|
|
20882
|
+
}
|
|
20883
|
+
// ---- SessionStore 工厂(缓存) ----
|
|
20884
|
+
forBare() {
|
|
20885
|
+
if (!this.bareStore) {
|
|
20886
|
+
this.bareStore = new SessionStore({ root: this.bareRoot() });
|
|
20887
|
+
}
|
|
20888
|
+
return this.bareStore;
|
|
20889
|
+
}
|
|
20890
|
+
forVmOwner(personaId) {
|
|
20891
|
+
const key = personaId;
|
|
20892
|
+
const cached = this.vmOwnerStores.get(key);
|
|
20893
|
+
if (cached) return cached;
|
|
20894
|
+
const st = new SessionStore({ root: this.vmOwnerRoot(personaId) });
|
|
20895
|
+
this.vmOwnerStores.set(key, st);
|
|
20896
|
+
return st;
|
|
20897
|
+
}
|
|
20898
|
+
forVmGuest(personaId, capabilityId) {
|
|
20899
|
+
const key = `${personaId}::${capabilityId}`;
|
|
20900
|
+
const cached = this.vmGuestStores.get(key);
|
|
20901
|
+
if (cached) return cached;
|
|
20902
|
+
const st = new SessionStore({ root: this.vmGuestRoot(personaId, capabilityId) });
|
|
20903
|
+
this.vmGuestStores.set(key, st);
|
|
20904
|
+
return st;
|
|
20905
|
+
}
|
|
20906
|
+
};
|
|
20907
|
+
|
|
20753
20908
|
// src/session/manager.ts
|
|
20754
20909
|
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
20755
20910
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
@@ -20909,7 +21064,10 @@ function buildSpawnContext(state, deps) {
|
|
|
20909
21064
|
toolSessionId: file.toolSessionId,
|
|
20910
21065
|
model: file.model,
|
|
20911
21066
|
permissionMode: file.permissionMode,
|
|
20912
|
-
effort: file.effort
|
|
21067
|
+
effort: file.effort,
|
|
21068
|
+
// Phase 2 capability platform (plan §3): personaRoot 透传给 buildSpawnArgs;
|
|
21069
|
+
// 内部 shouldSandbox(cwd, personaRoot) 决定是否注入 --settings sandbox-settings.
|
|
21070
|
+
personaRoot: deps.personaRoot
|
|
20913
21071
|
};
|
|
20914
21072
|
const meta = state.subSessionMeta;
|
|
20915
21073
|
if (meta?.personaMode) {
|
|
@@ -21636,7 +21794,8 @@ var SessionRunner = class {
|
|
|
21636
21794
|
now: this.hooks.now ?? Date.now,
|
|
21637
21795
|
resolveContextWindow: this.hooks.resolveContextWindow,
|
|
21638
21796
|
genUuid: this.hooks.genUuid ?? v4_default,
|
|
21639
|
-
ownerDisplayName: this.hooks.ownerDisplayName
|
|
21797
|
+
ownerDisplayName: this.hooks.ownerDisplayName,
|
|
21798
|
+
personaRoot: this.hooks.personaRoot
|
|
21640
21799
|
};
|
|
21641
21800
|
const { state, effects } = reduceSession(this.state, inputMsg, deps);
|
|
21642
21801
|
this.state = state;
|
|
@@ -22060,9 +22219,20 @@ var SessionManager = class {
|
|
|
22060
22219
|
attachObserver(observer) {
|
|
22061
22220
|
this.attachedObserver = observer;
|
|
22062
22221
|
}
|
|
22063
|
-
// 按 scope 拿对应的 SessionStore
|
|
22064
|
-
//
|
|
22222
|
+
// 按 scope 拿对应的 SessionStore.
|
|
22223
|
+
// Phase 2 (capability platform plan §1) 路由切到 SessionStoreFactory:
|
|
22224
|
+
// default → factory.forBare() <dataDir>/sessions/
|
|
22225
|
+
// persona/<pid>/owner → factory.forVmOwner(pid) <dataDir>/personas/<pid>/.clawd/sessions/owner/
|
|
22226
|
+
// persona/<pid>/listener → throw (listener 角色 #698 已下线, schema 字段保留但运行时不该出现)
|
|
22227
|
+
// 缺省 (无 factory 注入): 回退 dataDir+scope 老路径 (向后兼容旧 spec).
|
|
22065
22228
|
storeFor(scope) {
|
|
22229
|
+
if (this.deps.storeFactory) {
|
|
22230
|
+
if (scope.kind === "default") return this.deps.storeFactory.forBare();
|
|
22231
|
+
if (scope.mode === "owner") return this.deps.storeFactory.forVmOwner(scope.personaId);
|
|
22232
|
+
throw new Error(
|
|
22233
|
+
`SessionManager: listener scope is deprecated (#698); use forVmGuest in Phase 3+ instead`
|
|
22234
|
+
);
|
|
22235
|
+
}
|
|
22066
22236
|
if (scope.kind === "default") return this.deps.store;
|
|
22067
22237
|
const key = scopeKey(scope);
|
|
22068
22238
|
const cached = this.storesByScope.get(key);
|
|
@@ -22090,11 +22260,13 @@ var SessionManager = class {
|
|
|
22090
22260
|
scopeForFile(file) {
|
|
22091
22261
|
return file.ownerPersonaId ? { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" } : { kind: "default" };
|
|
22092
22262
|
}
|
|
22093
|
-
//
|
|
22094
|
-
//
|
|
22263
|
+
// 扫所有 persona 命名空间. 用于 findOwnedSession / listAllOwned 跨 scope 查询.
|
|
22264
|
+
// Phase 2 capability platform: 新布局下 persona 资产在 <dataDir>/personas/<pid>/,
|
|
22265
|
+
// 直接 readdir 这个目录拿所有 personaId. 老布局 (无 storeFactory) fallback 扫
|
|
22266
|
+
// <dataDir>/sessions/ 列子目录 (排除 'default').
|
|
22095
22267
|
listPersonaIdsOnDisk() {
|
|
22096
22268
|
if (!this.deps.dataDir) return [];
|
|
22097
|
-
const root = import_node_path6.default.join(this.deps.dataDir, "sessions");
|
|
22269
|
+
const root = this.deps.storeFactory ? import_node_path6.default.join(this.deps.dataDir, "personas") : import_node_path6.default.join(this.deps.dataDir, "sessions");
|
|
22098
22270
|
let entries;
|
|
22099
22271
|
try {
|
|
22100
22272
|
entries = import_node_fs5.default.readdirSync(root, { withFileTypes: true });
|
|
@@ -22106,19 +22278,9 @@ var SessionManager = class {
|
|
|
22106
22278
|
return entries.filter((e) => e.isDirectory() && e.name !== "default").map((e) => e.name);
|
|
22107
22279
|
}
|
|
22108
22280
|
// owner / default 两个分类下按 sessionId 找文件——前端只传 sessionId 不带 scope,
|
|
22109
|
-
// SessionManager
|
|
22110
|
-
// 1) active runner(用户当前在用的 session):runner.state.file 是 SessionFile 内存权威副本,
|
|
22111
|
-
// 直接返回,零磁盘 I/O。覆盖 attachment / file-sharing 等"用户操作 → 紧接着 RPC"的
|
|
22112
|
-
// 绝大多数场景
|
|
22113
|
-
// 2) default scope 磁盘:inactive default session 命中
|
|
22114
|
-
// 3) 所有 persona owner 目录扫盘:inactive persona owner session 命中
|
|
22281
|
+
// SessionManager 在这里扫盘定位(default 直接试,未命中再轮询所有 persona owner 目录)。
|
|
22115
22282
|
// listener sub-session 不走这条——transport 始终明确传 (personaId, sessionId)。
|
|
22116
|
-
//
|
|
22117
|
-
// 公开方法:attachment / file-sharing handler 通过 deps 闭包消费,不应直接持 SessionStore
|
|
22118
|
-
// (持 default-only store 是 file-sharing v1 的预存 bug 根因——见 fix #703)
|
|
22119
22283
|
findOwnedSession(sessionId) {
|
|
22120
|
-
const runner = this.runners.get(sessionId);
|
|
22121
|
-
if (runner) return runner.getState().file;
|
|
22122
22284
|
const dflt = this.deps.store.read(sessionId);
|
|
22123
22285
|
if (dflt) return dflt;
|
|
22124
22286
|
for (const personaId of this.listPersonaIdsOnDisk()) {
|
|
@@ -22128,13 +22290,6 @@ var SessionManager = class {
|
|
|
22128
22290
|
}
|
|
22129
22291
|
return null;
|
|
22130
22292
|
}
|
|
22131
|
-
// findOwnedSession + scopeForFile 收口。把"sessionId → SessionScope"的派生
|
|
22132
|
-
// 集中到 manager(scope 单一来源),调用方拿 scope 不再自己拼 object 也不依赖
|
|
22133
|
-
// ownerPersonaId 字段语义。给 attachment handler 通过 deps.getSessionScope 闭包消费。
|
|
22134
|
-
findOwnedSessionScope(sessionId) {
|
|
22135
|
-
const file = this.findOwnedSession(sessionId);
|
|
22136
|
-
return file ? this.scopeForFile(file) : null;
|
|
22137
|
-
}
|
|
22138
22293
|
// 合并 default + 所有 persona owner 的 SessionFile —— 桌面 App 主 session 列表入口。
|
|
22139
22294
|
// 同样不含 listener sub-session(listener 走 persona:listSubSessions RPC 单独入口)。
|
|
22140
22295
|
listAllOwned() {
|
|
@@ -22186,6 +22341,9 @@ var SessionManager = class {
|
|
|
22186
22341
|
dataDir: this.deps.dataDir,
|
|
22187
22342
|
personaStore: this.deps.personaStore,
|
|
22188
22343
|
ownerDisplayName: this.deps.ownerDisplayName,
|
|
22344
|
+
// Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
|
|
22345
|
+
// cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
|
|
22346
|
+
personaRoot: this.deps.personaRoot,
|
|
22189
22347
|
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
22190
22348
|
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
22191
22349
|
scope,
|
|
@@ -23198,7 +23356,7 @@ var SessionManager = class {
|
|
|
23198
23356
|
|
|
23199
23357
|
// src/persona/store.ts
|
|
23200
23358
|
var fs6 = __toESM(require("fs"), 1);
|
|
23201
|
-
var
|
|
23359
|
+
var path8 = __toESM(require("path"), 1);
|
|
23202
23360
|
init_protocol();
|
|
23203
23361
|
var DEFAULT_SETTINGS = {
|
|
23204
23362
|
permissions: {
|
|
@@ -23227,13 +23385,13 @@ var PersonaStore = class {
|
|
|
23227
23385
|
}
|
|
23228
23386
|
root;
|
|
23229
23387
|
personaDir(personaId) {
|
|
23230
|
-
return
|
|
23388
|
+
return path8.join(this.root, safeFileName(personaId));
|
|
23231
23389
|
}
|
|
23232
23390
|
metaPath(personaId) {
|
|
23233
|
-
return
|
|
23391
|
+
return path8.join(this.personaDir(personaId), ".clawd", "persona.json");
|
|
23234
23392
|
}
|
|
23235
23393
|
claudeMdPath(personaId) {
|
|
23236
|
-
return
|
|
23394
|
+
return path8.join(this.personaDir(personaId), "CLAUDE.md");
|
|
23237
23395
|
}
|
|
23238
23396
|
/**
|
|
23239
23397
|
* Sandbox settings 落盘路径 —— 故意放在 `.clawd/` 而不是 `.claude/`,让 CC 的
|
|
@@ -23243,11 +23401,11 @@ var PersonaStore = class {
|
|
|
23243
23401
|
* 加载 persona 人格,只有 listener 多一层 OS sandbox。
|
|
23244
23402
|
*/
|
|
23245
23403
|
sandboxSettingsPath(personaId) {
|
|
23246
|
-
return
|
|
23404
|
+
return path8.join(this.personaDir(personaId), ".clawd", "sandbox-settings.json");
|
|
23247
23405
|
}
|
|
23248
23406
|
write(persona, personality) {
|
|
23249
23407
|
const dir = this.personaDir(persona.personaId);
|
|
23250
|
-
fs6.mkdirSync(
|
|
23408
|
+
fs6.mkdirSync(path8.join(dir, ".clawd"), { recursive: true });
|
|
23251
23409
|
this.atomicWrite(this.claudeMdPath(persona.personaId), personality);
|
|
23252
23410
|
this.atomicWrite(
|
|
23253
23411
|
this.sandboxSettingsPath(persona.personaId),
|
|
@@ -23290,12 +23448,12 @@ var PersonaStore = class {
|
|
|
23290
23448
|
}
|
|
23291
23449
|
/** Persona 私有 skills 目录路径:<personaDir>/.claude/skills */
|
|
23292
23450
|
skillsDir(personaId) {
|
|
23293
|
-
return
|
|
23451
|
+
return path8.join(this.personaDir(personaId), ".claude", "skills");
|
|
23294
23452
|
}
|
|
23295
23453
|
list() {
|
|
23296
23454
|
if (!fs6.existsSync(this.root)) return [];
|
|
23297
23455
|
return fs6.readdirSync(this.root).filter((name) => {
|
|
23298
|
-
return fs6.existsSync(
|
|
23456
|
+
return fs6.existsSync(path8.join(this.root, name, ".clawd", "persona.json"));
|
|
23299
23457
|
});
|
|
23300
23458
|
}
|
|
23301
23459
|
remove(personaId) {
|
|
@@ -23694,7 +23852,17 @@ var PersonaManager = class {
|
|
|
23694
23852
|
}
|
|
23695
23853
|
/**
|
|
23696
23854
|
* 删除 persona。
|
|
23697
|
-
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/
|
|
23855
|
+
* PersonaStore.remove(personaId) 已级联删除整个 <personaRoot>/<personaId>/ 目录,
|
|
23856
|
+
* Phase 2 capability platform 新布局下含 .clawd/sessions/owner/ +
|
|
23857
|
+
* .clawd/sessions/guests/<capId>/, rmSync recursive force 一锅清.
|
|
23858
|
+
*
|
|
23859
|
+
* ⚠️ TODO (Phase 3+ design gap, reviewer P1 flagged):
|
|
23860
|
+
* ~/.clawd/capabilities.json 里若有 cap.grants 含本 personaId, grant 会变成
|
|
23861
|
+
* 悬空引用 (guest 仍能持 token 接入, 但调依赖该 persona 的 RPC 会 fail).
|
|
23862
|
+
* 选项: (A) 这里扫所有 cap 过滤掉该 personaId 的 grant; (B) revoke 所有 grant
|
|
23863
|
+
* 含该 personaId 的 cap. 当前因 cleanupGuestSessionsForCapability 用 rmSync
|
|
23864
|
+
* force=true 吞 ENOENT 不会 crash, 但 UI CapabilityManagerDrawer 会展示
|
|
23865
|
+
* "无效的 persona" grant. Phase 3 加 cross-reference 矩阵后正式实现.
|
|
23698
23866
|
*/
|
|
23699
23867
|
delete(personaId) {
|
|
23700
23868
|
this.deps.store.remove(personaId);
|
|
@@ -23759,7 +23927,7 @@ var PersonaManager = class {
|
|
|
23759
23927
|
|
|
23760
23928
|
// src/persona/seed.ts
|
|
23761
23929
|
var fs8 = __toESM(require("fs"), 1);
|
|
23762
|
-
var
|
|
23930
|
+
var path10 = __toESM(require("path"), 1);
|
|
23763
23931
|
var import_node_url = require("url");
|
|
23764
23932
|
var import_meta = {};
|
|
23765
23933
|
var DEFAULT_PERSONAS = [
|
|
@@ -23795,14 +23963,14 @@ var DEFAULT_PERSONAS = [
|
|
|
23795
23963
|
function findDefaultsRoot() {
|
|
23796
23964
|
const candidates = [];
|
|
23797
23965
|
try {
|
|
23798
|
-
const here =
|
|
23799
|
-
candidates.push(
|
|
23800
|
-
candidates.push(
|
|
23966
|
+
const here = path10.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
23967
|
+
candidates.push(path10.resolve(here, "defaults"));
|
|
23968
|
+
candidates.push(path10.resolve(here, "persona-defaults"));
|
|
23801
23969
|
} catch {
|
|
23802
23970
|
}
|
|
23803
23971
|
if (process.argv[1]) {
|
|
23804
|
-
const argvDir =
|
|
23805
|
-
candidates.push(
|
|
23972
|
+
const argvDir = path10.dirname(process.argv[1]);
|
|
23973
|
+
candidates.push(path10.resolve(argvDir, "persona-defaults"));
|
|
23806
23974
|
}
|
|
23807
23975
|
for (const c of candidates) {
|
|
23808
23976
|
try {
|
|
@@ -23819,7 +23987,7 @@ function seedDefaultPersonas(args) {
|
|
|
23819
23987
|
args.logger.info("persona.seed.skip", { personaId: entry.personaId, reason: "exists" });
|
|
23820
23988
|
continue;
|
|
23821
23989
|
}
|
|
23822
|
-
const bundleDir =
|
|
23990
|
+
const bundleDir = path10.join(args.defaultsRoot, entry.personaId);
|
|
23823
23991
|
if (!fs8.existsSync(bundleDir)) {
|
|
23824
23992
|
args.logger.warn("persona.seed.skip", {
|
|
23825
23993
|
personaId: entry.personaId,
|
|
@@ -23828,7 +23996,7 @@ function seedDefaultPersonas(args) {
|
|
|
23828
23996
|
});
|
|
23829
23997
|
continue;
|
|
23830
23998
|
}
|
|
23831
|
-
const claudeMdPath =
|
|
23999
|
+
const claudeMdPath = path10.join(bundleDir, "CLAUDE.md");
|
|
23832
24000
|
if (!fs8.existsSync(claudeMdPath)) {
|
|
23833
24001
|
args.logger.warn("persona.seed.skip", {
|
|
23834
24002
|
personaId: entry.personaId,
|
|
@@ -23857,8 +24025,8 @@ function seedDefaultPersonas(args) {
|
|
|
23857
24025
|
function copyBundleExtras(srcDir, dstDir) {
|
|
23858
24026
|
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
23859
24027
|
if (entry.name === "CLAUDE.md" || entry.name === ".clawd") continue;
|
|
23860
|
-
const srcPath =
|
|
23861
|
-
const dstPath =
|
|
24028
|
+
const srcPath = path10.join(srcDir, entry.name);
|
|
24029
|
+
const dstPath = path10.join(dstDir, entry.name);
|
|
23862
24030
|
if (entry.isDirectory()) {
|
|
23863
24031
|
fs8.cpSync(srcPath, dstPath, { recursive: true, dereference: true });
|
|
23864
24032
|
} else if (entry.isFile()) {
|
|
@@ -25240,6 +25408,9 @@ var LocalWsServer = class {
|
|
|
25240
25408
|
httpServer = null;
|
|
25241
25409
|
frameHandler = null;
|
|
25242
25410
|
clients = /* @__PURE__ */ new Map();
|
|
25411
|
+
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
25412
|
+
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
25413
|
+
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
25243
25414
|
logger;
|
|
25244
25415
|
pingIntervalMs;
|
|
25245
25416
|
async start() {
|
|
@@ -25330,6 +25501,17 @@ var LocalWsServer = class {
|
|
|
25330
25501
|
this.safeSend(c.ws, frame);
|
|
25331
25502
|
}
|
|
25332
25503
|
}
|
|
25504
|
+
// Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
|
|
25505
|
+
// 用于 capability:tokenIssued / capability:revoked 等 owner-only push frame——
|
|
25506
|
+
// guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
|
|
25507
|
+
broadcastToOwners(frame) {
|
|
25508
|
+
const gate = this.opts.authGate;
|
|
25509
|
+
for (const c of this.clients.values()) {
|
|
25510
|
+
if (gate && !gate.isAuthed(c.handle.id)) continue;
|
|
25511
|
+
if (c.ctx && c.ctx.principal.kind !== "owner") continue;
|
|
25512
|
+
this.safeSend(c.ws, frame);
|
|
25513
|
+
}
|
|
25514
|
+
}
|
|
25333
25515
|
firstSubscriber(sessionId) {
|
|
25334
25516
|
for (const c of this.clients.values()) {
|
|
25335
25517
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -25351,6 +25533,40 @@ var LocalWsServer = class {
|
|
|
25351
25533
|
if (!c) return;
|
|
25352
25534
|
this.safeSend(c.ws, frame);
|
|
25353
25535
|
}
|
|
25536
|
+
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
25537
|
+
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
25538
|
+
attachClientContext(clientId, ctx) {
|
|
25539
|
+
const c = this.clients.get(clientId);
|
|
25540
|
+
if (!c) return;
|
|
25541
|
+
c.ctx = ctx;
|
|
25542
|
+
if (ctx.capabilityId) {
|
|
25543
|
+
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
25544
|
+
if (!set) {
|
|
25545
|
+
set = /* @__PURE__ */ new Set();
|
|
25546
|
+
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
25547
|
+
}
|
|
25548
|
+
set.add(clientId);
|
|
25549
|
+
}
|
|
25550
|
+
}
|
|
25551
|
+
// 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
|
|
25552
|
+
getClientContext(clientId) {
|
|
25553
|
+
return this.clients.get(clientId)?.ctx ?? null;
|
|
25554
|
+
}
|
|
25555
|
+
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
25556
|
+
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
25557
|
+
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
25558
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25559
|
+
if (!set) return;
|
|
25560
|
+
const ids = [...set];
|
|
25561
|
+
for (const id of ids) {
|
|
25562
|
+
const c = this.clients.get(id);
|
|
25563
|
+
if (!c) continue;
|
|
25564
|
+
try {
|
|
25565
|
+
c.ws.close(code, reason);
|
|
25566
|
+
} catch {
|
|
25567
|
+
}
|
|
25568
|
+
}
|
|
25569
|
+
}
|
|
25354
25570
|
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25355
25571
|
routeConnection(socket, req) {
|
|
25356
25572
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
@@ -25385,7 +25601,7 @@ var LocalWsServer = class {
|
|
|
25385
25601
|
} catch {
|
|
25386
25602
|
}
|
|
25387
25603
|
}, this.pingIntervalMs);
|
|
25388
|
-
this.clients.set(id, { handle, ws: socket, pingTimer });
|
|
25604
|
+
this.clients.set(id, { handle, ws: socket, pingTimer, ctx: null });
|
|
25389
25605
|
this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
|
|
25390
25606
|
const authGate = this.opts.authGate;
|
|
25391
25607
|
let authed = true;
|
|
@@ -25409,6 +25625,12 @@ var LocalWsServer = class {
|
|
|
25409
25625
|
socket.on("close", () => {
|
|
25410
25626
|
const c = this.clients.get(id);
|
|
25411
25627
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
25628
|
+
const capId = c?.ctx?.capabilityId;
|
|
25629
|
+
if (capId) {
|
|
25630
|
+
const set = this.capabilityIdToClients.get(capId);
|
|
25631
|
+
set?.delete(id);
|
|
25632
|
+
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
25633
|
+
}
|
|
25412
25634
|
this.clients.delete(id);
|
|
25413
25635
|
authGate?.unregister(id);
|
|
25414
25636
|
this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
|
|
@@ -25557,15 +25779,27 @@ var AuthGate = class {
|
|
|
25557
25779
|
this.markFailed(handle.id);
|
|
25558
25780
|
return frame.type === "auth" ? "consumed" : "reject";
|
|
25559
25781
|
}
|
|
25560
|
-
|
|
25561
|
-
|
|
25562
|
-
this.
|
|
25563
|
-
|
|
25564
|
-
|
|
25565
|
-
|
|
25566
|
-
|
|
25567
|
-
|
|
25568
|
-
|
|
25782
|
+
let ctx = null;
|
|
25783
|
+
if (this.opts.authenticate) {
|
|
25784
|
+
const r = this.opts.authenticate(parsed.data.token);
|
|
25785
|
+
if (!r.ok) {
|
|
25786
|
+
this.opts.closeConnection(handle, 4401, r.code);
|
|
25787
|
+
this.markFailed(handle.id);
|
|
25788
|
+
return "consumed";
|
|
25789
|
+
}
|
|
25790
|
+
ctx = r.context;
|
|
25791
|
+
} else {
|
|
25792
|
+
if (!this.opts.expectedToken) {
|
|
25793
|
+
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
25794
|
+
this.markFailed(handle.id);
|
|
25795
|
+
return "consumed";
|
|
25796
|
+
}
|
|
25797
|
+
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
25798
|
+
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
25799
|
+
this.markFailed(handle.id);
|
|
25800
|
+
return "consumed";
|
|
25801
|
+
}
|
|
25802
|
+
ctx = this.opts.buildOwnerContext?.() ?? null;
|
|
25569
25803
|
}
|
|
25570
25804
|
if (st.timer != null) {
|
|
25571
25805
|
const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -25573,6 +25807,7 @@ var AuthGate = class {
|
|
|
25573
25807
|
}
|
|
25574
25808
|
st.authed = true;
|
|
25575
25809
|
st.timer = null;
|
|
25810
|
+
if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
|
|
25576
25811
|
this.opts.sendOk(handle, { type: "auth:ok" });
|
|
25577
25812
|
return "consumed";
|
|
25578
25813
|
}
|
|
@@ -25648,6 +25883,328 @@ function constantTimeEqual2(a, b2) {
|
|
|
25648
25883
|
return diff2 === 0;
|
|
25649
25884
|
}
|
|
25650
25885
|
|
|
25886
|
+
// ../protocol/src/index.ts
|
|
25887
|
+
init_runtime();
|
|
25888
|
+
|
|
25889
|
+
// src/transport/connection-context.ts
|
|
25890
|
+
function ownerContext() {
|
|
25891
|
+
return {
|
|
25892
|
+
principal: OWNER_PRINCIPAL,
|
|
25893
|
+
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
25894
|
+
};
|
|
25895
|
+
}
|
|
25896
|
+
function guestContext(cap) {
|
|
25897
|
+
return {
|
|
25898
|
+
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
25899
|
+
grants: cap.grants,
|
|
25900
|
+
capabilityId: cap.id
|
|
25901
|
+
};
|
|
25902
|
+
}
|
|
25903
|
+
function authenticate(token, deps) {
|
|
25904
|
+
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
25905
|
+
if (deps.isOwnerToken(token)) return { ok: true, context: ownerContext() };
|
|
25906
|
+
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
25907
|
+
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
25908
|
+
if (v2.ok) return { ok: true, context: guestContext(v2.capability) };
|
|
25909
|
+
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
25910
|
+
return { ok: false, code: v2.code };
|
|
25911
|
+
}
|
|
25912
|
+
|
|
25913
|
+
// src/permission/capability-store.ts
|
|
25914
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
25915
|
+
var path18 = __toESM(require("path"), 1);
|
|
25916
|
+
var CAPABILITIES_FILE_NAME = "capabilities.json";
|
|
25917
|
+
var FILE_VERSION = 1;
|
|
25918
|
+
var CapabilityStore = class {
|
|
25919
|
+
constructor(dataDir) {
|
|
25920
|
+
this.dataDir = dataDir;
|
|
25921
|
+
fs15.mkdirSync(dataDir, { recursive: true });
|
|
25922
|
+
this.cache = this.readFromDisk();
|
|
25923
|
+
}
|
|
25924
|
+
dataDir;
|
|
25925
|
+
cache;
|
|
25926
|
+
list() {
|
|
25927
|
+
return [...this.cache];
|
|
25928
|
+
}
|
|
25929
|
+
upsert(cap) {
|
|
25930
|
+
const idx = this.cache.findIndex((c) => c.id === cap.id);
|
|
25931
|
+
if (idx >= 0) {
|
|
25932
|
+
this.cache[idx] = cap;
|
|
25933
|
+
} else {
|
|
25934
|
+
this.cache.push(cap);
|
|
25935
|
+
}
|
|
25936
|
+
this.flush();
|
|
25937
|
+
}
|
|
25938
|
+
remove(id) {
|
|
25939
|
+
const next = this.cache.filter((c) => c.id !== id);
|
|
25940
|
+
if (next.length === this.cache.length) return;
|
|
25941
|
+
this.cache = next;
|
|
25942
|
+
this.flush();
|
|
25943
|
+
}
|
|
25944
|
+
filePath() {
|
|
25945
|
+
return path18.join(this.dataDir, CAPABILITIES_FILE_NAME);
|
|
25946
|
+
}
|
|
25947
|
+
readFromDisk() {
|
|
25948
|
+
const file = this.filePath();
|
|
25949
|
+
let raw;
|
|
25950
|
+
try {
|
|
25951
|
+
raw = fs15.readFileSync(file, "utf8");
|
|
25952
|
+
} catch (err) {
|
|
25953
|
+
if (err?.code === "ENOENT") return [];
|
|
25954
|
+
return [];
|
|
25955
|
+
}
|
|
25956
|
+
if (!raw.trim()) return [];
|
|
25957
|
+
let parsed;
|
|
25958
|
+
try {
|
|
25959
|
+
parsed = JSON.parse(raw);
|
|
25960
|
+
} catch {
|
|
25961
|
+
return [];
|
|
25962
|
+
}
|
|
25963
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
25964
|
+
const arr = parsed.capabilities;
|
|
25965
|
+
if (!Array.isArray(arr)) return [];
|
|
25966
|
+
const out = [];
|
|
25967
|
+
for (const item of arr) {
|
|
25968
|
+
const r = CapabilitySchema.safeParse(item);
|
|
25969
|
+
if (r.success) out.push(r.data);
|
|
25970
|
+
}
|
|
25971
|
+
return out;
|
|
25972
|
+
}
|
|
25973
|
+
flush() {
|
|
25974
|
+
const content = { version: FILE_VERSION, capabilities: this.cache };
|
|
25975
|
+
this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
|
|
25976
|
+
}
|
|
25977
|
+
atomicWrite(file, content) {
|
|
25978
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
25979
|
+
fs15.writeFileSync(tmp, content, { mode: 384 });
|
|
25980
|
+
fs15.renameSync(tmp, file);
|
|
25981
|
+
try {
|
|
25982
|
+
fs15.chmodSync(file, 384);
|
|
25983
|
+
} catch {
|
|
25984
|
+
}
|
|
25985
|
+
}
|
|
25986
|
+
};
|
|
25987
|
+
|
|
25988
|
+
// src/permission/capability-registry.ts
|
|
25989
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
25990
|
+
var CapabilityRegistry = class {
|
|
25991
|
+
constructor(store, now = () => Date.now()) {
|
|
25992
|
+
this.store = store;
|
|
25993
|
+
this.now = now;
|
|
25994
|
+
for (const cap of store.list()) {
|
|
25995
|
+
this.bySecretHash.set(cap.secretHash, cap);
|
|
25996
|
+
}
|
|
25997
|
+
}
|
|
25998
|
+
store;
|
|
25999
|
+
now;
|
|
26000
|
+
// sha256(token) → Capability
|
|
26001
|
+
bySecretHash = /* @__PURE__ */ new Map();
|
|
26002
|
+
list() {
|
|
26003
|
+
return this.store.list();
|
|
26004
|
+
}
|
|
26005
|
+
verifyToken(token) {
|
|
26006
|
+
if (!token) return { ok: false, code: "TOKEN_INVALID" };
|
|
26007
|
+
const hash = sha256Hex(token);
|
|
26008
|
+
const cap = this.bySecretHash.get(hash);
|
|
26009
|
+
if (!cap) return { ok: false, code: "TOKEN_INVALID" };
|
|
26010
|
+
if (cap.revokedAt) return { ok: false, code: "TOKEN_REVOKED" };
|
|
26011
|
+
if (cap.expiresAt && this.now() >= cap.expiresAt) {
|
|
26012
|
+
return { ok: false, code: "TOKEN_EXPIRED" };
|
|
26013
|
+
}
|
|
26014
|
+
if (cap.maxUses && cap.usedCount >= cap.maxUses) {
|
|
26015
|
+
return { ok: false, code: "TOKEN_EXHAUSTED" };
|
|
26016
|
+
}
|
|
26017
|
+
const updated = { ...cap, usedCount: cap.usedCount + 1 };
|
|
26018
|
+
this.bySecretHash.set(hash, updated);
|
|
26019
|
+
this.store.upsert(updated);
|
|
26020
|
+
return { ok: true, capability: updated };
|
|
26021
|
+
}
|
|
26022
|
+
upsertCapability(cap) {
|
|
26023
|
+
this.bySecretHash.set(cap.secretHash, cap);
|
|
26024
|
+
this.store.upsert(cap);
|
|
26025
|
+
}
|
|
26026
|
+
markRevoked(id, revokedAt) {
|
|
26027
|
+
const current = this.store.list().find((c) => c.id === id);
|
|
26028
|
+
if (!current) return null;
|
|
26029
|
+
if (current.revokedAt) return current;
|
|
26030
|
+
const updated = { ...current, revokedAt };
|
|
26031
|
+
this.bySecretHash.set(updated.secretHash, updated);
|
|
26032
|
+
this.store.upsert(updated);
|
|
26033
|
+
return updated;
|
|
26034
|
+
}
|
|
26035
|
+
findById(id) {
|
|
26036
|
+
return this.store.list().find((c) => c.id === id) ?? null;
|
|
26037
|
+
}
|
|
26038
|
+
};
|
|
26039
|
+
function sha256Hex(s) {
|
|
26040
|
+
return crypto4.createHash("sha256").update(s).digest("hex");
|
|
26041
|
+
}
|
|
26042
|
+
|
|
26043
|
+
// src/permission/capability-manager.ts
|
|
26044
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
26045
|
+
var CapabilityManager = class {
|
|
26046
|
+
constructor(registry2, hooks = {}) {
|
|
26047
|
+
this.registry = registry2;
|
|
26048
|
+
this.hooks = hooks;
|
|
26049
|
+
}
|
|
26050
|
+
registry;
|
|
26051
|
+
hooks;
|
|
26052
|
+
list() {
|
|
26053
|
+
return this.registry.list();
|
|
26054
|
+
}
|
|
26055
|
+
issue(args) {
|
|
26056
|
+
if (!args.displayName.trim()) {
|
|
26057
|
+
throw new Error("CapabilityManager.issue: displayName must be non-empty");
|
|
26058
|
+
}
|
|
26059
|
+
const token = (this.hooks.generateToken ?? defaultGenerateToken)();
|
|
26060
|
+
const id = (this.hooks.generateId ?? defaultGenerateId)();
|
|
26061
|
+
const now = (this.hooks.now ?? Date.now)();
|
|
26062
|
+
const cap = {
|
|
26063
|
+
id,
|
|
26064
|
+
secretHash: sha256Hex2(token),
|
|
26065
|
+
displayName: args.displayName,
|
|
26066
|
+
grants: args.grants,
|
|
26067
|
+
issuedAt: now,
|
|
26068
|
+
usedCount: 0,
|
|
26069
|
+
...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
|
|
26070
|
+
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
26071
|
+
};
|
|
26072
|
+
this.registry.upsertCapability(cap);
|
|
26073
|
+
this.hooks.onIssued?.(cap, token);
|
|
26074
|
+
return { token, capability: cap };
|
|
26075
|
+
}
|
|
26076
|
+
revoke(id) {
|
|
26077
|
+
const existing = this.registry.findById(id);
|
|
26078
|
+
if (!existing) return null;
|
|
26079
|
+
const now = (this.hooks.now ?? Date.now)();
|
|
26080
|
+
if (existing.revokedAt) {
|
|
26081
|
+
return { revokedAt: existing.revokedAt, capability: existing };
|
|
26082
|
+
}
|
|
26083
|
+
const updated = this.registry.markRevoked(id, now);
|
|
26084
|
+
if (!updated) return null;
|
|
26085
|
+
this.hooks.onRevoked?.(updated);
|
|
26086
|
+
return { revokedAt: now, capability: updated };
|
|
26087
|
+
}
|
|
26088
|
+
};
|
|
26089
|
+
function defaultGenerateToken() {
|
|
26090
|
+
return crypto5.randomBytes(24).toString("base64url");
|
|
26091
|
+
}
|
|
26092
|
+
function defaultGenerateId() {
|
|
26093
|
+
return "cap_" + crypto5.randomBytes(6).toString("base64url");
|
|
26094
|
+
}
|
|
26095
|
+
function sha256Hex2(s) {
|
|
26096
|
+
return crypto5.createHash("sha256").update(s).digest("hex");
|
|
26097
|
+
}
|
|
26098
|
+
|
|
26099
|
+
// src/permission/cleanup.ts
|
|
26100
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
26101
|
+
function cleanupGuestSessionsForCapability(cap, factory) {
|
|
26102
|
+
const removed = [];
|
|
26103
|
+
for (const g2 of cap.grants) {
|
|
26104
|
+
if (g2.resource.type !== "persona") continue;
|
|
26105
|
+
const dir = factory.vmGuestRoot(g2.resource.id, cap.id);
|
|
26106
|
+
try {
|
|
26107
|
+
fs16.rmSync(dir, { recursive: true, force: true });
|
|
26108
|
+
removed.push(dir);
|
|
26109
|
+
} catch {
|
|
26110
|
+
}
|
|
26111
|
+
}
|
|
26112
|
+
return { removed };
|
|
26113
|
+
}
|
|
26114
|
+
|
|
26115
|
+
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
26116
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
26117
|
+
var path19 = __toESM(require("path"), 1);
|
|
26118
|
+
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
26119
|
+
function migrateFlattenSessions(opts) {
|
|
26120
|
+
const dataDir = opts.dataDir;
|
|
26121
|
+
const now = opts.now ?? Date.now;
|
|
26122
|
+
const sessionsDir = path19.join(dataDir, "sessions");
|
|
26123
|
+
const flagPath = path19.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
26124
|
+
if (existsSync3(flagPath)) {
|
|
26125
|
+
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
26126
|
+
}
|
|
26127
|
+
let movedBare = 0;
|
|
26128
|
+
let movedVmOwner = 0;
|
|
26129
|
+
let archivedListener = 0;
|
|
26130
|
+
const defaultDir = path19.join(sessionsDir, "default");
|
|
26131
|
+
if (existsSync3(defaultDir)) {
|
|
26132
|
+
for (const entry of readdirSafe(defaultDir)) {
|
|
26133
|
+
if (!entry.endsWith(".json")) continue;
|
|
26134
|
+
const src = path19.join(defaultDir, entry);
|
|
26135
|
+
const dst = path19.join(sessionsDir, entry);
|
|
26136
|
+
fs17.renameSync(src, dst);
|
|
26137
|
+
movedBare += 1;
|
|
26138
|
+
}
|
|
26139
|
+
rmdirIfEmpty(defaultDir);
|
|
26140
|
+
}
|
|
26141
|
+
for (const pid of readdirSafe(sessionsDir)) {
|
|
26142
|
+
const personaDir = path19.join(sessionsDir, pid);
|
|
26143
|
+
if (!isDir(personaDir)) continue;
|
|
26144
|
+
if (pid === "default") continue;
|
|
26145
|
+
const ownerSrc = path19.join(personaDir, "owner");
|
|
26146
|
+
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
26147
|
+
const ownerDst = path19.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
26148
|
+
fs17.mkdirSync(ownerDst, { recursive: true });
|
|
26149
|
+
for (const file of readdirSafe(ownerSrc)) {
|
|
26150
|
+
if (!file.endsWith(".json")) continue;
|
|
26151
|
+
fs17.renameSync(path19.join(ownerSrc, file), path19.join(ownerDst, file));
|
|
26152
|
+
movedVmOwner += 1;
|
|
26153
|
+
}
|
|
26154
|
+
rmdirIfEmpty(ownerSrc);
|
|
26155
|
+
}
|
|
26156
|
+
const listenerSrc = path19.join(personaDir, "listener");
|
|
26157
|
+
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
26158
|
+
const archiveDst = path19.join(dataDir, ".legacy", `listener-${pid}`);
|
|
26159
|
+
fs17.mkdirSync(archiveDst, { recursive: true });
|
|
26160
|
+
for (const file of readdirSafe(listenerSrc)) {
|
|
26161
|
+
if (!file.endsWith(".json")) continue;
|
|
26162
|
+
fs17.renameSync(path19.join(listenerSrc, file), path19.join(archiveDst, file));
|
|
26163
|
+
archivedListener += 1;
|
|
26164
|
+
}
|
|
26165
|
+
rmdirIfEmpty(listenerSrc);
|
|
26166
|
+
}
|
|
26167
|
+
rmdirIfEmpty(personaDir);
|
|
26168
|
+
}
|
|
26169
|
+
fs17.mkdirSync(sessionsDir, { recursive: true });
|
|
26170
|
+
fs17.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
26171
|
+
return {
|
|
26172
|
+
skipped: false,
|
|
26173
|
+
flagWritten: true,
|
|
26174
|
+
movedBare,
|
|
26175
|
+
movedVmOwner,
|
|
26176
|
+
archivedListener
|
|
26177
|
+
};
|
|
26178
|
+
}
|
|
26179
|
+
function existsSync3(p2) {
|
|
26180
|
+
try {
|
|
26181
|
+
fs17.statSync(p2);
|
|
26182
|
+
return true;
|
|
26183
|
+
} catch {
|
|
26184
|
+
return false;
|
|
26185
|
+
}
|
|
26186
|
+
}
|
|
26187
|
+
function isDir(p2) {
|
|
26188
|
+
try {
|
|
26189
|
+
return fs17.statSync(p2).isDirectory();
|
|
26190
|
+
} catch {
|
|
26191
|
+
return false;
|
|
26192
|
+
}
|
|
26193
|
+
}
|
|
26194
|
+
function readdirSafe(p2) {
|
|
26195
|
+
try {
|
|
26196
|
+
return fs17.readdirSync(p2);
|
|
26197
|
+
} catch {
|
|
26198
|
+
return [];
|
|
26199
|
+
}
|
|
26200
|
+
}
|
|
26201
|
+
function rmdirIfEmpty(p2) {
|
|
26202
|
+
try {
|
|
26203
|
+
fs17.rmdirSync(p2);
|
|
26204
|
+
} catch {
|
|
26205
|
+
}
|
|
26206
|
+
}
|
|
26207
|
+
|
|
25651
26208
|
// src/transport/http-router.ts
|
|
25652
26209
|
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
25653
26210
|
var import_node_path16 = __toESM(require("path"), 1);
|
|
@@ -26869,24 +27426,14 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
26869
27426
|
function authFilePath(dataDir) {
|
|
26870
27427
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
26871
27428
|
}
|
|
26872
|
-
function
|
|
27429
|
+
function loadOrCreateAuthToken(opts) {
|
|
26873
27430
|
const file = authFilePath(opts.dataDir);
|
|
26874
|
-
const generate = opts.generate ?? defaultGenerate;
|
|
26875
|
-
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
26876
27431
|
const existing = readAuthFile(file);
|
|
26877
|
-
if (existing && existing.token
|
|
26878
|
-
|
|
26879
|
-
|
|
26880
|
-
|
|
26881
|
-
|
|
26882
|
-
};
|
|
26883
|
-
}
|
|
26884
|
-
const token = existing?.token || generate();
|
|
26885
|
-
const signSecret = existing?.signSecret || generate();
|
|
26886
|
-
const createdAt = existing?.createdAt || now().toISOString();
|
|
26887
|
-
const next = { token, signSecret, createdAt };
|
|
26888
|
-
writeAuthFile(file, next);
|
|
26889
|
-
return next;
|
|
27432
|
+
if (existing && existing.token) return existing.token;
|
|
27433
|
+
const token = (opts.generate ?? defaultGenerate)();
|
|
27434
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
27435
|
+
writeAuthFile(file, { token, createdAt: now.toISOString() });
|
|
27436
|
+
return token;
|
|
26890
27437
|
}
|
|
26891
27438
|
function defaultGenerate() {
|
|
26892
27439
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
@@ -26895,14 +27442,13 @@ function readAuthFile(file) {
|
|
|
26895
27442
|
try {
|
|
26896
27443
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
26897
27444
|
const parsed = JSON.parse(raw);
|
|
26898
|
-
if (typeof parsed?.token
|
|
26899
|
-
return
|
|
27445
|
+
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
27446
|
+
return {
|
|
27447
|
+
token: parsed.token,
|
|
27448
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
27449
|
+
};
|
|
26900
27450
|
}
|
|
26901
|
-
return
|
|
26902
|
-
token: parsed.token,
|
|
26903
|
-
signSecret: typeof parsed.signSecret === "string" && parsed.signSecret.length > 0 ? parsed.signSecret : void 0,
|
|
26904
|
-
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : void 0
|
|
26905
|
-
};
|
|
27451
|
+
return null;
|
|
26906
27452
|
} catch (err) {
|
|
26907
27453
|
const code = err?.code;
|
|
26908
27454
|
if (code === "ENOENT") return null;
|
|
@@ -27446,6 +27992,65 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
27446
27992
|
};
|
|
27447
27993
|
}
|
|
27448
27994
|
|
|
27995
|
+
// src/handlers/capability.ts
|
|
27996
|
+
init_zod();
|
|
27997
|
+
init_protocol();
|
|
27998
|
+
var IssueArgsSchema = external_exports.object({
|
|
27999
|
+
displayName: external_exports.string().min(1),
|
|
28000
|
+
grants: external_exports.array(GrantSchema),
|
|
28001
|
+
expiresAt: external_exports.number().int().positive().optional(),
|
|
28002
|
+
maxUses: external_exports.number().int().positive().optional()
|
|
28003
|
+
}).strict();
|
|
28004
|
+
var RevokeArgsSchema = external_exports.object({
|
|
28005
|
+
capabilityId: external_exports.string().min(1)
|
|
28006
|
+
}).strict();
|
|
28007
|
+
function buildCapabilityHandlers(deps) {
|
|
28008
|
+
const { manager } = deps;
|
|
28009
|
+
const issue = async (frame) => {
|
|
28010
|
+
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28011
|
+
const args = IssueArgsSchema.parse(rest);
|
|
28012
|
+
const { token, capability } = manager.issue(args);
|
|
28013
|
+
return {
|
|
28014
|
+
response: {
|
|
28015
|
+
type: "capability:issued",
|
|
28016
|
+
token,
|
|
28017
|
+
capability: stripSecretHash(capability)
|
|
28018
|
+
}
|
|
28019
|
+
};
|
|
28020
|
+
};
|
|
28021
|
+
const list = async () => {
|
|
28022
|
+
return {
|
|
28023
|
+
response: {
|
|
28024
|
+
type: "capability:list",
|
|
28025
|
+
capabilities: manager.list().map(stripSecretHash)
|
|
28026
|
+
}
|
|
28027
|
+
};
|
|
28028
|
+
};
|
|
28029
|
+
const revoke = async (frame) => {
|
|
28030
|
+
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
28031
|
+
const args = RevokeArgsSchema.parse(rest);
|
|
28032
|
+
const result = manager.revoke(args.capabilityId);
|
|
28033
|
+
if (!result) {
|
|
28034
|
+
throw new ClawdError(
|
|
28035
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
28036
|
+
`capability not found: ${args.capabilityId}`
|
|
28037
|
+
);
|
|
28038
|
+
}
|
|
28039
|
+
return {
|
|
28040
|
+
response: {
|
|
28041
|
+
type: "capability:revoked",
|
|
28042
|
+
capabilityId: args.capabilityId,
|
|
28043
|
+
revokedAt: result.revokedAt
|
|
28044
|
+
}
|
|
28045
|
+
};
|
|
28046
|
+
};
|
|
28047
|
+
return {
|
|
28048
|
+
"capability:issue": issue,
|
|
28049
|
+
"capability:list": list,
|
|
28050
|
+
"capability:revoke": revoke
|
|
28051
|
+
};
|
|
28052
|
+
}
|
|
28053
|
+
|
|
27449
28054
|
// src/handlers/meta.ts
|
|
27450
28055
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
27451
28056
|
init_protocol();
|
|
@@ -27571,7 +28176,6 @@ function buildPersonaHandlers(deps) {
|
|
|
27571
28176
|
}
|
|
27572
28177
|
|
|
27573
28178
|
// src/handlers/attachment.ts
|
|
27574
|
-
var import_node_path26 = __toESM(require("path"), 1);
|
|
27575
28179
|
init_protocol();
|
|
27576
28180
|
init_protocol();
|
|
27577
28181
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -27596,41 +28200,8 @@ function buildAttachmentHandlers(deps) {
|
|
|
27596
28200
|
"httpBaseUrl unavailable (daemon HTTP not ready)"
|
|
27597
28201
|
);
|
|
27598
28202
|
}
|
|
27599
|
-
if (!deps.sessionStore || !deps.getSessionScope || !deps.groupFileStore) {
|
|
27600
|
-
throw new ClawdError(
|
|
27601
|
-
ERROR_CODES.METHOD_NOT_IMPLEMENTED,
|
|
27602
|
-
"signUrl requires session/group stores"
|
|
27603
|
-
);
|
|
27604
|
-
}
|
|
27605
|
-
const sessionFile = deps.sessionStore.read(args.sessionId);
|
|
27606
|
-
if (!sessionFile) {
|
|
27607
|
-
throw new ClawdError(
|
|
27608
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
27609
|
-
`session ${args.sessionId} not found`
|
|
27610
|
-
);
|
|
27611
|
-
}
|
|
27612
|
-
const scope = deps.getSessionScope(args.sessionId);
|
|
27613
|
-
if (!scope) {
|
|
27614
|
-
throw new ClawdError(
|
|
27615
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
27616
|
-
`session ${args.sessionId} scope unresolved`
|
|
27617
|
-
);
|
|
27618
|
-
}
|
|
27619
|
-
const cwdAbs = import_node_path26.default.resolve(sessionFile.cwd);
|
|
27620
|
-
const candidateAbs = import_node_path26.default.isAbsolute(args.relPath) ? import_node_path26.default.resolve(args.relPath) : import_node_path26.default.resolve(cwdAbs, args.relPath);
|
|
27621
|
-
const entries = deps.groupFileStore.list(scope, args.sessionId);
|
|
27622
|
-
const entry = entries.find((e) => {
|
|
27623
|
-
const storedAbs = import_node_path26.default.isAbsolute(e.relPath) ? import_node_path26.default.resolve(e.relPath) : import_node_path26.default.resolve(cwdAbs, e.relPath);
|
|
27624
|
-
return storedAbs === candidateAbs && !e.stale;
|
|
27625
|
-
});
|
|
27626
|
-
if (!entry) {
|
|
27627
|
-
throw new ClawdError(
|
|
27628
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
27629
|
-
`relPath not in session group files or stale: ${args.relPath}`
|
|
27630
|
-
);
|
|
27631
|
-
}
|
|
27632
28203
|
const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
27633
|
-
const parts = signUrlParts(secret,
|
|
28204
|
+
const parts = signUrlParts(secret, args.absPath, ttl);
|
|
27634
28205
|
const url = buildSignedFileUrl(httpBaseUrl, parts);
|
|
27635
28206
|
return {
|
|
27636
28207
|
response: {
|
|
@@ -27731,15 +28302,112 @@ function buildMethodHandlers(deps) {
|
|
|
27731
28302
|
personaManager: deps.personaManager,
|
|
27732
28303
|
personaRegistry: deps.personaRegistry
|
|
27733
28304
|
}),
|
|
28305
|
+
...buildCapabilityHandlers({ manager: deps.capabilityManager }),
|
|
27734
28306
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
27735
28307
|
};
|
|
27736
28308
|
}
|
|
27737
28309
|
|
|
28310
|
+
// src/handlers/method-grants.ts
|
|
28311
|
+
var ADMIN_ANY = {
|
|
28312
|
+
kind: "fixed",
|
|
28313
|
+
resource: { type: "*" },
|
|
28314
|
+
action: "admin"
|
|
28315
|
+
};
|
|
28316
|
+
var METHOD_GRANT_MAP = {
|
|
28317
|
+
// ---- public(meta-only,guest 也能调) ----
|
|
28318
|
+
"info": { kind: "public" },
|
|
28319
|
+
"ping": { kind: "public" },
|
|
28320
|
+
// ---- capability platform(admin-only,本 PR 新增) ----
|
|
28321
|
+
"capability:issue": ADMIN_ANY,
|
|
28322
|
+
"capability:list": ADMIN_ANY,
|
|
28323
|
+
"capability:revoke": ADMIN_ANY,
|
|
28324
|
+
// ---- 业务方法:Phase 1 全 admin-only(owner 自动通过;guest 无法调用) ----
|
|
28325
|
+
"session:create": ADMIN_ANY,
|
|
28326
|
+
"session:list": ADMIN_ANY,
|
|
28327
|
+
"session:get": ADMIN_ANY,
|
|
28328
|
+
"session:update": ADMIN_ANY,
|
|
28329
|
+
"session:delete": ADMIN_ANY,
|
|
28330
|
+
"session:send": ADMIN_ANY,
|
|
28331
|
+
"session:stop": ADMIN_ANY,
|
|
28332
|
+
"session:interrupt": ADMIN_ANY,
|
|
28333
|
+
"session:rewind": ADMIN_ANY,
|
|
28334
|
+
"session:rewind-diff": ADMIN_ANY,
|
|
28335
|
+
"session:rewindable-message-ids": ADMIN_ANY,
|
|
28336
|
+
"session:fork": ADMIN_ANY,
|
|
28337
|
+
"session:new": ADMIN_ANY,
|
|
28338
|
+
"session:resume": ADMIN_ANY,
|
|
28339
|
+
"session:observe": ADMIN_ANY,
|
|
28340
|
+
"session:events": ADMIN_ANY,
|
|
28341
|
+
"session:subscribe": ADMIN_ANY,
|
|
28342
|
+
"session:unsubscribe": ADMIN_ANY,
|
|
28343
|
+
"session:pin": ADMIN_ANY,
|
|
28344
|
+
"session:reorderPins": ADMIN_ANY,
|
|
28345
|
+
"permission:respond": ADMIN_ANY,
|
|
28346
|
+
"session:answerQuestion": ADMIN_ANY,
|
|
28347
|
+
"session:cancelQuestion": ADMIN_ANY,
|
|
28348
|
+
"history:projects": ADMIN_ANY,
|
|
28349
|
+
"history:list": ADMIN_ANY,
|
|
28350
|
+
"history:read": ADMIN_ANY,
|
|
28351
|
+
"history:subagents": ADMIN_ANY,
|
|
28352
|
+
"history:subagent-read": ADMIN_ANY,
|
|
28353
|
+
"history:recentDirs": ADMIN_ANY,
|
|
28354
|
+
"workspace:list": ADMIN_ANY,
|
|
28355
|
+
"workspace:read": ADMIN_ANY,
|
|
28356
|
+
"skills:list": ADMIN_ANY,
|
|
28357
|
+
"agents:list": ADMIN_ANY,
|
|
28358
|
+
"git:root": ADMIN_ANY,
|
|
28359
|
+
"git:branch": ADMIN_ANY,
|
|
28360
|
+
"git:branches": ADMIN_ANY,
|
|
28361
|
+
"capabilities:get": ADMIN_ANY,
|
|
28362
|
+
"persona:create": ADMIN_ANY,
|
|
28363
|
+
"persona:list": ADMIN_ANY,
|
|
28364
|
+
"persona:get": ADMIN_ANY,
|
|
28365
|
+
"persona:update": ADMIN_ANY,
|
|
28366
|
+
"persona:delete": ADMIN_ANY,
|
|
28367
|
+
"persona:issueToken": ADMIN_ANY,
|
|
28368
|
+
"persona:revokeToken": ADMIN_ANY,
|
|
28369
|
+
"session:pty:input": ADMIN_ANY,
|
|
28370
|
+
"session:pty:resize": ADMIN_ANY,
|
|
28371
|
+
// file-sharing attachment.*:handler 内部已有 requireOwner(HTTP 路径同),dispatcher 这里
|
|
28372
|
+
// 用 admin-only 兜底(双保险,wire-level 也拦)
|
|
28373
|
+
"attachment.signUrl": ADMIN_ANY,
|
|
28374
|
+
"attachment.groupAdd": ADMIN_ANY,
|
|
28375
|
+
"attachment.groupRemove": ADMIN_ANY,
|
|
28376
|
+
"attachment.groupList": ADMIN_ANY,
|
|
28377
|
+
"attachment.groupListPersona": ADMIN_ANY
|
|
28378
|
+
};
|
|
28379
|
+
function computeGrantForFrame(method, frame) {
|
|
28380
|
+
const rule = METHOD_GRANT_MAP[method];
|
|
28381
|
+
if (!rule) return { kind: "public" };
|
|
28382
|
+
if (rule.kind === "public") return { kind: "public" };
|
|
28383
|
+
if (rule.kind === "fixed") {
|
|
28384
|
+
return { kind: "check", resource: rule.resource, action: rule.action };
|
|
28385
|
+
}
|
|
28386
|
+
const picked = rule.pick(frame);
|
|
28387
|
+
if (!picked) return { kind: "public" };
|
|
28388
|
+
return { kind: "check", resource: picked.resource, action: picked.action };
|
|
28389
|
+
}
|
|
28390
|
+
|
|
28391
|
+
// src/permission/capability.ts
|
|
28392
|
+
function matchResource(grant, target) {
|
|
28393
|
+
if (grant.type === "*") return true;
|
|
28394
|
+
if (grant.type !== target.type) return false;
|
|
28395
|
+
return grant.id === target.id;
|
|
28396
|
+
}
|
|
28397
|
+
function assertGrant(grants, resource, action) {
|
|
28398
|
+
for (const g2 of grants) {
|
|
28399
|
+
if (!matchResource(g2.resource, resource)) continue;
|
|
28400
|
+
if (g2.actions.includes(action)) return true;
|
|
28401
|
+
if (g2.actions.includes("admin")) return true;
|
|
28402
|
+
}
|
|
28403
|
+
return false;
|
|
28404
|
+
}
|
|
28405
|
+
|
|
27738
28406
|
// src/index.ts
|
|
27739
28407
|
async function startDaemon(config) {
|
|
27740
28408
|
const logger = createLogger({
|
|
27741
28409
|
level: config.logLevel,
|
|
27742
|
-
file:
|
|
28410
|
+
file: import_node_path26.default.join(config.dataDir, "clawd.log")
|
|
27743
28411
|
});
|
|
27744
28412
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
27745
28413
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -27751,29 +28419,70 @@ async function startDaemon(config) {
|
|
|
27751
28419
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
27752
28420
|
}
|
|
27753
28421
|
let resolvedAuthToken = null;
|
|
27754
|
-
let authFile = null;
|
|
27755
28422
|
if (config.authToken && config.authToken.trim()) {
|
|
27756
28423
|
resolvedAuthToken = config.authToken.trim();
|
|
27757
28424
|
} else if (config.tunnel) {
|
|
27758
|
-
|
|
27759
|
-
resolvedAuthToken = authFile.token;
|
|
28425
|
+
resolvedAuthToken = loadOrCreateAuthToken({ dataDir: config.dataDir });
|
|
27760
28426
|
}
|
|
27761
28427
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
28428
|
+
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
28429
|
+
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
28430
|
+
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
28431
|
+
onIssued: (cap, token) => {
|
|
28432
|
+
wsServer?.broadcastToOwners({
|
|
28433
|
+
type: "capability:tokenIssued",
|
|
28434
|
+
capability: stripSecretHash(cap),
|
|
28435
|
+
token
|
|
28436
|
+
});
|
|
28437
|
+
},
|
|
28438
|
+
onRevoked: (cap) => {
|
|
28439
|
+
wsServer?.broadcastToOwners({
|
|
28440
|
+
type: "capability:tokenRevoked",
|
|
28441
|
+
capabilityId: cap.id,
|
|
28442
|
+
revokedAt: cap.revokedAt
|
|
28443
|
+
});
|
|
28444
|
+
wsServer?.closeConnectionsByCapability(cap.id);
|
|
28445
|
+
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
28446
|
+
if (cleanup.removed.length > 0) {
|
|
28447
|
+
logger.info("capability revoke cascade: guest sessions removed", {
|
|
28448
|
+
capabilityId: cap.id,
|
|
28449
|
+
removedDirs: cleanup.removed
|
|
28450
|
+
});
|
|
28451
|
+
}
|
|
28452
|
+
}
|
|
28453
|
+
});
|
|
27762
28454
|
let wsServer = null;
|
|
27763
28455
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
27764
28456
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
28457
|
+
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
28458
|
+
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
27765
28459
|
expectedToken: resolvedAuthToken,
|
|
28460
|
+
authenticate: (t) => authenticate(t, {
|
|
28461
|
+
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
28462
|
+
capabilityRegistry
|
|
28463
|
+
}),
|
|
28464
|
+
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
28465
|
+
buildOwnerContext: ownerContext,
|
|
27766
28466
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
27767
28467
|
sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
|
|
27768
28468
|
}) : null;
|
|
27769
28469
|
resetRegistry();
|
|
27770
|
-
const
|
|
28470
|
+
const migrateResult = migrateFlattenSessions({ dataDir: config.dataDir });
|
|
28471
|
+
if (!migrateResult.skipped && (migrateResult.movedBare || migrateResult.movedVmOwner || migrateResult.archivedListener)) {
|
|
28472
|
+
logger.info("sessions migration applied", {
|
|
28473
|
+
movedBare: migrateResult.movedBare,
|
|
28474
|
+
movedVmOwner: migrateResult.movedVmOwner,
|
|
28475
|
+
archivedListener: migrateResult.archivedListener
|
|
28476
|
+
});
|
|
28477
|
+
}
|
|
28478
|
+
const sessionStoreFactory = new SessionStoreFactory({ dataDir: config.dataDir });
|
|
28479
|
+
const store = sessionStoreFactory.forBare();
|
|
27771
28480
|
const workspace = new WorkspaceBrowser();
|
|
27772
28481
|
const skills = new SkillsScanner();
|
|
27773
28482
|
const agents = new AgentsScanner();
|
|
27774
28483
|
const history = new ClaudeHistoryReader();
|
|
27775
28484
|
let transport = null;
|
|
27776
|
-
const personaStore = new PersonaStore(
|
|
28485
|
+
const personaStore = new PersonaStore(import_node_path26.default.join(config.dataDir, "personas"));
|
|
27777
28486
|
const defaultsRoot = findDefaultsRoot();
|
|
27778
28487
|
if (defaultsRoot) {
|
|
27779
28488
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -27784,11 +28493,14 @@ async function startDaemon(config) {
|
|
|
27784
28493
|
const groupFileStore = new GroupFileStore({ dataDir: config.dataDir, logger });
|
|
27785
28494
|
const manager = new SessionManager({
|
|
27786
28495
|
store,
|
|
28496
|
+
// Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
|
|
28497
|
+
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
28498
|
+
storeFactory: sessionStoreFactory,
|
|
27787
28499
|
logger,
|
|
27788
28500
|
getAdapter,
|
|
27789
28501
|
historyReader: history,
|
|
27790
28502
|
dataDir: config.dataDir,
|
|
27791
|
-
personaRoot:
|
|
28503
|
+
personaRoot: import_node_path26.default.join(config.dataDir, "personas"),
|
|
27792
28504
|
personaStore,
|
|
27793
28505
|
ownerDisplayName,
|
|
27794
28506
|
mode: config.mode,
|
|
@@ -27811,7 +28523,7 @@ async function startDaemon(config) {
|
|
|
27811
28523
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
27812
28524
|
attachmentGroup: {
|
|
27813
28525
|
onFileEdit: (input) => {
|
|
27814
|
-
const absPath =
|
|
28526
|
+
const absPath = import_node_path26.default.isAbsolute(input.relPath) ? input.relPath : import_node_path26.default.join(input.cwd, input.relPath);
|
|
27815
28527
|
let size = 0;
|
|
27816
28528
|
try {
|
|
27817
28529
|
size = import_node_fs24.default.statSync(absPath).size;
|
|
@@ -27910,23 +28622,25 @@ async function startDaemon(config) {
|
|
|
27910
28622
|
httpToken: resolvedAuthToken,
|
|
27911
28623
|
// file-sharing attachment.* RPC。signUrl 用 owner token 做 HMAC secret;group RPC
|
|
27912
28624
|
// 根据 sessionId 反查 scope 写盘。
|
|
27913
|
-
//
|
|
27914
|
-
// sessionStore / getSessionScope 都走 manager 的跨 scope 公开 API(findOwnedSession /
|
|
27915
|
-
// findOwnedSessionScope)—— 否则 default-only SessionStore 找不到 persona owner-mode
|
|
27916
|
-
// session,attachment 整套 RPC 对 persona session 都会报 "session not found"。
|
|
27917
|
-
// 详见 fix(daemon) #703:file-sharing v1 预存 wiring bug,#701 把 desktop 默认 --tunnel
|
|
27918
|
-
// 后首次让 desktop 用户用上 file-sharing 才被暴露。
|
|
27919
28625
|
attachment: {
|
|
27920
28626
|
groupFileStore,
|
|
27921
|
-
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
27922
28627
|
getHttpBaseUrl,
|
|
27923
|
-
// HMAC sign secret
|
|
27924
|
-
//
|
|
27925
|
-
getSignSecret: () =>
|
|
27926
|
-
// group RPC
|
|
28628
|
+
// HMAC sign secret:复用 ~/.clawd/auth.json owner token(持久跨重启)。
|
|
28629
|
+
// noAuth 模式 resolvedAuthToken 为 null → handler 自己返 NOT_IMPLEMENTED。
|
|
28630
|
+
getSignSecret: () => resolvedAuthToken ?? "",
|
|
28631
|
+
// group RPC:根据 sessionId 反查 scope;owner-mode persona session 走
|
|
27927
28632
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
27928
|
-
getSessionScope: (
|
|
27929
|
-
|
|
28633
|
+
getSessionScope: (sessionId) => {
|
|
28634
|
+
const file = store.read(sessionId);
|
|
28635
|
+
if (!file) return null;
|
|
28636
|
+
if (file.ownerPersonaId) {
|
|
28637
|
+
return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
|
|
28638
|
+
}
|
|
28639
|
+
return { kind: "default" };
|
|
28640
|
+
}
|
|
28641
|
+
},
|
|
28642
|
+
// Task 1.9: capability:issue/list/revoke handler 依赖
|
|
28643
|
+
capabilityManager
|
|
27930
28644
|
});
|
|
27931
28645
|
const authResolver = new AuthContextResolver({
|
|
27932
28646
|
ownerToken: resolvedAuthToken,
|
|
@@ -27939,9 +28653,8 @@ async function startDaemon(config) {
|
|
|
27939
28653
|
personaStore,
|
|
27940
28654
|
groupFileStore,
|
|
27941
28655
|
sessionStore: store,
|
|
27942
|
-
// /files HMAC verify
|
|
27943
|
-
|
|
27944
|
-
getSignSecret: () => authFile?.signSecret ?? null
|
|
28656
|
+
// /files HMAC verify 用同一份 owner token 做 secret(与 attachment.signUrl 同源)
|
|
28657
|
+
getSignSecret: () => resolvedAuthToken ?? null
|
|
27945
28658
|
});
|
|
27946
28659
|
wsServer = new LocalWsServer({
|
|
27947
28660
|
host: config.host,
|
|
@@ -28014,7 +28727,18 @@ async function startDaemon(config) {
|
|
|
28014
28727
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
28015
28728
|
const handler = handlers[type];
|
|
28016
28729
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
28017
|
-
const
|
|
28730
|
+
const ctx = wsServer.getClientContext(client.id) ?? ownerContext();
|
|
28731
|
+
const verdict = computeGrantForFrame(type, frame);
|
|
28732
|
+
if (verdict.kind === "check") {
|
|
28733
|
+
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|
|
28734
|
+
if (!ok) {
|
|
28735
|
+
throw new ClawdError(
|
|
28736
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
28737
|
+
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
28738
|
+
);
|
|
28739
|
+
}
|
|
28740
|
+
}
|
|
28741
|
+
const result = await handler(frame, client, ctx);
|
|
28018
28742
|
if (requestId && result.response) {
|
|
28019
28743
|
client.send({ ...result.response, requestId });
|
|
28020
28744
|
}
|
|
@@ -28072,15 +28796,15 @@ async function startDaemon(config) {
|
|
|
28072
28796
|
});
|
|
28073
28797
|
try {
|
|
28074
28798
|
const r = await tunnelMgr.start({ localPort: config.port });
|
|
28075
|
-
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url
|
|
28799
|
+
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
|
|
28076
28800
|
stateMgr.write(stateSnapshot);
|
|
28077
28801
|
currentTunnelUrl = r.url;
|
|
28078
28802
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
28079
28803
|
const lines = [
|
|
28080
28804
|
`Tunnel: ${r.url}`,
|
|
28081
28805
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
28082
|
-
`Frpc config: ${
|
|
28083
|
-
`Frpc log: ${
|
|
28806
|
+
`Frpc config: ${import_node_path26.default.join(config.dataDir, "frpc.toml")}`,
|
|
28807
|
+
`Frpc log: ${import_node_path26.default.join(config.dataDir, "frpc.log")}`
|
|
28084
28808
|
];
|
|
28085
28809
|
const width = Math.max(...lines.map((l) => l.length));
|
|
28086
28810
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -28093,30 +28817,19 @@ ${bar}
|
|
|
28093
28817
|
|
|
28094
28818
|
`);
|
|
28095
28819
|
try {
|
|
28096
|
-
const connectPath =
|
|
28820
|
+
const connectPath = import_node_path26.default.join(config.dataDir, "connect.txt");
|
|
28097
28821
|
import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
28098
28822
|
} catch {
|
|
28099
28823
|
}
|
|
28100
28824
|
} catch (err) {
|
|
28101
|
-
const tunnelError = err?.message ?? String(err);
|
|
28102
28825
|
try {
|
|
28103
28826
|
await tunnelMgr.stop();
|
|
28104
28827
|
} catch {
|
|
28105
28828
|
}
|
|
28106
28829
|
tunnelMgr = null;
|
|
28107
|
-
|
|
28108
|
-
|
|
28109
|
-
|
|
28110
|
-
} catch {
|
|
28111
|
-
}
|
|
28112
|
-
wss.broadcastAll({
|
|
28113
|
-
type: "tunnel:unavailable",
|
|
28114
|
-
reason: tunnelError,
|
|
28115
|
-
failedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
28116
|
-
});
|
|
28117
|
-
process.stdout.write(`Tunnel: unavailable (local mode) \u2014 ${tunnelError}
|
|
28118
|
-
`);
|
|
28119
|
-
logger.warn("tunnel unavailable, degraded to local mode", { reason: tunnelError });
|
|
28830
|
+
stateMgr.delete();
|
|
28831
|
+
await wss.stop();
|
|
28832
|
+
throw err;
|
|
28120
28833
|
}
|
|
28121
28834
|
}
|
|
28122
28835
|
const shutdown = async () => {
|