@clawos-dev/clawd 0.2.71-beta.128.2805736 → 0.2.71-beta.130.ea9dc82

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.
Files changed (2) hide show
  1. package/dist/cli.cjs +133 -59
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -5196,6 +5196,10 @@ var init_remote_persona = __esm({
5196
5196
  alias: external_exports.string().min(1),
5197
5197
  remoteUrl: external_exports.string().min(1),
5198
5198
  capabilityToken: external_exports.string().min(1),
5199
+ // v2 Phase 7: 远端 daemon 给我颁发的 capability id。preview 时 whoami 返回的
5200
+ // capability.id 直接存进来。旧 v1 持久化文件没此字段 → schema parse fail →
5201
+ // RemotePersonaStore.list 静默跳过 (alpha 阶段可接受, 老板重新 add 即可)。
5202
+ myCapabilityId: external_exports.string().min(1),
5199
5203
  remotePersonaId: external_exports.string().min(1),
5200
5204
  remoteDisplayName: external_exports.string(),
5201
5205
  ownerDisplayName: external_exports.string().optional(),
@@ -5207,6 +5211,8 @@ var init_remote_persona = __esm({
5207
5211
  alias: external_exports.string().min(1),
5208
5212
  remoteUrl: external_exports.string().min(1),
5209
5213
  capabilityToken: external_exports.string().min(1),
5214
+ // v2 Phase 7: UI 调 preview 后从 whoami response 取 capability.id 传入
5215
+ myCapabilityId: external_exports.string().min(1),
5210
5216
  remotePersonaId: external_exports.string().min(1),
5211
5217
  remoteDisplayName: external_exports.string(),
5212
5218
  ownerDisplayName: external_exports.string().optional()
@@ -27950,10 +27956,62 @@ function forkSession(input) {
27950
27956
  return { forkedToolSessionId, forkedFilePath };
27951
27957
  }
27952
27958
 
27959
+ // src/permission/capability.ts
27960
+ function matchResource(grant, target) {
27961
+ if (grant.type === "*") return true;
27962
+ if (grant.type !== target.type) return false;
27963
+ return grant.id === target.id;
27964
+ }
27965
+ function assertGrant(grants, resource, action) {
27966
+ for (const g2 of grants) {
27967
+ if (!matchResource(g2.resource, resource)) continue;
27968
+ if (g2.actions.includes(action)) return true;
27969
+ if (g2.actions.includes("admin")) return true;
27970
+ }
27971
+ return false;
27972
+ }
27973
+
27974
+ // src/permission/session-access.ts
27975
+ function canAccessSession(ctx, sessionId, action, deps) {
27976
+ if (ctx.principal.kind === "owner") return true;
27977
+ const file = deps.readSession(sessionId);
27978
+ if (!file) return true;
27979
+ return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
27980
+ }
27981
+ function canAccessPersona(grants, personaId, action) {
27982
+ if (!personaId) return false;
27983
+ const resource = { type: "persona", id: personaId };
27984
+ return assertGrant(grants, resource, action);
27985
+ }
27986
+
27953
27987
  // src/handlers/session.ts
27988
+ init_protocol();
27954
27989
  function buildSessionHandlers(deps) {
27955
- const { manager, observer, getAdapter: getAdapter2 } = deps;
27956
- const create = async (frame) => {
27990
+ const { manager, observer, getAdapter: getAdapter2, store } = deps;
27991
+ const ensureSessionAccess = (ctx, sessionId, action) => {
27992
+ if (!ctx) return;
27993
+ const ok = canAccessSession(ctx, sessionId, action, {
27994
+ readSession: (sid) => store.read(sid)
27995
+ });
27996
+ if (!ok) {
27997
+ throw new ClawdError(
27998
+ ERROR_CODES.UNAUTHORIZED,
27999
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} session ${sessionId}`
28000
+ );
28001
+ }
28002
+ };
28003
+ const ensurePersonaAccess = (ctx, personaId, action) => {
28004
+ if (!ctx) return;
28005
+ if (ctx.principal.kind === "owner") return;
28006
+ const ok = canAccessPersona(ctx.grants, personaId, action);
28007
+ if (!ok) {
28008
+ throw new ClawdError(
28009
+ ERROR_CODES.UNAUTHORIZED,
28010
+ `principal ${ctx.principal.kind}:${ctx.principal.id} cannot ${action} on persona:${personaId ?? "<none>"}`
28011
+ );
28012
+ }
28013
+ };
28014
+ const create = async (frame, _client, ctx) => {
27957
28015
  const args = SessionCreateArgs.parse(frame);
27958
28016
  if (args.ownerPersonaId) {
27959
28017
  const persona = deps.personaRegistry.get(args.ownerPersonaId);
@@ -27961,58 +28019,75 @@ function buildSessionHandlers(deps) {
27961
28019
  throw new Error(`persona not found: ${args.ownerPersonaId}`);
27962
28020
  }
27963
28021
  }
28022
+ ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
27964
28023
  const { response, broadcast } = manager.create(args);
27965
28024
  return { response: { type: "session:info", ...response }, broadcast };
27966
28025
  };
27967
- const list = async () => {
28026
+ const list = async (_frame, _client, ctx) => {
27968
28027
  const { response } = manager.list();
28028
+ if (ctx && ctx.principal.kind === "guest") {
28029
+ const sessions = response.sessions ?? [];
28030
+ const filtered = sessions.filter(
28031
+ (s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")
28032
+ );
28033
+ return { response: { type: "session:list", sessions: filtered } };
28034
+ }
27969
28035
  return { response: { type: "session:list", ...response } };
27970
28036
  };
27971
- const get = async (frame) => {
28037
+ const get = async (frame, _client, ctx) => {
27972
28038
  const args = SessionIdArgs.parse(frame);
28039
+ ensureSessionAccess(ctx, args.sessionId, "read");
27973
28040
  const { response } = manager.get(args);
27974
28041
  return { response: { type: "session:info", ...response } };
27975
28042
  };
27976
- const update = async (frame) => {
28043
+ const update = async (frame, _client, ctx) => {
27977
28044
  const args = SessionUpdateArgs.parse(frame);
28045
+ ensureSessionAccess(ctx, args.sessionId, "send");
27978
28046
  const { response, broadcast } = manager.update(args);
27979
28047
  return { response: { type: "session:info", ...response }, broadcast };
27980
28048
  };
27981
- const del = async (frame) => {
28049
+ const del = async (frame, _client, ctx) => {
27982
28050
  const args = SessionIdArgs.parse(frame);
28051
+ ensureSessionAccess(ctx, args.sessionId, "send");
27983
28052
  const { broadcast } = manager.delete(args);
27984
28053
  return {
27985
28054
  response: { type: "session:deleted", sessionId: args.sessionId },
27986
28055
  broadcast
27987
28056
  };
27988
28057
  };
27989
- const send = async (frame) => {
28058
+ const send = async (frame, _client, ctx) => {
27990
28059
  const args = SessionSendArgs.parse(frame);
28060
+ ensureSessionAccess(ctx, args.sessionId, "send");
27991
28061
  const { broadcast } = manager.send(args);
27992
28062
  return { response: { type: "session:send", ok: true }, broadcast };
27993
28063
  };
27994
- const stop = async (frame) => {
28064
+ const stop = async (frame, _client, ctx) => {
27995
28065
  const args = SessionIdArgs.parse(frame);
28066
+ ensureSessionAccess(ctx, args.sessionId, "send");
27996
28067
  const { broadcast } = await manager.stop(args);
27997
28068
  return { response: { type: "session:stop", ok: true }, broadcast };
27998
28069
  };
27999
- const interrupt = async (frame) => {
28070
+ const interrupt = async (frame, _client, ctx) => {
28000
28071
  const args = SessionIdArgs.parse(frame);
28072
+ ensureSessionAccess(ctx, args.sessionId, "send");
28001
28073
  const { broadcast } = await manager.interrupt(args);
28002
28074
  return { response: { type: "session:interrupt", ok: true }, broadcast };
28003
28075
  };
28004
- const rewind = async (frame) => {
28076
+ const rewind = async (frame, _client, ctx) => {
28005
28077
  const args = SessionRewindArgs.parse(frame);
28078
+ ensureSessionAccess(ctx, args.sessionId, "send");
28006
28079
  const { response, broadcast } = await manager.rewind(args);
28007
28080
  return { response: { type: "session:rewind", ...response }, broadcast };
28008
28081
  };
28009
- const rewindDiff = async (frame) => {
28082
+ const rewindDiff = async (frame, _client, ctx) => {
28010
28083
  const args = SessionRewindDiffArgs.parse(frame);
28084
+ ensureSessionAccess(ctx, args.sessionId, "read");
28011
28085
  const { response } = await manager.rewindDiff(args);
28012
28086
  return { response: { type: "session:rewind-diff", ...response } };
28013
28087
  };
28014
- const rewindableMessageIds = async (frame) => {
28088
+ const rewindableMessageIds = async (frame, _client, ctx) => {
28015
28089
  const args = SessionRewindableMessageIdsArgs.parse(frame);
28090
+ ensureSessionAccess(ctx, args.sessionId, "read");
28016
28091
  const { response } = manager.rewindableMessageIds(args);
28017
28092
  return {
28018
28093
  response: { type: "session:rewindable-message-ids", ...response }
@@ -28023,19 +28098,22 @@ function buildSessionHandlers(deps) {
28023
28098
  const result = forkSession(args);
28024
28099
  return { response: { type: "session:fork", ...result } };
28025
28100
  };
28026
- const newSession = async (frame) => {
28101
+ const newSession = async (frame, _client, ctx) => {
28027
28102
  const args = SessionIdArgs.parse(frame);
28103
+ ensureSessionAccess(ctx, args.sessionId, "send");
28028
28104
  observer.stop(args.sessionId);
28029
28105
  const { response, broadcast } = manager.newSession(args);
28030
28106
  return { response: { type: "session:info", ...response }, broadcast };
28031
28107
  };
28032
- const resume = async (frame) => {
28108
+ const resume = async (frame, _client, ctx) => {
28033
28109
  const args = SessionResumeArgs.parse(frame);
28110
+ ensureSessionAccess(ctx, args.sessionId, "send");
28034
28111
  const { response, broadcast } = manager.resume(args);
28035
28112
  return { response: { type: "session:info", ...response }, broadcast };
28036
28113
  };
28037
- const observe = async (frame) => {
28114
+ const observe = async (frame, _client, ctx) => {
28038
28115
  const args = SessionObserveArgs.parse(frame);
28116
+ ensureSessionAccess(ctx, args.sessionId, "read");
28039
28117
  const { response: file } = manager.get({ sessionId: args.sessionId });
28040
28118
  const sessionFile = file;
28041
28119
  manager.ensureSession(sessionFile);
@@ -28049,14 +28127,17 @@ function buildSessionHandlers(deps) {
28049
28127
  });
28050
28128
  return { response: { type: "session:observe", ok: true } };
28051
28129
  };
28052
- const events = async (frame) => {
28130
+ const events = async (frame, _client, ctx) => {
28053
28131
  const args = SessionEventsArgs.parse(frame);
28132
+ ensureSessionAccess(ctx, args.sessionId, "read");
28054
28133
  const { response } = manager.getEvents(args);
28055
28134
  return { response: { type: "session:events", ...response } };
28056
28135
  };
28057
- const subscribe = async (frame, client) => {
28058
- if (typeof frame.sessionId === "string")
28136
+ const subscribe = async (frame, client, ctx) => {
28137
+ if (typeof frame.sessionId === "string") {
28138
+ ensureSessionAccess(ctx, frame.sessionId, "read");
28059
28139
  addSubscription(client, frame.sessionId);
28140
+ }
28060
28141
  return {
28061
28142
  response: { type: "subscribed", sessionId: frame.sessionId }
28062
28143
  };
@@ -28068,8 +28149,9 @@ function buildSessionHandlers(deps) {
28068
28149
  response: { type: "unsubscribed", sessionId: frame.sessionId }
28069
28150
  };
28070
28151
  };
28071
- const pin = async (frame) => {
28152
+ const pin = async (frame, _client, ctx) => {
28072
28153
  const args = SessionPinArgs.parse(frame);
28154
+ ensureSessionAccess(ctx, args.sessionId, "send");
28073
28155
  const { response, broadcast } = manager.pin(args);
28074
28156
  return { response: { type: "session:info", ...response }, broadcast };
28075
28157
  };
@@ -28078,13 +28160,15 @@ function buildSessionHandlers(deps) {
28078
28160
  const { response, broadcast } = manager.reorderPins(args);
28079
28161
  return { response: { type: "session:reorderPins", ...response }, broadcast };
28080
28162
  };
28081
- const answerQuestion = async (frame) => {
28163
+ const answerQuestion = async (frame, _client, ctx) => {
28082
28164
  const args = AnswerQuestionArgs.parse(frame);
28165
+ ensureSessionAccess(ctx, args.sessionId, "send");
28083
28166
  const { response, broadcast } = manager.answerQuestion(args);
28084
28167
  return { response: { type: "session:answerQuestion", ...response }, broadcast };
28085
28168
  };
28086
- const cancelQuestion = async (frame) => {
28169
+ const cancelQuestion = async (frame, _client, ctx) => {
28087
28170
  const args = CancelQuestionArgs.parse(frame);
28171
+ ensureSessionAccess(ctx, args.sessionId, "send");
28088
28172
  const { response, broadcast } = await manager.cancelQuestion(args);
28089
28173
  return { response: { type: "session:cancelQuestion", ...response }, broadcast };
28090
28174
  };
@@ -28872,6 +28956,7 @@ var ADMIN_ANY = {
28872
28956
  resource: { type: "*" },
28873
28957
  action: "admin"
28874
28958
  };
28959
+ var CAPABILITY_SCOPED = { kind: "capability-scoped" };
28875
28960
  var METHOD_GRANT_MAP = {
28876
28961
  // ---- public(meta-only,guest 也能调) ----
28877
28962
  "info": { kind: "public" },
@@ -28894,30 +28979,33 @@ var METHOD_GRANT_MAP = {
28894
28979
  "remote-persona:add": ADMIN_ANY,
28895
28980
  "remote-persona:list": ADMIN_ANY,
28896
28981
  "remote-persona:remove": ADMIN_ANY,
28897
- // ---- 业务方法:Phase 1 admin-only(owner 自动通过;guest 无法调用) ----
28898
- "session:create": ADMIN_ANY,
28899
- "session:list": ADMIN_ANY,
28900
- "session:get": ADMIN_ANY,
28901
- "session:update": ADMIN_ANY,
28902
- "session:delete": ADMIN_ANY,
28903
- "session:send": ADMIN_ANY,
28904
- "session:stop": ADMIN_ANY,
28905
- "session:interrupt": ADMIN_ANY,
28906
- "session:rewind": ADMIN_ANY,
28907
- "session:rewind-diff": ADMIN_ANY,
28908
- "session:rewindable-message-ids": ADMIN_ANY,
28909
- "session:fork": ADMIN_ANY,
28910
- "session:new": ADMIN_ANY,
28911
- "session:resume": ADMIN_ANY,
28912
- "session:observe": ADMIN_ANY,
28913
- "session:events": ADMIN_ANY,
28914
- "session:subscribe": ADMIN_ANY,
28915
- "session:unsubscribe": ADMIN_ANY,
28916
- "session:pin": ADMIN_ANY,
28982
+ // ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
28983
+ // dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
28984
+ // owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
28985
+ "session:create": CAPABILITY_SCOPED,
28986
+ "session:list": CAPABILITY_SCOPED,
28987
+ "session:get": CAPABILITY_SCOPED,
28988
+ "session:update": CAPABILITY_SCOPED,
28989
+ "session:delete": CAPABILITY_SCOPED,
28990
+ "session:send": CAPABILITY_SCOPED,
28991
+ "session:stop": CAPABILITY_SCOPED,
28992
+ "session:interrupt": CAPABILITY_SCOPED,
28993
+ "session:rewind": CAPABILITY_SCOPED,
28994
+ "session:rewind-diff": CAPABILITY_SCOPED,
28995
+ "session:rewindable-message-ids": CAPABILITY_SCOPED,
28996
+ "session:fork": CAPABILITY_SCOPED,
28997
+ "session:new": CAPABILITY_SCOPED,
28998
+ "session:resume": CAPABILITY_SCOPED,
28999
+ "session:observe": CAPABILITY_SCOPED,
29000
+ "session:events": CAPABILITY_SCOPED,
29001
+ "session:subscribe": CAPABILITY_SCOPED,
29002
+ "session:unsubscribe": CAPABILITY_SCOPED,
29003
+ "session:pin": CAPABILITY_SCOPED,
28917
29004
  "session:reorderPins": ADMIN_ANY,
28918
- "permission:respond": ADMIN_ANY,
28919
- "session:answerQuestion": ADMIN_ANY,
28920
- "session:cancelQuestion": ADMIN_ANY,
29005
+ // owner 全局操作,无 personaId 维度
29006
+ "permission:respond": CAPABILITY_SCOPED,
29007
+ "session:answerQuestion": CAPABILITY_SCOPED,
29008
+ "session:cancelQuestion": CAPABILITY_SCOPED,
28921
29009
  "history:projects": ADMIN_ANY,
28922
29010
  "history:list": ADMIN_ANY,
28923
29011
  "history:read": ADMIN_ANY,
@@ -28953,6 +29041,7 @@ function computeGrantForFrame(method, frame) {
28953
29041
  const rule = METHOD_GRANT_MAP[method];
28954
29042
  if (!rule) return { kind: "public" };
28955
29043
  if (rule.kind === "public") return { kind: "public" };
29044
+ if (rule.kind === "capability-scoped") return { kind: "public" };
28956
29045
  if (rule.kind === "fixed") {
28957
29046
  return { kind: "check", resource: rule.resource, action: rule.action };
28958
29047
  }
@@ -28961,21 +29050,6 @@ function computeGrantForFrame(method, frame) {
28961
29050
  return { kind: "check", resource: picked.resource, action: picked.action };
28962
29051
  }
28963
29052
 
28964
- // src/permission/capability.ts
28965
- function matchResource(grant, target) {
28966
- if (grant.type === "*") return true;
28967
- if (grant.type !== target.type) return false;
28968
- return grant.id === target.id;
28969
- }
28970
- function assertGrant(grants, resource, action) {
28971
- for (const g2 of grants) {
28972
- if (!matchResource(g2.resource, resource)) continue;
28973
- if (g2.actions.includes(action)) return true;
28974
- if (g2.actions.includes("admin")) return true;
28975
- }
28976
- return false;
28977
- }
28978
-
28979
29053
  // src/index.ts
28980
29054
  async function startDaemon(config) {
28981
29055
  const logger = createLogger({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.71-beta.128.2805736",
3
+ "version": "0.2.71-beta.130.ea9dc82",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",