@quantiya/codevibe-claude-plugin 1.0.46 → 1.0.47

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevibe-claude",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
4
4
  "description": "Sync Claude Code sessions with iOS mobile app via AWS backend. Control Claude Code from your phone with real-time bidirectional synchronization.",
5
5
  "author": {
6
6
  "name": "CodeVibe Team"
@@ -253,4 +253,4 @@ Environment:`),console.log(' Set ENVIRONMENT env var to "development" or "produ
253
253
  `).map(h=>h.trim()),t=hr(e,h=>/\[(?:y\/n|Y\/n|y\/N)\]/.test(h)),r=t>=0?e[t]:null;if(!r)return null;let i=Nt(e,t),s=i.length>0?i.join(`
254
254
  `):r,o=s.toLowerCase(),a=o.includes("what to change")||o.includes("what should")||o.includes("provide")||o.includes("instructions");return{kind:"yes_no",promptText:s,options:a?[{number:"1",text:"Yes"},{number:"2",text:"No, provide instructions"}]:[{number:"1",text:"Yes"},{number:"2",text:"No"}],submitMap:{1:"y",2:"n"},requiresFollowUpText:a}}function gr(n){let e=n.split(`
255
255
  `).map(d=>d.trim()),t=mr(e);if(t.length<2)return null;let r=t.map(({line:d})=>Je(d)).filter(d=>!!d),i={};for(let d of r)i[d.number]=d.number;let s=t[0]?.index??-1,o=Nt(e,s-1);return{kind:"numbered",promptText:o.length>0?o.join(`
256
- `):"Select an option",options:r,submitMap:i}}function hr(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function Je(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function yr(n){return Je(n)!==null}function mr(n){let e=[];for(let r=0;r<n.length;r+=1){let i=Je(n[r]);i&&e.push({index:r,number:Number(i.number)})}if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(fr(n,i.index,s.index))break;i.number===s.number-1&&t.unshift(i)}return t.map((r,i)=>{let s=i+1<t.length?t[i+1].index-1:vr(n,r.index);return{index:r.index,line:Sr(n,r.index,s)}})}function fr(n,e,t){for(let r=e+1;r<t;r+=1)if(!n[r])return!0;return!1}var Pt=/\((?:[a-z0-9]|esc|escape)\)\s*$/i;function vr(n,e){let t=Pt.test(n[e])?e:-1;for(let r=e+1;r<n.length&&!(!n[r]||yr(n[r]));r+=1)Pt.test(n[r])&&(t=r);return t>=0?t:e}function Sr(n,e,t){let r=n[e];for(let i=e+1;i<=t;i+=1)r+=n[i];return r}function Nt(n,e){if(e<0)return[];let t=qe(n,e);if(t<0)return[];let{start:r,end:i}=Ve(n,t),s=n.slice(r,i+1).filter(Boolean);if(kr(s)){let p=wr(n,r-1);return p.length>0?p:s}if(r<=1)return s;let o=r-1;if(o=qe(n,o),o<0||o===r-1)return s;let{start:a,end:d}=Ve(n,o),l=n.slice(a,d+1).filter(Boolean);return l.some(Ut)?[...l,...s]:s}function Ut(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function qe(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ve(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function wr(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=qe(n,r),!(r<0));){let{start:s,end:o}=Ve(n,r),a=n.slice(s,o+1).filter(Boolean);a.length>0&&t.unshift(a),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(Ut));return i>=0?t.slice(i).flat():t[t.length-1]}function kr(n){return n.length===0?!1:n.filter(br).length>=Math.max(2,Math.ceil(n.length/2))}function br(n){return/^\d+\s/.test(n)}ae();P();ae();P();H();async function Y(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(p){return c.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}if(!i)return c.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(p=>p.deviceId)),a=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(p){return c.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}let l=d.filter(p=>!o.has(p.deviceId)||a.has(p.deviceId));if(l.length===0)return 0;c.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:l.length,grantDeviceIds:l.map(p=>p.deviceId),forceCount:a.size});let h=0;for(let p of l)try{let y=E.encryptSessionKey(e,p.publicKey);await t.grantSessionKey({sessionId:n,deviceId:p.deviceId,encryptedKey:y.encryptedKey,ephemeralPublicKey:y.ephemeralPublicKey}),h++,c.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:p.deviceId,platform:p.platform})}catch(y){c.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:p.deviceId,error:y instanceof Error?y.message:String(y)})}return h>0&&c.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:h,requestedCount:l.length}),h}async function $t(n,e){let t=e.pollIntervalMs??5e3,r=e.maxAttempts??6,i,s;try{i=await g.getDeviceId(),s=await g.getDevicePrivateKey()}catch(o){c.warn("[SessionRekey] A1 pre-loop keychain read failed",{sessionId:n,error:o instanceof Error?o.message:String(o)});try{e.onTimeout?.(0)}catch{}return null}for(let o=1;o<=r;o++){o>1&&await new Promise(y=>setTimeout(y,t));let a;try{a=await e.appSyncClient.getSession(n)}catch(y){c.warn("[SessionRekey] A1 getSession failed during poll, will retry",{sessionId:n,attempt:o,error:y instanceof Error?y.message:String(y)});continue}let d=a?.encryptedKeys??[],l=d.filter(y=>y.deviceId===i);if(l.length===0){c.info("[SessionRekey] A1 our deviceId still not in encryptedKeys",{sessionId:n,attempt:o,freshDeviceCount:d.length});continue}let h=null,p=[];for(let y=l.length-1;y>=0;y--)try{h=E.decryptSessionKey(l[y],s);break}catch(f){p.push(f instanceof Error?f.message:String(f))}if(h){g.cacheSessionKey(n,h);try{e.onSuccess?.(o)}catch{}return c.info("[SessionRekey] A1 self-rekey successful",{sessionId:n,attempt:o,entriesTriedToDecrypt:l.length}),h}c.warn("[SessionRekey] A1 found entries but all decrypt-failed, will retry",{sessionId:n,attempt:o,entriesTried:l.length,errors:p})}try{e.onTimeout?.(r)}catch{}return c.warn("[SessionRekey] A1 self-rekey exhausted maxAttempts",{sessionId:n,maxAttempts:r}),null}P();async function he(n,e){try{let t=await g.getDeviceId(),r=await g.getDevicePublicKey(),i=g.getDevicePlatform(),s=g.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),g.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}async function Ae(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let i=Ee(n),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}=g.createSessionKey(r,{onDeviceSkipped:d=>{Et({skipped_count_bucket:re(d),session_hash:i}).catch(()=>{})}});return a.length>0&&It({session_hash:i,encrypted_count_bucket:re(o.length),skipped_count_bucket:re(a.length)}).catch(()=>{}),t.info("Session encryption prepared",{sessionId:n,deviceCount:o.length,skippedCount:a.length}),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function je(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:a}=n,d=null;try{d=await e.getSession(r)}catch(f){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:f})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(m){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:m})}let f=null,W=d.encryptedKeys??[];if(d.isEncrypted){if(W.length>0){try{let m=await g.getSessionKey(r,W);m&&(f=m,g.cacheSessionKey(r,m),t.info("Session key retrieved for resumed session",{sessionId:r}))}catch(m){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:m})}if(!f){let m=Ee(r);t.info("Self-rekey: re-registering device key + awaiting grant",{sessionId:r,otherDeviceCount:W.length}),Tt({session_hash:m,other_device_count_bucket:re(W.length)}).catch(()=>{});try{await he(e,t),f=await $t(r,{appSyncClient:e,onSuccess:S=>{xt({session_hash:m,attempt_count:S}).catch(()=>{})},onTimeout:S=>{Ct({session_hash:m,attempt_count:S}).catch(()=>{})}})}catch(S){t.warn("Self-rekey path failed",{sessionId:r,error:S instanceof Error?S.message:String(S)})}}}else t.warn("Encrypted session has empty encryptedKeys; cannot self-rekey",{sessionId:r});if(!f){let m=new Error(`Cannot resume encrypted session ${r}: `+(W.length===0?"session is marked encrypted but session.encryptedKeys is empty (corrupt state). Cannot self-rekey without a peer device. Start a new session.":"this device's key is not in session.encryptedKeys and self-rekey did not complete within 30s. This typically means the device key was rotated and mobile has not yet granted access to this device. Open the mobile app to refresh device keys, then retry."));throw m.code="ENCRYPTED_SESSION_NO_KEY",m}}if(f)try{let m=await Y(r,f,e);m>0&&(t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:m}),At({session_hash:Ee(r),granted_count_bucket:re(m)}).catch(()=>{}))}catch(m){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:m instanceof Error?m.message:String(m)})}return{resumed:!0,sessionKey:f}}let l=await Ae(r,e,t),h=o,p=a;l&&(h=E.encryptContent(o,l.sessionKey),p&&Object.keys(p).length>0&&(p={encrypted:E.encryptMetadata(p,l.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!l}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:h,status:"ACTIVE",metadata:p,isEncrypted:l?!0:void 0,creatorDeviceId:l?await g.getDeviceId():void 0,encryptionVersion:l?1:void 0,encryptedKeys:l?.encryptedKeys});let y=l?.sessionKey||null;return l&&g.cacheSessionKey(r,l.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!l}),{resumed:!1,sessionKey:y}}P();function ze(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let a=g.getCachedSessionIds();if(a.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:a.length,forceDeviceCount:o?.size??0});for(let d of a){let l=g.getCachedSessionKey(d);if(l)try{let h=await Y(d,l,n,o?{forceDeviceIds:o}:void 0);h>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:h,reason:s})}catch(h){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:h instanceof Error?h.message:String(h)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}var M=new Map;function Ye(n){let e=Date.now(),t=n.agentClock?Date.parse(n.agentClock):NaN,r=Number.isNaN(t)?e:t,i=M.get(n.orderingKey)??0,s=Math.max(r,i+1);if(M.has(n.orderingKey)&&M.delete(n.orderingKey),M.set(n.orderingKey,s),M.size>1024){let o=M.keys().next().value;o!==void 0&&M.delete(o)}return new Date(s).toISOString()}function Xe(){M.clear()}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,PORT_RANGE_SIZE,PRIMARY_PORT,SessionStatus,_resetPrepareEventTimestampForTesting,authService,bindOAuthServer,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareEventTimestamp,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
256
+ `):"Select an option",options:r,submitMap:i}}function hr(n,e){for(let t=n.length-1;t>=0;t-=1)if(e(n[t]))return t;return-1}function Je(n){let e=n.match(/^(?:[>›❯▸▶➜➤*●]\s*)?(\d+)\.\s+(.*)$/);return e?{number:e[1],text:e[2]}:null}function yr(n){return Je(n)!==null}function mr(n){let e=[];for(let r=0;r<n.length;r+=1){let i=Je(n[r]);i&&e.push({index:r,number:Number(i.number)})}if(e.length===0)return[];let t=[e[e.length-1]];for(let r=e.length-2;r>=0;r-=1){let i=e[r],s=t[0];if(fr(n,i.index,s.index))break;i.number===s.number-1&&t.unshift(i)}return t.map((r,i)=>{let s=i+1<t.length?t[i+1].index-1:vr(n,r.index);return{index:r.index,line:Sr(n,r.index,s)}})}function fr(n,e,t){for(let r=e+1;r<t;r+=1)if(!n[r])return!0;return!1}var Pt=/\((?:[a-z0-9]|esc|escape)\)\s*$/i;function vr(n,e){let t=Pt.test(n[e])?e:-1;for(let r=e+1;r<n.length&&!(!n[r]||yr(n[r]));r+=1)Pt.test(n[r])&&(t=r);return t>=0?t:e}function Sr(n,e,t){let r=n[e];for(let i=e+1;i<=t;i+=1)r+=n[i];return r}function Nt(n,e){if(e<0)return[];let t=qe(n,e);if(t<0)return[];let{start:r,end:i}=Ve(n,t),s=n.slice(r,i+1).filter(Boolean);if(kr(s)){let p=wr(n,r-1);return p.length>0?p:s}if(r<=1)return s;let o=r-1;if(o=qe(n,o),o<0||o===r-1)return s;let{start:a,end:d}=Ve(n,o),l=n.slice(a,d+1).filter(Boolean);return l.some(Ut)?[...l,...s]:s}function Ut(n){return/^(?:would you like to|do you want to|the model would like to|action required|confirm)\b/i.test(n)}function qe(n,e){let t=e;for(;t>=0&&!n[t];)t-=1;return t}function Ve(n,e){let t=e;for(;t>=0&&n[t];)t-=1;return{start:t+1,end:e}}function wr(n,e){let t=[],r=e;for(;r>=0&&t.length<2&&(r=qe(n,r),!(r<0));){let{start:s,end:o}=Ve(n,r),a=n.slice(s,o+1).filter(Boolean);a.length>0&&t.unshift(a),r=s-1}if(t.length===0)return[];let i=t.findIndex(s=>s.some(Ut));return i>=0?t.slice(i).flat():t[t.length-1]}function kr(n){return n.length===0?!1:n.filter(br).length>=Math.max(2,Math.ceil(n.length/2))}function br(n){return/^\d+\s/.test(n)}ae();P();ae();P();H();async function Y(n,e,t,r={}){let i;try{i=await t.getSession(n)}catch(p){return c.warn("[SessionRekey] Failed to fetch session state for re-key",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}if(!i)return c.warn("[SessionRekey] Session not found, skipping re-key",{sessionId:n}),0;if(!i.isEncrypted)return 0;let s=i.encryptedKeys||[],o=new Set(s.map(p=>p.deviceId)),a=r.forceDeviceIds??new Set,d;try{d=await t.listUserDeviceKeys()}catch(p){return c.warn("[SessionRekey] Failed to fetch user device keys",{sessionId:n,error:p instanceof Error?p.message:String(p)}),0}let l=d.filter(p=>!o.has(p.deviceId)||a.has(p.deviceId));if(l.length===0)return 0;c.info("[SessionRekey] Granting session key to devices",{sessionId:n,existingDeviceCount:s.length,grantCount:l.length,grantDeviceIds:l.map(p=>p.deviceId),forceCount:a.size});let h=0;for(let p of l)try{let y=E.encryptSessionKey(e,p.publicKey);await t.grantSessionKey({sessionId:n,deviceId:p.deviceId,encryptedKey:y.encryptedKey,ephemeralPublicKey:y.ephemeralPublicKey}),h++,c.info("[SessionRekey] Granted session key to device",{sessionId:n,deviceId:p.deviceId,platform:p.platform})}catch(y){c.warn("[SessionRekey] Failed to grant session key to device",{sessionId:n,deviceId:p.deviceId,error:y instanceof Error?y.message:String(y)})}return h>0&&c.info("[SessionRekey] Re-key complete",{sessionId:n,grantedCount:h,requestedCount:l.length}),h}async function $t(n,e){let t=e.pollIntervalMs??5e3,r=e.maxAttempts??6,i,s;try{i=await g.getDeviceId(),s=await g.getDevicePrivateKey()}catch(o){c.warn("[SessionRekey] A1 pre-loop keychain read failed",{sessionId:n,error:o instanceof Error?o.message:String(o)});try{e.onTimeout?.(0)}catch{}return null}for(let o=1;o<=r;o++){o>1&&await new Promise(y=>setTimeout(y,t));let a;try{a=await e.appSyncClient.getSession(n)}catch(y){c.warn("[SessionRekey] A1 getSession failed during poll, will retry",{sessionId:n,attempt:o,error:y instanceof Error?y.message:String(y)});continue}let d=a?.encryptedKeys??[],l=d.filter(y=>y.deviceId===i);if(l.length===0){c.info("[SessionRekey] A1 our deviceId still not in encryptedKeys",{sessionId:n,attempt:o,freshDeviceCount:d.length});continue}let h=null,p=[];for(let y=l.length-1;y>=0;y--)try{h=E.decryptSessionKey(l[y],s);break}catch(f){p.push(f instanceof Error?f.message:String(f))}if(h){g.cacheSessionKey(n,h);try{e.onSuccess?.(o)}catch{}return c.info("[SessionRekey] A1 self-rekey successful",{sessionId:n,attempt:o,entriesTriedToDecrypt:l.length}),h}c.warn("[SessionRekey] A1 found entries but all decrypt-failed, will retry",{sessionId:n,attempt:o,entriesTried:l.length,errors:p})}try{e.onTimeout?.(r)}catch{}return c.warn("[SessionRekey] A1 self-rekey exhausted maxAttempts",{sessionId:n,maxAttempts:r}),null}P();async function he(n,e){try{let t=await g.getDeviceId(),r=await g.getDevicePublicKey(),i=g.getDevicePlatform(),s=g.getDeviceName();e.info("Registering device encryption key",{deviceId:t,platform:i,deviceName:s}),await n.registerDeviceKey(t,r,i,s),g.setIsRegistered(!0),e.info("Device encryption key registered successfully",{deviceId:t})}catch(t){e.warn("Failed to register device encryption key (E2E encryption may not work):",t)}}async function Ae(n,e,t){try{let r=await e.listUserDeviceKeys();if(r.length===0)return t.info("No device keys found, session will not be encrypted"),null;t.info("Preparing session encryption",{sessionId:n,deviceCount:r.length});let i=Ee(n),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}=g.createSessionKey(r,{onDeviceSkipped:d=>{Et({skipped_count_bucket:re(d),session_hash:i}).catch(()=>{})}});return a.length>0&&It({session_hash:i,encrypted_count_bucket:re(o.length),skipped_count_bucket:re(a.length)}).catch(()=>{}),t.info("Session encryption prepared",{sessionId:n,deviceCount:o.length,skippedCount:a.length}),{sessionKey:s,encryptedKeys:o,skippedDeviceIds:a}}catch(r){return t.warn("Failed to prepare session encryption:",r),null}}async function je(n,e,t){let{sessionId:r,userId:i,agentType:s,projectPath:o,metadata:a}=n,d=null;try{d=await e.getSession(r)}catch(f){t.warn("Failed to get session (will attempt to create new)",{sessionId:r,error:f})}if(d){t.info("Session exists in backend - reactivating",{sessionId:r,previousStatus:d.status});try{await e.updateSession({sessionId:r,status:"ACTIVE"})}catch(m){t.warn("Failed to reactivate existing session, will continue",{sessionId:r,error:m})}let f=null,W=d.encryptedKeys??[];if(d.isEncrypted){if(W.length>0){try{let m=await g.getSessionKey(r,W);m&&(f=m,g.cacheSessionKey(r,m),t.info("Session key retrieved for resumed session",{sessionId:r}))}catch(m){t.warn("Failed to retrieve session key for resumed session",{sessionId:r,error:m})}if(!f){let m=Ee(r);t.info("Self-rekey: re-registering device key + awaiting grant",{sessionId:r,otherDeviceCount:W.length}),Tt({session_hash:m,other_device_count_bucket:re(W.length)}).catch(()=>{});try{await he(e,t),f=await $t(r,{appSyncClient:e,onSuccess:S=>{xt({session_hash:m,attempt_count:S}).catch(()=>{})},onTimeout:S=>{Ct({session_hash:m,attempt_count:S}).catch(()=>{})}})}catch(S){t.warn("Self-rekey path failed",{sessionId:r,error:S instanceof Error?S.message:String(S)})}}}else t.warn("Encrypted session has empty encryptedKeys; cannot self-rekey",{sessionId:r});if(!f){let m=new Error(`Cannot resume encrypted session ${r}: `+(W.length===0?"session is marked encrypted but session.encryptedKeys is empty (corrupt state). Cannot self-rekey without a peer device. Start a new session.":"this device's key is not in session.encryptedKeys and self-rekey did not complete within 30s. This typically means the device key was rotated and mobile has not yet granted access to this device. Open the mobile app to refresh device keys, then retry."));throw m.code="ENCRYPTED_SESSION_NO_KEY",m}}if(f)try{let m=await Y(r,f,e);m>0&&(t.info("Session re-keyed for newly registered devices on resume",{sessionId:r,newDeviceCount:m}),At({session_hash:Ee(r),granted_count_bucket:re(m)}).catch(()=>{}))}catch(m){t.warn("Session re-key on resume failed (non-fatal)",{sessionId:r,error:m instanceof Error?m.message:String(m)})}return{resumed:!0,sessionKey:f}}let l=await Ae(r,e,t),h=o,p=a;l&&(h=E.encryptContent(o,l.sessionKey),p&&Object.keys(p).length>0&&(p={encrypted:E.encryptMetadata(p,l.sessionKey)}),t.info("Session data encrypted",{sessionId:r})),t.info("Creating new session in backend",{sessionId:r,userId:i,agentType:s,isEncrypted:!!l}),await e.createSession({sessionId:r,userId:i,agentType:s,projectPath:h,status:"ACTIVE",metadata:p,isEncrypted:l?!0:void 0,creatorDeviceId:l?await g.getDeviceId():void 0,encryptionVersion:l?1:void 0,encryptedKeys:l?.encryptedKeys});let y=l?.sessionKey||null;return l&&g.cacheSessionKey(r,l.sessionKey),t.info("Session created",{sessionId:r,userId:i,isEncrypted:!!l}),{resumed:!1,sessionKey:y}}P();function ze(n,e){let t=n.getCurrentUserId(),r=async(s,o)=>{let a=g.getCachedSessionIds();if(a.length===0){e.info("[DeviceKeyWatcher] No active sessions to re-key",{reason:s});return}e.info("[DeviceKeyWatcher] Running re-key pass",{reason:s,activeSessionCount:a.length,forceDeviceCount:o?.size??0});for(let d of a){let l=g.getCachedSessionKey(d);if(l)try{let h=await Y(d,l,n,o?{forceDeviceIds:o}:void 0);h>0&&e.info("[DeviceKeyWatcher] Session re-keyed",{sessionId:d,newDeviceCount:h,reason:s})}catch(h){e.warn("[DeviceKeyWatcher] Re-key failed for session (non-fatal)",{sessionId:d,reason:s,error:h instanceof Error?h.message:String(h)})}}},i=n.subscribeToDeviceKeyRegistered(t,s=>{e.info("[DeviceKeyWatcher] New device observed, triggering re-key",{userId:t,newDeviceId:s.deviceId,platform:s.platform,deviceName:s.deviceName}),r(`new-device:${s.deviceId}`,new Set([s.deviceId]))},()=>{r("watcher-reconnect")},s=>{e.warn("[DeviceKeyWatcher] Subscription error (will retry)",{error:s instanceof Error?s.message:String(s)})});return e.info("[DeviceKeyWatcher] Started",{userId:t}),i}var M=new Map;function Ye(n){let e=Date.now(),t=n.agentClock?Date.parse(n.agentClock):NaN,r=Number.isNaN(t)?e:t,i=M.get(n.orderingKey)??0,s=typeof n.notBeforeMs=="number"&&Number.isFinite(n.notBeforeMs)?n.notBeforeMs+1:0,o=Math.max(r,i+1,s);if(M.has(n.orderingKey)&&M.delete(n.orderingKey),M.set(n.orderingKey,o),M.size>1024){let a=M.keys().next().value;a!==void 0&&M.delete(a)}return new Date(o).toISOString()}function Xe(){M.clear()}0&&(module.exports={AgentType,AppSyncClient,AuthService,CryptoError,CryptoService,DeliveryStatus,ENCRYPTION_VERSION,EventSource,EventType,KeychainError,KeychainManager,Logger,PORT_RANGE_SIZE,PRIMARY_PORT,SessionStatus,_resetPrepareEventTimestampForTesting,authService,bindOAuthServer,createLogger,cryptoService,errorWasBeaconed,fireAuthCompletedBeacon,fireAuthFailedBeacon,getConfig,getEnvironment,getErrorReason,keychainManager,loadConfig,logger,markErrorBeaconed,mutations,normalizeSnapshot,parseInteractivePrompt,prepareEventTimestamp,prepareSessionEncryption,queries,registerDeviceEncryptionKey,rekeySessionForNewDevices,resumeOrCreateSession,runAuthCli,startDeviceKeyWatcher,subscriptions});
@@ -3,7 +3,8 @@
3
3
  * STRICTLY GREATER than any previous timestamp returned for the same
4
4
  * `orderingKey` within this process.
5
5
  *
6
- * Formula: `Math.max(baseMs, lastMs + 1)` where:
6
+ * Formula: `Math.max(baseMs, lastMs + 1, floorMs)` where `floorMs` is
7
+ * `notBeforeMs + 1` when a finite `notBeforeMs` is supplied (else 0), and:
7
8
  * - baseMs = parsed agentClock if provided (and valid), else Date.now()
8
9
  * - lastMs = previously-emitted ms for this orderingKey (or 0 if first)
9
10
  *
@@ -20,6 +21,7 @@
20
21
  export declare function prepareEventTimestamp(opts: {
21
22
  orderingKey: string;
22
23
  agentClock?: string;
24
+ notBeforeMs?: number;
23
25
  }): string;
24
26
  /**
25
27
  * Test-only: clear the per-key map. Used by jest/vitest to reset state
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-claude-plugin",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
4
4
  "description": "Control Claude Code from your iPhone and Android — real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
5
5
  "main": "dist/server.js",
6
6
  "bin": {
@@ -47,7 +47,7 @@
47
47
  "node": ">=18.0.0"
48
48
  },
49
49
  "dependencies": {
50
- "@quantiya/codevibe-core": "^1.0.27",
50
+ "@quantiya/codevibe-core": "^1.0.28",
51
51
  "dotenv": "^16.6.1",
52
52
  "express": "^5.1.0",
53
53
  "graphql": "^16.12.0",