@clawos-dev/clawd 0.2.70-beta.122.93375b1 → 0.2.71-beta.123.6a5ed6f
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 +667 -202
- 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
|
];
|
|
@@ -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
|
|
|
@@ -6247,7 +6312,7 @@ 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 fs29 = require("fs");
|
|
6251
6316
|
var EventEmitter2 = require("events");
|
|
6252
6317
|
var inherits = require("util").inherits;
|
|
6253
6318
|
var path32 = require("path");
|
|
@@ -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) fs29.mkdirSync(path32.dirname(file), { recursive: true });
|
|
6373
|
+
const fd = fs29.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
|
+
fs29.mkdir(path32.dirname(file), { recursive: true }, (err) => {
|
|
6316
6381
|
if (err) return fileOpened(err);
|
|
6317
|
-
|
|
6382
|
+
fs29.open(file, flags, mode, fileOpened);
|
|
6318
6383
|
});
|
|
6319
6384
|
} else {
|
|
6320
|
-
|
|
6385
|
+
fs29.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 = () => fs29.writeSync(this.fd, this._writingBuf);
|
|
6427
|
+
fsWrite = () => fs29.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 fs29.writeSync(this.fd, this._writingBuf);
|
|
6372
6437
|
}
|
|
6373
|
-
return
|
|
6438
|
+
return fs29.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6374
6439
|
};
|
|
6375
6440
|
fsWrite = () => {
|
|
6376
6441
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6377
|
-
return
|
|
6442
|
+
return fs29.write(this.fd, this._writingBuf, this.release);
|
|
6378
6443
|
}
|
|
6379
|
-
return
|
|
6444
|
+
return fs29.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
|
+
fs29.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
|
+
fs29.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
|
+
fs29.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) ? fs29.writeSync(this.fd, buf) : fs29.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
|
+
fs29.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 = fs29.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) ? fs29.writeSync(this.fd, this._writingBuf) : fs29.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
|
+
fs29.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 = fs29.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
|
+
fs29.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
|
+
fs29.fsync(sonic.fd, closeWrapped);
|
|
6807
6872
|
} catch {
|
|
6808
6873
|
}
|
|
6809
6874
|
function closeWrapped() {
|
|
6810
6875
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
6811
|
-
|
|
6876
|
+
fs29.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: join5 } = 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"] || join5(__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: join5, isAbsolute, sep } = 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"] || join5(__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"] || join5(__dirname, "worker.js");
|
|
7572
7637
|
options.pipelines = [pipeline2.map((dest) => {
|
|
7573
7638
|
return {
|
|
7574
7639
|
...dest,
|
|
@@ -7590,7 +7655,7 @@ var require_transport = __commonJS({
|
|
|
7590
7655
|
return origin;
|
|
7591
7656
|
}
|
|
7592
7657
|
if (origin === "pino/file") {
|
|
7593
|
-
return
|
|
7658
|
+
return join5(__dirname, "..", "file.js");
|
|
7594
7659
|
}
|
|
7595
7660
|
let fixTarget2;
|
|
7596
7661
|
for (const filePath of callers) {
|
|
@@ -8580,7 +8645,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
8580
8645
|
return circularValue;
|
|
8581
8646
|
}
|
|
8582
8647
|
let res = "";
|
|
8583
|
-
let
|
|
8648
|
+
let join5 = ",";
|
|
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
|
+
join5 = `,
|
|
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 += join5;
|
|
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 += `${join5}"... ${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
|
+
join5 = `,
|
|
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 = join5;
|
|
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 = join5;
|
|
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 join5 = ",";
|
|
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
|
+
join5 = `,
|
|
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 += join5;
|
|
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 += `${join5}"... ${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
|
+
join5 = `,
|
|
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 = join5;
|
|
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 join6 = `,
|
|
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 += join6;
|
|
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 += `${join6}"... ${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 join5 = `,
|
|
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, join5, maximumBreadth);
|
|
8836
8901
|
keys = keys.slice(value.length);
|
|
8837
8902
|
maximumPropertiesToStringify -= value.length;
|
|
8838
|
-
separator =
|
|
8903
|
+
separator = join5;
|
|
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 = join5;
|
|
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 = join5;
|
|
8856
8921
|
}
|
|
8857
8922
|
if (separator !== "") {
|
|
8858
8923
|
res = `
|
|
@@ -18696,7 +18761,7 @@ var require_websocket = __commonJS({
|
|
|
18696
18761
|
var http2 = require("http");
|
|
18697
18762
|
var net = require("net");
|
|
18698
18763
|
var tls = require("tls");
|
|
18699
|
-
var { randomBytes, createHash } = require("crypto");
|
|
18764
|
+
var { randomBytes: randomBytes2, createHash: createHash3 } = require("crypto");
|
|
18700
18765
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
18701
18766
|
var { URL: URL2 } = require("url");
|
|
18702
18767
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -19226,7 +19291,7 @@ var require_websocket = __commonJS({
|
|
|
19226
19291
|
}
|
|
19227
19292
|
}
|
|
19228
19293
|
const defaultPort = isSecure ? 443 : 80;
|
|
19229
|
-
const key =
|
|
19294
|
+
const key = randomBytes2(16).toString("base64");
|
|
19230
19295
|
const request = isSecure ? https.request : http2.request;
|
|
19231
19296
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19232
19297
|
let perMessageDeflate;
|
|
@@ -19356,7 +19421,7 @@ var require_websocket = __commonJS({
|
|
|
19356
19421
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
19357
19422
|
return;
|
|
19358
19423
|
}
|
|
19359
|
-
const digest =
|
|
19424
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
19360
19425
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
19361
19426
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
19362
19427
|
return;
|
|
@@ -19723,7 +19788,7 @@ var require_websocket_server = __commonJS({
|
|
|
19723
19788
|
var EventEmitter2 = require("events");
|
|
19724
19789
|
var http2 = require("http");
|
|
19725
19790
|
var { Duplex } = require("stream");
|
|
19726
|
-
var { createHash } = require("crypto");
|
|
19791
|
+
var { createHash: createHash3 } = require("crypto");
|
|
19727
19792
|
var extension2 = require_extension();
|
|
19728
19793
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
19729
19794
|
var subprotocol2 = require_subprotocol();
|
|
@@ -20024,7 +20089,7 @@ var require_websocket_server = __commonJS({
|
|
|
20024
20089
|
);
|
|
20025
20090
|
}
|
|
20026
20091
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
20027
|
-
const digest =
|
|
20092
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
20028
20093
|
const headers = [
|
|
20029
20094
|
"HTTP/1.1 101 Switching Protocols",
|
|
20030
20095
|
"Upgrade: websocket",
|
|
@@ -20112,7 +20177,7 @@ var require_websocket_server = __commonJS({
|
|
|
20112
20177
|
// src/run-case/recorder.ts
|
|
20113
20178
|
function startRunCaseRecorder(opts) {
|
|
20114
20179
|
const now = opts.now ?? Date.now;
|
|
20115
|
-
const dir =
|
|
20180
|
+
const dir = import_node_path27.default.dirname(opts.recordPath);
|
|
20116
20181
|
let stream = null;
|
|
20117
20182
|
let closing = false;
|
|
20118
20183
|
let closedSettled = false;
|
|
@@ -20152,12 +20217,12 @@ function startRunCaseRecorder(opts) {
|
|
|
20152
20217
|
};
|
|
20153
20218
|
return { tap, close, closed };
|
|
20154
20219
|
}
|
|
20155
|
-
var import_node_fs25,
|
|
20220
|
+
var import_node_fs25, import_node_path27;
|
|
20156
20221
|
var init_recorder = __esm({
|
|
20157
20222
|
"src/run-case/recorder.ts"() {
|
|
20158
20223
|
"use strict";
|
|
20159
20224
|
import_node_fs25 = __toESM(require("fs"), 1);
|
|
20160
|
-
|
|
20225
|
+
import_node_path27 = __toESM(require("path"), 1);
|
|
20161
20226
|
}
|
|
20162
20227
|
});
|
|
20163
20228
|
|
|
@@ -20200,7 +20265,7 @@ var init_wire = __esm({
|
|
|
20200
20265
|
// src/run-case/controller.ts
|
|
20201
20266
|
async function runController(opts) {
|
|
20202
20267
|
const now = opts.now ?? Date.now;
|
|
20203
|
-
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(
|
|
20268
|
+
const cwd = opts.cwd ?? (0, import_node_fs26.mkdtempSync)(import_node_path28.default.join(import_node_os14.default.tmpdir(), "clawd-runcase-"));
|
|
20204
20269
|
const ownsCwd = opts.cwd === void 0;
|
|
20205
20270
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
20206
20271
|
const spawnCtx = { cwd };
|
|
@@ -20367,13 +20432,13 @@ async function runController(opts) {
|
|
|
20367
20432
|
}
|
|
20368
20433
|
return exitCode ?? 0;
|
|
20369
20434
|
}
|
|
20370
|
-
var import_node_fs26, import_node_os14,
|
|
20435
|
+
var import_node_fs26, import_node_os14, import_node_path28;
|
|
20371
20436
|
var init_controller = __esm({
|
|
20372
20437
|
"src/run-case/controller.ts"() {
|
|
20373
20438
|
"use strict";
|
|
20374
20439
|
import_node_fs26 = require("fs");
|
|
20375
20440
|
import_node_os14 = __toESM(require("os"), 1);
|
|
20376
|
-
|
|
20441
|
+
import_node_path28 = __toESM(require("path"), 1);
|
|
20377
20442
|
init_claude();
|
|
20378
20443
|
init_stdout_splitter();
|
|
20379
20444
|
init_permission_stdio();
|
|
@@ -20605,7 +20670,7 @@ Env (advanced):
|
|
|
20605
20670
|
`;
|
|
20606
20671
|
|
|
20607
20672
|
// src/index.ts
|
|
20608
|
-
var
|
|
20673
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
20609
20674
|
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
20610
20675
|
|
|
20611
20676
|
// src/logger.ts
|
|
@@ -22106,19 +22171,9 @@ var SessionManager = class {
|
|
|
22106
22171
|
return entries.filter((e) => e.isDirectory() && e.name !== "default").map((e) => e.name);
|
|
22107
22172
|
}
|
|
22108
22173
|
// 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 命中
|
|
22174
|
+
// SessionManager 在这里扫盘定位(default 直接试,未命中再轮询所有 persona owner 目录)。
|
|
22115
22175
|
// 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
22176
|
findOwnedSession(sessionId) {
|
|
22120
|
-
const runner = this.runners.get(sessionId);
|
|
22121
|
-
if (runner) return runner.getState().file;
|
|
22122
22177
|
const dflt = this.deps.store.read(sessionId);
|
|
22123
22178
|
if (dflt) return dflt;
|
|
22124
22179
|
for (const personaId of this.listPersonaIdsOnDisk()) {
|
|
@@ -22128,13 +22183,6 @@ var SessionManager = class {
|
|
|
22128
22183
|
}
|
|
22129
22184
|
return null;
|
|
22130
22185
|
}
|
|
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
22186
|
// 合并 default + 所有 persona owner 的 SessionFile —— 桌面 App 主 session 列表入口。
|
|
22139
22187
|
// 同样不含 listener sub-session(listener 走 persona:listSubSessions RPC 单独入口)。
|
|
22140
22188
|
listAllOwned() {
|
|
@@ -25240,6 +25288,9 @@ var LocalWsServer = class {
|
|
|
25240
25288
|
httpServer = null;
|
|
25241
25289
|
frameHandler = null;
|
|
25242
25290
|
clients = /* @__PURE__ */ new Map();
|
|
25291
|
+
// Task 1.7 capability platform:capId → Set<clientId>,撤销时 O(1) 找到该 cap 的
|
|
25292
|
+
// 所有活跃连接做关闭级联(Task 1.10)。同一 cap 可能多端同时连(多设备 / 多 tab)。
|
|
25293
|
+
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
25243
25294
|
logger;
|
|
25244
25295
|
pingIntervalMs;
|
|
25245
25296
|
async start() {
|
|
@@ -25330,6 +25381,17 @@ var LocalWsServer = class {
|
|
|
25330
25381
|
this.safeSend(c.ws, frame);
|
|
25331
25382
|
}
|
|
25332
25383
|
}
|
|
25384
|
+
// Task 1.9 capability platform:仅广播给 owner 连接(跳过 guest ws)。
|
|
25385
|
+
// 用于 capability:tokenIssued / capability:revoked 等 owner-only push frame——
|
|
25386
|
+
// guest 没必要也无法消费这类管理帧。noAuth localhost ws 没有 ctx, 视作 owner.
|
|
25387
|
+
broadcastToOwners(frame) {
|
|
25388
|
+
const gate = this.opts.authGate;
|
|
25389
|
+
for (const c of this.clients.values()) {
|
|
25390
|
+
if (gate && !gate.isAuthed(c.handle.id)) continue;
|
|
25391
|
+
if (c.ctx && c.ctx.principal.kind !== "owner") continue;
|
|
25392
|
+
this.safeSend(c.ws, frame);
|
|
25393
|
+
}
|
|
25394
|
+
}
|
|
25333
25395
|
firstSubscriber(sessionId) {
|
|
25334
25396
|
for (const c of this.clients.values()) {
|
|
25335
25397
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -25351,6 +25413,40 @@ var LocalWsServer = class {
|
|
|
25351
25413
|
if (!c) return;
|
|
25352
25414
|
this.safeSend(c.ws, frame);
|
|
25353
25415
|
}
|
|
25416
|
+
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
25417
|
+
// 绑到 client;guest 连接同时进 capId 索引(Task 1.10 撤销级联用)。
|
|
25418
|
+
attachClientContext(clientId, ctx) {
|
|
25419
|
+
const c = this.clients.get(clientId);
|
|
25420
|
+
if (!c) return;
|
|
25421
|
+
c.ctx = ctx;
|
|
25422
|
+
if (ctx.capabilityId) {
|
|
25423
|
+
let set = this.capabilityIdToClients.get(ctx.capabilityId);
|
|
25424
|
+
if (!set) {
|
|
25425
|
+
set = /* @__PURE__ */ new Set();
|
|
25426
|
+
this.capabilityIdToClients.set(ctx.capabilityId, set);
|
|
25427
|
+
}
|
|
25428
|
+
set.add(clientId);
|
|
25429
|
+
}
|
|
25430
|
+
}
|
|
25431
|
+
// 测试 / Task 1.8 dispatcher 用:拿 client 当前 ConnectionContext(null = 未鉴权 / noAuth)
|
|
25432
|
+
getClientContext(clientId) {
|
|
25433
|
+
return this.clients.get(clientId)?.ctx ?? null;
|
|
25434
|
+
}
|
|
25435
|
+
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
25436
|
+
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
25437
|
+
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
25438
|
+
const set = this.capabilityIdToClients.get(capabilityId);
|
|
25439
|
+
if (!set) return;
|
|
25440
|
+
const ids = [...set];
|
|
25441
|
+
for (const id of ids) {
|
|
25442
|
+
const c = this.clients.get(id);
|
|
25443
|
+
if (!c) continue;
|
|
25444
|
+
try {
|
|
25445
|
+
c.ws.close(code, reason);
|
|
25446
|
+
} catch {
|
|
25447
|
+
}
|
|
25448
|
+
}
|
|
25449
|
+
}
|
|
25354
25450
|
// URL path 路由:'/' → 主连接路径;其他 → close 4404
|
|
25355
25451
|
routeConnection(socket, req) {
|
|
25356
25452
|
const remoteAddress = req?.socket?.remoteAddress;
|
|
@@ -25385,7 +25481,7 @@ var LocalWsServer = class {
|
|
|
25385
25481
|
} catch {
|
|
25386
25482
|
}
|
|
25387
25483
|
}, this.pingIntervalMs);
|
|
25388
|
-
this.clients.set(id, { handle, ws: socket, pingTimer });
|
|
25484
|
+
this.clients.set(id, { handle, ws: socket, pingTimer, ctx: null });
|
|
25389
25485
|
this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
|
|
25390
25486
|
const authGate = this.opts.authGate;
|
|
25391
25487
|
let authed = true;
|
|
@@ -25409,6 +25505,12 @@ var LocalWsServer = class {
|
|
|
25409
25505
|
socket.on("close", () => {
|
|
25410
25506
|
const c = this.clients.get(id);
|
|
25411
25507
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
25508
|
+
const capId = c?.ctx?.capabilityId;
|
|
25509
|
+
if (capId) {
|
|
25510
|
+
const set = this.capabilityIdToClients.get(capId);
|
|
25511
|
+
set?.delete(id);
|
|
25512
|
+
if (set && set.size === 0) this.capabilityIdToClients.delete(capId);
|
|
25513
|
+
}
|
|
25412
25514
|
this.clients.delete(id);
|
|
25413
25515
|
authGate?.unregister(id);
|
|
25414
25516
|
this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
|
|
@@ -25557,15 +25659,27 @@ var AuthGate = class {
|
|
|
25557
25659
|
this.markFailed(handle.id);
|
|
25558
25660
|
return frame.type === "auth" ? "consumed" : "reject";
|
|
25559
25661
|
}
|
|
25560
|
-
|
|
25561
|
-
|
|
25562
|
-
this.
|
|
25563
|
-
|
|
25564
|
-
|
|
25565
|
-
|
|
25566
|
-
|
|
25567
|
-
|
|
25568
|
-
|
|
25662
|
+
let ctx = null;
|
|
25663
|
+
if (this.opts.authenticate) {
|
|
25664
|
+
const r = this.opts.authenticate(parsed.data.token);
|
|
25665
|
+
if (!r.ok) {
|
|
25666
|
+
this.opts.closeConnection(handle, 4401, r.code);
|
|
25667
|
+
this.markFailed(handle.id);
|
|
25668
|
+
return "consumed";
|
|
25669
|
+
}
|
|
25670
|
+
ctx = r.context;
|
|
25671
|
+
} else {
|
|
25672
|
+
if (!this.opts.expectedToken) {
|
|
25673
|
+
this.opts.closeConnection(handle, 1008, "auth not configured");
|
|
25674
|
+
this.markFailed(handle.id);
|
|
25675
|
+
return "consumed";
|
|
25676
|
+
}
|
|
25677
|
+
if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
|
|
25678
|
+
this.opts.closeConnection(handle, 1008, "auth failed");
|
|
25679
|
+
this.markFailed(handle.id);
|
|
25680
|
+
return "consumed";
|
|
25681
|
+
}
|
|
25682
|
+
ctx = this.opts.buildOwnerContext?.() ?? null;
|
|
25569
25683
|
}
|
|
25570
25684
|
if (st.timer != null) {
|
|
25571
25685
|
const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
@@ -25573,6 +25687,7 @@ var AuthGate = class {
|
|
|
25573
25687
|
}
|
|
25574
25688
|
st.authed = true;
|
|
25575
25689
|
st.timer = null;
|
|
25690
|
+
if (ctx && this.opts.onAuthed) this.opts.onAuthed(handle, ctx);
|
|
25576
25691
|
this.opts.sendOk(handle, { type: "auth:ok" });
|
|
25577
25692
|
return "consumed";
|
|
25578
25693
|
}
|
|
@@ -25648,6 +25763,219 @@ function constantTimeEqual2(a, b2) {
|
|
|
25648
25763
|
return diff2 === 0;
|
|
25649
25764
|
}
|
|
25650
25765
|
|
|
25766
|
+
// ../protocol/src/index.ts
|
|
25767
|
+
init_runtime();
|
|
25768
|
+
|
|
25769
|
+
// src/transport/connection-context.ts
|
|
25770
|
+
function ownerContext() {
|
|
25771
|
+
return {
|
|
25772
|
+
principal: OWNER_PRINCIPAL,
|
|
25773
|
+
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
25774
|
+
};
|
|
25775
|
+
}
|
|
25776
|
+
function guestContext(cap) {
|
|
25777
|
+
return {
|
|
25778
|
+
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
25779
|
+
grants: cap.grants,
|
|
25780
|
+
capabilityId: cap.id
|
|
25781
|
+
};
|
|
25782
|
+
}
|
|
25783
|
+
function authenticate(token, deps) {
|
|
25784
|
+
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
25785
|
+
if (deps.isOwnerToken(token)) return { ok: true, context: ownerContext() };
|
|
25786
|
+
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
25787
|
+
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
25788
|
+
if (v2.ok) return { ok: true, context: guestContext(v2.capability) };
|
|
25789
|
+
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
25790
|
+
return { ok: false, code: v2.code };
|
|
25791
|
+
}
|
|
25792
|
+
|
|
25793
|
+
// src/permission/capability-store.ts
|
|
25794
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
25795
|
+
var path16 = __toESM(require("path"), 1);
|
|
25796
|
+
var CAPABILITIES_FILE_NAME = "capabilities.json";
|
|
25797
|
+
var FILE_VERSION = 1;
|
|
25798
|
+
var CapabilityStore = class {
|
|
25799
|
+
constructor(dataDir) {
|
|
25800
|
+
this.dataDir = dataDir;
|
|
25801
|
+
fs15.mkdirSync(dataDir, { recursive: true });
|
|
25802
|
+
this.cache = this.readFromDisk();
|
|
25803
|
+
}
|
|
25804
|
+
dataDir;
|
|
25805
|
+
cache;
|
|
25806
|
+
list() {
|
|
25807
|
+
return [...this.cache];
|
|
25808
|
+
}
|
|
25809
|
+
upsert(cap) {
|
|
25810
|
+
const idx = this.cache.findIndex((c) => c.id === cap.id);
|
|
25811
|
+
if (idx >= 0) {
|
|
25812
|
+
this.cache[idx] = cap;
|
|
25813
|
+
} else {
|
|
25814
|
+
this.cache.push(cap);
|
|
25815
|
+
}
|
|
25816
|
+
this.flush();
|
|
25817
|
+
}
|
|
25818
|
+
remove(id) {
|
|
25819
|
+
const next = this.cache.filter((c) => c.id !== id);
|
|
25820
|
+
if (next.length === this.cache.length) return;
|
|
25821
|
+
this.cache = next;
|
|
25822
|
+
this.flush();
|
|
25823
|
+
}
|
|
25824
|
+
filePath() {
|
|
25825
|
+
return path16.join(this.dataDir, CAPABILITIES_FILE_NAME);
|
|
25826
|
+
}
|
|
25827
|
+
readFromDisk() {
|
|
25828
|
+
const file = this.filePath();
|
|
25829
|
+
let raw;
|
|
25830
|
+
try {
|
|
25831
|
+
raw = fs15.readFileSync(file, "utf8");
|
|
25832
|
+
} catch (err) {
|
|
25833
|
+
if (err?.code === "ENOENT") return [];
|
|
25834
|
+
return [];
|
|
25835
|
+
}
|
|
25836
|
+
if (!raw.trim()) return [];
|
|
25837
|
+
let parsed;
|
|
25838
|
+
try {
|
|
25839
|
+
parsed = JSON.parse(raw);
|
|
25840
|
+
} catch {
|
|
25841
|
+
return [];
|
|
25842
|
+
}
|
|
25843
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
25844
|
+
const arr = parsed.capabilities;
|
|
25845
|
+
if (!Array.isArray(arr)) return [];
|
|
25846
|
+
const out = [];
|
|
25847
|
+
for (const item of arr) {
|
|
25848
|
+
const r = CapabilitySchema.safeParse(item);
|
|
25849
|
+
if (r.success) out.push(r.data);
|
|
25850
|
+
}
|
|
25851
|
+
return out;
|
|
25852
|
+
}
|
|
25853
|
+
flush() {
|
|
25854
|
+
const content = { version: FILE_VERSION, capabilities: this.cache };
|
|
25855
|
+
this.atomicWrite(this.filePath(), JSON.stringify(content, null, 2));
|
|
25856
|
+
}
|
|
25857
|
+
atomicWrite(file, content) {
|
|
25858
|
+
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
25859
|
+
fs15.writeFileSync(tmp, content, { mode: 384 });
|
|
25860
|
+
fs15.renameSync(tmp, file);
|
|
25861
|
+
try {
|
|
25862
|
+
fs15.chmodSync(file, 384);
|
|
25863
|
+
} catch {
|
|
25864
|
+
}
|
|
25865
|
+
}
|
|
25866
|
+
};
|
|
25867
|
+
|
|
25868
|
+
// src/permission/capability-registry.ts
|
|
25869
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
25870
|
+
var CapabilityRegistry = class {
|
|
25871
|
+
constructor(store, now = () => Date.now()) {
|
|
25872
|
+
this.store = store;
|
|
25873
|
+
this.now = now;
|
|
25874
|
+
for (const cap of store.list()) {
|
|
25875
|
+
this.bySecretHash.set(cap.secretHash, cap);
|
|
25876
|
+
}
|
|
25877
|
+
}
|
|
25878
|
+
store;
|
|
25879
|
+
now;
|
|
25880
|
+
// sha256(token) → Capability
|
|
25881
|
+
bySecretHash = /* @__PURE__ */ new Map();
|
|
25882
|
+
list() {
|
|
25883
|
+
return this.store.list();
|
|
25884
|
+
}
|
|
25885
|
+
verifyToken(token) {
|
|
25886
|
+
if (!token) return { ok: false, code: "TOKEN_INVALID" };
|
|
25887
|
+
const hash = sha256Hex(token);
|
|
25888
|
+
const cap = this.bySecretHash.get(hash);
|
|
25889
|
+
if (!cap) return { ok: false, code: "TOKEN_INVALID" };
|
|
25890
|
+
if (cap.revokedAt) return { ok: false, code: "TOKEN_REVOKED" };
|
|
25891
|
+
if (cap.expiresAt && this.now() >= cap.expiresAt) {
|
|
25892
|
+
return { ok: false, code: "TOKEN_EXPIRED" };
|
|
25893
|
+
}
|
|
25894
|
+
if (cap.maxUses && cap.usedCount >= cap.maxUses) {
|
|
25895
|
+
return { ok: false, code: "TOKEN_EXHAUSTED" };
|
|
25896
|
+
}
|
|
25897
|
+
const updated = { ...cap, usedCount: cap.usedCount + 1 };
|
|
25898
|
+
this.bySecretHash.set(hash, updated);
|
|
25899
|
+
this.store.upsert(updated);
|
|
25900
|
+
return { ok: true, capability: updated };
|
|
25901
|
+
}
|
|
25902
|
+
upsertCapability(cap) {
|
|
25903
|
+
this.bySecretHash.set(cap.secretHash, cap);
|
|
25904
|
+
this.store.upsert(cap);
|
|
25905
|
+
}
|
|
25906
|
+
markRevoked(id, revokedAt) {
|
|
25907
|
+
const current = this.store.list().find((c) => c.id === id);
|
|
25908
|
+
if (!current) return null;
|
|
25909
|
+
if (current.revokedAt) return current;
|
|
25910
|
+
const updated = { ...current, revokedAt };
|
|
25911
|
+
this.bySecretHash.set(updated.secretHash, updated);
|
|
25912
|
+
this.store.upsert(updated);
|
|
25913
|
+
return updated;
|
|
25914
|
+
}
|
|
25915
|
+
findById(id) {
|
|
25916
|
+
return this.store.list().find((c) => c.id === id) ?? null;
|
|
25917
|
+
}
|
|
25918
|
+
};
|
|
25919
|
+
function sha256Hex(s) {
|
|
25920
|
+
return crypto4.createHash("sha256").update(s).digest("hex");
|
|
25921
|
+
}
|
|
25922
|
+
|
|
25923
|
+
// src/permission/capability-manager.ts
|
|
25924
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
25925
|
+
var CapabilityManager = class {
|
|
25926
|
+
constructor(registry2, hooks = {}) {
|
|
25927
|
+
this.registry = registry2;
|
|
25928
|
+
this.hooks = hooks;
|
|
25929
|
+
}
|
|
25930
|
+
registry;
|
|
25931
|
+
hooks;
|
|
25932
|
+
list() {
|
|
25933
|
+
return this.registry.list();
|
|
25934
|
+
}
|
|
25935
|
+
issue(args) {
|
|
25936
|
+
if (!args.displayName.trim()) {
|
|
25937
|
+
throw new Error("CapabilityManager.issue: displayName must be non-empty");
|
|
25938
|
+
}
|
|
25939
|
+
const token = (this.hooks.generateToken ?? defaultGenerateToken)();
|
|
25940
|
+
const id = (this.hooks.generateId ?? defaultGenerateId)();
|
|
25941
|
+
const now = (this.hooks.now ?? Date.now)();
|
|
25942
|
+
const cap = {
|
|
25943
|
+
id,
|
|
25944
|
+
secretHash: sha256Hex2(token),
|
|
25945
|
+
displayName: args.displayName,
|
|
25946
|
+
grants: args.grants,
|
|
25947
|
+
issuedAt: now,
|
|
25948
|
+
usedCount: 0,
|
|
25949
|
+
...args.expiresAt !== void 0 ? { expiresAt: args.expiresAt } : {},
|
|
25950
|
+
...args.maxUses !== void 0 ? { maxUses: args.maxUses } : {}
|
|
25951
|
+
};
|
|
25952
|
+
this.registry.upsertCapability(cap);
|
|
25953
|
+
this.hooks.onIssued?.(cap, token);
|
|
25954
|
+
return { token, capability: cap };
|
|
25955
|
+
}
|
|
25956
|
+
revoke(id) {
|
|
25957
|
+
const existing = this.registry.findById(id);
|
|
25958
|
+
if (!existing) return null;
|
|
25959
|
+
const now = (this.hooks.now ?? Date.now)();
|
|
25960
|
+
if (existing.revokedAt) {
|
|
25961
|
+
return { revokedAt: existing.revokedAt, capability: existing };
|
|
25962
|
+
}
|
|
25963
|
+
const updated = this.registry.markRevoked(id, now);
|
|
25964
|
+
if (!updated) return null;
|
|
25965
|
+
this.hooks.onRevoked?.(updated);
|
|
25966
|
+
return { revokedAt: now, capability: updated };
|
|
25967
|
+
}
|
|
25968
|
+
};
|
|
25969
|
+
function defaultGenerateToken() {
|
|
25970
|
+
return crypto5.randomBytes(24).toString("base64url");
|
|
25971
|
+
}
|
|
25972
|
+
function defaultGenerateId() {
|
|
25973
|
+
return "cap_" + crypto5.randomBytes(6).toString("base64url");
|
|
25974
|
+
}
|
|
25975
|
+
function sha256Hex2(s) {
|
|
25976
|
+
return crypto5.createHash("sha256").update(s).digest("hex");
|
|
25977
|
+
}
|
|
25978
|
+
|
|
25651
25979
|
// src/transport/http-router.ts
|
|
25652
25980
|
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
25653
25981
|
var import_node_path16 = __toESM(require("path"), 1);
|
|
@@ -26869,24 +27197,14 @@ var AUTH_FILE_NAME = "auth.json";
|
|
|
26869
27197
|
function authFilePath(dataDir) {
|
|
26870
27198
|
return import_node_path22.default.join(dataDir, AUTH_FILE_NAME);
|
|
26871
27199
|
}
|
|
26872
|
-
function
|
|
27200
|
+
function loadOrCreateAuthToken(opts) {
|
|
26873
27201
|
const file = authFilePath(opts.dataDir);
|
|
26874
|
-
const generate = opts.generate ?? defaultGenerate;
|
|
26875
|
-
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
26876
27202
|
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;
|
|
27203
|
+
if (existing && existing.token) return existing.token;
|
|
27204
|
+
const token = (opts.generate ?? defaultGenerate)();
|
|
27205
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
27206
|
+
writeAuthFile(file, { token, createdAt: now.toISOString() });
|
|
27207
|
+
return token;
|
|
26890
27208
|
}
|
|
26891
27209
|
function defaultGenerate() {
|
|
26892
27210
|
return import_node_crypto8.default.randomBytes(32).toString("base64url");
|
|
@@ -26895,14 +27213,13 @@ function readAuthFile(file) {
|
|
|
26895
27213
|
try {
|
|
26896
27214
|
const raw = import_node_fs20.default.readFileSync(file, "utf8");
|
|
26897
27215
|
const parsed = JSON.parse(raw);
|
|
26898
|
-
if (typeof parsed?.token
|
|
26899
|
-
return
|
|
27216
|
+
if (typeof parsed?.token === "string" && parsed.token.length > 0) {
|
|
27217
|
+
return {
|
|
27218
|
+
token: parsed.token,
|
|
27219
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
27220
|
+
};
|
|
26900
27221
|
}
|
|
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
|
-
};
|
|
27222
|
+
return null;
|
|
26906
27223
|
} catch (err) {
|
|
26907
27224
|
const code = err?.code;
|
|
26908
27225
|
if (code === "ENOENT") return null;
|
|
@@ -27446,6 +27763,65 @@ function buildCapabilitiesHandlers(deps) {
|
|
|
27446
27763
|
};
|
|
27447
27764
|
}
|
|
27448
27765
|
|
|
27766
|
+
// src/handlers/capability.ts
|
|
27767
|
+
init_zod();
|
|
27768
|
+
init_protocol();
|
|
27769
|
+
var IssueArgsSchema = external_exports.object({
|
|
27770
|
+
displayName: external_exports.string().min(1),
|
|
27771
|
+
grants: external_exports.array(GrantSchema),
|
|
27772
|
+
expiresAt: external_exports.number().int().positive().optional(),
|
|
27773
|
+
maxUses: external_exports.number().int().positive().optional()
|
|
27774
|
+
}).strict();
|
|
27775
|
+
var RevokeArgsSchema = external_exports.object({
|
|
27776
|
+
capabilityId: external_exports.string().min(1)
|
|
27777
|
+
}).strict();
|
|
27778
|
+
function buildCapabilityHandlers(deps) {
|
|
27779
|
+
const { manager } = deps;
|
|
27780
|
+
const issue = async (frame) => {
|
|
27781
|
+
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
27782
|
+
const args = IssueArgsSchema.parse(rest);
|
|
27783
|
+
const { token, capability } = manager.issue(args);
|
|
27784
|
+
return {
|
|
27785
|
+
response: {
|
|
27786
|
+
type: "capability:issued",
|
|
27787
|
+
token,
|
|
27788
|
+
capability: stripSecretHash(capability)
|
|
27789
|
+
}
|
|
27790
|
+
};
|
|
27791
|
+
};
|
|
27792
|
+
const list = async () => {
|
|
27793
|
+
return {
|
|
27794
|
+
response: {
|
|
27795
|
+
type: "capability:list",
|
|
27796
|
+
capabilities: manager.list().map(stripSecretHash)
|
|
27797
|
+
}
|
|
27798
|
+
};
|
|
27799
|
+
};
|
|
27800
|
+
const revoke = async (frame) => {
|
|
27801
|
+
const { type: _type, requestId: _requestId, ...rest } = frame;
|
|
27802
|
+
const args = RevokeArgsSchema.parse(rest);
|
|
27803
|
+
const result = manager.revoke(args.capabilityId);
|
|
27804
|
+
if (!result) {
|
|
27805
|
+
throw new ClawdError(
|
|
27806
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
27807
|
+
`capability not found: ${args.capabilityId}`
|
|
27808
|
+
);
|
|
27809
|
+
}
|
|
27810
|
+
return {
|
|
27811
|
+
response: {
|
|
27812
|
+
type: "capability:revoked",
|
|
27813
|
+
capabilityId: args.capabilityId,
|
|
27814
|
+
revokedAt: result.revokedAt
|
|
27815
|
+
}
|
|
27816
|
+
};
|
|
27817
|
+
};
|
|
27818
|
+
return {
|
|
27819
|
+
"capability:issue": issue,
|
|
27820
|
+
"capability:list": list,
|
|
27821
|
+
"capability:revoke": revoke
|
|
27822
|
+
};
|
|
27823
|
+
}
|
|
27824
|
+
|
|
27449
27825
|
// src/handlers/meta.ts
|
|
27450
27826
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
27451
27827
|
init_protocol();
|
|
@@ -27571,7 +27947,6 @@ function buildPersonaHandlers(deps) {
|
|
|
27571
27947
|
}
|
|
27572
27948
|
|
|
27573
27949
|
// src/handlers/attachment.ts
|
|
27574
|
-
var import_node_path26 = __toESM(require("path"), 1);
|
|
27575
27950
|
init_protocol();
|
|
27576
27951
|
init_protocol();
|
|
27577
27952
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -27596,41 +27971,8 @@ function buildAttachmentHandlers(deps) {
|
|
|
27596
27971
|
"httpBaseUrl unavailable (daemon HTTP not ready)"
|
|
27597
27972
|
);
|
|
27598
27973
|
}
|
|
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
27974
|
const ttl = args.ttlSeconds === null ? null : args.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
27633
|
-
const parts = signUrlParts(secret,
|
|
27975
|
+
const parts = signUrlParts(secret, args.absPath, ttl);
|
|
27634
27976
|
const url = buildSignedFileUrl(httpBaseUrl, parts);
|
|
27635
27977
|
return {
|
|
27636
27978
|
response: {
|
|
@@ -27731,15 +28073,112 @@ function buildMethodHandlers(deps) {
|
|
|
27731
28073
|
personaManager: deps.personaManager,
|
|
27732
28074
|
personaRegistry: deps.personaRegistry
|
|
27733
28075
|
}),
|
|
28076
|
+
...buildCapabilityHandlers({ manager: deps.capabilityManager }),
|
|
27734
28077
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {}
|
|
27735
28078
|
};
|
|
27736
28079
|
}
|
|
27737
28080
|
|
|
28081
|
+
// src/handlers/method-grants.ts
|
|
28082
|
+
var ADMIN_ANY = {
|
|
28083
|
+
kind: "fixed",
|
|
28084
|
+
resource: { type: "*" },
|
|
28085
|
+
action: "admin"
|
|
28086
|
+
};
|
|
28087
|
+
var METHOD_GRANT_MAP = {
|
|
28088
|
+
// ---- public(meta-only,guest 也能调) ----
|
|
28089
|
+
"info": { kind: "public" },
|
|
28090
|
+
"ping": { kind: "public" },
|
|
28091
|
+
// ---- capability platform(admin-only,本 PR 新增) ----
|
|
28092
|
+
"capability:issue": ADMIN_ANY,
|
|
28093
|
+
"capability:list": ADMIN_ANY,
|
|
28094
|
+
"capability:revoke": ADMIN_ANY,
|
|
28095
|
+
// ---- 业务方法:Phase 1 全 admin-only(owner 自动通过;guest 无法调用) ----
|
|
28096
|
+
"session:create": ADMIN_ANY,
|
|
28097
|
+
"session:list": ADMIN_ANY,
|
|
28098
|
+
"session:get": ADMIN_ANY,
|
|
28099
|
+
"session:update": ADMIN_ANY,
|
|
28100
|
+
"session:delete": ADMIN_ANY,
|
|
28101
|
+
"session:send": ADMIN_ANY,
|
|
28102
|
+
"session:stop": ADMIN_ANY,
|
|
28103
|
+
"session:interrupt": ADMIN_ANY,
|
|
28104
|
+
"session:rewind": ADMIN_ANY,
|
|
28105
|
+
"session:rewind-diff": ADMIN_ANY,
|
|
28106
|
+
"session:rewindable-message-ids": ADMIN_ANY,
|
|
28107
|
+
"session:fork": ADMIN_ANY,
|
|
28108
|
+
"session:new": ADMIN_ANY,
|
|
28109
|
+
"session:resume": ADMIN_ANY,
|
|
28110
|
+
"session:observe": ADMIN_ANY,
|
|
28111
|
+
"session:events": ADMIN_ANY,
|
|
28112
|
+
"session:subscribe": ADMIN_ANY,
|
|
28113
|
+
"session:unsubscribe": ADMIN_ANY,
|
|
28114
|
+
"session:pin": ADMIN_ANY,
|
|
28115
|
+
"session:reorderPins": ADMIN_ANY,
|
|
28116
|
+
"permission:respond": ADMIN_ANY,
|
|
28117
|
+
"session:answerQuestion": ADMIN_ANY,
|
|
28118
|
+
"session:cancelQuestion": ADMIN_ANY,
|
|
28119
|
+
"history:projects": ADMIN_ANY,
|
|
28120
|
+
"history:list": ADMIN_ANY,
|
|
28121
|
+
"history:read": ADMIN_ANY,
|
|
28122
|
+
"history:subagents": ADMIN_ANY,
|
|
28123
|
+
"history:subagent-read": ADMIN_ANY,
|
|
28124
|
+
"history:recentDirs": ADMIN_ANY,
|
|
28125
|
+
"workspace:list": ADMIN_ANY,
|
|
28126
|
+
"workspace:read": ADMIN_ANY,
|
|
28127
|
+
"skills:list": ADMIN_ANY,
|
|
28128
|
+
"agents:list": ADMIN_ANY,
|
|
28129
|
+
"git:root": ADMIN_ANY,
|
|
28130
|
+
"git:branch": ADMIN_ANY,
|
|
28131
|
+
"git:branches": ADMIN_ANY,
|
|
28132
|
+
"capabilities:get": ADMIN_ANY,
|
|
28133
|
+
"persona:create": ADMIN_ANY,
|
|
28134
|
+
"persona:list": ADMIN_ANY,
|
|
28135
|
+
"persona:get": ADMIN_ANY,
|
|
28136
|
+
"persona:update": ADMIN_ANY,
|
|
28137
|
+
"persona:delete": ADMIN_ANY,
|
|
28138
|
+
"persona:issueToken": ADMIN_ANY,
|
|
28139
|
+
"persona:revokeToken": ADMIN_ANY,
|
|
28140
|
+
"session:pty:input": ADMIN_ANY,
|
|
28141
|
+
"session:pty:resize": ADMIN_ANY,
|
|
28142
|
+
// file-sharing attachment.*:handler 内部已有 requireOwner(HTTP 路径同),dispatcher 这里
|
|
28143
|
+
// 用 admin-only 兜底(双保险,wire-level 也拦)
|
|
28144
|
+
"attachment.signUrl": ADMIN_ANY,
|
|
28145
|
+
"attachment.groupAdd": ADMIN_ANY,
|
|
28146
|
+
"attachment.groupRemove": ADMIN_ANY,
|
|
28147
|
+
"attachment.groupList": ADMIN_ANY,
|
|
28148
|
+
"attachment.groupListPersona": ADMIN_ANY
|
|
28149
|
+
};
|
|
28150
|
+
function computeGrantForFrame(method, frame) {
|
|
28151
|
+
const rule = METHOD_GRANT_MAP[method];
|
|
28152
|
+
if (!rule) return { kind: "public" };
|
|
28153
|
+
if (rule.kind === "public") return { kind: "public" };
|
|
28154
|
+
if (rule.kind === "fixed") {
|
|
28155
|
+
return { kind: "check", resource: rule.resource, action: rule.action };
|
|
28156
|
+
}
|
|
28157
|
+
const picked = rule.pick(frame);
|
|
28158
|
+
if (!picked) return { kind: "public" };
|
|
28159
|
+
return { kind: "check", resource: picked.resource, action: picked.action };
|
|
28160
|
+
}
|
|
28161
|
+
|
|
28162
|
+
// src/permission/capability.ts
|
|
28163
|
+
function matchResource(grant, target) {
|
|
28164
|
+
if (grant.type === "*") return true;
|
|
28165
|
+
if (grant.type !== target.type) return false;
|
|
28166
|
+
return grant.id === target.id;
|
|
28167
|
+
}
|
|
28168
|
+
function assertGrant(grants, resource, action) {
|
|
28169
|
+
for (const g2 of grants) {
|
|
28170
|
+
if (!matchResource(g2.resource, resource)) continue;
|
|
28171
|
+
if (g2.actions.includes(action)) return true;
|
|
28172
|
+
if (g2.actions.includes("admin")) return true;
|
|
28173
|
+
}
|
|
28174
|
+
return false;
|
|
28175
|
+
}
|
|
28176
|
+
|
|
27738
28177
|
// src/index.ts
|
|
27739
28178
|
async function startDaemon(config) {
|
|
27740
28179
|
const logger = createLogger({
|
|
27741
28180
|
level: config.logLevel,
|
|
27742
|
-
file:
|
|
28181
|
+
file: import_node_path26.default.join(config.dataDir, "clawd.log")
|
|
27743
28182
|
});
|
|
27744
28183
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
27745
28184
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -27751,18 +28190,43 @@ async function startDaemon(config) {
|
|
|
27751
28190
|
logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
|
|
27752
28191
|
}
|
|
27753
28192
|
let resolvedAuthToken = null;
|
|
27754
|
-
let authFile = null;
|
|
27755
28193
|
if (config.authToken && config.authToken.trim()) {
|
|
27756
28194
|
resolvedAuthToken = config.authToken.trim();
|
|
27757
28195
|
} else if (config.tunnel) {
|
|
27758
|
-
|
|
27759
|
-
resolvedAuthToken = authFile.token;
|
|
28196
|
+
resolvedAuthToken = loadOrCreateAuthToken({ dataDir: config.dataDir });
|
|
27760
28197
|
}
|
|
27761
28198
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
28199
|
+
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
28200
|
+
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
28201
|
+
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
28202
|
+
onIssued: (cap, token) => {
|
|
28203
|
+
wsServer?.broadcastToOwners({
|
|
28204
|
+
type: "capability:tokenIssued",
|
|
28205
|
+
capability: stripSecretHash(cap),
|
|
28206
|
+
token
|
|
28207
|
+
});
|
|
28208
|
+
},
|
|
28209
|
+
onRevoked: (cap) => {
|
|
28210
|
+
wsServer?.broadcastToOwners({
|
|
28211
|
+
type: "capability:tokenRevoked",
|
|
28212
|
+
capabilityId: cap.id,
|
|
28213
|
+
revokedAt: cap.revokedAt
|
|
28214
|
+
});
|
|
28215
|
+
wsServer?.closeConnectionsByCapability(cap.id);
|
|
28216
|
+
}
|
|
28217
|
+
});
|
|
27762
28218
|
let wsServer = null;
|
|
27763
28219
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
27764
28220
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
28221
|
+
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
28222
|
+
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
27765
28223
|
expectedToken: resolvedAuthToken,
|
|
28224
|
+
authenticate: (t) => authenticate(t, {
|
|
28225
|
+
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
28226
|
+
capabilityRegistry
|
|
28227
|
+
}),
|
|
28228
|
+
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
28229
|
+
buildOwnerContext: ownerContext,
|
|
27766
28230
|
closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
|
|
27767
28231
|
sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
|
|
27768
28232
|
}) : null;
|
|
@@ -27773,7 +28237,7 @@ async function startDaemon(config) {
|
|
|
27773
28237
|
const agents = new AgentsScanner();
|
|
27774
28238
|
const history = new ClaudeHistoryReader();
|
|
27775
28239
|
let transport = null;
|
|
27776
|
-
const personaStore = new PersonaStore(
|
|
28240
|
+
const personaStore = new PersonaStore(import_node_path26.default.join(config.dataDir, "personas"));
|
|
27777
28241
|
const defaultsRoot = findDefaultsRoot();
|
|
27778
28242
|
if (defaultsRoot) {
|
|
27779
28243
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -27788,7 +28252,7 @@ async function startDaemon(config) {
|
|
|
27788
28252
|
getAdapter,
|
|
27789
28253
|
historyReader: history,
|
|
27790
28254
|
dataDir: config.dataDir,
|
|
27791
|
-
personaRoot:
|
|
28255
|
+
personaRoot: import_node_path26.default.join(config.dataDir, "personas"),
|
|
27792
28256
|
personaStore,
|
|
27793
28257
|
ownerDisplayName,
|
|
27794
28258
|
mode: config.mode,
|
|
@@ -27811,7 +28275,7 @@ async function startDaemon(config) {
|
|
|
27811
28275
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
27812
28276
|
attachmentGroup: {
|
|
27813
28277
|
onFileEdit: (input) => {
|
|
27814
|
-
const absPath =
|
|
28278
|
+
const absPath = import_node_path26.default.isAbsolute(input.relPath) ? input.relPath : import_node_path26.default.join(input.cwd, input.relPath);
|
|
27815
28279
|
let size = 0;
|
|
27816
28280
|
try {
|
|
27817
28281
|
size = import_node_fs24.default.statSync(absPath).size;
|
|
@@ -27910,23 +28374,25 @@ async function startDaemon(config) {
|
|
|
27910
28374
|
httpToken: resolvedAuthToken,
|
|
27911
28375
|
// file-sharing attachment.* RPC。signUrl 用 owner token 做 HMAC secret;group RPC
|
|
27912
28376
|
// 根据 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
28377
|
attachment: {
|
|
27920
28378
|
groupFileStore,
|
|
27921
|
-
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
27922
28379
|
getHttpBaseUrl,
|
|
27923
|
-
// HMAC sign secret
|
|
27924
|
-
//
|
|
27925
|
-
getSignSecret: () =>
|
|
27926
|
-
// group RPC
|
|
28380
|
+
// HMAC sign secret:复用 ~/.clawd/auth.json owner token(持久跨重启)。
|
|
28381
|
+
// noAuth 模式 resolvedAuthToken 为 null → handler 自己返 NOT_IMPLEMENTED。
|
|
28382
|
+
getSignSecret: () => resolvedAuthToken ?? "",
|
|
28383
|
+
// group RPC:根据 sessionId 反查 scope;owner-mode persona session 走
|
|
27927
28384
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
27928
|
-
getSessionScope: (
|
|
27929
|
-
|
|
28385
|
+
getSessionScope: (sessionId) => {
|
|
28386
|
+
const file = store.read(sessionId);
|
|
28387
|
+
if (!file) return null;
|
|
28388
|
+
if (file.ownerPersonaId) {
|
|
28389
|
+
return { kind: "persona", personaId: file.ownerPersonaId, mode: "owner" };
|
|
28390
|
+
}
|
|
28391
|
+
return { kind: "default" };
|
|
28392
|
+
}
|
|
28393
|
+
},
|
|
28394
|
+
// Task 1.9: capability:issue/list/revoke handler 依赖
|
|
28395
|
+
capabilityManager
|
|
27930
28396
|
});
|
|
27931
28397
|
const authResolver = new AuthContextResolver({
|
|
27932
28398
|
ownerToken: resolvedAuthToken,
|
|
@@ -27939,9 +28405,8 @@ async function startDaemon(config) {
|
|
|
27939
28405
|
personaStore,
|
|
27940
28406
|
groupFileStore,
|
|
27941
28407
|
sessionStore: store,
|
|
27942
|
-
// /files HMAC verify
|
|
27943
|
-
|
|
27944
|
-
getSignSecret: () => authFile?.signSecret ?? null
|
|
28408
|
+
// /files HMAC verify 用同一份 owner token 做 secret(与 attachment.signUrl 同源)
|
|
28409
|
+
getSignSecret: () => resolvedAuthToken ?? null
|
|
27945
28410
|
});
|
|
27946
28411
|
wsServer = new LocalWsServer({
|
|
27947
28412
|
host: config.host,
|
|
@@ -28014,7 +28479,18 @@ async function startDaemon(config) {
|
|
|
28014
28479
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
28015
28480
|
const handler = handlers[type];
|
|
28016
28481
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
28017
|
-
const
|
|
28482
|
+
const ctx = wsServer.getClientContext(client.id) ?? ownerContext();
|
|
28483
|
+
const verdict = computeGrantForFrame(type, frame);
|
|
28484
|
+
if (verdict.kind === "check") {
|
|
28485
|
+
const ok = assertGrant(ctx.grants, verdict.resource, verdict.action);
|
|
28486
|
+
if (!ok) {
|
|
28487
|
+
throw new ClawdError(
|
|
28488
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
28489
|
+
`principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${verdict.action} on ${verdict.resource.type}${"id" in verdict.resource ? ":" + verdict.resource.id : ""}`
|
|
28490
|
+
);
|
|
28491
|
+
}
|
|
28492
|
+
}
|
|
28493
|
+
const result = await handler(frame, client, ctx);
|
|
28018
28494
|
if (requestId && result.response) {
|
|
28019
28495
|
client.send({ ...result.response, requestId });
|
|
28020
28496
|
}
|
|
@@ -28072,15 +28548,15 @@ async function startDaemon(config) {
|
|
|
28072
28548
|
});
|
|
28073
28549
|
try {
|
|
28074
28550
|
const r = await tunnelMgr.start({ localPort: config.port });
|
|
28075
|
-
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url
|
|
28551
|
+
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
|
|
28076
28552
|
stateMgr.write(stateSnapshot);
|
|
28077
28553
|
currentTunnelUrl = r.url;
|
|
28078
28554
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
28079
28555
|
const lines = [
|
|
28080
28556
|
`Tunnel: ${r.url}`,
|
|
28081
28557
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
28082
|
-
`Frpc config: ${
|
|
28083
|
-
`Frpc log: ${
|
|
28558
|
+
`Frpc config: ${import_node_path26.default.join(config.dataDir, "frpc.toml")}`,
|
|
28559
|
+
`Frpc log: ${import_node_path26.default.join(config.dataDir, "frpc.log")}`
|
|
28084
28560
|
];
|
|
28085
28561
|
const width = Math.max(...lines.map((l) => l.length));
|
|
28086
28562
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -28093,30 +28569,19 @@ ${bar}
|
|
|
28093
28569
|
|
|
28094
28570
|
`);
|
|
28095
28571
|
try {
|
|
28096
|
-
const connectPath =
|
|
28572
|
+
const connectPath = import_node_path26.default.join(config.dataDir, "connect.txt");
|
|
28097
28573
|
import_node_fs24.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
28098
28574
|
} catch {
|
|
28099
28575
|
}
|
|
28100
28576
|
} catch (err) {
|
|
28101
|
-
const tunnelError = err?.message ?? String(err);
|
|
28102
28577
|
try {
|
|
28103
28578
|
await tunnelMgr.stop();
|
|
28104
28579
|
} catch {
|
|
28105
28580
|
}
|
|
28106
28581
|
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 });
|
|
28582
|
+
stateMgr.delete();
|
|
28583
|
+
await wss.stop();
|
|
28584
|
+
throw err;
|
|
28120
28585
|
}
|
|
28121
28586
|
}
|
|
28122
28587
|
const shutdown = async () => {
|