snipara-companion 3.7.3 → 3.7.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Release notes for `snipara-companion`, newest first.
4
4
 
5
+ ## 3.7.4
6
+
7
+ - Fix hosted workflow session creation by separating logical workflow identity from the server-issued session ID. Reuse persisted IDs for claims and release, scope mappings by project, actor and worktree, and skip hosted claims when session creation fails.
8
+ - Cover session creation, repeated starts, distinct workflows/actors, refusal and lease release.
9
+
5
10
  ## Unreleased
6
11
 
7
12
  ## New In 3.7.3
package/dist/index.d.ts CHANGED
@@ -3985,6 +3985,7 @@ interface CollaborationReviewOnlyAcknowledgement {
3985
3985
  expiresAt: string;
3986
3986
  }
3987
3987
  interface CollaborationLocalState {
3988
+ workflowSessions?: Record<string, string>;
3988
3989
  schemaVersion: "snipara.collaboration.v1";
3989
3990
  updatedAt: string;
3990
3991
  workSessionId?: string;
package/dist/index.js CHANGED
@@ -21495,6 +21495,9 @@ function loadCollaborationState(rootDir = process.cwd()) {
21495
21495
  schemaVersion: "snipara.collaboration.v1",
21496
21496
  updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString(),
21497
21497
  workSessionId: normalizeOptionalString3(parsed.workSessionId),
21498
+ workflowSessions: Object.fromEntries(Object.entries(parsed.workflowSessions ?? {}).filter(
21499
+ ([key, value]) => /^[a-f0-9]{64}$/.test(key) && typeof value === "string" && value.length > 0
21500
+ )),
21498
21501
  sessionId: normalizeOptionalString3(parsed.sessionId),
21499
21502
  actorId: normalizeOptionalString3(parsed.actorId),
21500
21503
  actorType: normalizeActorType(parsed.actorType),
@@ -21723,26 +21726,32 @@ async function collaborationClaimCommand(options) {
21723
21726
  }
21724
21727
  async function workflowCollaborationStart(options) {
21725
21728
  const rootDir = path25.resolve(options.rootDir ?? process.cwd());
21726
- const workSessionId = workflowWorkSessionId(options.workflowId);
21729
+ const logicalSessionId = workflowWorkSessionId(options.workflowId);
21727
21730
  const context = resolveCollaborationContext({
21728
21731
  dir: rootDir,
21729
- workSessionId,
21730
21732
  client: "snipara-companion:workflow"
21731
21733
  });
21732
21734
  const state = loadCollaborationState(rootDir);
21735
+ const sessionKey = workflowSessionKey(context, options.workflowId);
21736
+ const persistedSessionId = state.workflowSessions?.[sessionKey];
21737
+ context.actor.sessionId = `${logicalSessionId}:${sessionKey}`;
21733
21738
  const files = normalizeCollaborationFiles(options.files);
21734
21739
  const resources = deriveLocalCollaborationResourcesFromFiles(files, rootDir);
21735
21740
  const hostedSession = await maybeStartHostedSession(context, {
21736
- workSessionId,
21741
+ workSessionId: persistedSessionId,
21737
21742
  task: options.goal,
21738
21743
  files,
21739
21744
  resources
21740
21745
  });
21741
21746
  const session = hostedSession.status === "ok" ? hostedSession.data?.session : void 0;
21747
+ const workSessionId = session?.id ?? persistedSessionId ?? logicalSessionId;
21748
+ if (session) {
21749
+ state.workflowSessions = { ...state.workflowSessions, [sessionKey]: session.id };
21750
+ }
21742
21751
  const effectiveResources = hostedSession.status === "ok" ? hostedSession.data?.resources ?? resources : resources;
21743
21752
  let hostedClaim = { status: "skipped" };
21744
21753
  const shouldClaim = options.mode === "full" || options.mode === "orchestrate";
21745
- if (shouldClaim && (files.length > 0 || effectiveResources.length > 0)) {
21754
+ if (hostedSession.status === "ok" && shouldClaim && (files.length > 0 || effectiveResources.length > 0)) {
21746
21755
  hostedClaim = await maybeCreateHostedLeases(context, {
21747
21756
  workSessionId,
21748
21757
  files,
@@ -21800,7 +21809,7 @@ async function workflowCollaborationRelease(options) {
21800
21809
  client: "snipara-companion:workflow"
21801
21810
  });
21802
21811
  const state = loadCollaborationState(rootDir);
21803
- const targetSessionId = options.workflowId ? workflowWorkSessionId(options.workflowId) : state.workSessionId;
21812
+ const targetSessionId = options.workflowId ? state.workflowSessions?.[workflowSessionKey(context, options.workflowId)] ?? workflowWorkSessionId(options.workflowId) : state.workSessionId;
21804
21813
  const activeLeases = state.leases.filter(
21805
21814
  (lease) => lease.status === "ACTIVE" && (lease.workSessionId === targetSessionId || !lease.workSessionId && Boolean(targetSessionId) && state.workSessionId === targetSessionId)
21806
21815
  );
@@ -22767,6 +22776,9 @@ function expireLocalLeases(state, now = /* @__PURE__ */ new Date()) {
22767
22776
  state.updatedAt = now.toISOString();
22768
22777
  }
22769
22778
  }
22779
+ function workflowSessionKey(context, workflowId) {
22780
+ return (0, import_node_crypto13.createHash)("sha256").update(JSON.stringify([context.config.apiUrl, context.config.projectId, context.actor.actorType, context.actor.actorId, context.worktree, workflowId])).digest("hex");
22781
+ }
22770
22782
  function workflowWorkSessionId(workflowId) {
22771
22783
  return `workflow:${workflowId}`;
22772
22784
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snipara-companion",
3
- "version": "3.7.3",
3
+ "version": "3.7.4",
4
4
  "description": "Local-first CLI that asks your repo what breaks before an AI coding agent edits it.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {