@clawos-dev/clawd 0.2.71-beta.129.3d783e6 → 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 +127 -59
  2. package/package.json +1 -1
package/dist/cli.cjs CHANGED
@@ -27956,10 +27956,62 @@ function forkSession(input) {
27956
27956
  return { forkedToolSessionId, forkedFilePath };
27957
27957
  }
27958
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
+
27959
27987
  // src/handlers/session.ts
27988
+ init_protocol();
27960
27989
  function buildSessionHandlers(deps) {
27961
- const { manager, observer, getAdapter: getAdapter2 } = deps;
27962
- 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) => {
27963
28015
  const args = SessionCreateArgs.parse(frame);
27964
28016
  if (args.ownerPersonaId) {
27965
28017
  const persona = deps.personaRegistry.get(args.ownerPersonaId);
@@ -27967,58 +28019,75 @@ function buildSessionHandlers(deps) {
27967
28019
  throw new Error(`persona not found: ${args.ownerPersonaId}`);
27968
28020
  }
27969
28021
  }
28022
+ ensurePersonaAccess(ctx, args.ownerPersonaId, "send");
27970
28023
  const { response, broadcast } = manager.create(args);
27971
28024
  return { response: { type: "session:info", ...response }, broadcast };
27972
28025
  };
27973
- const list = async () => {
28026
+ const list = async (_frame, _client, ctx) => {
27974
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
+ }
27975
28035
  return { response: { type: "session:list", ...response } };
27976
28036
  };
27977
- const get = async (frame) => {
28037
+ const get = async (frame, _client, ctx) => {
27978
28038
  const args = SessionIdArgs.parse(frame);
28039
+ ensureSessionAccess(ctx, args.sessionId, "read");
27979
28040
  const { response } = manager.get(args);
27980
28041
  return { response: { type: "session:info", ...response } };
27981
28042
  };
27982
- const update = async (frame) => {
28043
+ const update = async (frame, _client, ctx) => {
27983
28044
  const args = SessionUpdateArgs.parse(frame);
28045
+ ensureSessionAccess(ctx, args.sessionId, "send");
27984
28046
  const { response, broadcast } = manager.update(args);
27985
28047
  return { response: { type: "session:info", ...response }, broadcast };
27986
28048
  };
27987
- const del = async (frame) => {
28049
+ const del = async (frame, _client, ctx) => {
27988
28050
  const args = SessionIdArgs.parse(frame);
28051
+ ensureSessionAccess(ctx, args.sessionId, "send");
27989
28052
  const { broadcast } = manager.delete(args);
27990
28053
  return {
27991
28054
  response: { type: "session:deleted", sessionId: args.sessionId },
27992
28055
  broadcast
27993
28056
  };
27994
28057
  };
27995
- const send = async (frame) => {
28058
+ const send = async (frame, _client, ctx) => {
27996
28059
  const args = SessionSendArgs.parse(frame);
28060
+ ensureSessionAccess(ctx, args.sessionId, "send");
27997
28061
  const { broadcast } = manager.send(args);
27998
28062
  return { response: { type: "session:send", ok: true }, broadcast };
27999
28063
  };
28000
- const stop = async (frame) => {
28064
+ const stop = async (frame, _client, ctx) => {
28001
28065
  const args = SessionIdArgs.parse(frame);
28066
+ ensureSessionAccess(ctx, args.sessionId, "send");
28002
28067
  const { broadcast } = await manager.stop(args);
28003
28068
  return { response: { type: "session:stop", ok: true }, broadcast };
28004
28069
  };
28005
- const interrupt = async (frame) => {
28070
+ const interrupt = async (frame, _client, ctx) => {
28006
28071
  const args = SessionIdArgs.parse(frame);
28072
+ ensureSessionAccess(ctx, args.sessionId, "send");
28007
28073
  const { broadcast } = await manager.interrupt(args);
28008
28074
  return { response: { type: "session:interrupt", ok: true }, broadcast };
28009
28075
  };
28010
- const rewind = async (frame) => {
28076
+ const rewind = async (frame, _client, ctx) => {
28011
28077
  const args = SessionRewindArgs.parse(frame);
28078
+ ensureSessionAccess(ctx, args.sessionId, "send");
28012
28079
  const { response, broadcast } = await manager.rewind(args);
28013
28080
  return { response: { type: "session:rewind", ...response }, broadcast };
28014
28081
  };
28015
- const rewindDiff = async (frame) => {
28082
+ const rewindDiff = async (frame, _client, ctx) => {
28016
28083
  const args = SessionRewindDiffArgs.parse(frame);
28084
+ ensureSessionAccess(ctx, args.sessionId, "read");
28017
28085
  const { response } = await manager.rewindDiff(args);
28018
28086
  return { response: { type: "session:rewind-diff", ...response } };
28019
28087
  };
28020
- const rewindableMessageIds = async (frame) => {
28088
+ const rewindableMessageIds = async (frame, _client, ctx) => {
28021
28089
  const args = SessionRewindableMessageIdsArgs.parse(frame);
28090
+ ensureSessionAccess(ctx, args.sessionId, "read");
28022
28091
  const { response } = manager.rewindableMessageIds(args);
28023
28092
  return {
28024
28093
  response: { type: "session:rewindable-message-ids", ...response }
@@ -28029,19 +28098,22 @@ function buildSessionHandlers(deps) {
28029
28098
  const result = forkSession(args);
28030
28099
  return { response: { type: "session:fork", ...result } };
28031
28100
  };
28032
- const newSession = async (frame) => {
28101
+ const newSession = async (frame, _client, ctx) => {
28033
28102
  const args = SessionIdArgs.parse(frame);
28103
+ ensureSessionAccess(ctx, args.sessionId, "send");
28034
28104
  observer.stop(args.sessionId);
28035
28105
  const { response, broadcast } = manager.newSession(args);
28036
28106
  return { response: { type: "session:info", ...response }, broadcast };
28037
28107
  };
28038
- const resume = async (frame) => {
28108
+ const resume = async (frame, _client, ctx) => {
28039
28109
  const args = SessionResumeArgs.parse(frame);
28110
+ ensureSessionAccess(ctx, args.sessionId, "send");
28040
28111
  const { response, broadcast } = manager.resume(args);
28041
28112
  return { response: { type: "session:info", ...response }, broadcast };
28042
28113
  };
28043
- const observe = async (frame) => {
28114
+ const observe = async (frame, _client, ctx) => {
28044
28115
  const args = SessionObserveArgs.parse(frame);
28116
+ ensureSessionAccess(ctx, args.sessionId, "read");
28045
28117
  const { response: file } = manager.get({ sessionId: args.sessionId });
28046
28118
  const sessionFile = file;
28047
28119
  manager.ensureSession(sessionFile);
@@ -28055,14 +28127,17 @@ function buildSessionHandlers(deps) {
28055
28127
  });
28056
28128
  return { response: { type: "session:observe", ok: true } };
28057
28129
  };
28058
- const events = async (frame) => {
28130
+ const events = async (frame, _client, ctx) => {
28059
28131
  const args = SessionEventsArgs.parse(frame);
28132
+ ensureSessionAccess(ctx, args.sessionId, "read");
28060
28133
  const { response } = manager.getEvents(args);
28061
28134
  return { response: { type: "session:events", ...response } };
28062
28135
  };
28063
- const subscribe = async (frame, client) => {
28064
- if (typeof frame.sessionId === "string")
28136
+ const subscribe = async (frame, client, ctx) => {
28137
+ if (typeof frame.sessionId === "string") {
28138
+ ensureSessionAccess(ctx, frame.sessionId, "read");
28065
28139
  addSubscription(client, frame.sessionId);
28140
+ }
28066
28141
  return {
28067
28142
  response: { type: "subscribed", sessionId: frame.sessionId }
28068
28143
  };
@@ -28074,8 +28149,9 @@ function buildSessionHandlers(deps) {
28074
28149
  response: { type: "unsubscribed", sessionId: frame.sessionId }
28075
28150
  };
28076
28151
  };
28077
- const pin = async (frame) => {
28152
+ const pin = async (frame, _client, ctx) => {
28078
28153
  const args = SessionPinArgs.parse(frame);
28154
+ ensureSessionAccess(ctx, args.sessionId, "send");
28079
28155
  const { response, broadcast } = manager.pin(args);
28080
28156
  return { response: { type: "session:info", ...response }, broadcast };
28081
28157
  };
@@ -28084,13 +28160,15 @@ function buildSessionHandlers(deps) {
28084
28160
  const { response, broadcast } = manager.reorderPins(args);
28085
28161
  return { response: { type: "session:reorderPins", ...response }, broadcast };
28086
28162
  };
28087
- const answerQuestion = async (frame) => {
28163
+ const answerQuestion = async (frame, _client, ctx) => {
28088
28164
  const args = AnswerQuestionArgs.parse(frame);
28165
+ ensureSessionAccess(ctx, args.sessionId, "send");
28089
28166
  const { response, broadcast } = manager.answerQuestion(args);
28090
28167
  return { response: { type: "session:answerQuestion", ...response }, broadcast };
28091
28168
  };
28092
- const cancelQuestion = async (frame) => {
28169
+ const cancelQuestion = async (frame, _client, ctx) => {
28093
28170
  const args = CancelQuestionArgs.parse(frame);
28171
+ ensureSessionAccess(ctx, args.sessionId, "send");
28094
28172
  const { response, broadcast } = await manager.cancelQuestion(args);
28095
28173
  return { response: { type: "session:cancelQuestion", ...response }, broadcast };
28096
28174
  };
@@ -28878,6 +28956,7 @@ var ADMIN_ANY = {
28878
28956
  resource: { type: "*" },
28879
28957
  action: "admin"
28880
28958
  };
28959
+ var CAPABILITY_SCOPED = { kind: "capability-scoped" };
28881
28960
  var METHOD_GRANT_MAP = {
28882
28961
  // ---- public(meta-only,guest 也能调) ----
28883
28962
  "info": { kind: "public" },
@@ -28900,30 +28979,33 @@ var METHOD_GRANT_MAP = {
28900
28979
  "remote-persona:add": ADMIN_ANY,
28901
28980
  "remote-persona:list": ADMIN_ANY,
28902
28981
  "remote-persona:remove": ADMIN_ANY,
28903
- // ---- 业务方法:Phase 1 admin-only(owner 自动通过;guest 无法调用) ----
28904
- "session:create": ADMIN_ANY,
28905
- "session:list": ADMIN_ANY,
28906
- "session:get": ADMIN_ANY,
28907
- "session:update": ADMIN_ANY,
28908
- "session:delete": ADMIN_ANY,
28909
- "session:send": ADMIN_ANY,
28910
- "session:stop": ADMIN_ANY,
28911
- "session:interrupt": ADMIN_ANY,
28912
- "session:rewind": ADMIN_ANY,
28913
- "session:rewind-diff": ADMIN_ANY,
28914
- "session:rewindable-message-ids": ADMIN_ANY,
28915
- "session:fork": ADMIN_ANY,
28916
- "session:new": ADMIN_ANY,
28917
- "session:resume": ADMIN_ANY,
28918
- "session:observe": ADMIN_ANY,
28919
- "session:events": ADMIN_ANY,
28920
- "session:subscribe": ADMIN_ANY,
28921
- "session:unsubscribe": ADMIN_ANY,
28922
- "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,
28923
29004
  "session:reorderPins": ADMIN_ANY,
28924
- "permission:respond": ADMIN_ANY,
28925
- "session:answerQuestion": ADMIN_ANY,
28926
- "session:cancelQuestion": ADMIN_ANY,
29005
+ // owner 全局操作,无 personaId 维度
29006
+ "permission:respond": CAPABILITY_SCOPED,
29007
+ "session:answerQuestion": CAPABILITY_SCOPED,
29008
+ "session:cancelQuestion": CAPABILITY_SCOPED,
28927
29009
  "history:projects": ADMIN_ANY,
28928
29010
  "history:list": ADMIN_ANY,
28929
29011
  "history:read": ADMIN_ANY,
@@ -28959,6 +29041,7 @@ function computeGrantForFrame(method, frame) {
28959
29041
  const rule = METHOD_GRANT_MAP[method];
28960
29042
  if (!rule) return { kind: "public" };
28961
29043
  if (rule.kind === "public") return { kind: "public" };
29044
+ if (rule.kind === "capability-scoped") return { kind: "public" };
28962
29045
  if (rule.kind === "fixed") {
28963
29046
  return { kind: "check", resource: rule.resource, action: rule.action };
28964
29047
  }
@@ -28967,21 +29050,6 @@ function computeGrantForFrame(method, frame) {
28967
29050
  return { kind: "check", resource: picked.resource, action: picked.action };
28968
29051
  }
28969
29052
 
28970
- // src/permission/capability.ts
28971
- function matchResource(grant, target) {
28972
- if (grant.type === "*") return true;
28973
- if (grant.type !== target.type) return false;
28974
- return grant.id === target.id;
28975
- }
28976
- function assertGrant(grants, resource, action) {
28977
- for (const g2 of grants) {
28978
- if (!matchResource(g2.resource, resource)) continue;
28979
- if (g2.actions.includes(action)) return true;
28980
- if (g2.actions.includes("admin")) return true;
28981
- }
28982
- return false;
28983
- }
28984
-
28985
29053
  // src/index.ts
28986
29054
  async function startDaemon(config) {
28987
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.129.3d783e6",
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",