콘텐츠로 이동

TTSR 주입 생명주기

이 문서는 규칙 탐색부터 스트림 중단, 재시도 주입, 확장 알림, 세션 상태 처리에 이르는 현재의 Time Traveling Stream Rules (TTSR) 런타임 경로를 다룹니다.

세션 생성 시, createAgentSession()은 탐색된 모든 규칙을 로드하고 TtsrManager를 구성합니다:

const ttsrSettings = settings.getGroup("ttsr");
const ttsrManager = new TtsrManager(ttsrSettings);
const rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });
for (const rule of rulesResult.items) {
if (rule.ttsrTrigger) ttsrManager.addRule(rule);
}

loadCapability("rules")rule.name 기준으로 선착순 의미론(우선순위가 높은 제공자 우선)으로 중복을 제거합니다. 가려진 중복 항목은 TTSR 등록 전에 제거됩니다.

다음의 경우 등록이 건너뛰어집니다:

  • rule.ttsrTrigger가 없는 경우
  • 동일한 rule.name을 가진 규칙이 이 매니저에 이미 등록된 경우
  • 정규식 컴파일이 실패하는 경우 (new RegExp(rule.ttsrTrigger)에서 예외 발생)

유효하지 않은 정규식 트리거는 경고로 기록되고 무시됩니다; 세션 시작은 계속됩니다.

TtsrSettings.enabled는 매니저에 로드되지만 현재 런타임 게이팅에서 확인되지 않습니다. 규칙이 존재하면 매칭이 여전히 실행됩니다.

TTSR 감지는 AgentSession.#handleAgentEvent 내에서 실행됩니다.

turn_start 시, 스트림 버퍼가 초기화됩니다:

  • ttsrManager.resetBuffer()

어시스턴트 업데이트가 도착하고 규칙이 존재하는 경우:

  • text_deltatoolcall_delta를 모니터링
  • 델타를 매니저 버퍼에 추가
  • check(buffer) 호출

check()는 등록된 규칙을 순회하며 반복 정책(#canTrigger)을 통과하는 모든 매칭 규칙을 반환합니다.

3. 트리거 결정 및 즉시 중단 경로

섹션 제목: “3. 트리거 결정 및 즉시 중단 경로”

하나 이상의 규칙이 매칭되면:

  1. markInjected(matches)가 매니저 주입 상태에 규칙 이름을 기록합니다.
  2. 매칭된 규칙이 #pendingTtsrInjections에 대기열로 추가됩니다.
  3. #ttsrAbortPending = true로 설정됩니다.
  4. agent.abort()가 즉시 호출됩니다.
  5. ttsr_triggered 이벤트가 비동기적으로 발행됩니다(fire-and-forget).
  6. 재시도 작업이 setTimeout(..., 50)을 통해 예약됩니다.

중단은 확장 콜백을 기다리지 않습니다.

4. 재시도 스케줄링, 컨텍스트 모드, 리마인더 주입

섹션 제목: “4. 재시도 스케줄링, 컨텍스트 모드, 리마인더 주입”

50ms 타임아웃 이후:

  1. #ttsrAbortPending = false
  2. ttsrManager.getSettings().contextMode 읽기
  3. contextMode === "discard"인 경우, agent.popMessage()로 부분 어시스턴트 출력 삭제
  4. ttsr-interrupt.md 템플릿을 사용하여 대기 중인 규칙으로부터 주입 콘텐츠 생성
  5. 규칙당 하나의 <system-interrupt ...> 블록을 포함하는 합성 사용자 메시지 추가
  6. agent.continue()를 호출하여 생성 재시도

템플릿 페이로드:

<system-interrupt reason="rule_violation" rule="{{name}}" path="{{path}}">
...
{{content}}
</system-interrupt>

대기 중인 주입은 콘텐츠 생성 후 정리됩니다.

부분 출력에 대한 contextMode 동작

섹션 제목: “부분 출력에 대한 contextMode 동작”
  • discard: 부분/중단된 어시스턴트 메시지가 재시도 전에 제거됩니다.
  • keep: 부분 어시스턴트 출력이 대화 상태에 유지됩니다; 리마인더가 그 뒤에 추가됩니다.

TtsrManager#messageCount와 규칙별 lastInjectedAt을 추적합니다.

규칙은 주입 기록이 있으면 한 번만 트리거될 수 있습니다.

규칙은 다음 조건에서만 재트리거될 수 있습니다:

  • messageCount - lastInjectedAt >= repeatGap

messageCountturn_end 시 증가하므로, 간격은 스트림 청크가 아닌 완료된 턴 단위로 측정됩니다.

6. 이벤트 발행 및 확장/훅 인터페이스

섹션 제목: “6. 이벤트 발행 및 확장/훅 인터페이스”

AgentSessionEvent는 다음을 포함합니다:

{ type: "ttsr_triggered"; rules: Rule[] }

#emitSessionEvent()는 이벤트를 다음으로 라우팅합니다:

  • 확장 리스너 (ExtensionRunner.emit({ type: "ttsr_triggered", rules }))
  • 로컬 세션 구독자
  • 확장 API는 on("ttsr_triggered", ...)를 노출합니다
  • 훅 API는 on("ttsr_triggered", ...)를 노출합니다
  • 커스텀 도구는 onSession({ reason: "ttsr_triggered", rules })를 수신합니다

인터랙티브 모드는 TTSR 중단 중에 중단된 어시스턴트 중지 사유를 표시되는 실패로 보여주는 것을 억제하기 위해 session.isTtsrAbortPending을 사용하며, 이벤트가 도착하면 TtsrNotificationComponent를 렌더링합니다.

7. 영속성 및 재개 상태 (현재 구현)

섹션 제목: “7. 영속성 및 재개 상태 (현재 구현)”

SessionManager는 주입된 규칙 영속성에 대한 전체 스키마 지원을 갖추고 있습니다:

  • 항목 타입: ttsr_injection
  • 추가 API: appendTtsrInjection(ruleNames)
  • 조회 API: getInjectedTtsrRules()
  • 컨텍스트 재구성에 SessionContext.injectedTtsrRules 포함

TtsrManagerrestoreInjected(ruleNames)를 통한 복원도 지원합니다.

현재 런타임 경로에서:

  • AgentSession은 TTSR 트리거 시 ttsr_injection 항목을 추가하지 않습니다.
  • createAgentSession()existingSession.injectedTtsrRulesttsrManager에 다시 복원하지 않습니다.

실질적 효과: 주입된 규칙 억제는 실행 중인 프로세스의 인메모리에서만 적용되며, 현재 이 경로를 통한 세션 리로드/재개 시 영속성/복원이 이루어지지 않습니다.

  • 중단은 TTSR 핸들러 관점에서 동기적입니다 (agent.abort()가 즉시 호출됨)
  • 재시도는 타이머로 지연됩니다 (50ms)
  • 확장 알림은 비동기적이며 의도적으로 중단/재시도 스케줄링 전에 대기하지 않습니다

동일 스트림 윈도우 내 다중 매칭

섹션 제목: “동일 스트림 윈도우 내 다중 매칭”

check()는 현재 매칭되는 모든 적격 규칙을 반환합니다. 이들은 다음 재시도 메시지에서 일괄 주입됩니다.

타이머 윈도우 동안 상태가 변경될 수 있습니다(사용자 중단, 모드 액션, 추가 이벤트). 재시도 호출은 최선 노력 방식입니다: agent.continue().catch(() => {})가 후속 오류를 흡수합니다.

  • 유효하지 않은 ttsr_trigger 정규식: 경고와 함께 건너뛰어짐; 다른 규칙은 계속됩니다.
  • 능력 계층에서의 중복 규칙 이름: 우선순위가 낮은 중복 항목은 등록 전에 가려집니다.
  • 매니저 계층에서의 중복 이름: 두 번째 등록은 무시됩니다.
  • contextMode: "keep": 위반하는 부분 출력이 리마인더 재시도 전에 컨텍스트에 남을 수 있습니다.
  • after-gap 반복은 turn_end 시의 턴 카운트 증가에 의존합니다; 턴 중간 청크는 간격 카운터를 진행시키지 않습니다.