@ironbee-ai/cli 0.29.0 → 0.31.0
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 +12 -0
- package/dist/analytics/claude/emit.js +1 -1
- package/dist/analytics/claude/state.js +1 -1
- package/dist/analytics/codex/events-emit.js +2 -2
- package/dist/analytics/codex/subagent-transcripts.js +3 -3
- package/dist/clients/claude/agents/ironbee-scenario.md +191 -0
- package/dist/clients/claude/agents/ironbee-verifier.md +22 -5
- package/dist/clients/claude/commands/ironbee-manage-scenario.md +36 -0
- package/dist/clients/claude/commands/ironbee-search-scenario.md +22 -0
- package/dist/clients/claude/commands/ironbee-sync-scenario.md +31 -0
- package/dist/clients/claude/commands/ironbee-verify.md +13 -12
- package/dist/clients/claude/hooks/activity-end.js +1 -1
- package/dist/clients/claude/hooks/activity-start.js +1 -1
- package/dist/clients/claude/hooks/clear-verdict.js +1 -1
- package/dist/clients/claude/hooks/require-verdict.js +2 -2
- package/dist/clients/claude/hooks/require-verification.js +3 -3
- package/dist/clients/claude/hooks/session-end.js +1 -1
- package/dist/clients/claude/hooks/session-start.js +4 -4
- package/dist/clients/claude/hooks/session-status.js +2 -2
- package/dist/clients/claude/hooks/subagent-start.js +1 -1
- package/dist/clients/claude/hooks/subagent-stop.js +1 -1
- package/dist/clients/claude/hooks/track-action-monitor.js +1 -1
- package/dist/clients/claude/hooks/track-action.js +1 -1
- package/dist/clients/claude/hooks/verify-gate.js +4 -4
- package/dist/clients/claude/index.js +4 -4
- package/dist/clients/claude/platforms/scenario.android.md +32 -0
- package/dist/clients/claude/platforms/scenario.backend.md +26 -0
- package/dist/clients/claude/platforms/scenario.browser.md +41 -0
- package/dist/clients/claude/platforms/scenario.node.md +27 -0
- package/dist/clients/claude/platforms/skill.android.md +4 -0
- package/dist/clients/claude/process-analytics.js +1 -1
- package/dist/clients/claude/statusline-toggle.js +2 -2
- package/dist/clients/claude/trust.js +1 -0
- package/dist/clients/codex/agents/ironbee-scenario.md +179 -0
- package/dist/clients/codex/agents/ironbee-verifier.md +22 -5
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.main.md +102 -0
- package/dist/clients/codex/commands/ironbee-manage-scenario/SKILL.md +38 -0
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.main.md +37 -0
- package/dist/clients/codex/commands/ironbee-search-scenario/SKILL.md +23 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.main.md +55 -0
- package/dist/clients/codex/commands/ironbee-sync-scenario/SKILL.md +33 -0
- package/dist/clients/codex/commands/ironbee-verify/SKILL.main.md +12 -3
- package/dist/clients/codex/commands/ironbee-verify/SKILL.md +4 -3
- package/dist/clients/codex/hooks/activity-end.js +1 -1
- package/dist/clients/codex/hooks/activity-start.js +1 -1
- package/dist/clients/codex/hooks/clear-verdict.js +3 -3
- package/dist/clients/codex/hooks/require-verdict.js +2 -2
- package/dist/clients/codex/hooks/require-verification.js +3 -3
- package/dist/clients/codex/hooks/session-start.js +3 -3
- package/dist/clients/codex/hooks/subagent-start.js +1 -1
- package/dist/clients/codex/hooks/subagent-stop.js +1 -1
- package/dist/clients/codex/hooks/track-action-monitor.js +1 -1
- package/dist/clients/codex/hooks/track-action-pre.js +1 -1
- package/dist/clients/codex/hooks/track-action.js +1 -1
- package/dist/clients/codex/hooks/verify-gate.js +1 -1
- package/dist/clients/codex/index.js +2 -2
- package/dist/clients/codex/platforms/command-verify.android.md +1 -0
- package/dist/clients/codex/platforms/rule.android.md +2 -1
- package/dist/clients/codex/platforms/scenario.android.md +32 -0
- package/dist/clients/codex/platforms/scenario.backend.md +26 -0
- package/dist/clients/codex/platforms/scenario.browser.md +40 -0
- package/dist/clients/codex/platforms/scenario.node.md +27 -0
- package/dist/clients/codex/platforms/skill.android.md +4 -0
- package/dist/clients/codex/process-analytics.js +2 -2
- package/dist/clients/codex/thread-map.js +1 -1
- package/dist/clients/codex/util.js +44 -31
- package/dist/clients/cursor/commands/ironbee-manage-scenario/SKILL.md +100 -0
- package/dist/clients/cursor/commands/ironbee-search-scenario/SKILL.md +34 -0
- package/dist/clients/cursor/commands/ironbee-sync-scenario/SKILL.md +54 -0
- package/dist/clients/cursor/commands/ironbee-verify/SKILL.md +2 -1
- package/dist/clients/cursor/hooks/activity-end.js +1 -1
- package/dist/clients/cursor/hooks/activity-start.js +1 -1
- package/dist/clients/cursor/hooks/clear-verdict.js +1 -1
- package/dist/clients/cursor/hooks/require-verdict.js +2 -2
- package/dist/clients/cursor/hooks/require-verification.js +3 -3
- package/dist/clients/cursor/hooks/session-end.js +1 -1
- package/dist/clients/cursor/hooks/session-start.js +4 -4
- package/dist/clients/cursor/hooks/track-action-monitor.js +1 -1
- package/dist/clients/cursor/hooks/track-action.js +1 -1
- package/dist/clients/cursor/hooks/verify-gate.js +1 -1
- package/dist/clients/cursor/index.js +1 -1
- package/dist/clients/cursor/platforms/command-verify.android.md +1 -0
- package/dist/clients/cursor/platforms/rule.android.md +2 -1
- package/dist/clients/cursor/platforms/scenario.android.md +32 -0
- package/dist/clients/cursor/platforms/scenario.backend.md +26 -0
- package/dist/clients/cursor/platforms/scenario.browser.md +40 -0
- package/dist/clients/cursor/platforms/scenario.node.md +27 -0
- package/dist/clients/cursor/platforms/skill.android.md +4 -0
- package/dist/commands/config.js +1 -1
- package/dist/commands/hook.js +10 -10
- package/dist/commands/import.js +3 -3
- package/dist/commands/process-job-file.js +1 -1
- package/dist/commands/queue.js +16 -16
- package/dist/commands/scenario.js +1 -0
- package/dist/commands/status.js +1 -1
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/verify.js +2 -2
- package/dist/hooks/core/actions.js +7 -7
- package/dist/hooks/core/nested-tools.js +1 -1
- package/dist/hooks/core/scenario-tools.js +1 -0
- package/dist/hooks/core/session-state.js +1 -1
- package/dist/hooks/core/verification-context.js +8 -8
- package/dist/import/marker.js +2 -2
- package/dist/import/skip.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/git.js +1 -1
- package/dist/lib/install-version.js +1 -1
- package/dist/lib/platform-section.js +3 -3
- package/dist/lib/runtime-paths.js +1 -0
- package/dist/lib/scenario-staleness.js +1 -0
- package/dist/otel/claude/daemon/process.js +1 -1
- package/dist/otel/claude/daemon/reprocess.js +1 -1
- package/dist/otel/claude/daemon/response-usage.js +2 -2
- package/dist/queue/drain.js +1 -1
- package/dist/queue/flush.js +1 -1
- package/dist/queue/paths.js +1 -1
- package/dist/queue/process-file.js +2 -2
- package/dist/queue/spawn.js +1 -1
- package/dist/tui/config/schema.js +1 -1
- package/dist/tui/queue/read.js +4 -4
- package/dist/tui/scenarios/area.js +2 -0
- package/dist/tui/sessions/read.js +2 -2
- package/dist/tui/shell/registry.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.31.0 (2026-06-27)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
* **android:** support devtools 0.14.0 tools — add network-evidence path ([e7a14ee](https://github.com/ironbee-ai/ironbee-cli/commit/e7a14eef2fb9ea53b585cd134f2ed9221aec469a))
|
|
8
|
+
|
|
9
|
+
## 0.30.0 (2026-06-26)
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **scenario:** add manage/search scenario slash commands across all clients and support verifying over saved scenarios ([#33](https://github.com/ironbee-ai/ironbee-cli/issues/33)) ([0636ce6](https://github.com/ironbee-ai/ironbee-cli/commit/0636ce613cce2f5637e4b3de0af539b183c64010))
|
|
14
|
+
|
|
3
15
|
## 0.29.0 (2026-06-22)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var ne=Object.defineProperty;var je=Object.getOwnPropertyDescriptor;var ze=Object.getOwnPropertyNames;var Le=Object.prototype.hasOwnProperty;var D=(c,t)=>ne(c,"name",{value:t,configurable:!0});var Oe=(c,t)=>{for(var o in t)ne(c,o,{get:t[o],enumerable:!0})},Ce=(c,t,o,x)=>{if(t&&typeof t=="object"||typeof t=="function")for(let y of ze(t))!Le.call(c,y)&&y!==o&&ne(c,y,{get:()=>t[y],enumerable:!(x=je(t,y))||x.enumerable});return c};var Ne=c=>Ce(ne({},"__esModule",{value:!0}),c);var Be={};Oe(Be,{__test:()=>Me,emitAnalytics:()=>Fe});module.exports=Ne(Be);var he=require("path"),j=require("../../lib/logger"),z=require("../../hooks/core/actions"),ue=require("../shared/types"),E=require("./transcript"),I=require("./state"),ye=require("./merge"),$e=require("./subagent-fold"),Se=require("./otel-fold"),R=require("./projection"),F=require("../../lib/collector"),B=require("../../lib/config");async function Fe(c){try{return await Ue(c)}catch(t){return j.logger.debug(`analytics emit: unexpected error: ${t instanceof Error?t.message:t}`),{status:"error",reason:t instanceof Error?t.message:String(t)}}}D(Fe,"emitAnalytics");async function Ue(c){const{projectDir:t,sessionId:o,triggerType:x,endReason:y,log:i}=c,d=x==="SessionEnd",g=c.transcriptSource??"claude-code",A=(0,z.resolveProjectName)(t),v=(0,B.isAnalyticsTurnEventsEnabled)(t),T=(0,B.isAnalyticsStepEventsEnabled)(t),u=(0,B.isAnalyticsApiRequestEventsEnabled)(t);i?.debug(`gating: emitTurnEvents=${v} emitStepEvents=${T} emitApiRequestEvents=${u}`);const m=g==="claude-code"?(0,E.findClaudeTranscriptPath)(t,o):null;if(m===null)return j.logger.debug(`analytics emit: transcript not found for session ${o}`),i?.warn(`transcript: not found (source=${g}) \u2014 skipping`),{status:"no-transcript",reason:"transcript path unresolved or missing"};i?.info(`transcript: path=${m} source=${g} project=${A}`);const _=(0,I.readState)(t,o);let n=_??(0,I.initialState)(o,m,A,g);_?i?.info(`state: loaded offset=${_.offset} last_emitted_offset=${_.last_emitted_offset} last_emitted_is_final=${_.last_emitted_is_final} schema=${_.accumulated.schema_version} user_count=${_.accumulated.turns.user_count} duration_min=${_.accumulated.time.duration_minutes}`):i?.info("state: fresh init (no existing state.json)");let l;try{l=(0,E.statTranscript)(m)}catch(e){return j.logger.debug(`analytics emit: stat failed for ${m}: ${e instanceof Error?e.message:e}`),i?.error(`transcript: stat failed: ${e instanceof Error?e.message:e}`),{status:"no-transcript",reason:"stat failed (file missing or unreadable)"}}const O=Math.min(E.FIRST_KB_HASH_LENGTH,n.transcript_size_at_last_read),ce=O>0?(0,E.firstKbSha256)(m,O):"",V=(0,E.firstKbSha256)(m);i?.debug(`transcript: stat inode=${l.inode} size=${l.size} first_kb_hash=${V.slice(0,12)}\u2026`);let C=null;n.accumulated.schema_version!==ue.SCHEMA_VERSION?C="schema_version_changed":l.inode!==n.transcript_inode&&n.transcript_inode!==0?C="inode_changed":l.size<n.offset?C="size_shrunk":ce!==""&&n.transcript_first_kb_sha256!==""&&ce!==n.transcript_first_kb_sha256&&(C="content_replaced"),C!==null&&(j.logger.debug(`analytics emit: reset state for session ${o}: ${C}`),i?.warn(`reset: ${C} \u2014 discarding accumulated state and re-parsing from offset 0`),n=(0,I.initialState)(o,m,A,g),l=(0,E.statTranscript)(m));let q=!1,le=[],r=n.accumulated,s=n.internal,H=l.size,Y=n.offset;if(l.size===n.offset){if(!d)return i?.debug(`carve-out: no new bytes (offset=${n.offset}) and trigger=Stop \u2014 skipping`),{status:"skipped",reason:"no new bytes"};if(n.last_emitted_is_final)return i?.debug("carve-out: session already finalized \u2014 skipping"),{status:"skipped",reason:"session already finalized"};q=!0,H=n.offset,i?.info(`carve-out: SessionEnd with no new bytes (offset=${n.offset}) \u2014 emitting is_final on existing accumulated`)}else{const e=(0,E.readSlice)(m,n.offset,l.size);if(e.truncated)return j.logger.debug(`analytics emit: file truncated mid-read (session ${o}); deferring to next trigger`),i?.warn(`read: file truncated mid-read (offset=${n.offset}, expected_end=${l.size}) \u2014 deferring`),{status:"skipped",reason:"truncated mid-read; eventual consistency"};H=l.size,i?.debug(`read: bytes=${e.bytes.length} (offset ${n.offset} \u2192 ${l.size})`);const a=(0,E.parseJsonl)(e.bytes,n.offset);Y=(0,E.findTurnSafeBoundary)(a.lines,a.last_complete_byte,d),i?.debug(`parse: lines=${a.lines.length} last_complete_byte=${a.last_complete_byte} boundary_offset=${Y} is_final=${d}`),le=a.lines.filter(b=>b.parsed!==null&&b.end<=Y);const k=le.filter(b=>b.parsed!==null).map(b=>b.parsed),$=n.accumulated.last_activity_time!==""?Date.parse(n.accumulated.last_activity_time):Number.NaN,S=Number.isFinite($)?$:void 0,L={lines:k,startingTurnIndex:n.accumulated.turns.assistant_count+1,sessionId:o,projectName:A,transcriptSource:g,priorLastAssistantTsMs:n.internal.last_assistant_ts_ms,priorPendingToolUses:n.internal.pending_tool_uses,priorLastActivityTsMs:S,priorCurrentTurn:n.internal.current_turn,priorNextTurnIndex:n.internal.next_turn_index,priorSeenAssistantMessageIds:n.internal.seen_assistant_message_ids,priorSeenToolUseIds:n.internal.seen_tool_use_ids},p=(0,R.projectDelta)(L),K=(0,R.projectDeltaInternal)(L);!v&&p.completed_turns.length>0&&(i?.debug(`gating: emitTurnEvents=false \u2192 dropping ${p.completed_turns.length} completed turn(s) from delta`),p.completed_turns=[]),!T&&p.completed_steps.length>0&&(i?.debug(`gating: emitStepEvents=false \u2192 dropping ${p.completed_steps.length} completed step(s) from delta`),p.completed_steps=[]),!u&&p.api_request_events.length>0&&(i?.debug(`gating: emitApiRequestEvents=false \u2192 dropping ${p.api_request_events.length} api_request event(s) from delta`),p.api_request_events=[]),i?.debug(`delta: user_count+=${p.turns.user_count} assistant_msgs+=${p.turns.assistant_count} tool_uses=${Object.values(p.tools).reduce((b,G)=>b+G.count,0)} files_modified+=${p.code_changes.files_modified} lines_added+=${p.code_changes.lines_added} lines_removed+=${p.code_changes.lines_removed} errors+=${p.errors.tool_errors_total} interruptions+=${p.errors.user_interruptions} pending_tool_uses=${Object.keys(p.closing_pending_tool_uses).length}`);const M=(0,ye.mergeAccumulated)(n.accumulated,n.internal,p,K);r=M.accumulated,s=M.internal;{const b=(0,$e.foldSubAgents)({merged:r,internal:s,mainLines:k,sessionId:o,projectName:A,transcriptPath:m,transcriptSource:g,apiRequestEventsEnabled:u});r=b.merged,s=b.internal,b.foldedCount>0&&i?.debug(`subagent-fold: folded ${b.foldedCount} sub-agent(s) into session aggregates`)}const J=Object.values(r.tools).reduce((b,G)=>b+G.count,0);i?.debug(`merged: user_count=${r.turns.user_count} duration_min=${r.time.duration_minutes.toFixed(2)} tool_calls=${J} files_modified=${r.code_changes.files_modified} lines_added=${r.code_changes.lines_added} lines_removed=${r.code_changes.lines_removed} ctx_latest=${r.context_tokens.latest} ctx_peak=${r.context_tokens.peak}`)}{const e=(0,he.join)(t,".ironbee","sessions",o,"analytics","otel-requests.jsonl"),a=(0,Se.foldOtelRequests)({merged:r,internal:s,sidecarPath:e,sessionId:o,apiRequestEventsEnabled:u});r=a.merged,s=a.internal,a.foldedCount>0&&i?.debug(`otel-fold: folded ${a.foldedCount} non-transcript feature call(s) into session aggregates`)}const N=(0,F.getCollectorTarget)(t),w=q?n.offset:Y,ve=w===n.last_emitted_offset,Te=d&&!n.last_emitted_is_final;if(ve&&!Te){if(s.current_turn!==void 0&&s.current_turn.assistant_messages>0){const h=s.current_turn,xe=h.last_activity_time!==""?h.last_activity_time:r.last_activity_time,de=d?"session_end":"stop",P=(0,R.closeTurn)(h,{endTime:xe,endReason:de,sessionId:o,projectName:A,transcriptSource:g});(0,R.applyBreakdownDelta)(r.classification.category_breakdown,P),r.turns.with_retry+=P.turns_with_retry_inc,r.turns.one_shot+=P.one_shot_inc;const _e=v?[...s.pending_turn_events??[],P.turn_event]:s.pending_turn_events??[],pe=T&&P.step_events.length>0?[...s.pending_step_events??[],...P.step_events]:s.pending_step_events??[];s={...s,..._e.length>0?{pending_turn_events:_e}:{},...pe.length>0?{pending_step_events:pe}:{}},delete s.current_turn,i?.debug(`force-close (idempotent path): turn_index=${P.turn_event.turn_index} turn_id=${P.turn_event.turn_id} end_reason=${de} category=${P.turn_event.category} duration=${P.turn_event.duration}ms step_events=${P.step_events.length} gates: turn=${v} step=${T}`)}else if(s.current_turn!==void 0){const h=s.current_turn;i?.debug(`force-close (idempotent path): skipping empty turn turn_index=${h.turn_index} (assistant_messages=0)`),r.turns.user_count>0&&(r.turns.user_count-=1),s={...s},delete s.current_turn}const e=s.pending_turn_events??[],a=s.pending_step_events??[],k=s.pending_api_request_events??[];if(e.length===0&&a.length===0&&k.length===0){const h={...n,offset:w,accumulated:r,internal:s,last_read_at:new Date().toISOString(),transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:V};return(0,I.writeState)(t,o,h),i?.info(`skip: idempotent \u2014 offset=${w} matches last_emitted_offset and is_final unchanged`),{status:"skipped",reason:"offset unchanged and final-flag unchanged"}}i?.info(`idempotent-with-drain: offset=${w} pending_turn_events=${e.length} pending_step_events=${a.length} pending_api_request_events=${k.length} \u2014 attempting drain`);const $=(0,z.baseFields)(`${t}/.ironbee/sessions/${o}/actions.jsonl`);let S=s,L=0,p=0,K=0,M=e.length,J=a.length,b=k.length;if(e.length>0)if(v){const h=await me(e,$,o,t,i,"drain submit",N);S=ie(S,h.remaining),L=h.sent,M=h.remaining.length}else i?.info(`gating: emitTurnEvents=false \u2192 dropping ${e.length} stale pending turn event(s) without sending`),S=ie(S,[]),M=0;if(a.length>0)if(T){const h=await fe(a,$,o,t,i,"drain submit",N);S=re(S,h.remaining),p=h.sent,J=h.remaining.length}else i?.info(`gating: emitStepEvents=false \u2192 dropping ${a.length} stale pending step event(s) without sending`),S=re(S,[]),J=0;if(k.length>0)if(u){const h=await be(k,$,o,t,i,"drain submit",N);S=ae(S,h.remaining),K=h.sent,b=h.remaining.length}else i?.info(`gating: emitApiRequestEvents=false \u2192 dropping ${k.length} stale pending api_request event(s) without sending`),S=ae(S,[]),b=0;const G={...n,offset:w,accumulated:r,internal:S,last_read_at:new Date().toISOString(),transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:V};(0,I.writeState)(t,o,G),i?.info(`idempotent-with-drain: turn_sent=${L}/${e.length} retained=${M} step_sent=${p}/${a.length} retained=${J} api_request_sent=${K}/${k.length} retained=${b}`);const te=["offset unchanged"];return e.length>0&&te.push(`drained ${L}/${e.length} pending turn events`),a.length>0&&te.push(`drained ${p}/${a.length} pending step events`),k.length>0&&te.push(`drained ${K}/${k.length} pending api_request events`),{status:"skipped",reason:te.join("; ")}}if(s.current_turn!==void 0&&s.current_turn.assistant_messages>0){const e=s.current_turn,a=e.last_activity_time!==""?e.last_activity_time:r.last_activity_time,k=d?"session_end":"stop",$=(0,R.closeTurn)(e,{endTime:a,endReason:k,sessionId:o,projectName:A,transcriptSource:g});(0,R.applyBreakdownDelta)(r.classification.category_breakdown,$),r.turns.with_retry+=$.turns_with_retry_inc,r.turns.one_shot+=$.one_shot_inc;const S=v?[...s.pending_turn_events??[],$.turn_event]:s.pending_turn_events??[],L=T&&$.step_events.length>0?[...s.pending_step_events??[],...$.step_events]:s.pending_step_events??[];s={...s,...S.length>0?{pending_turn_events:S}:{},...L.length>0?{pending_step_events:L}:{}},delete s.current_turn,i?.debug(`force-close: turn_index=${$.turn_event.turn_index} turn_id=${$.turn_event.turn_id} end_reason=${k} category=${$.turn_event.category} duration=${$.turn_event.duration}ms step_events=${$.step_events.length} gates: turn=${v} step=${T}`)}else if(s.current_turn!==void 0){const e=s.current_turn;i?.debug(`force-close: skipping empty turn turn_index=${e.turn_index} (assistant_messages=0) \u2014 no event emitted`),r.turns.user_count>0&&(r.turns.user_count-=1),s={...s},delete s.current_turn}const Ae=`${t}/.ironbee/sessions/${o}/actions.jsonl`,U=(0,z.baseFields)(Ae),f={session_id:r.session_id,project_name:r.project_name,...U.user_email!==void 0?{user_email:U.user_email}:{},schema_version:ue.SCHEMA_VERSION,transcript_source:r.transcript_source,is_final:d,snapshot_at:new Date().toISOString(),offset:w,end_reason:d?y:void 0,start_time:r.start_time,last_activity_time:r.last_activity_time,time:r.time,turns:r.turns,classification:r.classification,usage:r.usage,models:r.models,user_messages:r.user_messages,tools:r.tools,mcp_servers:r.mcp_servers,skills:r.skills,sub_agents:r.sub_agents,bash_binaries:r.bash_binaries,tool_meta:r.tool_meta,code_changes:r.code_changes,errors:r.errors,user_activity:r.user_activity,context_tokens:r.context_tokens,process_errors:r.process_errors},Z={...U,id:(0,R.deriveSessionAnalyticsEventId)(r.session_id),type:z.EventType.SESSION_ANALYTICS,timestamp:Date.now(),analytics:f},Ee=Buffer.byteLength(JSON.stringify(Z),"utf-8"),Re=Object.values(f.tools).reduce((e,a)=>e+a.count,0),ke=Object.entries(f.models).sort((e,a)=>a[1].count-e[1].count).map(([e,a])=>`${e}:${a.count}@$${a.cost_usd.toFixed(4)}`).join(",")||"none",we=Object.entries(f.tools).sort((e,a)=>a[1].output_size-e[1].output_size).slice(0,5).map(([e,a])=>`${e}:${a.count}/err${a.errors}/in:${a.input_size}/out:${a.output_size}`).join(",")||"none",qe=Object.entries(f.mcp_servers).sort((e,a)=>a[1].output_size-e[1].output_size).map(([e,a])=>`${e}:${a.count}/err${a.errors}/in:${a.input_size}/out:${a.output_size}`).join(",")||"none",Pe=Object.values(f.process_errors.items).reduce((e,a)=>e+a.count,0),De=Object.keys(f.process_errors.items).length,Ie=Object.entries(f.classification.category_breakdown).sort((e,a)=>a[1].turns-e[1].turns).slice(0,5).map(([e,a])=>`${e}:${a.turns}`).join(",")||"none";i?.info(`submit: type=session_analytics event_id=${Z.id} offset=${w} is_final=${d}${y?` end_reason=${y}`:""} wire_bytes=${Ee} user_count=${f.turns.user_count} duration_min=${f.time.duration_minutes} active_min=${f.time.active_minutes} idle_min=${f.time.idle_minutes} tool_calls=${Re} session_type=${f.classification.session_type} categories=${Ie} retries=${f.turns.with_retry} one_shot=${f.turns.one_shot} tokens=in:${f.usage.input_tokens}/out:${f.usage.output_tokens}/cache_w:${f.usage.cache_creation_tokens}/cache_r:${f.usage.cache_read_tokens} cost_usd=${f.usage.cost_usd.toFixed(4)} models=${ke} mcp_servers=${qe} top_tools=${we}`+(f.process_errors.has?` process_errors=${Pe}/${De}`:""));let Q=!1;try{await(0,F.sendEventsBatchToCollector)([Z],o,t,N),Q=!0}catch(e){j.logger.debug(`analytics emit: send failed: ${e instanceof Error?e.message:e}`),i?.error(`submit: failed event_id=${Z.id}: ${e instanceof Error?e.message:e}`)}const W=s.pending_turn_events??[],X=s.pending_step_events??[];if(Q&&W.length>0)if(v){const e=await me(W,U,o,t,i,"submit",N);i?.info(`turn-events: sent=${e.sent}/${W.length} retained=${e.remaining.length}`),s=ie(s,e.remaining)}else i?.info(`gating: emitTurnEvents=false \u2192 dropping ${W.length} stale pending turn event(s) without sending`),s=ie(s,[]);if(Q&&X.length>0)if(T){const e=await fe(X,U,o,t,i,"submit",N);i?.info(`step-events: sent=${e.sent}/${X.length} retained=${e.remaining.length}`),s=re(s,e.remaining)}else i?.info(`gating: emitStepEvents=false \u2192 dropping ${X.length} stale pending step event(s) without sending`),s=re(s,[]);const ee=s.pending_api_request_events??[];if(Q&&ee.length>0)if(u){const e=await be(ee,U,o,t,i,"submit",N);i?.info(`api-request-events: sent=${e.sent}/${ee.length} retained=${e.remaining.length}`),s=ae(s,e.remaining)}else i?.info(`gating: emitApiRequestEvents=false \u2192 dropping ${ee.length} stale pending api_request event(s) without sending`),s=ae(s,[]);if(Q){const e={session_id:o,transcript_path:m,transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:V,offset:w,last_read_at:new Date().toISOString(),last_emitted_offset:w,last_emitted_is_final:d,accumulated:r,internal:s};return(0,I.writeState)(t,o,e),i?.info(`state persisted: offset=${w} last_emitted_offset=${w} last_emitted_is_final=${d}`),{status:"emitted",reason:d?"final snapshot":"interim snapshot",offset:w}}return{status:"error",reason:"submit failed; state not advanced"}}D(Ue,"runEmit");const se=100;function ge(c){if(typeof c!="string"||c.length===0)return Date.now();const t=Date.parse(c);return Number.isFinite(t)?t:Date.now()}D(ge,"parseIsoOrNow");function oe(c){return typeof c.batchSize=="number"&&c.batchSize>=1?Math.floor(c.batchSize):se}D(oe,"resolveBatchSize");async function me(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:(0,R.deriveTurnEventId)(o,u.turn_id),type:z.EventType.SESSION_TURN,timestamp:ge(u.end_time),turn:u})),A=d!==null?oe(d):se,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=session_turn_analytics batch_count=${_.length} first_turn_index=${n[0].turn_index}`)}catch(l){for(const O of n)v.push(O);j.logger.debug(`analytics emit: turn batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=session_turn_analytics batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(me,"drainPendingTurns");function ie(c,t){if(t.length>0)return{...c,pending_turn_events:t};const o={...c};return delete o.pending_turn_events,o}D(ie,"withPendingTurnEvents");async function fe(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:(0,R.deriveStepEventId)(o,u.step_id),type:z.EventType.SESSION_TURN_STEP,timestamp:ge(u.end_time),step:u})),A=d!==null?oe(d):se,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=session_turn_step_analytics batch_count=${_.length} first=(turn_index=${n[0].turn_index},step_index=${n[0].step_index})`)}catch(l){for(const O of n)v.push(O);j.logger.debug(`analytics emit: step batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=session_turn_step_analytics batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(fe,"drainPendingSteps");function re(c,t){if(t.length>0)return{...c,pending_step_events:t};const o={...c};return delete o.pending_step_events,o}D(re,"withPendingStepEvents");async function be(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:u.id,type:z.EventType.API_REQUEST,timestamp:u.timestamp_ms,request_id:u.request_id,success:u.success,error:u.error,status_code:u.status_code,model:u.model,speed:u.speed,input_tokens:u.input_tokens,output_tokens:u.output_tokens,cache_read_tokens:u.cache_read_tokens,cache_creation_tokens:u.cache_creation_tokens,cost_usd:u.cost_usd,duration:u.duration})),A=d!==null?oe(d):se,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=api_request batch_count=${_.length} first_request_id=${n[0].request_id??"<null>"} success_count=${n.filter(l=>l.success).length}`)}catch(l){for(const O of n)v.push(O);j.logger.debug(`analytics emit: api_request batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=api_request batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(be,"drainPendingApiRequests");function ae(c,t){if(t.length>0)return{...c,pending_api_request_events:t};const o={...c};return delete o.pending_api_request_events,o}D(ae,"withPendingApiRequestEvents");const Me={emptyAccumulated:I.emptyAccumulated};0&&(module.exports={__test,emitAnalytics});
|
|
1
|
+
"use strict";var se=Object.defineProperty;var je=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Oe=Object.prototype.hasOwnProperty;var D=(c,t)=>se(c,"name",{value:t,configurable:!0});var Ce=(c,t)=>{for(var o in t)se(c,o,{get:t[o],enumerable:!0})},Ne=(c,t,o,x)=>{if(t&&typeof t=="object"||typeof t=="function")for(let y of Le(t))!Oe.call(c,y)&&y!==o&&se(c,y,{get:()=>t[y],enumerable:!(x=je(t,y))||x.enumerable});return c};var Fe=c=>Ne(se({},"__esModule",{value:!0}),c);var He={};Ce(He,{__test:()=>Be,emitAnalytics:()=>Ue});module.exports=Fe(He);var ye=require("path"),z=require("../../lib/logger"),j=require("../../hooks/core/actions"),V=require("../../lib/runtime-paths"),ce=require("../shared/types"),E=require("./transcript"),I=require("./state"),$e=require("./merge"),Se=require("./subagent-fold"),ve=require("./otel-fold"),R=require("./projection"),F=require("../../lib/collector"),B=require("../../lib/config");async function Ue(c){try{return await Me(c)}catch(t){return z.logger.debug(`analytics emit: unexpected error: ${t instanceof Error?t.message:t}`),{status:"error",reason:t instanceof Error?t.message:String(t)}}}D(Ue,"emitAnalytics");async function Me(c){const{projectDir:t,sessionId:o,triggerType:x,endReason:y,log:i}=c,d=x==="SessionEnd",g=c.transcriptSource??"claude-code",A=(0,j.resolveProjectName)(t),v=(0,B.isAnalyticsTurnEventsEnabled)(t),T=(0,B.isAnalyticsStepEventsEnabled)(t),u=(0,B.isAnalyticsApiRequestEventsEnabled)(t);i?.debug(`gating: emitTurnEvents=${v} emitStepEvents=${T} emitApiRequestEvents=${u}`);const m=g==="claude-code"?(0,E.findClaudeTranscriptPath)(t,o):null;if(m===null)return z.logger.debug(`analytics emit: transcript not found for session ${o}`),i?.warn(`transcript: not found (source=${g}) \u2014 skipping`),{status:"no-transcript",reason:"transcript path unresolved or missing"};i?.info(`transcript: path=${m} source=${g} project=${A}`);const _=(0,I.readState)(t,o);let n=_??(0,I.initialState)(o,m,A,g);_?i?.info(`state: loaded offset=${_.offset} last_emitted_offset=${_.last_emitted_offset} last_emitted_is_final=${_.last_emitted_is_final} schema=${_.accumulated.schema_version} user_count=${_.accumulated.turns.user_count} duration_min=${_.accumulated.time.duration_minutes}`):i?.info("state: fresh init (no existing state.json)");let l;try{l=(0,E.statTranscript)(m)}catch(e){return z.logger.debug(`analytics emit: stat failed for ${m}: ${e instanceof Error?e.message:e}`),i?.error(`transcript: stat failed: ${e instanceof Error?e.message:e}`),{status:"no-transcript",reason:"stat failed (file missing or unreadable)"}}const O=Math.min(E.FIRST_KB_HASH_LENGTH,n.transcript_size_at_last_read),le=O>0?(0,E.firstKbSha256)(m,O):"",Y=(0,E.firstKbSha256)(m);i?.debug(`transcript: stat inode=${l.inode} size=${l.size} first_kb_hash=${Y.slice(0,12)}\u2026`);let C=null;n.accumulated.schema_version!==ce.SCHEMA_VERSION?C="schema_version_changed":l.inode!==n.transcript_inode&&n.transcript_inode!==0?C="inode_changed":l.size<n.offset?C="size_shrunk":le!==""&&n.transcript_first_kb_sha256!==""&&le!==n.transcript_first_kb_sha256&&(C="content_replaced"),C!==null&&(z.logger.debug(`analytics emit: reset state for session ${o}: ${C}`),i?.warn(`reset: ${C} \u2014 discarding accumulated state and re-parsing from offset 0`),n=(0,I.initialState)(o,m,A,g),l=(0,E.statTranscript)(m));let q=!1,de=[],r=n.accumulated,s=n.internal,H=l.size,Z=n.offset;if(l.size===n.offset){if(!d)return i?.debug(`carve-out: no new bytes (offset=${n.offset}) and trigger=Stop \u2014 skipping`),{status:"skipped",reason:"no new bytes"};if(n.last_emitted_is_final)return i?.debug("carve-out: session already finalized \u2014 skipping"),{status:"skipped",reason:"session already finalized"};q=!0,H=n.offset,i?.info(`carve-out: SessionEnd with no new bytes (offset=${n.offset}) \u2014 emitting is_final on existing accumulated`)}else{const e=(0,E.readSlice)(m,n.offset,l.size);if(e.truncated)return z.logger.debug(`analytics emit: file truncated mid-read (session ${o}); deferring to next trigger`),i?.warn(`read: file truncated mid-read (offset=${n.offset}, expected_end=${l.size}) \u2014 deferring`),{status:"skipped",reason:"truncated mid-read; eventual consistency"};H=l.size,i?.debug(`read: bytes=${e.bytes.length} (offset ${n.offset} \u2192 ${l.size})`);const a=(0,E.parseJsonl)(e.bytes,n.offset);Z=(0,E.findTurnSafeBoundary)(a.lines,a.last_complete_byte,d),i?.debug(`parse: lines=${a.lines.length} last_complete_byte=${a.last_complete_byte} boundary_offset=${Z} is_final=${d}`),de=a.lines.filter(h=>h.parsed!==null&&h.end<=Z);const k=de.filter(h=>h.parsed!==null).map(h=>h.parsed),$=n.accumulated.last_activity_time!==""?Date.parse(n.accumulated.last_activity_time):Number.NaN,S=Number.isFinite($)?$:void 0,L={lines:k,startingTurnIndex:n.accumulated.turns.assistant_count+1,sessionId:o,projectName:A,transcriptSource:g,priorLastAssistantTsMs:n.internal.last_assistant_ts_ms,priorPendingToolUses:n.internal.pending_tool_uses,priorLastActivityTsMs:S,priorCurrentTurn:n.internal.current_turn,priorNextTurnIndex:n.internal.next_turn_index,priorSeenAssistantMessageIds:n.internal.seen_assistant_message_ids,priorSeenToolUseIds:n.internal.seen_tool_use_ids},p=(0,R.projectDelta)(L),K=(0,R.projectDeltaInternal)(L);!v&&p.completed_turns.length>0&&(i?.debug(`gating: emitTurnEvents=false \u2192 dropping ${p.completed_turns.length} completed turn(s) from delta`),p.completed_turns=[]),!T&&p.completed_steps.length>0&&(i?.debug(`gating: emitStepEvents=false \u2192 dropping ${p.completed_steps.length} completed step(s) from delta`),p.completed_steps=[]),!u&&p.api_request_events.length>0&&(i?.debug(`gating: emitApiRequestEvents=false \u2192 dropping ${p.api_request_events.length} api_request event(s) from delta`),p.api_request_events=[]),i?.debug(`delta: user_count+=${p.turns.user_count} assistant_msgs+=${p.turns.assistant_count} tool_uses=${Object.values(p.tools).reduce((h,G)=>h+G.count,0)} files_modified+=${p.code_changes.files_modified} lines_added+=${p.code_changes.lines_added} lines_removed+=${p.code_changes.lines_removed} errors+=${p.errors.tool_errors_total} interruptions+=${p.errors.user_interruptions} pending_tool_uses=${Object.keys(p.closing_pending_tool_uses).length}`);const M=(0,$e.mergeAccumulated)(n.accumulated,n.internal,p,K);r=M.accumulated,s=M.internal;{const h=(0,Se.foldSubAgents)({merged:r,internal:s,mainLines:k,sessionId:o,projectName:A,transcriptPath:m,transcriptSource:g,apiRequestEventsEnabled:u});r=h.merged,s=h.internal,h.foldedCount>0&&i?.debug(`subagent-fold: folded ${h.foldedCount} sub-agent(s) into session aggregates`)}const J=Object.values(r.tools).reduce((h,G)=>h+G.count,0);i?.debug(`merged: user_count=${r.turns.user_count} duration_min=${r.time.duration_minutes.toFixed(2)} tool_calls=${J} files_modified=${r.code_changes.files_modified} lines_added=${r.code_changes.lines_added} lines_removed=${r.code_changes.lines_removed} ctx_latest=${r.context_tokens.latest} ctx_peak=${r.context_tokens.peak}`)}{const e=(0,ye.join)((0,V.sessionAnalyticsDir)(t,o),"otel-requests.jsonl"),a=(0,ve.foldOtelRequests)({merged:r,internal:s,sidecarPath:e,sessionId:o,apiRequestEventsEnabled:u});r=a.merged,s=a.internal,a.foldedCount>0&&i?.debug(`otel-fold: folded ${a.foldedCount} non-transcript feature call(s) into session aggregates`)}const N=(0,F.getCollectorTarget)(t),w=q?n.offset:Z,Te=w===n.last_emitted_offset,Ae=d&&!n.last_emitted_is_final;if(Te&&!Ae){if(s.current_turn!==void 0&&s.current_turn.assistant_messages>0){const b=s.current_turn,ze=b.last_activity_time!==""?b.last_activity_time:r.last_activity_time,_e=d?"session_end":"stop",P=(0,R.closeTurn)(b,{endTime:ze,endReason:_e,sessionId:o,projectName:A,transcriptSource:g});(0,R.applyBreakdownDelta)(r.classification.category_breakdown,P),r.turns.with_retry+=P.turns_with_retry_inc,r.turns.one_shot+=P.one_shot_inc;const pe=v?[...s.pending_turn_events??[],P.turn_event]:s.pending_turn_events??[],ge=T&&P.step_events.length>0?[...s.pending_step_events??[],...P.step_events]:s.pending_step_events??[];s={...s,...pe.length>0?{pending_turn_events:pe}:{},...ge.length>0?{pending_step_events:ge}:{}},delete s.current_turn,i?.debug(`force-close (idempotent path): turn_index=${P.turn_event.turn_index} turn_id=${P.turn_event.turn_id} end_reason=${_e} category=${P.turn_event.category} duration=${P.turn_event.duration}ms step_events=${P.step_events.length} gates: turn=${v} step=${T}`)}else if(s.current_turn!==void 0){const b=s.current_turn;i?.debug(`force-close (idempotent path): skipping empty turn turn_index=${b.turn_index} (assistant_messages=0)`),r.turns.user_count>0&&(r.turns.user_count-=1),s={...s},delete s.current_turn}const e=s.pending_turn_events??[],a=s.pending_step_events??[],k=s.pending_api_request_events??[];if(e.length===0&&a.length===0&&k.length===0){const b={...n,offset:w,accumulated:r,internal:s,last_read_at:new Date().toISOString(),transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:Y};return(0,I.writeState)(t,o,b),i?.info(`skip: idempotent \u2014 offset=${w} matches last_emitted_offset and is_final unchanged`),{status:"skipped",reason:"offset unchanged and final-flag unchanged"}}i?.info(`idempotent-with-drain: offset=${w} pending_turn_events=${e.length} pending_step_events=${a.length} pending_api_request_events=${k.length} \u2014 attempting drain`);const $=(0,j.baseFields)((0,V.sessionActionsFile)(t,o));let S=s,L=0,p=0,K=0,M=e.length,J=a.length,h=k.length;if(e.length>0)if(v){const b=await fe(e,$,o,t,i,"drain submit",N);S=re(S,b.remaining),L=b.sent,M=b.remaining.length}else i?.info(`gating: emitTurnEvents=false \u2192 dropping ${e.length} stale pending turn event(s) without sending`),S=re(S,[]),M=0;if(a.length>0)if(T){const b=await he(a,$,o,t,i,"drain submit",N);S=ae(S,b.remaining),p=b.sent,J=b.remaining.length}else i?.info(`gating: emitStepEvents=false \u2192 dropping ${a.length} stale pending step event(s) without sending`),S=ae(S,[]),J=0;if(k.length>0)if(u){const b=await be(k,$,o,t,i,"drain submit",N);S=oe(S,b.remaining),K=b.sent,h=b.remaining.length}else i?.info(`gating: emitApiRequestEvents=false \u2192 dropping ${k.length} stale pending api_request event(s) without sending`),S=oe(S,[]),h=0;const G={...n,offset:w,accumulated:r,internal:S,last_read_at:new Date().toISOString(),transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:Y};(0,I.writeState)(t,o,G),i?.info(`idempotent-with-drain: turn_sent=${L}/${e.length} retained=${M} step_sent=${p}/${a.length} retained=${J} api_request_sent=${K}/${k.length} retained=${h}`);const ne=["offset unchanged"];return e.length>0&&ne.push(`drained ${L}/${e.length} pending turn events`),a.length>0&&ne.push(`drained ${p}/${a.length} pending step events`),k.length>0&&ne.push(`drained ${K}/${k.length} pending api_request events`),{status:"skipped",reason:ne.join("; ")}}if(s.current_turn!==void 0&&s.current_turn.assistant_messages>0){const e=s.current_turn,a=e.last_activity_time!==""?e.last_activity_time:r.last_activity_time,k=d?"session_end":"stop",$=(0,R.closeTurn)(e,{endTime:a,endReason:k,sessionId:o,projectName:A,transcriptSource:g});(0,R.applyBreakdownDelta)(r.classification.category_breakdown,$),r.turns.with_retry+=$.turns_with_retry_inc,r.turns.one_shot+=$.one_shot_inc;const S=v?[...s.pending_turn_events??[],$.turn_event]:s.pending_turn_events??[],L=T&&$.step_events.length>0?[...s.pending_step_events??[],...$.step_events]:s.pending_step_events??[];s={...s,...S.length>0?{pending_turn_events:S}:{},...L.length>0?{pending_step_events:L}:{}},delete s.current_turn,i?.debug(`force-close: turn_index=${$.turn_event.turn_index} turn_id=${$.turn_event.turn_id} end_reason=${k} category=${$.turn_event.category} duration=${$.turn_event.duration}ms step_events=${$.step_events.length} gates: turn=${v} step=${T}`)}else if(s.current_turn!==void 0){const e=s.current_turn;i?.debug(`force-close: skipping empty turn turn_index=${e.turn_index} (assistant_messages=0) \u2014 no event emitted`),r.turns.user_count>0&&(r.turns.user_count-=1),s={...s},delete s.current_turn}const Ee=(0,V.sessionActionsFile)(t,o),U=(0,j.baseFields)(Ee),f={session_id:r.session_id,project_name:r.project_name,...U.user_email!==void 0?{user_email:U.user_email}:{},schema_version:ce.SCHEMA_VERSION,transcript_source:r.transcript_source,is_final:d,snapshot_at:new Date().toISOString(),offset:w,end_reason:d?y:void 0,start_time:r.start_time,last_activity_time:r.last_activity_time,time:r.time,turns:r.turns,classification:r.classification,usage:r.usage,models:r.models,user_messages:r.user_messages,tools:r.tools,mcp_servers:r.mcp_servers,skills:r.skills,sub_agents:r.sub_agents,bash_binaries:r.bash_binaries,tool_meta:r.tool_meta,code_changes:r.code_changes,errors:r.errors,user_activity:r.user_activity,context_tokens:r.context_tokens,process_errors:r.process_errors},W={...U,id:(0,R.deriveSessionAnalyticsEventId)(r.session_id),type:j.EventType.SESSION_ANALYTICS,timestamp:Date.now(),analytics:f},Re=Buffer.byteLength(JSON.stringify(W),"utf-8"),ke=Object.values(f.tools).reduce((e,a)=>e+a.count,0),we=Object.entries(f.models).sort((e,a)=>a[1].count-e[1].count).map(([e,a])=>`${e}:${a.count}@$${a.cost_usd.toFixed(4)}`).join(",")||"none",qe=Object.entries(f.tools).sort((e,a)=>a[1].output_size-e[1].output_size).slice(0,5).map(([e,a])=>`${e}:${a.count}/err${a.errors}/in:${a.input_size}/out:${a.output_size}`).join(",")||"none",Pe=Object.entries(f.mcp_servers).sort((e,a)=>a[1].output_size-e[1].output_size).map(([e,a])=>`${e}:${a.count}/err${a.errors}/in:${a.input_size}/out:${a.output_size}`).join(",")||"none",De=Object.values(f.process_errors.items).reduce((e,a)=>e+a.count,0),Ie=Object.keys(f.process_errors.items).length,xe=Object.entries(f.classification.category_breakdown).sort((e,a)=>a[1].turns-e[1].turns).slice(0,5).map(([e,a])=>`${e}:${a.turns}`).join(",")||"none";i?.info(`submit: type=session_analytics event_id=${W.id} offset=${w} is_final=${d}${y?` end_reason=${y}`:""} wire_bytes=${Re} user_count=${f.turns.user_count} duration_min=${f.time.duration_minutes} active_min=${f.time.active_minutes} idle_min=${f.time.idle_minutes} tool_calls=${ke} session_type=${f.classification.session_type} categories=${xe} retries=${f.turns.with_retry} one_shot=${f.turns.one_shot} tokens=in:${f.usage.input_tokens}/out:${f.usage.output_tokens}/cache_w:${f.usage.cache_creation_tokens}/cache_r:${f.usage.cache_read_tokens} cost_usd=${f.usage.cost_usd.toFixed(4)} models=${we} mcp_servers=${Pe} top_tools=${qe}`+(f.process_errors.has?` process_errors=${De}/${Ie}`:""));let Q=!1;try{await(0,F.sendEventsBatchToCollector)([W],o,t,N),Q=!0}catch(e){z.logger.debug(`analytics emit: send failed: ${e instanceof Error?e.message:e}`),i?.error(`submit: failed event_id=${W.id}: ${e instanceof Error?e.message:e}`)}const X=s.pending_turn_events??[],ee=s.pending_step_events??[];if(Q&&X.length>0)if(v){const e=await fe(X,U,o,t,i,"submit",N);i?.info(`turn-events: sent=${e.sent}/${X.length} retained=${e.remaining.length}`),s=re(s,e.remaining)}else i?.info(`gating: emitTurnEvents=false \u2192 dropping ${X.length} stale pending turn event(s) without sending`),s=re(s,[]);if(Q&&ee.length>0)if(T){const e=await he(ee,U,o,t,i,"submit",N);i?.info(`step-events: sent=${e.sent}/${ee.length} retained=${e.remaining.length}`),s=ae(s,e.remaining)}else i?.info(`gating: emitStepEvents=false \u2192 dropping ${ee.length} stale pending step event(s) without sending`),s=ae(s,[]);const te=s.pending_api_request_events??[];if(Q&&te.length>0)if(u){const e=await be(te,U,o,t,i,"submit",N);i?.info(`api-request-events: sent=${e.sent}/${te.length} retained=${e.remaining.length}`),s=oe(s,e.remaining)}else i?.info(`gating: emitApiRequestEvents=false \u2192 dropping ${te.length} stale pending api_request event(s) without sending`),s=oe(s,[]);if(Q){const e={session_id:o,transcript_path:m,transcript_inode:q?n.transcript_inode:l.inode,transcript_size_at_last_read:q?n.transcript_size_at_last_read:H,transcript_first_kb_sha256:q?n.transcript_first_kb_sha256:Y,offset:w,last_read_at:new Date().toISOString(),last_emitted_offset:w,last_emitted_is_final:d,accumulated:r,internal:s};return(0,I.writeState)(t,o,e),i?.info(`state persisted: offset=${w} last_emitted_offset=${w} last_emitted_is_final=${d}`),{status:"emitted",reason:d?"final snapshot":"interim snapshot",offset:w}}return{status:"error",reason:"submit failed; state not advanced"}}D(Me,"runEmit");const ie=100;function me(c){if(typeof c!="string"||c.length===0)return Date.now();const t=Date.parse(c);return Number.isFinite(t)?t:Date.now()}D(me,"parseIsoOrNow");function ue(c){return typeof c.batchSize=="number"&&c.batchSize>=1?Math.floor(c.batchSize):ie}D(ue,"resolveBatchSize");async function fe(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:(0,R.deriveTurnEventId)(o,u.turn_id),type:j.EventType.SESSION_TURN,timestamp:me(u.end_time),turn:u})),A=d!==null?ue(d):ie,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=session_turn_analytics batch_count=${_.length} first_turn_index=${n[0].turn_index}`)}catch(l){for(const O of n)v.push(O);z.logger.debug(`analytics emit: turn batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=session_turn_analytics batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(fe,"drainPendingTurns");function re(c,t){if(t.length>0)return{...c,pending_turn_events:t};const o={...c};return delete o.pending_turn_events,o}D(re,"withPendingTurnEvents");async function he(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:(0,R.deriveStepEventId)(o,u.step_id),type:j.EventType.SESSION_TURN_STEP,timestamp:me(u.end_time),step:u})),A=d!==null?ue(d):ie,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=session_turn_step_analytics batch_count=${_.length} first=(turn_index=${n[0].turn_index},step_index=${n[0].step_index})`)}catch(l){for(const O of n)v.push(O);z.logger.debug(`analytics emit: step batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=session_turn_step_analytics batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(he,"drainPendingSteps");function ae(c,t){if(t.length>0)return{...c,pending_step_events:t};const o={...c};return delete o.pending_step_events,o}D(ae,"withPendingStepEvents");async function be(c,t,o,x,y,i,d){const g=c.map(u=>({...t,id:u.id,type:j.EventType.API_REQUEST,timestamp:u.timestamp_ms,request_id:u.request_id,success:u.success,error:u.error,status_code:u.status_code,model:u.model,speed:u.speed,input_tokens:u.input_tokens,output_tokens:u.output_tokens,cache_read_tokens:u.cache_read_tokens,cache_creation_tokens:u.cache_creation_tokens,cost_usd:u.cost_usd,duration:u.duration})),A=d!==null?ue(d):ie,v=[];let T=0;for(let u=0;u<g.length;u+=A){const m=Math.min(u+A,g.length),_=g.slice(u,m),n=c.slice(u,m);try{await(0,F.sendEventsBatchToCollector)(_,o,x,d),T+=_.length,y?.debug(`${i}: type=api_request batch_count=${_.length} first_request_id=${n[0].request_id??"<null>"} success_count=${n.filter(l=>l.success).length}`)}catch(l){for(const O of n)v.push(O);z.logger.debug(`analytics emit: api_request batch send failed: ${l instanceof Error?l.message:l}`),y?.error(`${i} failed: type=api_request batch_count=${_.length}: ${l instanceof Error?l.message:l}`)}}return{sent:T,remaining:v}}D(be,"drainPendingApiRequests");function oe(c,t){if(t.length>0)return{...c,pending_api_request_events:t};const o={...c};return delete o.pending_api_request_events,o}D(oe,"withPendingApiRequestEvents");const Be={emptyAccumulated:I.emptyAccumulated};0&&(module.exports={__test,emitAnalytics});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var d=Object.defineProperty;var
|
|
1
|
+
"use strict";var d=Object.defineProperty;var S=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var c=(t,e)=>d(t,"name",{value:e,configurable:!0});var A=(t,e)=>{for(var n in e)d(t,n,{get:e[n],enumerable:!0})},x=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of w(e))!$.call(t,r)&&r!==n&&d(t,r,{get:()=>e[r],enumerable:!(o=S(e,r))||o.enumerable});return t};var v=t=>x(d({},"__esModule",{value:!0}),t);var T={};A(T,{analyticsDir:()=>g,emptyAccumulated:()=>k,ensureAnalyticsDir:()=>R,initialState:()=>E,isUnderAnalyticsDir:()=>O,readState:()=>z,stateFile:()=>p,writeState:()=>D});module.exports=v(T);var y=require("crypto"),a=require("fs"),f=require("path"),u=require("../../lib/logger"),h=require("../shared/types"),m=require("./transcript"),b=require("../../lib/runtime-paths");function g(t,e){return(0,b.sessionAnalyticsDir)(t,e)}c(g,"analyticsDir");function p(t,e){return(0,f.join)(g(t,e),"state.json")}c(p,"stateFile");function k(t,e,n){return{session_id:t,project_name:e,schema_version:h.SCHEMA_VERSION,transcript_source:n,start_time:"",last_activity_time:"",time:{duration_minutes:0,active_minutes:0,idle_minutes:0,start_hour:0,last_activity_hour:0},turns:{user_count:0,assistant_count:0,with_retry:0,one_shot:0},classification:{category_breakdown:{},session_type:"general"},usage:{input_tokens:0,output_tokens:0,cache_creation_tokens:0,cache_read_tokens:0,cost_usd:0},models:{},user_messages:{count:0,size:0,approximated_tokens:0},tools:{},mcp_servers:{},skills:{},sub_agents:{},bash_binaries:{},tool_meta:{uses_sub_agent:!1,uses_skill:!1,uses_mcp:!1,uses_web_search:!1,uses_web_fetch:!1},code_changes:{files_modified:0,lines_added:0,lines_removed:0,hot_files:[],languages:{}},errors:{tool_errors_total:0,tool_error_categories:{},user_interruptions:0},user_activity:{response_time_buckets:{},messages_by_hour:{},messages_by_date:{},messages_by_weekday:{}},context_tokens:{latest:0,peak:0,buckets:{}},process_errors:{has:!1,items:{}}}}c(k,"emptyAccumulated");function E(t,e,n,o){let r=0,i=0,_="";if(e.length>0&&(0,a.existsSync)(e))try{const l=(0,m.statTranscript)(e);r=l.inode,i=l.size,_=(0,m.firstKbSha256)(e)}catch(l){u.logger.debug(`analytics state: stat ${e} failed: ${l instanceof Error?l.message:l}`)}return{session_id:t,transcript_path:e,transcript_inode:r,transcript_size_at_last_read:i,transcript_first_kb_sha256:_,offset:0,last_read_at:new Date().toISOString(),last_emitted_offset:0,last_emitted_is_final:!1,accumulated:k(t,n,o),internal:{file_path_change_counts:{},distinct_file_paths_seen:[],next_turn_index:1,pending_turn_events:[],pending_step_events:[]}}}c(E,"initialState");function z(t,e){const n=p(t,e);if(!(0,a.existsSync)(n))return null;let o;try{o=(0,a.readFileSync)(n,"utf-8")}catch(i){return u.logger.debug(`analytics state: read ${n} failed: ${i instanceof Error?i.message:i}`),null}let r;try{r=JSON.parse(o)}catch(i){return u.logger.debug(`analytics state: parse ${n} failed: ${i instanceof Error?i.message:i}`),null}return j(r)?r:(u.logger.debug(`analytics state: ${n} has unexpected shape; falling back to fresh init`),null)}c(z,"readState");function j(t){if(t===null||typeof t!="object")return!1;const e=t;if(typeof e.session_id!="string"||typeof e.transcript_path!="string"||typeof e.transcript_inode!="number"||typeof e.transcript_size_at_last_read!="number"||typeof e.transcript_first_kb_sha256!="string"||typeof e.offset!="number"||typeof e.last_read_at!="string"||typeof e.last_emitted_offset!="number"||typeof e.last_emitted_is_final!="boolean"||e.accumulated===null||typeof e.accumulated!="object"||e.internal===null||typeof e.internal!="object")return!1;const n=e.accumulated;return!(typeof n.session_id!="string"||typeof n.schema_version!="string")}c(j,"isValidState");function D(t,e,n){const o=g(t,e);try{(0,a.mkdirSync)(o,{recursive:!0,mode:448})}catch(s){u.logger.debug(`analytics state: mkdir ${o} failed: ${s instanceof Error?s.message:s}`)}const r={...n,internal:{...n.internal,distinct_file_paths_seen:[...n.internal.distinct_file_paths_seen].sort()}},i=p(t,e),_=(0,f.join)(o,`state.json.tmp.${(0,y.randomUUID)()}`);let l;try{l=JSON.stringify(r,null,2)}catch(s){u.logger.debug(`analytics state: stringify failed: ${s instanceof Error?s.message:s}`);return}try{(0,a.writeFileSync)(_,l,{encoding:"utf-8",mode:384})}catch(s){u.logger.debug(`analytics state: write ${_} failed: ${s instanceof Error?s.message:s}`);return}try{(0,a.renameSync)(_,i)}catch(s){u.logger.debug(`analytics state: rename ${_} \u2192 ${i} failed: ${s instanceof Error?s.message:s}`);return}}c(D,"writeState");function R(t,e){const n=g(t,e);(0,a.existsSync)(n)||(0,a.mkdirSync)(n,{recursive:!0,mode:448})}c(R,"ensureAnalyticsDir");function O(t){return(0,f.dirname)(t).endsWith("/analytics")||(0,f.dirname)(t).endsWith("\\analytics")}c(O,"isUnderAnalyticsDir");0&&(module.exports={analyticsDir,emptyAccumulated,ensureAnalyticsDir,initialState,isUnderAnalyticsDir,readState,stateFile,writeState});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`,p);if(y<0)break;const
|
|
1
|
+
"use strict";var I=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var S=(t,o)=>I(t,"name",{value:o,configurable:!0});var te=(t,o)=>{for(var i in o)I(t,i,{get:o[i],enumerable:!0})},ne=(t,o,i,e)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of Z(o))!ee.call(t,s)&&s!==i&&I(t,s,{get:()=>o[s],enumerable:!(e=Y(o,s))||e.enumerable});return t};var se=t=>ne(I({},"__esModule",{value:!0}),t);var de={};te(de,{emitCodexAnalytics:()=>le});module.exports=se(de);var r=require("fs"),j=require("path"),d=require("../../lib/logger"),U=require("../../hooks/core/actions"),J=require("../../lib/runtime-paths"),u=require("../../lib/config"),W=require("../../lib/collector"),L=require("./api-request"),D=require("./status-snapshot"),V=require("./transcript"),G=require("./subagent-fold");function $(){return{rollout_inode:0,rollout_size:0,rollout_read_offset:0,last_status_signature:"",last_status_emit_ms:0,api_response_count:0,turn_token_seq:0,last_turn_id:null,last_model:"gpt-5.5",session_start_ms:0,session_meta_cached:!1,cumulative_cost_usd:0}}S($,"defaultState");function oe(t,o){return(0,J.sessionAnalyticsDir)(t,o)}S(oe,"stateDir");function B(t,o){return(0,j.join)(oe(t,o),"state.json")}S(B,"statePath");function ie(t,o){const i=B(t,o);if(!(0,r.existsSync)(i))return $();try{const e=(0,r.readFileSync)(i,"utf-8"),s=JSON.parse(e);if(s!==null&&typeof s=="object")return re($(),s)}catch(e){d.logger.debug(`codex emit: state read failed: ${e instanceof Error?e.message:e}`)}return $()}S(ie,"readState");function re(t,o){const i={...t};for(const[e,s]of Object.entries(o)){if(!(e in t))continue;const m=t[e],f=m===null?"object":typeof m,a=s===null?"object":typeof s;if(s===null&&m===null){i[e]=null;continue}f===a&&(a==="number"&&!Number.isFinite(s)||(i[e]=s))}return i}S(re,"mergeValidatedState");function ae(t,o,i){const e=B(t,o);try{(0,r.mkdirSync)((0,j.dirname)(e),{recursive:!0});const s=`${e}.tmp.${process.pid}`;(0,r.writeFileSync)(s,JSON.stringify(i,null,2),"utf-8"),(0,r.renameSync)(s,e)}catch(s){d.logger.debug(`codex emit: state write failed: ${s instanceof Error?s.message:s}`)}}S(ae,"writeState");async function le(t){const o=(0,u.loadConfig)(t.projectDir);if(!(0,u.isAnalyticsEnabled)(t.projectDir)){d.logger.debug("codex emit: analytics disabled \u2014 skipping");return}if(!(0,u.isAnalyticsEmitOnStopEnabled)(t.projectDir)){d.logger.debug("codex emit: emitOnStop disabled \u2014 skipping");return}let i=t.rolloutPath??null;if((i===null||!(0,r.existsSync)(i))&&(i=(0,V.findCodexRolloutPath)(t.sessionId)),i===null){d.logger.debug(`codex emit: rollout not found for session ${t.sessionId}`);return}const e=ie(t.projectDir,t.sessionId),s=t.projectName??(0,U.resolveProjectName)(t.projectDir);let m=[],f=e.rollout_read_offset,a=null,l=!1;if(t.preloadedLines!==void 0){m=t.preloadedLines;for(const n of t.preloadedLines)if(n.type==="session_meta"&&a===null){a=n.payload;break}try{const n=(0,r.statSync)(i);f=Number(n.size),e.rollout_inode!==0&&Number(n.ino)!==e.rollout_inode&&(l=!0)}catch{}}else try{const n=ue(i,e.rollout_read_offset,e.rollout_inode,e.rollout_size);m=n.lines,f=n.nextOffset,a=n.sessionMeta,l=n.resetDetected}catch(n){d.logger.debug(`codex emit: rollout read failed: ${n instanceof Error?n.message:n}`)}if(l&&(e.rollout_read_offset=0,e.turn_token_seq=0,e.last_turn_id=null,e.api_response_count=0,e.session_meta_cached=!1,e.session_start_ms=0,e.cumulative_cost_usd=0,e.last_status_signature="",e.last_status_emit_ms=0,e.last_model="gpt-5.5"),a!==null&&!e.session_meta_cached){const n=Date.parse(a.timestamp);e.session_start_ms=Number.isFinite(n)?n:0,e.session_meta_cached=!0,e.last_model=(0,L.defaultModelFromSessionMeta)(a)}const _=[];let C=null,E=null;const x=t.preloadedLines!==void 0;let g=x?0:e.api_response_count,b=x?null:e.last_turn_id,p=x?0:e.turn_token_seq,h=e.last_model,y=x?0:e.cumulative_cost_usd;const w=(0,u.isAnalyticsApiRequestEventsEnabled)(t.projectDir),M=(0,u.isSessionStatusEnabled)(o)&&e.session_meta_cached,R=Math.max(0,(0,u.getStatusLineEmitMinIntervalSeconds)(o))*1e3;let c=e.last_status_emit_ms,N=e.last_status_signature,F=!1;const H=M?(0,G.sumCodexSubAgentCostUsd)({projectDir:t.projectDir,sessionId:t.sessionId,projectName:s,defaultModel:e.last_model||"gpt-5.5",usageType:t.usageType,usagePlan:t.usagePlan}):0;if(m.length>0)try{const n={sessionId:t.sessionId,projectName:s,userEmail:t.userEmail,usageType:t.usageType,usagePlan:t.usagePlan,defaultModel:e.last_model},{events:O,tokenCounts:K,finalState:q}=(0,L.deriveCodexApiRequestEvents)(m,n,{turn_id:b,turn_token_seq:p,model:h});for(let k=0;k<O.length;k++){const P=O[k],{tc:Q,ts:A}=K[k];if(w&&_.push(P),g+=1,Number.isFinite(P.cost_usd)&&(y+=P.cost_usd),!M)continue;const X=k===O.length-1;if(!(R>0&&A-c<R&&!X))try{const v={sessionId:t.sessionId,projectName:s,userEmail:t.userEmail,usageType:t.usageType,usagePlan:t.usagePlan,model:P.model,sessionStartMs:e.session_start_ms,apiResponseCount:g,activityId:t.activityId??"",totalCostUsd:y+H},T=(0,D.buildCodexSessionStatusEvent)(Q,A,v);if(T===null)continue;const z=(0,D.computeCodexStatusSignature)(T);if(z===N)continue;_.push(T),N=z,c=A,F=!0}catch(v){d.logger.debug(`codex emit: session_status failed: ${v instanceof Error?v.message:v}`)}}b=q.turn_id,p=q.turn_token_seq,h=q.model}catch(n){d.logger.debug(`codex emit: api_request derivation failed: ${n instanceof Error?n.message:n}`)}if(F&&(C=N,E=c),_.length>0){if(!(0,u.isCollectorConfigured)(o)){d.logger.debug(`codex emit: collector not configured \u2014 dropping batch=${_.length}`);return}try{await(0,W.sendEventsBatchToCollector)(_,t.sessionId,t.projectDir)}catch(n){d.logger.debug(`codex emit: POST batch=${_.length} failed: ${n instanceof Error?n.message:n}`);return}C!==null&&E!==null&&(e.last_status_signature=C,e.last_status_emit_ms=E)}e.api_response_count=g,e.last_turn_id=b,e.turn_token_seq=p,e.last_model=h,e.cumulative_cost_usd=y;try{e.rollout_read_offset=f;const n=(0,r.statSync)(i);e.rollout_inode=Number(n.ino),e.rollout_size=Number(n.size),ae(t.projectDir,t.sessionId,e)}catch(n){d.logger.debug(`codex emit: state persist failed: ${n instanceof Error?n.message:n}`)}}S(le,"emitCodexAnalytics");function ue(t,o,i,e){const s=(0,r.statSync)(t),m=Number(s.ino),f=Number(s.size);let a=!1,l=o;if(i!==0&&m!==i&&(a=!0,l=0),e!==0&&f<e&&(a=!0,l=0),l>=f)return{lines:[],nextOffset:l,sessionMeta:null,resetDetected:a};const _=f-l,C=Buffer.alloc(_),E=(0,r.openSync)(t,"r");try{(0,r.readSync)(E,C,0,_,l)}finally{try{(0,r.closeSync)(E)}catch{}}const x=C.toString("utf-8"),g=[];let b=null,p=0,h=l;for(;p<x.length;){const y=x.indexOf(`
|
|
2
|
+
`,p);if(y<0)break;const w=x.slice(p,y),M=l+p,R=l+y+1;if(w.length>0)try{const c=JSON.parse(w);g.push(c),c.type==="session_meta"&&b===null&&(b=c.payload)}catch(c){d.logger.debug(`codex emit: malformed rollout line at byte ${M}: ${c instanceof Error?c.message:c}`)}h=R,p=y+1}return b===null&&l===0&&g.length>0&&g[0].type==="session_meta"&&(b=g[0].payload),{lines:g,nextOffset:h,sessionMeta:b,resetDetected:a}}S(ue,"readNewRolloutLines");0&&(module.exports={emitCodexAnalytics});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var c=Object.defineProperty;var
|
|
2
|
-
`,"utf-8")}catch(n){p.logger.debug(`codex subagent-transcript record failed: ${n instanceof Error?n.message:String(n)}`)}}g(
|
|
3
|
-
`)){const f=
|
|
1
|
+
"use strict";var c=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var g=(r,t)=>c(r,"name",{value:t,configurable:!0});var l=(r,t)=>{for(var e in t)c(r,e,{get:t[e],enumerable:!0})},h=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of S(t))!y.call(r,i)&&i!==e&&c(r,i,{get:()=>t[i],enumerable:!(n=m(t,i))||n.enumerable});return r};var T=r=>h(c({},"__esModule",{value:!0}),r);var R={};l(R,{readSubagentTranscripts:()=>x,recordSubagentTranscript:()=>_,subagentTranscriptsPath:()=>u});module.exports=T(R);var s=require("fs"),o=require("path"),p=require("../../lib/logger"),d=require("../../lib/runtime-paths");function u(r,t){return(0,o.join)((0,d.sessionAnalyticsDir)(r,t),"subagent-transcripts.jsonl")}g(u,"subagentTranscriptsPath");function _(r,t,e){if(!(!t||!e.agent_id||!e.transcript_path))try{const n=u(r,t);(0,s.mkdirSync)((0,o.dirname)(n),{recursive:!0}),(0,s.appendFileSync)(n,JSON.stringify(e)+`
|
|
2
|
+
`,"utf-8")}catch(n){p.logger.debug(`codex subagent-transcript record failed: ${n instanceof Error?n.message:String(n)}`)}}g(_,"recordSubagentTranscript");function x(r,t){const e=u(r,t);if(!(0,s.existsSync)(e))return[];const n=new Map;try{const i=(0,s.readFileSync)(e,"utf-8");for(const b of i.split(`
|
|
3
|
+
`)){const f=b.trim();if(f.length!==0)try{const a=JSON.parse(f);a&&a.agent_id&&a.transcript_path&&n.set(a.agent_id,a)}catch{}}}catch(i){return p.logger.debug(`codex subagent-transcript read failed: ${i instanceof Error?i.message:String(i)}`),[]}return Array.from(n.values())}g(x,"readSubagentTranscripts");0&&(module.exports={readSubagentTranscripts,recordSubagentTranscript,subagentTranscriptsPath});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ironbee-scenario
|
|
3
|
+
description: >
|
|
4
|
+
Manages and searches reusable IronBee verification scenarios via the devtools scenario tools.
|
|
5
|
+
Delegate to this sub-agent from the /ironbee-manage-scenario and /ironbee-search-scenario
|
|
6
|
+
commands. It authors / updates / deletes saved scenarios and finds them by
|
|
7
|
+
name·description·metadata, then returns a short summary. It is NOT a verification cycle — it
|
|
8
|
+
opens no verdict and does not gate completion. (Running a saved scenario to verify is done via
|
|
9
|
+
/ironbee-verify scenario:<name>, not here.)
|
|
10
|
+
tools: Bash, Read, Grep, Glob, mcp__browser-devtools__*, mcp__node-devtools__*, mcp__backend-devtools__*, mcp__android-devtools__*
|
|
11
|
+
# Prefer foreground (the default). Advisory only.
|
|
12
|
+
background: false
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# IronBee Scenario manager (manage / search)
|
|
16
|
+
|
|
17
|
+
You manage **reusable verification scenarios** stored by the IronBee DevTools MCP servers. A
|
|
18
|
+
scenario is a named, parameterizable script (`callTool('<tool>', {...})` JS) that drives one
|
|
19
|
+
platform's tools. The main agent delegated a scenario operation to you; do exactly the
|
|
20
|
+
operation named in the delegating prompt and return a short summary.
|
|
21
|
+
|
|
22
|
+
You drive ONLY the `*_scenario-*` tools (`scenario-add` / `scenario-update` / `scenario-delete`
|
|
23
|
+
/ `scenario-list` / `scenario-search` / `scenario-run`) for scenario work. The platform tools a
|
|
24
|
+
scenario *script* calls run INSIDE the sandbox at run time — you never call them directly. Use
|
|
25
|
+
`Bash` to build / start / stop the app for live authoring (exactly as a verification run would —
|
|
26
|
+
start it only if it isn't already running, and stop only what YOU started) and `Read`/`Grep`/`Glob`
|
|
27
|
+
to read content the user points you at + derive metadata. You have **no edit tools** — you never
|
|
28
|
+
edit project code.
|
|
29
|
+
|
|
30
|
+
## Operation: the delegating prompt names ONE of these
|
|
31
|
+
|
|
32
|
+
### `manage` — add / update / delete
|
|
33
|
+
- **Resolve intent.** If the prompt gives scenario CONTENT (a prompt or a file path) to save,
|
|
34
|
+
it's an add/update. If it only describes a TARGET to remove, it's a delete.
|
|
35
|
+
- **Add vs update (never duplicate).** Before adding, **`scenario-search` / `scenario-list`** to
|
|
36
|
+
check whether a same-name or clearly-the-same scenario already exists on the target platform.
|
|
37
|
+
If it does → **update** it instead of creating a duplicate.
|
|
38
|
+
- **Author the script** from the given content into the devtools format. Pick the **right platform**
|
|
39
|
+
from what the scenario does (see the platform sections for which platform fits) and call `scenario-add`/`scenario-update` on **that
|
|
40
|
+
platform's server**. A high-level scenario that spans platforms → split into one sub-scenario per
|
|
41
|
+
platform, linked by metadata (see "Metadata"). **By default author it against the LIVE app — see
|
|
42
|
+
"Live authoring" below** (skip with `Mode: draft` for a source-only draft). Script form: §Script format.
|
|
43
|
+
- **Delete is destructive — always confirm.** Resolve the target via search/list, then show the
|
|
44
|
+
matched **name + description + platform** and ask the user to confirm before deleting. If the
|
|
45
|
+
search returns multiple candidates or a low score, list them and ask which.
|
|
46
|
+
- **Update resolved by fuzzy description also confirms** (the script is overwritten — same risk
|
|
47
|
+
as delete). An **exact-name** match proceeds without a confirm prompt.
|
|
48
|
+
- **Scope**: write to `project` scope (default) unless the user asked for `global`. Pass
|
|
49
|
+
`scope` on every call.
|
|
50
|
+
- **Rename** isn't a devtools op (name is the key) → it's delete-old + add-new (with the delete
|
|
51
|
+
confirm).
|
|
52
|
+
|
|
53
|
+
### `search` — find scenarios
|
|
54
|
+
- **`scenario-search`** (fuzzy, ranked over name + description) for discovery ("find login
|
|
55
|
+
scenarios"). **`scenario-list` with `metadataMatch`** for precise structural lookup ("which
|
|
56
|
+
scenarios cover `src/auth/login.ts`") — metadata is NOT indexed by `scenario-search`.
|
|
57
|
+
- **Search every enabled platform's server** and union the results (each platform is a separate
|
|
58
|
+
server with its own store). Report name + description + platform + score; surface scope.
|
|
59
|
+
|
|
60
|
+
### `sync` — re-validate an existing scenario against current code, repair drift
|
|
61
|
+
- **Target.** `all` → every STALE scenario (those whose `ironbee.coveredPaths` changed since their
|
|
62
|
+
`ironbee.commit`, or authored as drafts); **`all force`** (a leading `force` token) → EVERY saved
|
|
63
|
+
scenario regardless of freshness; a name / description → resolve that one (`scenario-search` /
|
|
64
|
+
`scenario-list`). **Before a batch, list the targets + count first** (e.g. "syncing 3 stale of 7")
|
|
65
|
+
so the blast radius is visible.
|
|
66
|
+
- **Grouped scenarios.** When several targets share an `ironbee.group` (one high-level flow split
|
|
67
|
+
across platforms), run them in ascending `ironbee.order` — earlier steps set up state later ones need.
|
|
68
|
+
- **`Mode: check`** (a leading `check` token) → DRY-RUN: run + report drift, do NOT repair or update.
|
|
69
|
+
Otherwise: run + repair + `scenario-update`.
|
|
70
|
+
- **Run it** (`scenario-run`, against the live app — start it if needed, tear down what you started,
|
|
71
|
+
same discipline as live authoring) and classify the outcome:
|
|
72
|
+
- **passes** → still current. (non-check) `scenario-update` to stamp `ironbee.commit` → current HEAD
|
|
73
|
+
(read via `git rev-parse HEAD`) + `ironbee.liveValidated: true`; done. `scenario-update`
|
|
74
|
+
shallow-replaces metadata, so read the current metadata and re-send it MERGED with these two
|
|
75
|
+
keys — don't drop `coveredPaths` / `group` / `argsSchema`.
|
|
76
|
+
- **fails due to DRIFT** (the *mechanics* broke — the way to reach / drive the flow changed, not the
|
|
77
|
+
expected outcome) → repair the SCRIPT mechanics only, `scenario-update`, re-run until green, then
|
|
78
|
+
stamp commit / liveValidated.
|
|
79
|
+
- **fails due to a real DEFECT** (the app genuinely broke — the expected outcome is unreachable) →
|
|
80
|
+
**STOP, report the defect to the user, do NOT touch the scenario** (it correctly caught the bug;
|
|
81
|
+
leave it as-is). This is the "a genuine defect is a STOP, not a workaround" rule.
|
|
82
|
+
- **the expected outcome legitimately CHANGED** (a deliberate behavior / spec change) → **do NOT
|
|
83
|
+
auto-edit the assertion**; ask the user — changing *what* a scenario verifies is an authoring
|
|
84
|
+
decision, not a sync.
|
|
85
|
+
- **Classifying drift vs defect — the load-bearing call.** Repair is the ONLY branch that edits a
|
|
86
|
+
scenario, so a defect mistaken for drift silently masks a regression. Apply two rules before you
|
|
87
|
+
repair:
|
|
88
|
+
1. **HOW-vs-WHAT self-check:** would the fix change *how* the flow reaches its point (driving /
|
|
89
|
+
locating / navigating steps) or *what* it asserts (the expected terminal outcome / value /
|
|
90
|
+
state)? Only a HOW change is drift. A WHAT change is never drift — it's a defect (STOP) or a
|
|
91
|
+
deliberate expectation change (ask). Never edit the assertion to make a run pass.
|
|
92
|
+
2. **Failure-locus heuristic:** a failure while *reaching / driving* the flow (a step can't locate
|
|
93
|
+
or progress) leans drift; a failure at the *terminal assertion* after the flow completed (the
|
|
94
|
+
outcome was reached but is wrong) leans defect.
|
|
95
|
+
**When uncertain, treat it as a defect and STOP** — never auto-repair on a guess.
|
|
96
|
+
- **Hard rule: sync repairs MECHANICS, never the ASSERTION / expected outcome.** Silently relaxing an
|
|
97
|
+
assertion to make a stale scenario pass would mask a regression.
|
|
98
|
+
- **Scope / teardown / metadata**: same as `manage` live authoring (project scope by default; stop
|
|
99
|
+
only what you started; stamp metadata). Report per scenario: repaired / still-fresh / defect-reported
|
|
100
|
+
/ needs-user-decision.
|
|
101
|
+
|
|
102
|
+
(There is no `run` operation here. Running a saved scenario to **verify** is the verifier's job, via
|
|
103
|
+
`/ironbee-verify scenario:<name>` — not this agent. This agent **manages, searches, and syncs**
|
|
104
|
+
(re-validates + repairs drift in) scenarios; it runs them only to author / validate / repair, never to
|
|
105
|
+
gate completion.)
|
|
106
|
+
|
|
107
|
+
## Live authoring (default for add / update) — build it against the running app
|
|
108
|
+
|
|
109
|
+
Don't author a runtime scenario from source guesses (source rarely matches the running system exactly). By **default, drive the app to
|
|
110
|
+
understand it — exactly what you'd do when verifying** (exercise the relevant flow through this platform's tools, whatever it takes) — author the scenario from what you actually observe, then validate it by
|
|
111
|
+
running it.
|
|
112
|
+
|
|
113
|
+
1. **`draft` → skip:** if the prompt says `Mode: draft` (or "source only"), author from source, save,
|
|
114
|
+
note *"not live-validated — run it to verify"*. Done.
|
|
115
|
+
2. **Start the app only if it isn't already running** (check `docker compose ps` / process / config;
|
|
116
|
+
track whether YOU started it). Genuinely can't start it → **source-only draft + say so**, don't fail.
|
|
117
|
+
3. **Understand it by running probe scenarios:** `scenario-add` the draft **under the FINAL scenario
|
|
118
|
+
name** (step 4 then iterates that SAME entry via `scenario-update` — do NOT spawn a separate
|
|
119
|
+
`*-probe` / throwaway scenario in the store) and `scenario-run` it to exercise the relevant flow —
|
|
120
|
+
whatever it takes to learn how the real system behaves — and READ the returned snapshots/results.
|
|
121
|
+
4. **Author the full flow** from what you observed → `scenario-update`. Make it a **verification flow**,
|
|
122
|
+
not a superficial run: exercise the cycle's evidence tools, capture their output with
|
|
123
|
+
`returnOutput: true`, and assert / return the expected outcomes — so running it later via
|
|
124
|
+
`/ironbee-verify scenario:<name>` can judge it and satisfy the gate.
|
|
125
|
+
5. **Validate:** `scenario-run` end-to-end; fix the **SCRIPT** + `scenario-update` until it runs
|
|
126
|
+
cleanly, and **assert the real terminal outcome — not an optimistic intermediate signal**. Same
|
|
127
|
+
app/env considerations as any verification run (use a test/staging target for flows with real side
|
|
128
|
+
effects).
|
|
129
|
+
6. **Teardown — leave a clean store:** `scenario-delete` ANY temporary / probe / throwaway scenario you
|
|
130
|
+
added this session (anything named `*-probe`, a draft you decided not to keep, an exploratory copy);
|
|
131
|
+
the store must end with ONLY the finished deliverable scenario(s), never a leftover probe. THEN stop
|
|
132
|
+
ONLY the app / processes you started.
|
|
133
|
+
7. Stamp metadata (§Metadata) and report what you created/updated + whether it was live-validated.
|
|
134
|
+
|
|
135
|
+
> **A genuine defect is a STOP, not a workaround.** If validating shows the flow can't legitimately
|
|
136
|
+
> succeed — a real bug makes the expected outcome unreachable (an error, a failed state, wrong
|
|
137
|
+
> resulting data) — do NOT engineer the scenario around it: don't cherry-pick inputs / args / data that
|
|
138
|
+
> dodge the bug, and don't weaken the assertion to an optimistic intermediate signal instead of the
|
|
139
|
+
> real terminal outcome. That yields a green scenario that masks a broken flow and produces a FALSE
|
|
140
|
+
> PASS when it's later run to verify. Instead STOP and report the defect to the user **in your summary,
|
|
141
|
+
> not inside the scenario** — keep the saved scenario a clean verification flow (it asserts the real
|
|
142
|
+
> outcome and will simply fail until the bug is fixed; that's it doing its job). Do NOT bake bug /
|
|
143
|
+
> defect commentary into the scenario's `description` or metadata; `liveValidated: false` is the only
|
|
144
|
+
> signal needed when you couldn't get a passing run — or leave the scenario unsaved. ("Fix until it
|
|
145
|
+
> passes" means fixing the SCRIPT, never working around the app.)
|
|
146
|
+
|
|
147
|
+
Do all of this through `scenario-add` / `scenario-update` / `scenario-run` — do NOT open a verification
|
|
148
|
+
cycle or call the platform tools directly. That keeps the work gate-orthogonal (no `verification_id` on
|
|
149
|
+
the calls, so it can't false-block a later edit); `scenario-run` runs the platform tools for you inside
|
|
150
|
+
the sandbox and hands back their results.
|
|
151
|
+
|
|
152
|
+
## Script format
|
|
153
|
+
A scenario `script` is JS run in the devtools sandbox (async — top-level `await`/`return` work).
|
|
154
|
+
It reads params from the `args` binding and invokes the platform's tools via `callTool`:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const { baseUrl } = args; // declared via argsSchema
|
|
158
|
+
const result = await callTool('<bare-tool-name>', { /* tool input */ });
|
|
159
|
+
return { ok: true };
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`args` is opaque to devtools — document the expected shape in the scenario's `description` and
|
|
163
|
+
the `argsSchema` metadata. **Discover the available `callTool` tool names for a platform from
|
|
164
|
+
your connected MCP tool schemas** (the bare names) — don't guess.
|
|
165
|
+
|
|
166
|
+
## Metadata conventions (stamp these on add/update)
|
|
167
|
+
- `ironbee.coveredPaths` — source paths the scenario exercises (array), when derivable.
|
|
168
|
+
- `argsSchema` — declared params, e.g. `{ "baseUrl": "string" }`.
|
|
169
|
+
**Mandatory for any parametric scenario** (run reads it to know what to ask).
|
|
170
|
+
- `ironbee.liveValidated` — `true` when you validated the scenario by running it end-to-end against
|
|
171
|
+
the live app this session; `false` when authored source-only (`draft`, or the app couldn't be
|
|
172
|
+
started). Always stamp it so a later reader knows whether the script is proven.
|
|
173
|
+
- `ironbee.commit` — the commit the scenario was authored against (`git rev-parse HEAD`).
|
|
174
|
+
- `ironbee.group` / `ironbee.order` — for a high-level scenario split across platforms: a shared
|
|
175
|
+
group slug + integer run order.
|
|
176
|
+
- `scenario-update` does a **shallow replace** of metadata — to change one key, re-send the FULL
|
|
177
|
+
metadata object (read it first, merge, write back).
|
|
178
|
+
|
|
179
|
+
The platform sections below tell you each enabled cycle's server, tool prefix, and store dir.
|
|
180
|
+
|
|
181
|
+
<!--IRONBEE:PLATFORM:browser-->
|
|
182
|
+
<!--/IRONBEE:PLATFORM:browser-->
|
|
183
|
+
|
|
184
|
+
<!--IRONBEE:PLATFORM:node-->
|
|
185
|
+
<!--/IRONBEE:PLATFORM:node-->
|
|
186
|
+
|
|
187
|
+
<!--IRONBEE:PLATFORM:backend-->
|
|
188
|
+
<!--/IRONBEE:PLATFORM:backend-->
|
|
189
|
+
|
|
190
|
+
<!--IRONBEE:PLATFORM:android-->
|
|
191
|
+
<!--/IRONBEE:PLATFORM:android-->
|
|
@@ -28,11 +28,28 @@ session, so the main agent's completion gate sees your work.
|
|
|
28
28
|
devtools tools; a code-reading "pass" is banned.
|
|
29
29
|
|
|
30
30
|
## Scenario
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
The delegating prompt may tell you what to verify in one of two ways:
|
|
32
|
+
|
|
33
|
+
- **A SAVED scenario** — the prompt says `Saved scenario: <ref>` (`<ref>` is an exact name OR a
|
|
34
|
+
semantic description; optional `args:` may follow). RESOLVE it: try an exact-name match
|
|
35
|
+
(`*_scenario-list`) AND a semantic `*_scenario-search` across the enabled platforms, then pick the
|
|
36
|
+
single strong match. Several plausible matches → ask which; **no match → say so and fall back to
|
|
37
|
+
discovery** (the free-text path below). Then **run it in ONE call: `*_scenario-run <name>`** (pass
|
|
38
|
+
any given `args`) — this executes the whole pre-recorded flow, so you do NOT re-discover or drive it
|
|
39
|
+
step by step (that's the speed win). **JUDGE the result**: functional (the script's returned
|
|
40
|
+
values / assertions / errors) AND any visual evidence it returned (e.g. screenshots) — then submit the verdict as
|
|
41
|
+
usual. The scenario's nested tool calls run inside THIS verification cycle, so they satisfy the
|
|
42
|
+
gate's required-tools for you (as long as the scenario exercises them).
|
|
43
|
+
**On a PASS verdict, also keep the scenario fresh:** `*_scenario-update` its `ironbee.commit`
|
|
44
|
+
→ current HEAD (`git rev-parse HEAD`) + `liveValidated: true` — read the current metadata and
|
|
45
|
+
re-send it MERGED (shallow replace; don't drop `coveredPaths` / `group` / `argsSchema`). On a
|
|
46
|
+
FAIL / defect, do NOT stamp (leave it for `/ironbee-sync-scenario scenario:<name>` or the user).
|
|
47
|
+
- **A FREE-TEXT scenario / file path** — anything else is authoritative: verify exactly what it
|
|
48
|
+
describes, driving each active cycle's tools to exercise precisely the flows, states, and endpoints
|
|
49
|
+
it names (this replaces the default "exercise the changed pages/endpoints").
|
|
50
|
+
|
|
51
|
+
Map each `checks` entry to a scenario step, each `issues` entry to a step that failed. If no scenario
|
|
52
|
+
is given at all, exercise the changed pages/endpoints for each active cycle.
|
|
36
53
|
|
|
37
54
|
## Session id — you don't need it
|
|
38
55
|
The `ironbee hook` commands resolve the session automatically from the environment
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
argument-hint: "[draft] [scope: project|global] <scenario content | file path | target to update/delete>"
|
|
3
|
+
description: Add, update, or delete a reusable IronBee verification scenario. Delegates to the ironbee-scenario sub-agent, which authors the script against the LIVE app (by default) and saves it to the right platform's store (or finds and updates/deletes an existing one). A leading `draft` token authors from source only (no app run).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IronBee — Manage scenario
|
|
7
|
+
|
|
8
|
+
Add / update / delete a reusable verification **scenario** by **delegating to the `ironbee-scenario`
|
|
9
|
+
sub-agent**. It owns the devtools `scenario-*` tools; you resolve the request and relay its result.
|
|
10
|
+
**You do not run the scenario tools yourself.**
|
|
11
|
+
|
|
12
|
+
> Argument: `$ARGUMENTS`
|
|
13
|
+
|
|
14
|
+
## Steps
|
|
15
|
+
1. **If the request points to a file path** (scenario content to save), read that file now with your
|
|
16
|
+
file-read tool and pass its **contents** into the sub-agent's prompt (the sub-agent's `Read` is
|
|
17
|
+
for project files, but pass content you already resolved to be safe). If a given path doesn't
|
|
18
|
+
resolve, stop and report `scenario file not found: <path>`.
|
|
19
|
+
2. **Spawn** the Agent tool with `subagent_type: "ironbee-scenario"` and a prompt:
|
|
20
|
+
> Operation: manage
|
|
21
|
+
> Request: \<the user's request — content to add/update, or the target to update/delete>
|
|
22
|
+
> Scope: \<`global` if the user asked, else `project`>
|
|
23
|
+
> Mode: \<include `Mode: draft` ONLY if the user's request begins with a `draft` token (author from
|
|
24
|
+
> source only, no app run) — otherwise OMIT this line so the verifier authors against the live app>
|
|
25
|
+
The sub-agent decides add vs update (it checks for an existing same-name scenario first), picks
|
|
26
|
+
the right platform, authors the script — **against the live app by default** (it starts the app if
|
|
27
|
+
needed, observes the real behavior, validates by running once, then cleans up — deletes any probe /
|
|
28
|
+
throwaway scenarios it added and stops what it started; `draft`
|
|
29
|
+
skips this) — and stamps metadata (`argsSchema` for parametric ones).
|
|
30
|
+
**Delete and fuzzy-resolved update will ask you to confirm** the matched scenario first — relay
|
|
31
|
+
that confirmation prompt to the user and pass their answer back.
|
|
32
|
+
**Wait for the sub-agent in the same turn — do NOT background it.**
|
|
33
|
+
3. **Relay** the sub-agent's summary (what it created / updated / deleted, on which platform).
|
|
34
|
+
|
|
35
|
+
This is NOT a verification cycle — it submits no verdict and does not gate completion. The
|
|
36
|
+
per-platform "how to author scenarios" detail lives in the `ironbee-scenario` sub-agent.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
argument-hint: "<what to find: name / description / metadata, e.g. covered path>"
|
|
3
|
+
description: Find reusable IronBee verification scenarios by name, description, or metadata. Delegates to the ironbee-scenario sub-agent, which searches every enabled platform's store (fuzzy over name+description, or precise metadata match) and returns the matches.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IronBee — Search scenarios
|
|
7
|
+
|
|
8
|
+
Find saved verification **scenarios** by **delegating to the `ironbee-scenario` sub-agent**.
|
|
9
|
+
**You do not run the scenario tools yourself.**
|
|
10
|
+
|
|
11
|
+
> Query: `$ARGUMENTS`
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
1. **Spawn** the Agent tool with `subagent_type: "ironbee-scenario"` and a prompt:
|
|
15
|
+
> Operation: search
|
|
16
|
+
> Query: \<the user's description — a name/topic for fuzzy search, or a path/tag for metadata match>
|
|
17
|
+
The sub-agent picks the right surface (fuzzy name+description vs precise `metadataMatch`),
|
|
18
|
+
searches **every enabled platform's store**, and unions the results.
|
|
19
|
+
**Wait for the sub-agent in the same turn — do NOT background it.**
|
|
20
|
+
2. **Relay** the matches — name, description, platform, and (for fuzzy search) relevance score.
|
|
21
|
+
|
|
22
|
+
This is read-only — it changes nothing.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
argument-hint: "[check] [force] <name | description | all>"
|
|
3
|
+
description: Re-validate saved verification scenarios against the current code and repair drift. Delegates to the ironbee-scenario sub-agent (operation sync) — runs the target scenario(s), repairs MECHANICAL drift (the way the flow is driven), and stamps them current; it never auto-changes what a scenario verifies. A leading `check` token = dry-run (report drift, no repair).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IronBee — Sync scenario(s)
|
|
7
|
+
|
|
8
|
+
Re-validate + repair saved verification **scenarios** by **delegating to the `ironbee-scenario`
|
|
9
|
+
sub-agent** (operation `sync`). **You do not run the scenario tools yourself.**
|
|
10
|
+
|
|
11
|
+
> Argument: `$ARGUMENTS`
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
1. **Resolve the mode + target**: strip a leading `check` token (→ dry-run) and a leading `force`
|
|
15
|
+
token (→ sync ALL scenarios, not just stale); the remainder is the target — `all` (stale ones;
|
|
16
|
+
`force` = every one), or a name / semantic description (one scenario). Empty → `all`.
|
|
17
|
+
2. **Spawn** the Agent tool with `subagent_type: "ironbee-scenario"` and a prompt:
|
|
18
|
+
> Operation: sync
|
|
19
|
+
> Target: \<`all`, or the name / description>
|
|
20
|
+
> Force: \<include `Force: all` ONLY if the request began with `force` (sync every scenario, not just stale)>
|
|
21
|
+
> Mode: \<include `Mode: check` ONLY if the request began with `check` (dry-run — report drift, do
|
|
22
|
+
> NOT repair / update); otherwise OMIT (run + repair mechanics + update)>
|
|
23
|
+
The sub-agent runs each target scenario against the live app and classifies the outcome — still-fresh
|
|
24
|
+
/ **mechanical drift** → repair the SCRIPT only / **real defect** → STOP + report (don't touch the
|
|
25
|
+
scenario) / **expectation changed** → ask you — and, on a non-check run, stamps repaired scenarios
|
|
26
|
+
current. **It repairs MECHANICS, never WHAT a scenario verifies.**
|
|
27
|
+
**Wait for the sub-agent in the same turn — do NOT background it.**
|
|
28
|
+
3. **Relay** the sub-agent's summary (per scenario: repaired / still-fresh / defect-reported / needs decision).
|
|
29
|
+
|
|
30
|
+
This is NOT a verification cycle — it submits no verdict and does not gate completion. (To just *detect*
|
|
31
|
+
which scenarios are stale without running anything, use `ironbee scenario status`.)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
argument-hint: "[fix|report] [scenario text | path to scenario file]"
|
|
3
|
-
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent. Default is verify-only (report the verdict and stop); a leading `fix` argument adds the fix-and-re-verify loop until pass.
|
|
2
|
+
argument-hint: "[fix|report] [scenario:<name|description> | scenario text | path to scenario file]"
|
|
3
|
+
description: Delegate verification of the current code changes to the ironbee-verifier sub-agent. Default is verify-only (report the verdict and stop); a leading `fix` argument adds the fix-and-re-verify loop until pass. Pass `scenario:<name or description>` to run a SAVED scenario (fast, no re-discovery), or a custom scenario (inline text / file path) defining what to verify.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# IronBee Verify
|
|
@@ -19,23 +19,24 @@ The FIRST whitespace-delimited token of the arguments selects the mode; everythi
|
|
|
19
19
|
|
|
20
20
|
## Verification scenario
|
|
21
21
|
|
|
22
|
-
A custom verification scenario may be supplied when this command is invoked — either as **inline text** or as a **path to a file** (any location, any format; read at run time).
|
|
23
|
-
|
|
24
22
|
> Mode + scenario argument: `$ARGUMENTS`
|
|
25
|
-
> *(strip a leading `fix` / `report` mode token first — the remainder is the scenario
|
|
23
|
+
> *(strip a leading `fix` / `report` mode token first — the remainder is the scenario part)*
|
|
24
|
+
|
|
25
|
+
The scenario part (after the optional mode token) is one of three forms:
|
|
26
|
+
|
|
27
|
+
- **A SAVED scenario** — the remainder **starts with `scenario:`**. Then **everything after `scenario:` (to the end) is the reference** — an exact name OR a semantic description (`scenario:` must come AFTER the mode token; it consumes the rest of the line). Do NOT read a file or treat it as free text. Relay it to the verifier verbatim as a `Saved scenario: <ref>` line; the verifier resolves it (`scenario-search` semantic + exact-name match), runs it in one `scenario-run` call (no re-discovery), and judges the result (functional + any visual evidence it returned). (No exact name needed — a description like `scenario: the full purchase flow` works; the verifier picks the match, asks if ambiguous, falls back to discovery if none.)
|
|
28
|
+
- **A free-text scenario** — inline text describing what to verify. Authoritative: the verifier exercises exactly what it names (replaces the default "exercise the changed pages/endpoints").
|
|
29
|
+
- **A file path** — read that file yourself and pass its **contents** to the verifier (it has no file-read tool). If the path doesn't resolve, stop and report `scenario file not found: <path>` and ask how to proceed — don't delegate the literal path.
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
- **If the scenario is (or points to) a file path**, read that file with your file-read tool yourself and pass its **contents** into the verifier's prompt (the verifier has no file-read tool). Do not assume a fixed location or format — read whatever path was given.
|
|
29
|
-
- **If the path does not resolve to an existing file**, stop and report `scenario file not found: <path>`, then ask how to proceed — do not delegate with the literal path string or guess a target.
|
|
30
|
-
- **If no scenario is supplied**, the verifier falls back to exercising the changed pages/endpoints per the active cycles.
|
|
31
|
+
**If no scenario part is supplied**, the verifier exercises the changed pages/endpoints per the active cycles.
|
|
31
32
|
|
|
32
33
|
## Steps
|
|
33
34
|
|
|
34
|
-
1. **Resolve the mode and scenario**: strip a leading `fix` / `report` token (see **Mode**); then file path → read it now; inline text → use as-is; empty → none.
|
|
35
|
-
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task, the mode, and the
|
|
35
|
+
1. **Resolve the mode and scenario**: strip a leading `fix` / `report` token (see **Mode**); then on the remainder — starts with `scenario:` → it's a SAVED scenario reference (everything after `scenario:`); a file path → read it now; inline text → use as-is; empty → none.
|
|
36
|
+
2. **Spawn the verifier** via the Agent tool with `subagent_type: "ironbee-verifier"` and a prompt that states the task, the mode, and the scenario, e.g.:
|
|
36
37
|
> Verify the current code changes.
|
|
37
38
|
> Mode: \<`fix` in fix mode — OMIT this line entirely in verify-only mode>
|
|
38
|
-
>
|
|
39
|
+
> \<ONE of: `Saved scenario: <ref>` (when `scenario:` was given — the verifier resolves + runs it) — OR — `Scenario: <resolved text>` (free text / file contents) — OR — `Scenario: none — exercise the changed pages/endpoints`>>
|
|
39
40
|
The verifier runs `verification-start` (relaying the fix intent to IronBee's completion gate, which then enforces fix-until-pass on you) → drives every active cycle's tools → submits the single verdict, all in this shared session. It resolves the session id from the environment, so you don't pass one.
|
|
40
41
|
**Wait for the verifier in the same turn — do NOT background it.** Let it run to completion and read its verdict before responding; a backgrounded verifier can let your turn end (and the Stop gate fire) before its verdict is recorded.
|
|
41
42
|
3. **Relay the verifier's summary** — the verdict status and, on fail, the issues it found.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=Object.defineProperty;var
|
|
1
|
+
"use strict";var e=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(o,s)=>e(o,"name",{value:s,configurable:!0});var S=(o,s)=>{for(var i in s)e(o,i,{get:s[i],enumerable:!0})},y=(o,s,i,n)=>{if(s&&typeof s=="object"||typeof s=="function")for(let t of m(s))!f.call(o,t)&&t!==i&&e(o,t,{get:()=>s[t],enumerable:!(n=l(s,t))||n.enumerable});return o};var k=o=>y(e({},"__esModule",{value:!0}),o);var v={};S(v,{run:()=>I});module.exports=k(v);var p=require("../../../hooks/core/activity-end"),r=require("../../../lib/logger"),c=require("../../../lib/stdin"),d=require("../../../analytics/claude/hook-trigger"),u=require("../../../lib/runtime-paths");async function I(o){let s;try{s=JSON.parse((0,c.readStdin)())}catch(g){r.logger.debug(`failed to parse stdin: ${g}`),process.exit(0)}const i=s.session_id??"default",n=(0,u.sessionDir)(o,i),t=`${n}/actions.jsonl`;(0,r.setLogFile)(`${n}/session.log`),await(0,p.runActivityEnd)({sessionDir:n,actionsFile:t,projectDir:o,sessionId:i}),(0,d.runAnalyticsTrigger)({projectDir:o,sessionId:i,triggerType:"Stop",transcriptSource:"claude-code"}),process.exit(0)}a(I,"run");0&&(module.exports={run});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var e=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var a=(i,t)=>e(i,"name",{value:t,configurable:!0});var v=(i,t)=>{for(var r in t)e(i,r,{get:t[r],enumerable:!0})},x=(i,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of S(t))!U.call(i,s)&&s!==r&&e(i,s,{get:()=>t[s],enumerable:!(o=b(t,s))||o.enumerable});return i};var y=i=>x(e({},"__esModule",{value:!0}),i);var k={};v(k,{isNonUserUps:()=>f,run:()=>P});module.exports=y(k);var p=require("../../../hooks/core/actions"),c=require("../../../hooks/core/activity"),m=require("../../../hooks/core/session-state"),n=require("../../../lib/logger"),u=require("../../../lib/stdin"),d=require("../../../otel/claude/daemon/ensure"),l=require("../../../lib/runtime-paths");function f(i){return(i??"").replace(/^\s+/,"").startsWith("<task-notification")}a(f,"isNonUserUps");async function P(i){let t;try{t=JSON.parse((0,u.readStdin)())}catch(g){n.logger.debug(`failed to parse stdin: ${g}`),process.exit(0)}const r=t.session_id??"default",o=(0,l.sessionDir)(i,r);(0,n.setLogFile)(`${o}/session.log`);const s=`${o}/actions.jsonl`;f(t.prompt)&&(n.logger.debug("activity-start: skip non-user UPS (<task-notification> continuation)"),process.exit(0)),await(0,m.reconcileAbandonedActivity)(o,s,p.appendAction),await(0,c.startActivity)({sessionDir:o,actionsFile:s,source:"user_prompt"}),await(0,d.ensureOTELCollector)(i),process.exit(0)}a(P,"run");0&&(module.exports={isNonUserUps,run});
|