@agent-link/agent 0.1.344 → 0.1.345

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/copilot.js CHANGED
@@ -360,6 +360,7 @@ function sessionRef(sessionId) {
360
360
  };
361
361
  }
362
362
  function startCopilotTurn(state) {
363
+ state.steeringRequests = undefined;
363
364
  state.toolStartKeys = new Set();
364
365
  state.compactionRecord = undefined;
365
366
  state.reviewDocuments = new CopilotReviewDocuments();
@@ -418,6 +419,7 @@ function startCopilotTurn(state) {
418
419
  }
419
420
  }
420
421
  function finishCopilotTurn(state) {
422
+ settleCopilotSteering(state, 'failed');
421
423
  settleCopilotUserRequest(state, 'failed');
422
424
  state.turnActive = false;
423
425
  state.lastActivityAt = Date.now();
@@ -882,6 +884,115 @@ export function getCopilotConversation(conversationId) {
882
884
  conversationAliases.delete(conversationId);
883
885
  return undefined;
884
886
  }
887
+ function copilotSteeringReceipt(state, request) {
888
+ const sessionId = state.commandSessionId || state.copilotSessionId || state.lastCopilotSessionId;
889
+ return { type: 'backend_event', providerId: request.providerId, conversationId: state.conversationId,
890
+ workDir: state.workDir, claudeSessionId: sessionId, turnId: request.backendTurnId, stateVersion: state.stateVersion,
891
+ event: { type: 'user_message', session: sessionRef(sessionId || 'pending'), message: { ...request } } };
892
+ }
893
+ export function getCopilotSteeringReceipts() {
894
+ return [...conversations.values()].flatMap(state => [...(state.steeringRequests?.values() || [])]
895
+ .filter(request => request.providerId === state.providerId && request.backendTurnId === state.turnId)
896
+ .map(request => copilotSteeringReceipt(state, request)));
897
+ }
898
+ function publishCopilotSteering(state, request) {
899
+ const sessionId = state.commandSessionId || state.copilotSessionId || state.lastCopilotSessionId;
900
+ try {
901
+ if (sessionId)
902
+ saveCopilotUserRequest(COPILOT_SESSION_DIR, sessionId, request);
903
+ }
904
+ finally {
905
+ sendFn(copilotSteeringReceipt(state, request));
906
+ }
907
+ }
908
+ function settleCopilotSteering(state, status) {
909
+ for (const request of state.steeringRequests?.values() || []) {
910
+ if (request.copilotRequestState !== 'pending')
911
+ continue;
912
+ request.copilotRequestState = status;
913
+ try {
914
+ publishCopilotSteering(state, request);
915
+ }
916
+ catch (error) {
917
+ console.warn('[Copilot:steering] Receipt persistence failed:', error);
918
+ }
919
+ }
920
+ }
921
+ export async function handleCopilotSteering(prompt, scope, send) {
922
+ let state;
923
+ let request;
924
+ try {
925
+ if (typeof prompt !== 'string' || !prompt.trim() || prompt.trim().startsWith('/')
926
+ || typeof scope.requestId !== 'string' || !/^[\w-]{1,100}$/.test(scope.requestId)) {
927
+ throw new Error('Steering requires text and a valid request ID');
928
+ }
929
+ state = getCopilotConversation(scope.conversationId);
930
+ const sessionId = state?.commandSessionId;
931
+ if (!state || scope.providerId !== 'copilot' || state.providerId !== scope.providerId
932
+ || !sessionId || scope.claudeSessionId !== sessionId || typeof scope.workDir !== 'string'
933
+ || resolve(scope.workDir) !== resolve(state.workDir) || !scope.turnId || scope.turnId !== state.turnId) {
934
+ throw new Error('Steering conversation, provider, directory, session or turn no longer matches');
935
+ }
936
+ state.steeringRequests ??= new Map();
937
+ const existing = state.steeringRequests.get(scope.requestId)
938
+ || readCopilotUserRequests(COPILOT_SESSION_DIR, sessionId).find(row => row.copilotRequestId === scope.requestId);
939
+ if (existing) {
940
+ if (!existing.copilotSteering || existing.content !== prompt.trim() || existing.backendTurnId !== scope.turnId) {
941
+ publishCopilotSteering(state, existing);
942
+ send({ type: 'error', operation: 'copilot_request', providerId: scope.providerId,
943
+ conversationId: scope.conversationId, preserveActiveTurn: true,
944
+ message: 'Steering request ID was already used for a different request' });
945
+ return;
946
+ }
947
+ publishCopilotSteering(state, existing);
948
+ return;
949
+ }
950
+ if (!state.turnActive || state.isCompacting || state.copilotMode !== 'interactive'
951
+ || state.planMode || state.planModePending || state.activeOperation !== 'chat'
952
+ || getPendingCopilotDecisions(state.conversationId).length || !state.commandRuntime?.isRunning()) {
953
+ throw new Error('Steering is unavailable: the interactive task is no longer accepting requirements');
954
+ }
955
+ request = { role: 'user', content: prompt.trim(), copilotRequestId: scope.requestId,
956
+ backendTurnId: scope.turnId, providerId: scope.providerId, copilotSteering: true,
957
+ copilotRequestState: 'pending', timestamp: new Date().toISOString() };
958
+ state.steeringRequests.set(scope.requestId, request);
959
+ publishCopilotSteering(state, request);
960
+ const runtime = state.commandRuntime;
961
+ const nativeId = await runtime.steer(sessionId, request.content);
962
+ if (state.commandRuntime !== runtime || state.commandSessionId !== sessionId || state.turnId !== scope.turnId
963
+ || state.providerId !== scope.providerId || resolve(state.workDir) !== resolve(scope.workDir)) {
964
+ console.warn('[Copilot:steering] Ignoring acknowledgment after ownership changed');
965
+ return;
966
+ }
967
+ request.copilotUserMessageId = nativeId;
968
+ request.copilotRequestDispatched = true;
969
+ publishCopilotSteering(state, request);
970
+ }
971
+ catch (error) {
972
+ const message = error instanceof Error ? error.message : String(error);
973
+ console.warn('[Copilot:steering]', message);
974
+ if (request && state && state.commandSessionId === scope.claudeSessionId && state.turnId === scope.turnId
975
+ && state.providerId === scope.providerId && resolve(state.workDir) === resolve(scope.workDir)) {
976
+ if (request.copilotRequestState === 'pending')
977
+ request.copilotRequestState = 'failed';
978
+ request.copilotSteeringError = message;
979
+ try {
980
+ publishCopilotSteering(state, request);
981
+ }
982
+ catch (persistError) {
983
+ console.warn('[Copilot:steering] Failed receipt persistence:', persistError);
984
+ }
985
+ }
986
+ else {
987
+ send({ type: 'backend_event', providerId: scope.providerId, conversationId: scope.conversationId,
988
+ workDir: scope.workDir, claudeSessionId: scope.claudeSessionId, turnId: scope.turnId,
989
+ event: { type: 'user_message', session: sessionRef(scope.claudeSessionId),
990
+ message: { role: 'user', content: typeof prompt === 'string' ? prompt : '', copilotSteering: true,
991
+ copilotRequestId: scope.requestId, copilotRequestState: 'failed', copilotSteeringError: message,
992
+ timestamp: new Date().toISOString() } } });
993
+ }
994
+ }
995
+ }
885
996
  export async function handleCopilotBtwQuestion(question, scope, send) {
886
997
  const reply = (message) => send({ type: 'btw_answer', ...scope, ...message });
887
998
  try {
@@ -3407,7 +3518,20 @@ function handleCopilotEvent(state, event) {
3407
3518
  const id = data?.messageId || event.id;
3408
3519
  if (typeof id !== 'string' || (!message && !attachments.length))
3409
3520
  break;
3410
- if (state.userRequest?.copilotRequestState === 'pending') {
3521
+ const steering = [...(state.steeringRequests?.values() || [])].find(row => row.copilotUserMessageId === id)
3522
+ || [...(state.steeringRequests?.values() || [])].find(row => !row.copilotUserMessageId
3523
+ && row.content === content && ['steering', 'queued'].includes(data?.delivery));
3524
+ if (steering) {
3525
+ steering.copilotUserMessageId = id;
3526
+ steering.copilotRequestState = 'delivered';
3527
+ try {
3528
+ publishCopilotSteering(state, steering);
3529
+ }
3530
+ catch (error) {
3531
+ console.warn('[Copilot:steering] Could not save delivery:', error);
3532
+ }
3533
+ }
3534
+ if (state.userRequest?.copilotRequestState === 'pending' && data?.delivery !== 'steering' && !steering) {
3411
3535
  state.userRequest.copilotUserMessageId = id;
3412
3536
  settleCopilotUserRequest(state, 'delivered');
3413
3537
  }
@@ -3416,6 +3540,7 @@ function handleCopilotEvent(state, event) {
3416
3540
  ...(state.userRequest?.copilotUserMessageId === id ? {
3417
3541
  copilotRequestId: state.userRequest.copilotRequestId, copilotRequestState: 'delivered',
3418
3542
  } : {}),
3543
+ ...(steering ? { ...steering } : data?.delivery === 'steering' ? { copilotSteering: true } : {}),
3419
3544
  copilotUserMessageId: id, ...(attachments.length ? { attachments } : {}) } });
3420
3545
  break;
3421
3546
  }
@@ -3873,6 +3998,8 @@ function handleCopilotEvent(state, event) {
3873
3998
  // ── Cancel ──────────────────────────────────────────────────────────────────
3874
3999
  export function cancelCopilotExecution(conversationId) {
3875
4000
  const state = getCopilotConversation(conversationId);
4001
+ if (state)
4002
+ settleCopilotSteering(state, 'cancelled');
3876
4003
  if (state)
3877
4004
  settleCopilotUserRequest(state, 'cancelled');
3878
4005
  cancelPendingCopilotDecisions(state?.conversationId || conversationId);
@@ -5584,7 +5711,10 @@ function mergeCopilotRequestHistory(messages, sessionId) {
5584
5711
  else
5585
5712
  requests[index] = owner.userRequest;
5586
5713
  }
5587
- return mergeCopilotUserRequests(messages, requests, owner?.userRequest?.copilotRequestId);
5714
+ const liveIds = [owner?.userRequest?.copilotRequestId,
5715
+ ...[...(owner?.steeringRequests?.values() || [])].map(row => row.copilotRequestId)]
5716
+ .filter((id) => !!id);
5717
+ return mergeCopilotUserRequests(messages, requests, liveIds);
5588
5718
  }
5589
5719
  export function readCopilotSessionMode(sessionId) {
5590
5720
  const sessionRoot = resolve(COPILOT_SESSION_DIR);