github-issue-tower-defence-management 1.97.3 → 1.98.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 +14 -0
- package/README.md +12 -0
- package/bin/adapter/entry-points/cli/index.js +62 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/{index-CDGwyvDF.js → index-BFKRWKvS.js} +1 -1
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.js +48 -0
- package/bin/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.js.map +1 -0
- package/bin/adapter/repositories/ProcClaudeInteractiveSessionRepository.js +145 -0
- package/bin/adapter/repositories/ProcClaudeInteractiveSessionRepository.js.map +1 -0
- package/bin/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.js +41 -0
- package/bin/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.js.map +1 -0
- package/bin/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.js +3 -0
- package/bin/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +120 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleCloseActions.tsx +4 -4
- package/src/adapter/entry-points/console/ui-dist/assets/{index-CDGwyvDF.js → index-BFKRWKvS.js} +1 -1
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +123 -0
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.ts +77 -0
- package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.test.ts +184 -0
- package/src/adapter/repositories/ProcClaudeInteractiveSessionRepository.ts +138 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +169 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.ts +63 -0
- package/src/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.ts +9 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.d.ts +18 -0
- package/types/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.d.ts.map +1 -0
- package/types/adapter/repositories/ProcClaudeInteractiveSessionRepository.d.ts +11 -0
- package/types/adapter/repositories/ProcClaudeInteractiveSessionRepository.d.ts.map +1 -0
- package/types/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.d.ts +17 -0
- package/types/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.d.ts.map +1 -0
- package/types/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.d.ts +9 -0
- package/types/domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository.d.ts.map +1 -0
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
} from '../console/consoleProjectResolver';
|
|
44
44
|
import { OauthTokenSelectHandler } from '../handlers/OauthTokenSelectHandler';
|
|
45
45
|
import { LiveSessionOauthTokenSelectHandler } from '../handlers/LiveSessionOauthTokenSelectHandler';
|
|
46
|
+
import { InTmuxByHumanSessionTokenCountHandler } from '../handlers/InTmuxByHumanSessionTokenCountHandler';
|
|
46
47
|
|
|
47
48
|
type StartDaemonOptions = {
|
|
48
49
|
projectUrl?: string;
|
|
@@ -95,6 +96,12 @@ type SelectLiveSessionOauthTokenOptions = {
|
|
|
95
96
|
cacheDir?: string;
|
|
96
97
|
};
|
|
97
98
|
|
|
99
|
+
type CountInTmuxByHumanSessionsPerTokenOptions = {
|
|
100
|
+
configFilePath: string;
|
|
101
|
+
projectUrl?: string;
|
|
102
|
+
tokenListJsonPath?: string;
|
|
103
|
+
};
|
|
104
|
+
|
|
98
105
|
const buildGithubRepositoryParams = (
|
|
99
106
|
localStorageRepository: LocalStorageRepository,
|
|
100
107
|
token: string,
|
|
@@ -795,6 +802,119 @@ program
|
|
|
795
802
|
process.stdout.write(`${output.selectedToken}\n`);
|
|
796
803
|
});
|
|
797
804
|
|
|
805
|
+
program
|
|
806
|
+
.command('countInTmuxByHumanSessionsPerToken')
|
|
807
|
+
.description(
|
|
808
|
+
'Print, per Claude Code OAuth token, the count of live interactive sessions (cl-launched Claude processes carrying CLAUDE_CODE_OAUTH_TOKEN and CLAUDE_CODE_SESSION_ID with a --name <issue-url> argument, excluding Take ownership spawns) whose issue is currently in GitHub Project Status "In Tmux by human". One tab-separated line per token (<tokenName>\\t<count>) is written to stdout; the decision trace is written to stderr. Token values are never printed.',
|
|
809
|
+
)
|
|
810
|
+
.requiredOption(
|
|
811
|
+
'--configFilePath <path>',
|
|
812
|
+
'Path to config file for tower defence management',
|
|
813
|
+
)
|
|
814
|
+
.option('--projectUrl <url>', 'GitHub project URL (optional)')
|
|
815
|
+
.option(
|
|
816
|
+
'--tokenListJsonPath <path>',
|
|
817
|
+
'Path to the JSON array of { name, token } records. Falls back to the claudeCodeOauthTokenListJsonPath config value, then to the CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH environment variable.',
|
|
818
|
+
)
|
|
819
|
+
.action(async (options: CountInTmuxByHumanSessionsPerTokenOptions) => {
|
|
820
|
+
const token = process.env.GH_TOKEN;
|
|
821
|
+
if (!token) {
|
|
822
|
+
console.error('GH_TOKEN environment variable is required');
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const configFileValues = loadConfigFile(options.configFilePath);
|
|
827
|
+
|
|
828
|
+
const cliOverrides: ConfigFile = {
|
|
829
|
+
projectUrl: options.projectUrl,
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
const tempProjectUrl =
|
|
833
|
+
cliOverrides.projectUrl ?? configFileValues.projectUrl;
|
|
834
|
+
|
|
835
|
+
let readmeOverrides: ConfigFile = {};
|
|
836
|
+
if (tempProjectUrl) {
|
|
837
|
+
const readme = await fetchProjectReadme(tempProjectUrl, token);
|
|
838
|
+
if (readme) {
|
|
839
|
+
readmeOverrides = parseProjectReadmeConfig(readme, tempProjectUrl);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const config = mergeConfigs(
|
|
844
|
+
configFileValues,
|
|
845
|
+
cliOverrides,
|
|
846
|
+
readmeOverrides,
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
const projectUrl = config.projectUrl;
|
|
850
|
+
if (!projectUrl) {
|
|
851
|
+
console.error(
|
|
852
|
+
'projectUrl is required. Provide via --projectUrl, config file, or project README.',
|
|
853
|
+
);
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const projectName = config.projectName ?? 'default';
|
|
858
|
+
const localStorageRepository = new LocalStorageRepository();
|
|
859
|
+
const cachePath = `./tmp/cache/${projectName}`;
|
|
860
|
+
const localStorageCacheRepository = new LocalStorageCacheRepository(
|
|
861
|
+
localStorageRepository,
|
|
862
|
+
cachePath,
|
|
863
|
+
);
|
|
864
|
+
const githubRepositoryParams = buildGithubRepositoryParams(
|
|
865
|
+
localStorageRepository,
|
|
866
|
+
token,
|
|
867
|
+
);
|
|
868
|
+
const projectRepository = new GraphqlProjectRepository(
|
|
869
|
+
...githubRepositoryParams,
|
|
870
|
+
);
|
|
871
|
+
const apiV3IssueRepository = new ApiV3IssueRepository(
|
|
872
|
+
...githubRepositoryParams,
|
|
873
|
+
);
|
|
874
|
+
const restIssueRepository = new RestIssueRepository(
|
|
875
|
+
...githubRepositoryParams,
|
|
876
|
+
);
|
|
877
|
+
const graphqlProjectItemRepository = new GraphqlProjectItemRepository(
|
|
878
|
+
...githubRepositoryParams,
|
|
879
|
+
);
|
|
880
|
+
const issueRepository = new ApiV3CheerioRestIssueRepository(
|
|
881
|
+
apiV3IssueRepository,
|
|
882
|
+
restIssueRepository,
|
|
883
|
+
graphqlProjectItemRepository,
|
|
884
|
+
localStorageCacheRepository,
|
|
885
|
+
...githubRepositoryParams,
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
const projectId = await projectRepository.findProjectIdByUrl(projectUrl);
|
|
889
|
+
if (!projectId) {
|
|
890
|
+
console.error(`No project found for projectUrl ${projectUrl}`);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const allowIssueCacheMinutes = config.allowIssueCacheMinutes ?? 10;
|
|
895
|
+
const { issues } = await issueRepository.getAllIssues(
|
|
896
|
+
projectId,
|
|
897
|
+
allowIssueCacheMinutes,
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
const handler = new InTmuxByHumanSessionTokenCountHandler();
|
|
901
|
+
const output = handler.handle({
|
|
902
|
+
tokenListJsonPath:
|
|
903
|
+
options.tokenListJsonPath ??
|
|
904
|
+
config.claudeCodeOauthTokenListJsonPath ??
|
|
905
|
+
null,
|
|
906
|
+
issues,
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
for (const line of output.diagnostics) {
|
|
910
|
+
console.error(line);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
for (const line of output.lines) {
|
|
914
|
+
process.stdout.write(`${line}\n`);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
798
918
|
/* istanbul ignore next */
|
|
799
919
|
if (process.argv && require.main === module) {
|
|
800
920
|
program.parse(process.argv);
|
|
@@ -11,16 +11,16 @@ export const ConsoleCloseActions = ({
|
|
|
11
11
|
<button
|
|
12
12
|
type="button"
|
|
13
13
|
className="console-op-button"
|
|
14
|
-
onClick={() => onClose('
|
|
14
|
+
onClick={() => onClose('close_not_planned')}
|
|
15
15
|
>
|
|
16
|
-
Close
|
|
16
|
+
Close as not planned
|
|
17
17
|
</button>
|
|
18
18
|
<button
|
|
19
19
|
type="button"
|
|
20
20
|
className="console-op-button"
|
|
21
|
-
onClick={() => onClose('
|
|
21
|
+
onClick={() => onClose('close')}
|
|
22
22
|
>
|
|
23
|
-
Close
|
|
23
|
+
Close
|
|
24
24
|
</button>
|
|
25
25
|
</div>
|
|
26
26
|
);
|
package/src/adapter/entry-points/console/ui-dist/assets/{index-CDGwyvDF.js → index-BFKRWKvS.js}
RENAMED
|
@@ -98,4 +98,4 @@ Please report this to https://github.com/markedjs/marked.`,i){const f="<p>An err
|
|
|
98
98
|
`)}),d+=1,c=[])};for(const S of i){if(f===null&&/^```mermaid\s*$/.test(S.trim())){h(),f=[];continue}if(f!==null){if(S.trim()==="```"){o.push({kind:"mermaid",key:`mermaid:${d}`,code:f.join(`
|
|
99
99
|
`)}),d+=1,f=null;continue}f.push(S);continue}c.push(S)}return f!==null&&c.push("```mermaid",...f),h(),o},fb="https://cdn.jsdelivr.net/npm/mermaid@10.9.6/dist/mermaid.min.js";let au=null,Xh=0;const Qh=s=>(s.initialize({startOnLoad:!1,securityLevel:"strict",theme:"dark",themeVariables:{background:"#0d1117",primaryColor:"#161b22",primaryTextColor:"#e6edf3",primaryBorderColor:"#30363d",lineColor:"#8b949e",fontSize:"14px"}}),s),db=()=>au!==null?au:window.mermaid!==void 0?(au=Promise.resolve(Qh(window.mermaid)),au):(au=new Promise((s,i)=>{const o=document.createElement("script");o.src=fb,o.async=!0,o.onload=()=>{if(window.mermaid===void 0){i(new Error("mermaid failed to load"));return}s(Qh(window.mermaid))},o.onerror=()=>i(new Error("mermaid script failed to load")),document.head.appendChild(o)}),au),mb=async s=>{const i=await db();Xh+=1;const o=`console-mermaid-${Xh}`,{svg:c}=await i.render(o,s);return up.sanitize(c,{USE_PROFILES:{svg:!0,svgFilters:!0},ADD_TAGS:["foreignObject"]})},hb=({code:s})=>{const[i,o]=G.useState({status:"loading"}),c=G.useRef(null);return G.useEffect(()=>{let f=!1;return o({status:"loading"}),mb(s).then(d=>{f||o({status:"ready",svg:d})}).catch(d=>{f||o({status:"error",message:d instanceof Error?d.message:String(d)})}),()=>{f=!0}},[s]),G.useEffect(()=>{const f=c.current;f!==null&&(f.innerHTML=i.status==="ready"?i.svg:"")},[i]),i.status==="loading"?E.jsx("div",{className:"console-mermaid-loading",children:"Rendering diagram..."}):i.status==="error"?E.jsxs("div",{className:"console-mermaid",children:[E.jsxs("div",{className:"console-mermaid-error",children:["Mermaid render error: ",i.message]}),E.jsx("pre",{className:"console-mermaid-source",children:E.jsx("code",{children:s})})]}):E.jsx("div",{ref:c,className:"console-mermaid-rendered"})},pb=({source:s,buildImageProxyUrl:i})=>{const o=G.useMemo(()=>{const f=ob(s);return i===void 0?f:Jy(f,i)},[s,i]),c=G.useRef(null);return G.useEffect(()=>{const f=c.current;f!==null&&(f.innerHTML=o)},[o]),E.jsx("div",{ref:c,className:"console-markdown"})},$c=({body:s,buildImageProxyUrl:i})=>{const o=G.useMemo(()=>rb(s),[s]);return s.trim()===""?E.jsx("p",{className:"console-markdown-empty",children:"No description provided."}):E.jsx("div",{className:"console-markdown-view",children:o.map(c=>c.kind==="mermaid"?E.jsx(hb,{code:c.code},c.key):E.jsx(pb,{source:c.source,buildImageProxyUrl:i},c.key))})},gb=({isPr:s,now:i,onSubmit:o})=>{const[c,f]=G.useState(!s),[d,h]=G.useState(""),[S,g]=G.useState({kind:"idle"}),[T,z]=G.useState([]),R=async()=>{const C=d.trim();if(!(C.length===0||S.kind==="posting")){g({kind:"posting"});try{const H=await o(C);z(I=>[...I,H]),h(""),g({kind:"idle"})}catch(H){g({kind:"error",message:H instanceof Error?H.message:"failed to post"})}}};return E.jsxs("div",{className:"console-composer",children:[E.jsx("button",{type:"button",className:"console-composer-toggle","aria-expanded":c,onClick:()=>f(C=>!C),children:c?"✕ Cancel":"💬 Add a comment"}),T.length>0&&E.jsx("div",{className:"console-composer-posted",children:T.map(C=>E.jsxs("article",{className:"console-comment",children:[E.jsxs("header",{className:"console-comment-header",title:Sr(C.createdAt),children:[E.jsx("span",{className:"console-comment-author",children:C.author===""?"you":C.author}),E.jsx("span",{className:"console-comment-time",children:mi(C.createdAt,i)})]}),E.jsx($c,{body:C.body})]},`${C.author}:${C.createdAt}:${C.body}`))}),c&&E.jsxs("div",{className:"console-composer-form",children:[E.jsx("textarea",{className:"console-composer-input",rows:3,placeholder:"Leave a comment…",value:d,onChange:C=>h(C.target.value)}),E.jsxs("div",{className:"console-composer-row",children:[E.jsx("button",{type:"button",className:"console-composer-submit",disabled:S.kind==="posting",onClick:()=>{R()},children:"Comment"}),S.kind==="posting"&&E.jsx("span",{className:"console-composer-status",children:"Posting…"}),S.kind==="error"&&E.jsxs("span",{role:"alert",className:"console-composer-status console-composer-error",children:["Failed: ",S.message]})]})]})]})},sa=({title:s,count:i=null,defaultCollapsed:o=!1,headerAction:c,children:f})=>{const[d,h]=G.useState(o),S=i===null?s:`${s} (${i})`;return E.jsxs("section",{className:"console-panel",children:[E.jsxs("header",{className:"console-panel-header",children:[E.jsxs("button",{type:"button",className:"console-panel-toggle","aria-expanded":!d,onClick:()=>h(g=>!g),children:[E.jsx("span",{className:"console-panel-caret",children:d?"▸":"▾"}),E.jsx("span",{className:"console-panel-title",children:S})]}),c!==void 0&&E.jsx("div",{className:"console-panel-action",children:c})]}),!d&&E.jsx("div",{className:"console-panel-body",children:f})]})},yb=s=>{switch(s){case"added":return{label:"A",color:"#3fb950"};case"modified":return{label:"M",color:"#d29922"};case"removed":return{label:"D",color:"#f85149"};case"renamed":return{label:"R",color:"#a371f7"};case"changed":return{label:"M",color:"#d29922"};default:return{label:"?",color:"#8b949e"}}},bb=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/,vb=s=>{const i=[];let o=0,c=0;for(const f of s.split(`
|
|
100
100
|
`)){if(f.startsWith("@@")){const d=f.match(bb);d!==null&&(o=Number(d[1]),c=Number(d[2])),i.push({kind:"hunk",oldLineNumber:null,newLineNumber:null,content:f});continue}if(f.startsWith("+")){i.push({kind:"add",oldLineNumber:null,newLineNumber:c,content:f}),c+=1;continue}if(f.startsWith("-")){i.push({kind:"del",oldLineNumber:o,newLineNumber:null,content:f}),o+=1;continue}i.push({kind:"ctx",oldLineNumber:o,newLineNumber:c,content:f}),o+=1,c+=1}return i},Sb=({patch:s})=>{if(s===null||s==="")return E.jsx("p",{className:"console-file-diff-empty",children:"(no diff / binary or too large)"});const i=vb(s);return E.jsx("table",{className:"console-file-diff",children:E.jsx("tbody",{children:i.map(o=>E.jsxs("tr",{className:`console-diff-row console-diff-${o.kind}`,children:[E.jsx("td",{className:"console-diff-ln",children:o.oldLineNumber??""}),E.jsx("td",{className:"console-diff-ln",children:o.newLineNumber??""}),E.jsx("td",{className:"console-diff-code",children:o.content})]},`${o.kind}:${o.oldLineNumber??"x"}:${o.newLineNumber??"x"}:${o.content}`))})})},Tb=({file:s})=>{const[i,o]=G.useState(!1),c=yb(s.status);return E.jsxs("li",{className:"console-file",children:[E.jsxs("button",{type:"button",className:"console-file-row","aria-expanded":i,onClick:()=>o(f=>!f),children:[E.jsx("span",{className:"console-file-caret",children:i?"▾":"▸"}),E.jsx("span",{className:"console-file-badge",style:{color:c.color,borderColor:c.color},children:c.label}),E.jsx("span",{className:"console-file-path",children:s.path}),E.jsxs("span",{className:"console-file-stat console-file-add",children:["+",s.additions]}),E.jsxs("span",{className:"console-file-stat console-file-del",children:["-",s.deletions]})]}),i&&E.jsx(Sb,{patch:s.patch})]})},yp=({files:s,isLoading:i,error:o})=>o!==null?E.jsxs("p",{role:"alert",className:"console-files-error",children:["Failed to load changed files: ",o]}):i?E.jsx("p",{className:"console-files-loading",children:"Loading changed files..."}):s.length===0?E.jsx("p",{className:"console-files-empty",children:"No changed files."}):E.jsx("ul",{className:"console-files",children:s.map(c=>E.jsx(Tb,{file:c},c.path))}),Eb=({comments:s,isLoading:i,error:o,now:c,buildImageProxyUrl:f})=>{const[d,h]=G.useState(!1);if(o!==null)return E.jsxs("p",{role:"alert",className:"console-comment-error",children:["Failed to load comments: ",o]});if(i)return E.jsx("p",{className:"console-comment-loading",children:"Loading comments..."});if(s.length===0)return E.jsx("p",{className:"console-comment-empty",children:"No comments."});const S=d?s:s.slice(-1);return E.jsxs("div",{className:"console-comment-list",children:[!d&&s.length>1&&E.jsxs("button",{type:"button",className:"console-comment-show-all",onClick:()=>h(!0),children:["Show all ",s.length]}),S.map(g=>E.jsxs("article",{className:"console-comment",children:[E.jsxs("header",{className:"console-comment-header",children:[E.jsx("span",{className:"console-comment-author",children:g.author}),E.jsx("span",{className:"console-comment-time",children:mi(g.createdAt,c)})]}),E.jsx($c,{body:g.body,buildImageProxyUrl:f})]},`${g.author}:${g.createdAt}:${g.body}`))]})},Ab=s=>s.slice(0,7),_b=s=>s.split(`
|
|
101
|
-
`)[0],bp=({commits:s,isLoading:i,error:o,now:c})=>o!==null?E.jsxs("p",{role:"alert",className:"console-commits-error",children:["Failed to load commits: ",o]}):i?E.jsx("p",{className:"console-commits-loading",children:"Loading commits..."}):s.length===0?E.jsx("p",{className:"console-commits-empty",children:"No commits."}):E.jsx("ul",{className:"console-commits",children:s.map(f=>E.jsxs("li",{className:"console-commit",children:[E.jsx("span",{className:"console-commit-message",children:_b(f.message)}),E.jsx("span",{className:"console-commit-sha",children:Ab(f.sha)}),E.jsx("span",{className:"console-commit-author",children:f.author}),E.jsx("span",{className:"console-commit-time",children:mi(f.authoredAt,c)})]},f.sha))}),xb=({pullRequest:s,body:i,bodyIsLoading:o,files:c,filesAreLoading:f,filesError:d,commits:h,commitsAreLoading:S,commitsError:g,now:T,buildImageProxyUrl:z})=>{const R=s.summary,C=f||d!==null?null:c.length,H=S||g!==null?null:h.length;return E.jsxs(E.Fragment,{children:[E.jsxs("div",{className:"console-pr-header",children:[E.jsx("a",{href:s.url,className:"console-pr-section-title",target:"_blank",rel:"noopener noreferrer",children:(R==null?void 0:R.title)??s.url}),s.isDraft&&E.jsx("span",{className:"console-pr-section-state",children:"draft"}),E.jsxs("div",{className:"console-pr-statbar",children:[s.branchName!==null&&E.jsx("span",{className:"console-pr-branch",children:s.branchName}),R!==null&&E.jsxs(E.Fragment,{children:[E.jsxs("span",{className:"console-pr-add",children:["+",R.additions]}),E.jsxs("span",{className:"console-pr-del",children:["-",R.deletions]}),E.jsxs("span",{className:"console-pr-files-count",children:[R.changedFiles," files"]})]})]})]}),E.jsx(sa,{title:"Description",defaultCollapsed:!0,children:o?E.jsx("p",{className:"console-pr-body-loading",children:"Loading description..."}):E.jsx($c,{body:(R==null?void 0:R.body)??i,buildImageProxyUrl:z})}),E.jsx(sa,{title:"Changed files",count:C,children:E.jsx(yp,{files:c,isLoading:f,error:d})}),E.jsx(sa,{title:"Commits",count:H,defaultCollapsed:!0,children:E.jsx(bp,{commits:h,isLoading:S,error:g,now:T})})]})},Nb=({item:s,storyName:i,storyColorEnum:o,overlayStatus:c,state:f,body:d,bodyIsLoading:h,bodyError:S,comments:g,commentsAreLoading:T,commentsError:z,files:R,filesAreLoading:C,filesError:H,commits:I,commitsAreLoading:F,commitsError:K,relatedPullRequests:ct,now:J,commentComposer:yt,operationBar:st,buildImageProxyUrl:Nt})=>{const pt=(f==null?void 0:f.state)??"open",tt=(f==null?void 0:f.merged)??!1,Mt=!s.isPr&&pt==="closed"?"Closed":null,Pt=di(o),te=c?di(c.color):null,he=C||H!==null?null:R.length,Bt=T||z!==null?null:g.length,Ue=F||K!==null?null:I.length;return E.jsxs("article",{className:"console-detail",children:[i!==null&&E.jsx("div",{className:"console-detail-story",children:E.jsxs("span",{className:"console-storytag",children:[E.jsx("span",{className:"console-story-dot",style:{backgroundColor:Pt.dot}}),i]})}),c!==null&&te!==null&&E.jsx("span",{className:"console-detail-status-chip",style:{color:te.fg,borderColor:te.border,backgroundColor:te.bg},children:c.name}),E.jsxs("h2",{className:"console-detail-title",children:[E.jsx(Fh,{isPr:s.isPr,state:pt,merged:tt,isDraft:!1,stateReason:""}),E.jsx("span",{className:"console-detail-title-text",children:s.title}),E.jsx("span",{className:"console-detail-number",children:s.isPr?`PR #${s.number}`:`#${s.number}`}),Mt!==null&&E.jsx("span",{className:"console-detail-closed-label",children:Mt})]}),E.jsxs("div",{className:"console-detail-subbar",children:[E.jsx("a",{href:s.url,className:"console-detail-link",target:"_blank",rel:"noopener noreferrer",children:s.isPr?`PR #${s.number}`:`Issue #${s.number}`}),E.jsx("span",{className:"console-detail-repo",children:s.repo}),E.jsx("span",{className:"console-detail-pill",children:s.isPr?"PR":"Issue"})]}),s.labels.length>0&&E.jsx("div",{className:"console-detail-labels",children:s.labels.map(jt=>E.jsx("span",{className:"console-label-chip",children:jt},jt))}),E.jsxs("div",{className:"console-detail-createdat",title:Sr(s.createdAt),children:["opened ",mi(s.createdAt,J)]}),E.jsx(sa,{title:"Description",headerAction:E.jsx("a",{href:s.url,className:"console-panel-open-link",target:"_blank",rel:"noopener noreferrer",children:"open"}),children:S!==null?E.jsxs("p",{role:"alert",className:"console-detail-body-error",children:["Failed to load description: ",S]}):h?E.jsx("p",{className:"console-detail-body-loading",children:"Loading description..."}):E.jsx($c,{body:d,buildImageProxyUrl:Nt})}),s.isPr&&E.jsx(sa,{title:"Changed files",count:he,children:E.jsx(yp,{files:R,isLoading:C,error:H})}),E.jsx(sa,{title:"Comments",count:Bt,defaultCollapsed:s.isPr,children:E.jsx(Eb,{comments:g,isLoading:T,error:z,now:J,buildImageProxyUrl:Nt})}),s.isPr&&E.jsx(sa,{title:"Commits",count:Ue,defaultCollapsed:!0,children:E.jsx(bp,{commits:I,isLoading:F,error:K,now:J})}),!s.isPr&&ct.map(jt=>{var $t;return E.jsx(xb,{pullRequest:jt.pullRequest,body:(($t=jt.pullRequest.summary)==null?void 0:$t.body)??"",bodyIsLoading:!1,files:jt.files,filesAreLoading:jt.filesAreLoading,filesError:jt.filesError,commits:jt.commits,commitsAreLoading:jt.commitsAreLoading,commitsError:jt.commitsError,now:J,buildImageProxyUrl:Nt},jt.pullRequest.url)}),yt,E.jsx("div",{className:"console-actionbar",children:st})]})},Ob=({onClose:s})=>E.jsxs("div",{className:"console-op-group",children:[E.jsx("button",{type:"button",className:"console-op-button",onClick:()=>s("close"),children:"Close"}),E.jsx("button",{type:"button",className:"console-op-button",onClick:()=>s("close_not_planned"),children:"Close as not planned"})]}),zb=({isTodoByHuman:s,onSetNextActionDate:i})=>E.jsxs("div",{className:"console-op-group",children:[E.jsx("button",{type:"button",className:"console-op-button console-op-button-snooze",onClick:()=>i("snooze_1day"),children:"+1 day"}),E.jsx("button",{type:"button",className:"console-op-button console-op-button-snooze",onClick:()=>i("snooze_1week"),children:s?"+1 week and skip":"+1 week"})]}),Rb=[{action:"unnecessary",label:"Unnecessary",variant:"unneeded"},{action:"totally_wrong",label:"Totally wrong",variant:"wrong"},{action:"request_changes",label:"Reject",variant:"reject"},{action:"approve",label:"Approve",variant:"approve"}],Mb=({onReview:s})=>E.jsx("div",{className:"console-op-group console-op-group-review",children:Rb.map(i=>E.jsx("button",{type:"button",className:`console-op-button console-op-button-${i.variant}`,onClick:()=>s(i.action),children:i.label},i.action))}),Cb=(s,i)=>{const o=i.toLowerCase();return s.find(c=>c.name.toLowerCase()===o)??null},Db=({statusOptions:s,onSetStatus:i,onSetInTmuxByHuman:o})=>{const c=gy.map(f=>({name:f,option:Cb(s,f)})).filter(f=>f.option!==null);return c.length===0?null:E.jsx("div",{className:"console-op-group",children:c.map(({name:f,option:d})=>{const h=di(d.color),S=f===yy;return E.jsx("button",{type:"button",className:"console-op-button",style:{color:h.fg,borderColor:h.border,backgroundColor:h.bg},onClick:()=>S?o(d):i(d),children:d.name},d.id)})})},Ub=s=>s.name.toLowerCase().includes("no story"),wb=({storyOptions:s,onSetStory:i})=>{const o=s.filter(c=>!Ub(c));return o.length===0?null:E.jsx("div",{className:"console-op-group console-op-group-stories",children:o.map(c=>{const f=di(c.color);return E.jsx("button",{type:"button",className:"console-op-button",style:{color:f.fg,borderColor:f.border,backgroundColor:f.bg},onClick:()=>i(c),children:c.name},c.id)})})},Lb=({tab:s,item:i,hasPullRequest:o,statusOptions:c,storyOptions:f,handlers:d})=>{const h=s==="triage",S=!i.isPr;return E.jsxs("div",{className:"console-operation-bar",children:[o&&E.jsx(Mb,{onReview:d.onReview}),E.jsx(zb,{isTodoByHuman:by(s),onSetNextActionDate:d.onSetNextActionDate}),h&&E.jsx(wb,{storyOptions:f,onSetStory:d.onSetStory}),E.jsx(Db,{statusOptions:c,onSetStatus:d.onSetStatus,onSetInTmuxByHuman:d.onSetInTmuxByHuman}),S&&E.jsx(Ob,{onClose:d.onClose})]})},uu=(s,i,o,c)=>{const f=i!==null?s.peek(i):void 0,[d,h]=G.useState(f??c),[S,g]=G.useState(i!==null&&f===void 0),[T,z]=G.useState(null);return G.useEffect(()=>{if(i===null||o===null)return;const R=s.peek(i);if(R!==void 0){h(R),g(!1),z(null);return}let C=!1;return g(!0),z(null),s.load(i,o).then(H=>{C||(h(H),g(!1))}).catch(H=>{C||(z(H instanceof Error?H.message:String(H)),g(!1))}),()=>{C=!0}},[s,i,o]),{data:d,isLoading:S,error:T}},jb=[],Vh=[],Kh=[],Hb=[],kb={state:"open",merged:!1,isPullRequest:!1},Bb=(s,i)=>{const o=i!==null?`${i.repo}#${i.number}`:null,c=i!==null?i.url:null,f=(i==null?void 0:i.isPr)??!1,d=uu(s.body,o,c,""),h=uu(s.state,o,c,kb),S=uu(s.comments,o,c,jb),g=uu(s.files,f?o:null,f?c:null,Vh),T=uu(s.commits,f?o:null,f?c:null,Kh),z=uu(s.relatedPrs,f?null:o,f?null:c,Hb),[R,C]=G.useState([]);return G.useEffect(()=>{if(f||z.data.length===0){C([]);return}let H=!1;const I=z.data.map(K=>({pullRequest:K,files:Vh,filesAreLoading:!0,filesError:null,commits:Kh,commitsAreLoading:!0,commitsError:null}));C(I);const F=(K,ct)=>{H||C(J=>J.map(yt=>yt.pullRequest.url===K?{...yt,...ct}:yt))};for(const K of z.data){const ct=K.url;s.files.load(ct,K.url).then(J=>F(K.url,{files:J,filesAreLoading:!1})).catch(J=>F(K.url,{filesAreLoading:!1,filesError:J instanceof Error?J.message:String(J)})),s.commits.load(ct,K.url).then(J=>F(K.url,{commits:J,commitsAreLoading:!1})).catch(J=>F(K.url,{commitsAreLoading:!1,commitsError:J instanceof Error?J.message:String(J)}))}return()=>{H=!0}},[s,f,z.data]),{state:h.data,body:d.data,bodyIsLoading:d.isLoading,bodyError:d.error,comments:S.data,commentsAreLoading:S.isLoading,commentsError:S.error,files:g.data,filesAreLoading:g.isLoading,filesError:g.error,commits:T.data,commitsAreLoading:T.isLoading,commitsError:T.error,relatedPullRequests:R}},qb=({tab:s,item:i,caches:o,operations:c,statusOptions:f,storyOptions:d,storyColors:h,storyName:S,overlayStatus:g,now:T,onQueueAction:z})=>{const R=Bb(o,i),{token:C}=Kc(),H=G.useCallback(J=>Ky(J,C),[C]),I=i.isPr||R.relatedPullRequests.length>0,F={onReview:J=>{var st;const yt=i.isPr?i.url:((st=R.relatedPullRequests[0])==null?void 0:st.pullRequest.url)??i.url;z({kind:{type:"review",action:J},item:i,commit:()=>{c.reviewPullRequest(i,yt,J)}})},onSetNextActionDate:J=>{z({kind:{type:"next_action_date",action:J},item:i,commit:()=>{c.setNextActionDate(i,J)}})},onSetStory:J=>{z({kind:{type:"set_story",optionName:J.name},item:i,commit:()=>{c.setStory(i,J)}})},onSetStatus:J=>{z({kind:{type:"set_status",optionName:J.name},item:i,commit:()=>{c.setStatus(i,J)}})},onSetInTmuxByHuman:J=>{z({kind:{type:"set_in_tmux_by_human",optionName:J.name},item:i,commit:()=>{c.setInTmuxByHuman(i,J)}})},onClose:J=>{z({kind:{type:"close",action:J},item:i,commit:()=>{c.closeIssue(i,J)}})}},K=S??(i.story.trim()!==""?i.story:null),ct=K!==null?Jh(h,K):null;return E.jsx(Nb,{item:i,storyName:K,storyColorEnum:ct,overlayStatus:g,state:R.state,body:R.body,bodyIsLoading:R.bodyIsLoading,bodyError:R.bodyError,comments:R.comments,commentsAreLoading:R.commentsAreLoading,commentsError:R.commentsError,files:R.files,filesAreLoading:R.filesAreLoading,filesError:R.filesError,commits:R.commits,commitsAreLoading:R.commitsAreLoading,commitsError:R.commitsError,relatedPullRequests:R.relatedPullRequests,now:T,buildImageProxyUrl:H,commentComposer:E.jsx(gb,{isPr:i.isPr,now:T,onSubmit:J=>c.addComment(i,J)}),operationBar:E.jsx(Lb,{tab:s,item:i,hasPullRequest:I,statusOptions:f,storyOptions:d,handlers:F})})},Yb=()=>{const s={};for(const i of yl)s[i.name]=0;return s},Gb="console",Zb=()=>{const s=Cy(),{snapshots:i,isLoading:o,error:c}=qy(s),f=my(s),{activeTab:d,selectedItemKey:h,openItem:S,closeItem:g,selectTab:T}=f,z=Ry(s??Gb),R=oy(),C=xy(s,d,z),H=F0(),I=Date.now(),F=G.useMemo(()=>{const k=Yb();for(const X of yl){const bt=i[X.name];bt!==null&&(k[X.name]=vy(bt.items,z.overlay))}return k},[i,z.overlay]),K=i[d],ct=G.useMemo(()=>K===null?[]:Sy(K.items,z.overlay),[K,z.overlay]),J=G.useMemo(()=>ct.map(k=>Fl(k)),[ct]),yt=G.useMemo(()=>k0(ct,z.overlay),[ct,z.overlay]),st=(K==null?void 0:K.storyColors)??{},Nt=(K==null?void 0:K.statusOptions)??[],pt=(K==null?void 0:K.storyOptions)??[],tt=(K==null?void 0:K.generatedAt)??null,Mt=G.useMemo(()=>h===null||K===null?null:K.items.find(k=>k.projectItemId===h)??null,[h,K]),Pt=F[d],te=G.useRef({tab:d,count:Pt});G.useEffect(()=>{const k=te.current;if(te.current={tab:d,count:Pt},k.tab===d&&k.count>0&&Pt===0){const X=Xy(d,F);X!==null&&(T(X),g())}},[d,Pt,F,T,g]);const he=(()=>{if(Mt===null)return null;const k=z.overlay[Fl(Mt)];return(k==null?void 0:k.status)??null})(),Bt=Mt!==null?mr(Mt,z.overlay):null,Ue=G.useCallback(k=>{const X=Yy(J,k);X!==null?S(X):g()},[J,S,g]),jt=G.useCallback(k=>{const X=Fl(k.item);H.enqueue({message:$0(k.kind,k.item,d),color:K0(k.kind),commit:k.commit,advance:()=>{J0(k.kind,d)&&Ue(X)}})},[H,d,Ue]),$t=G.useCallback(k=>{if(h===null||k===null)return;const X=k==="next"?Gy(J,h):Zy(J,h);X!==null&&S(X)},[h,J,S]),D=jy($t);return E.jsxs("main",{className:"console-app",children:[H.pending!==null&&E.jsx(X0,{message:H.pending.message,color:H.pending.color,remainingSeconds:H.pending.remainingSeconds,progress:H.pending.progress,onUndo:H.undo}),E.jsx(j0,{activeTab:d,counts:F,pjcode:s,generatedAt:tt,tabHref:f.tabHref,onSelectTab:f.selectTab}),Mt===null?E.jsx(Z0,{rows:yt,storyColors:st,activeItemId:null,now:I,isLoading:o,error:c,onSelectItem:k=>f.openItem(k.projectItemId)}):E.jsxs("div",{className:"console-detail-screen",ref:D,children:[E.jsx("button",{type:"button",className:"console-back-button",onClick:g,children:"← Back to list"}),E.jsx(qb,{tab:d,item:Mt,caches:R,operations:C,statusOptions:Nt,storyOptions:pt,storyColors:st,storyName:Bt,overlayStatus:he,now:I,onQueueAction:jt})]})]})},vp=document.getElementById("root");if(vp===null)throw new Error("Root container #root not found");w0.createRoot(vp).render(E.jsx(G.StrictMode,{children:E.jsx(Zb,{})}));
|
|
101
|
+
`)[0],bp=({commits:s,isLoading:i,error:o,now:c})=>o!==null?E.jsxs("p",{role:"alert",className:"console-commits-error",children:["Failed to load commits: ",o]}):i?E.jsx("p",{className:"console-commits-loading",children:"Loading commits..."}):s.length===0?E.jsx("p",{className:"console-commits-empty",children:"No commits."}):E.jsx("ul",{className:"console-commits",children:s.map(f=>E.jsxs("li",{className:"console-commit",children:[E.jsx("span",{className:"console-commit-message",children:_b(f.message)}),E.jsx("span",{className:"console-commit-sha",children:Ab(f.sha)}),E.jsx("span",{className:"console-commit-author",children:f.author}),E.jsx("span",{className:"console-commit-time",children:mi(f.authoredAt,c)})]},f.sha))}),xb=({pullRequest:s,body:i,bodyIsLoading:o,files:c,filesAreLoading:f,filesError:d,commits:h,commitsAreLoading:S,commitsError:g,now:T,buildImageProxyUrl:z})=>{const R=s.summary,C=f||d!==null?null:c.length,H=S||g!==null?null:h.length;return E.jsxs(E.Fragment,{children:[E.jsxs("div",{className:"console-pr-header",children:[E.jsx("a",{href:s.url,className:"console-pr-section-title",target:"_blank",rel:"noopener noreferrer",children:(R==null?void 0:R.title)??s.url}),s.isDraft&&E.jsx("span",{className:"console-pr-section-state",children:"draft"}),E.jsxs("div",{className:"console-pr-statbar",children:[s.branchName!==null&&E.jsx("span",{className:"console-pr-branch",children:s.branchName}),R!==null&&E.jsxs(E.Fragment,{children:[E.jsxs("span",{className:"console-pr-add",children:["+",R.additions]}),E.jsxs("span",{className:"console-pr-del",children:["-",R.deletions]}),E.jsxs("span",{className:"console-pr-files-count",children:[R.changedFiles," files"]})]})]})]}),E.jsx(sa,{title:"Description",defaultCollapsed:!0,children:o?E.jsx("p",{className:"console-pr-body-loading",children:"Loading description..."}):E.jsx($c,{body:(R==null?void 0:R.body)??i,buildImageProxyUrl:z})}),E.jsx(sa,{title:"Changed files",count:C,children:E.jsx(yp,{files:c,isLoading:f,error:d})}),E.jsx(sa,{title:"Commits",count:H,defaultCollapsed:!0,children:E.jsx(bp,{commits:h,isLoading:S,error:g,now:T})})]})},Nb=({item:s,storyName:i,storyColorEnum:o,overlayStatus:c,state:f,body:d,bodyIsLoading:h,bodyError:S,comments:g,commentsAreLoading:T,commentsError:z,files:R,filesAreLoading:C,filesError:H,commits:I,commitsAreLoading:F,commitsError:K,relatedPullRequests:ct,now:J,commentComposer:yt,operationBar:st,buildImageProxyUrl:Nt})=>{const pt=(f==null?void 0:f.state)??"open",tt=(f==null?void 0:f.merged)??!1,Mt=!s.isPr&&pt==="closed"?"Closed":null,Pt=di(o),te=c?di(c.color):null,he=C||H!==null?null:R.length,Bt=T||z!==null?null:g.length,Ue=F||K!==null?null:I.length;return E.jsxs("article",{className:"console-detail",children:[i!==null&&E.jsx("div",{className:"console-detail-story",children:E.jsxs("span",{className:"console-storytag",children:[E.jsx("span",{className:"console-story-dot",style:{backgroundColor:Pt.dot}}),i]})}),c!==null&&te!==null&&E.jsx("span",{className:"console-detail-status-chip",style:{color:te.fg,borderColor:te.border,backgroundColor:te.bg},children:c.name}),E.jsxs("h2",{className:"console-detail-title",children:[E.jsx(Fh,{isPr:s.isPr,state:pt,merged:tt,isDraft:!1,stateReason:""}),E.jsx("span",{className:"console-detail-title-text",children:s.title}),E.jsx("span",{className:"console-detail-number",children:s.isPr?`PR #${s.number}`:`#${s.number}`}),Mt!==null&&E.jsx("span",{className:"console-detail-closed-label",children:Mt})]}),E.jsxs("div",{className:"console-detail-subbar",children:[E.jsx("a",{href:s.url,className:"console-detail-link",target:"_blank",rel:"noopener noreferrer",children:s.isPr?`PR #${s.number}`:`Issue #${s.number}`}),E.jsx("span",{className:"console-detail-repo",children:s.repo}),E.jsx("span",{className:"console-detail-pill",children:s.isPr?"PR":"Issue"})]}),s.labels.length>0&&E.jsx("div",{className:"console-detail-labels",children:s.labels.map(jt=>E.jsx("span",{className:"console-label-chip",children:jt},jt))}),E.jsxs("div",{className:"console-detail-createdat",title:Sr(s.createdAt),children:["opened ",mi(s.createdAt,J)]}),E.jsx(sa,{title:"Description",headerAction:E.jsx("a",{href:s.url,className:"console-panel-open-link",target:"_blank",rel:"noopener noreferrer",children:"open"}),children:S!==null?E.jsxs("p",{role:"alert",className:"console-detail-body-error",children:["Failed to load description: ",S]}):h?E.jsx("p",{className:"console-detail-body-loading",children:"Loading description..."}):E.jsx($c,{body:d,buildImageProxyUrl:Nt})}),s.isPr&&E.jsx(sa,{title:"Changed files",count:he,children:E.jsx(yp,{files:R,isLoading:C,error:H})}),E.jsx(sa,{title:"Comments",count:Bt,defaultCollapsed:s.isPr,children:E.jsx(Eb,{comments:g,isLoading:T,error:z,now:J,buildImageProxyUrl:Nt})}),s.isPr&&E.jsx(sa,{title:"Commits",count:Ue,defaultCollapsed:!0,children:E.jsx(bp,{commits:I,isLoading:F,error:K,now:J})}),!s.isPr&&ct.map(jt=>{var $t;return E.jsx(xb,{pullRequest:jt.pullRequest,body:(($t=jt.pullRequest.summary)==null?void 0:$t.body)??"",bodyIsLoading:!1,files:jt.files,filesAreLoading:jt.filesAreLoading,filesError:jt.filesError,commits:jt.commits,commitsAreLoading:jt.commitsAreLoading,commitsError:jt.commitsError,now:J,buildImageProxyUrl:Nt},jt.pullRequest.url)}),yt,E.jsx("div",{className:"console-actionbar",children:st})]})},Ob=({onClose:s})=>E.jsxs("div",{className:"console-op-group",children:[E.jsx("button",{type:"button",className:"console-op-button",onClick:()=>s("close_not_planned"),children:"Close as not planned"}),E.jsx("button",{type:"button",className:"console-op-button",onClick:()=>s("close"),children:"Close"})]}),zb=({isTodoByHuman:s,onSetNextActionDate:i})=>E.jsxs("div",{className:"console-op-group",children:[E.jsx("button",{type:"button",className:"console-op-button console-op-button-snooze",onClick:()=>i("snooze_1day"),children:"+1 day"}),E.jsx("button",{type:"button",className:"console-op-button console-op-button-snooze",onClick:()=>i("snooze_1week"),children:s?"+1 week and skip":"+1 week"})]}),Rb=[{action:"unnecessary",label:"Unnecessary",variant:"unneeded"},{action:"totally_wrong",label:"Totally wrong",variant:"wrong"},{action:"request_changes",label:"Reject",variant:"reject"},{action:"approve",label:"Approve",variant:"approve"}],Mb=({onReview:s})=>E.jsx("div",{className:"console-op-group console-op-group-review",children:Rb.map(i=>E.jsx("button",{type:"button",className:`console-op-button console-op-button-${i.variant}`,onClick:()=>s(i.action),children:i.label},i.action))}),Cb=(s,i)=>{const o=i.toLowerCase();return s.find(c=>c.name.toLowerCase()===o)??null},Db=({statusOptions:s,onSetStatus:i,onSetInTmuxByHuman:o})=>{const c=gy.map(f=>({name:f,option:Cb(s,f)})).filter(f=>f.option!==null);return c.length===0?null:E.jsx("div",{className:"console-op-group",children:c.map(({name:f,option:d})=>{const h=di(d.color),S=f===yy;return E.jsx("button",{type:"button",className:"console-op-button",style:{color:h.fg,borderColor:h.border,backgroundColor:h.bg},onClick:()=>S?o(d):i(d),children:d.name},d.id)})})},Ub=s=>s.name.toLowerCase().includes("no story"),wb=({storyOptions:s,onSetStory:i})=>{const o=s.filter(c=>!Ub(c));return o.length===0?null:E.jsx("div",{className:"console-op-group console-op-group-stories",children:o.map(c=>{const f=di(c.color);return E.jsx("button",{type:"button",className:"console-op-button",style:{color:f.fg,borderColor:f.border,backgroundColor:f.bg},onClick:()=>i(c),children:c.name},c.id)})})},Lb=({tab:s,item:i,hasPullRequest:o,statusOptions:c,storyOptions:f,handlers:d})=>{const h=s==="triage",S=!i.isPr;return E.jsxs("div",{className:"console-operation-bar",children:[o&&E.jsx(Mb,{onReview:d.onReview}),E.jsx(zb,{isTodoByHuman:by(s),onSetNextActionDate:d.onSetNextActionDate}),h&&E.jsx(wb,{storyOptions:f,onSetStory:d.onSetStory}),E.jsx(Db,{statusOptions:c,onSetStatus:d.onSetStatus,onSetInTmuxByHuman:d.onSetInTmuxByHuman}),S&&E.jsx(Ob,{onClose:d.onClose})]})},uu=(s,i,o,c)=>{const f=i!==null?s.peek(i):void 0,[d,h]=G.useState(f??c),[S,g]=G.useState(i!==null&&f===void 0),[T,z]=G.useState(null);return G.useEffect(()=>{if(i===null||o===null)return;const R=s.peek(i);if(R!==void 0){h(R),g(!1),z(null);return}let C=!1;return g(!0),z(null),s.load(i,o).then(H=>{C||(h(H),g(!1))}).catch(H=>{C||(z(H instanceof Error?H.message:String(H)),g(!1))}),()=>{C=!0}},[s,i,o]),{data:d,isLoading:S,error:T}},jb=[],Vh=[],Kh=[],Hb=[],kb={state:"open",merged:!1,isPullRequest:!1},Bb=(s,i)=>{const o=i!==null?`${i.repo}#${i.number}`:null,c=i!==null?i.url:null,f=(i==null?void 0:i.isPr)??!1,d=uu(s.body,o,c,""),h=uu(s.state,o,c,kb),S=uu(s.comments,o,c,jb),g=uu(s.files,f?o:null,f?c:null,Vh),T=uu(s.commits,f?o:null,f?c:null,Kh),z=uu(s.relatedPrs,f?null:o,f?null:c,Hb),[R,C]=G.useState([]);return G.useEffect(()=>{if(f||z.data.length===0){C([]);return}let H=!1;const I=z.data.map(K=>({pullRequest:K,files:Vh,filesAreLoading:!0,filesError:null,commits:Kh,commitsAreLoading:!0,commitsError:null}));C(I);const F=(K,ct)=>{H||C(J=>J.map(yt=>yt.pullRequest.url===K?{...yt,...ct}:yt))};for(const K of z.data){const ct=K.url;s.files.load(ct,K.url).then(J=>F(K.url,{files:J,filesAreLoading:!1})).catch(J=>F(K.url,{filesAreLoading:!1,filesError:J instanceof Error?J.message:String(J)})),s.commits.load(ct,K.url).then(J=>F(K.url,{commits:J,commitsAreLoading:!1})).catch(J=>F(K.url,{commitsAreLoading:!1,commitsError:J instanceof Error?J.message:String(J)}))}return()=>{H=!0}},[s,f,z.data]),{state:h.data,body:d.data,bodyIsLoading:d.isLoading,bodyError:d.error,comments:S.data,commentsAreLoading:S.isLoading,commentsError:S.error,files:g.data,filesAreLoading:g.isLoading,filesError:g.error,commits:T.data,commitsAreLoading:T.isLoading,commitsError:T.error,relatedPullRequests:R}},qb=({tab:s,item:i,caches:o,operations:c,statusOptions:f,storyOptions:d,storyColors:h,storyName:S,overlayStatus:g,now:T,onQueueAction:z})=>{const R=Bb(o,i),{token:C}=Kc(),H=G.useCallback(J=>Ky(J,C),[C]),I=i.isPr||R.relatedPullRequests.length>0,F={onReview:J=>{var st;const yt=i.isPr?i.url:((st=R.relatedPullRequests[0])==null?void 0:st.pullRequest.url)??i.url;z({kind:{type:"review",action:J},item:i,commit:()=>{c.reviewPullRequest(i,yt,J)}})},onSetNextActionDate:J=>{z({kind:{type:"next_action_date",action:J},item:i,commit:()=>{c.setNextActionDate(i,J)}})},onSetStory:J=>{z({kind:{type:"set_story",optionName:J.name},item:i,commit:()=>{c.setStory(i,J)}})},onSetStatus:J=>{z({kind:{type:"set_status",optionName:J.name},item:i,commit:()=>{c.setStatus(i,J)}})},onSetInTmuxByHuman:J=>{z({kind:{type:"set_in_tmux_by_human",optionName:J.name},item:i,commit:()=>{c.setInTmuxByHuman(i,J)}})},onClose:J=>{z({kind:{type:"close",action:J},item:i,commit:()=>{c.closeIssue(i,J)}})}},K=S??(i.story.trim()!==""?i.story:null),ct=K!==null?Jh(h,K):null;return E.jsx(Nb,{item:i,storyName:K,storyColorEnum:ct,overlayStatus:g,state:R.state,body:R.body,bodyIsLoading:R.bodyIsLoading,bodyError:R.bodyError,comments:R.comments,commentsAreLoading:R.commentsAreLoading,commentsError:R.commentsError,files:R.files,filesAreLoading:R.filesAreLoading,filesError:R.filesError,commits:R.commits,commitsAreLoading:R.commitsAreLoading,commitsError:R.commitsError,relatedPullRequests:R.relatedPullRequests,now:T,buildImageProxyUrl:H,commentComposer:E.jsx(gb,{isPr:i.isPr,now:T,onSubmit:J=>c.addComment(i,J)}),operationBar:E.jsx(Lb,{tab:s,item:i,hasPullRequest:I,statusOptions:f,storyOptions:d,handlers:F})})},Yb=()=>{const s={};for(const i of yl)s[i.name]=0;return s},Gb="console",Zb=()=>{const s=Cy(),{snapshots:i,isLoading:o,error:c}=qy(s),f=my(s),{activeTab:d,selectedItemKey:h,openItem:S,closeItem:g,selectTab:T}=f,z=Ry(s??Gb),R=oy(),C=xy(s,d,z),H=F0(),I=Date.now(),F=G.useMemo(()=>{const k=Yb();for(const X of yl){const bt=i[X.name];bt!==null&&(k[X.name]=vy(bt.items,z.overlay))}return k},[i,z.overlay]),K=i[d],ct=G.useMemo(()=>K===null?[]:Sy(K.items,z.overlay),[K,z.overlay]),J=G.useMemo(()=>ct.map(k=>Fl(k)),[ct]),yt=G.useMemo(()=>k0(ct,z.overlay),[ct,z.overlay]),st=(K==null?void 0:K.storyColors)??{},Nt=(K==null?void 0:K.statusOptions)??[],pt=(K==null?void 0:K.storyOptions)??[],tt=(K==null?void 0:K.generatedAt)??null,Mt=G.useMemo(()=>h===null||K===null?null:K.items.find(k=>k.projectItemId===h)??null,[h,K]),Pt=F[d],te=G.useRef({tab:d,count:Pt});G.useEffect(()=>{const k=te.current;if(te.current={tab:d,count:Pt},k.tab===d&&k.count>0&&Pt===0){const X=Xy(d,F);X!==null&&(T(X),g())}},[d,Pt,F,T,g]);const he=(()=>{if(Mt===null)return null;const k=z.overlay[Fl(Mt)];return(k==null?void 0:k.status)??null})(),Bt=Mt!==null?mr(Mt,z.overlay):null,Ue=G.useCallback(k=>{const X=Yy(J,k);X!==null?S(X):g()},[J,S,g]),jt=G.useCallback(k=>{const X=Fl(k.item);H.enqueue({message:$0(k.kind,k.item,d),color:K0(k.kind),commit:k.commit,advance:()=>{J0(k.kind,d)&&Ue(X)}})},[H,d,Ue]),$t=G.useCallback(k=>{if(h===null||k===null)return;const X=k==="next"?Gy(J,h):Zy(J,h);X!==null&&S(X)},[h,J,S]),D=jy($t);return E.jsxs("main",{className:"console-app",children:[H.pending!==null&&E.jsx(X0,{message:H.pending.message,color:H.pending.color,remainingSeconds:H.pending.remainingSeconds,progress:H.pending.progress,onUndo:H.undo}),E.jsx(j0,{activeTab:d,counts:F,pjcode:s,generatedAt:tt,tabHref:f.tabHref,onSelectTab:f.selectTab}),Mt===null?E.jsx(Z0,{rows:yt,storyColors:st,activeItemId:null,now:I,isLoading:o,error:c,onSelectItem:k=>f.openItem(k.projectItemId)}):E.jsxs("div",{className:"console-detail-screen",ref:D,children:[E.jsx("button",{type:"button",className:"console-back-button",onClick:g,children:"← Back to list"}),E.jsx(qb,{tab:d,item:Mt,caches:R,operations:C,statusOptions:Nt,storyOptions:pt,storyColors:st,storyName:Bt,overlayStatus:he,now:I,onQueueAction:jt})]})]})},vp=document.getElementById("root");if(vp===null)throw new Error("Root container #root not found");w0.createRoot(vp).render(E.jsx(G.StrictMode,{children:E.jsx(Zb,{})}));
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BFKRWKvS.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CzVHU81K.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { Issue } from '../../../domain/entities/Issue';
|
|
5
|
+
import {
|
|
6
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
7
|
+
IN_TMUX_STATUS_NAME,
|
|
8
|
+
} from '../../../domain/entities/WorkflowStatus';
|
|
9
|
+
import {
|
|
10
|
+
ClaudeInteractiveSession,
|
|
11
|
+
ClaudeInteractiveSessionRepository,
|
|
12
|
+
} from '../../../domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository';
|
|
13
|
+
import { InTmuxByHumanSessionTokenCountUseCase } from '../../../domain/usecases/InTmuxByHumanSessionTokenCountUseCase';
|
|
14
|
+
import { InTmuxByHumanSessionTokenCountHandler } from './InTmuxByHumanSessionTokenCountHandler';
|
|
15
|
+
|
|
16
|
+
class FakeClaudeInteractiveSessionRepository implements ClaudeInteractiveSessionRepository {
|
|
17
|
+
constructor(private readonly sessions: ClaudeInteractiveSession[]) {}
|
|
18
|
+
listInteractiveSessions = (): ClaudeInteractiveSession[] => this.sessions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const issueUrlA = 'https://github.com/HiromiShikata/example/issues/1';
|
|
22
|
+
const issueUrlB = 'https://github.com/HiromiShikata/example/issues/2';
|
|
23
|
+
|
|
24
|
+
const issue = (url: string, status: string): Issue => ({
|
|
25
|
+
nameWithOwner: 'HiromiShikata/example',
|
|
26
|
+
number: 1,
|
|
27
|
+
title: 'Example issue',
|
|
28
|
+
state: 'OPEN',
|
|
29
|
+
status,
|
|
30
|
+
story: null,
|
|
31
|
+
nextActionDate: null,
|
|
32
|
+
nextActionHour: null,
|
|
33
|
+
estimationMinutes: null,
|
|
34
|
+
dependedIssueUrls: [],
|
|
35
|
+
completionDate50PercentConfidence: null,
|
|
36
|
+
url,
|
|
37
|
+
assignees: ['hiromi'],
|
|
38
|
+
labels: [],
|
|
39
|
+
org: 'HiromiShikata',
|
|
40
|
+
repo: 'example',
|
|
41
|
+
body: '',
|
|
42
|
+
itemId: 'item-1',
|
|
43
|
+
isPr: false,
|
|
44
|
+
isInProgress: false,
|
|
45
|
+
isClosed: false,
|
|
46
|
+
createdAt: new Date('2026-01-01T00:00:00Z'),
|
|
47
|
+
author: 'hiromi',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('InTmuxByHumanSessionTokenCountHandler', () => {
|
|
51
|
+
let tokenListPath: string;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'token-list-'));
|
|
55
|
+
tokenListPath = path.join(directory, 'tokens.json');
|
|
56
|
+
fs.writeFileSync(
|
|
57
|
+
tokenListPath,
|
|
58
|
+
JSON.stringify([
|
|
59
|
+
{ name: 'alpha', token: 'token-alpha' },
|
|
60
|
+
{ name: 'beta', token: 'token-beta' },
|
|
61
|
+
]),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
fs.rmSync(path.dirname(tokenListPath), { recursive: true, force: true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('writes one tab-separated line per token with In-Tmux-by-human counts', () => {
|
|
70
|
+
const handler = new InTmuxByHumanSessionTokenCountHandler(
|
|
71
|
+
new InTmuxByHumanSessionTokenCountUseCase(),
|
|
72
|
+
new FakeClaudeInteractiveSessionRepository([
|
|
73
|
+
{ token: 'token-alpha', sessionId: 'session-a', issueUrl: issueUrlA },
|
|
74
|
+
{ token: 'token-alpha', sessionId: 'session-a', issueUrl: issueUrlA },
|
|
75
|
+
{ token: 'token-beta', sessionId: 'session-b', issueUrl: issueUrlB },
|
|
76
|
+
]),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const output = handler.handle({
|
|
80
|
+
tokenListJsonPath: tokenListPath,
|
|
81
|
+
issues: [
|
|
82
|
+
issue(issueUrlA, IN_TMUX_STATUS_NAME),
|
|
83
|
+
issue(issueUrlB, AWAITING_QUALITY_CHECK_STATUS_NAME),
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(output.lines).toEqual(['alpha\t1', 'beta\t0']);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('reports a diagnostic when no token list path is available', () => {
|
|
91
|
+
const handler = new InTmuxByHumanSessionTokenCountHandler(
|
|
92
|
+
new InTmuxByHumanSessionTokenCountUseCase(),
|
|
93
|
+
new FakeClaudeInteractiveSessionRepository([]),
|
|
94
|
+
);
|
|
95
|
+
const previous = process.env.CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH;
|
|
96
|
+
delete process.env.CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH;
|
|
97
|
+
|
|
98
|
+
const output = handler.handle({ tokenListJsonPath: null, issues: [] });
|
|
99
|
+
|
|
100
|
+
expect(output.lines).toEqual([]);
|
|
101
|
+
expect(output.diagnostics[0]).toContain('No token list path provided');
|
|
102
|
+
|
|
103
|
+
if (previous !== undefined) {
|
|
104
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH = previous;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('reports a diagnostic when the token list file has no usable entries', () => {
|
|
109
|
+
fs.writeFileSync(tokenListPath, JSON.stringify([]));
|
|
110
|
+
const handler = new InTmuxByHumanSessionTokenCountHandler(
|
|
111
|
+
new InTmuxByHumanSessionTokenCountUseCase(),
|
|
112
|
+
new FakeClaudeInteractiveSessionRepository([]),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const output = handler.handle({
|
|
116
|
+
tokenListJsonPath: tokenListPath,
|
|
117
|
+
issues: [],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(output.lines).toEqual([]);
|
|
121
|
+
expect(output.diagnostics[0]).toContain('No usable token entries');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Issue } from '../../../domain/entities/Issue';
|
|
2
|
+
import { ClaudeInteractiveSessionRepository } from '../../../domain/usecases/adapter-interfaces/ClaudeInteractiveSessionRepository';
|
|
3
|
+
import { InTmuxByHumanSessionTokenCountUseCase } from '../../../domain/usecases/InTmuxByHumanSessionTokenCountUseCase';
|
|
4
|
+
import { OauthTokenCandidate } from '../../../domain/usecases/OauthTokenSelectUseCase';
|
|
5
|
+
import { ProcClaudeInteractiveSessionRepository } from '../../repositories/ProcClaudeInteractiveSessionRepository';
|
|
6
|
+
import { loadTokenEntries } from '../../proxy/TokenListLoader';
|
|
7
|
+
import { resolveTokenListJsonPath } from './OauthTokenSelectHandler';
|
|
8
|
+
|
|
9
|
+
export type InTmuxByHumanSessionTokenCountHandlerInput = {
|
|
10
|
+
tokenListJsonPath: string | null;
|
|
11
|
+
issues: Issue[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type InTmuxByHumanSessionTokenCountHandlerOutput = {
|
|
15
|
+
lines: string[];
|
|
16
|
+
diagnostics: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export class InTmuxByHumanSessionTokenCountHandler {
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly useCase: InTmuxByHumanSessionTokenCountUseCase = new InTmuxByHumanSessionTokenCountUseCase(),
|
|
22
|
+
private readonly interactiveSessionRepository: ClaudeInteractiveSessionRepository = new ProcClaudeInteractiveSessionRepository(),
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
handle = (
|
|
26
|
+
input: InTmuxByHumanSessionTokenCountHandlerInput,
|
|
27
|
+
): InTmuxByHumanSessionTokenCountHandlerOutput => {
|
|
28
|
+
const tokenListJsonPath = resolveTokenListJsonPath(input.tokenListJsonPath);
|
|
29
|
+
if (tokenListJsonPath === null) {
|
|
30
|
+
return {
|
|
31
|
+
lines: [],
|
|
32
|
+
diagnostics: [
|
|
33
|
+
'No token list path provided. Pass --tokenListJsonPath or set CLAUDE_CODE_OAUTH_TOKEN_LIST_JSON_PATH.',
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const entries = loadTokenEntries(tokenListJsonPath);
|
|
39
|
+
if (entries === null) {
|
|
40
|
+
return {
|
|
41
|
+
lines: [],
|
|
42
|
+
diagnostics: [
|
|
43
|
+
`No usable token entries loaded from ${tokenListJsonPath}.`,
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const candidates: OauthTokenCandidate[] = entries.map(
|
|
49
|
+
({ name, token }) => ({
|
|
50
|
+
name,
|
|
51
|
+
token,
|
|
52
|
+
snapshot: null,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const interactiveSessions =
|
|
57
|
+
this.interactiveSessionRepository.listInteractiveSessions();
|
|
58
|
+
|
|
59
|
+
const result = this.useCase.run(
|
|
60
|
+
candidates,
|
|
61
|
+
interactiveSessions,
|
|
62
|
+
input.issues,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const lines = result.counts.map((count) => `${count.name}\t${count.count}`);
|
|
66
|
+
|
|
67
|
+
const totalSessions = result.counts.reduce(
|
|
68
|
+
(sum, count) => sum + count.count,
|
|
69
|
+
0,
|
|
70
|
+
);
|
|
71
|
+
const diagnostics = [
|
|
72
|
+
`Counted ${totalSessions} live In-Tmux-by-human session(s) across ${result.counts.length} token(s).`,
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
return { lines, diagnostics };
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { ProcClaudeInteractiveSessionRepository } from './ProcClaudeInteractiveSessionRepository';
|
|
5
|
+
|
|
6
|
+
type FakeProcess = {
|
|
7
|
+
pid: number;
|
|
8
|
+
cmdline: string;
|
|
9
|
+
environ: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const issueUrl = 'https://github.com/HiromiShikata/example/issues/1';
|
|
13
|
+
|
|
14
|
+
const argv = (...parts: string[]): string => `${parts.join('\0')}\0`;
|
|
15
|
+
|
|
16
|
+
describe('ProcClaudeInteractiveSessionRepository', () => {
|
|
17
|
+
let procDirectory: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
procDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'fake-proc-int-'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
fs.rmSync(procDirectory, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const writeProcess = (fakeProcess: FakeProcess): void => {
|
|
28
|
+
const processDirectory = path.join(procDirectory, String(fakeProcess.pid));
|
|
29
|
+
fs.mkdirSync(processDirectory, { recursive: true });
|
|
30
|
+
fs.writeFileSync(
|
|
31
|
+
path.join(processDirectory, 'cmdline'),
|
|
32
|
+
fakeProcess.cmdline,
|
|
33
|
+
);
|
|
34
|
+
const environBuffer = Object.entries(fakeProcess.environ)
|
|
35
|
+
.map(([key, value]) => `${key}=${value}\0`)
|
|
36
|
+
.join('');
|
|
37
|
+
fs.writeFileSync(path.join(processDirectory, 'environ'), environBuffer);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
it('reads token, session id and issue url from a cl-launched interactive process', () => {
|
|
41
|
+
writeProcess({
|
|
42
|
+
pid: 201,
|
|
43
|
+
cmdline: argv('claude', '--model', 'opus', '--name', issueUrl),
|
|
44
|
+
environ: {
|
|
45
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-a',
|
|
46
|
+
CLAUDE_CODE_SESSION_ID: 'session-a',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
51
|
+
procDirectory,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(repository.listInteractiveSessions()).toEqual([
|
|
55
|
+
{ token: 'token-a', sessionId: 'session-a', issueUrl },
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('ignores a process without a --name issue url argument', () => {
|
|
60
|
+
writeProcess({
|
|
61
|
+
pid: 202,
|
|
62
|
+
cmdline: argv('claude', '--model', 'opus'),
|
|
63
|
+
environ: {
|
|
64
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-b',
|
|
65
|
+
CLAUDE_CODE_SESSION_ID: 'session-b',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
70
|
+
procDirectory,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('excludes a Take ownership aw spawn even when it carries the token', () => {
|
|
77
|
+
writeProcess({
|
|
78
|
+
pid: 203,
|
|
79
|
+
cmdline: argv(
|
|
80
|
+
'claude-agent',
|
|
81
|
+
'--agent',
|
|
82
|
+
'impl',
|
|
83
|
+
'-p',
|
|
84
|
+
`Take ownership of ${issueUrl} and finish it`,
|
|
85
|
+
),
|
|
86
|
+
environ: {
|
|
87
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-c',
|
|
88
|
+
CLAUDE_CODE_SESSION_ID: 'session-c',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
93
|
+
procDirectory,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('ignores a --name process without an oauth token', () => {
|
|
100
|
+
writeProcess({
|
|
101
|
+
pid: 204,
|
|
102
|
+
cmdline: argv('claude', '--name', issueUrl),
|
|
103
|
+
environ: {
|
|
104
|
+
CLAUDE_CODE_SESSION_ID: 'session-d',
|
|
105
|
+
ANTHROPIC_API_KEY: 'api-key',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
110
|
+
procDirectory,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('ignores a --name process without a session id', () => {
|
|
117
|
+
writeProcess({
|
|
118
|
+
pid: 205,
|
|
119
|
+
cmdline: argv('claude', '--name', issueUrl),
|
|
120
|
+
environ: {
|
|
121
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-e',
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
126
|
+
procDirectory,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('ignores a --name value that is not an http url', () => {
|
|
133
|
+
writeProcess({
|
|
134
|
+
pid: 206,
|
|
135
|
+
cmdline: argv('claude', '--name', 'just-a-label'),
|
|
136
|
+
environ: {
|
|
137
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-f',
|
|
138
|
+
CLAUDE_CODE_SESSION_ID: 'session-f',
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
143
|
+
procDirectory,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('returns one entry per child process so the use case can dedupe by session id', () => {
|
|
150
|
+
writeProcess({
|
|
151
|
+
pid: 207,
|
|
152
|
+
cmdline: argv('claude', '--name', issueUrl),
|
|
153
|
+
environ: {
|
|
154
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-g',
|
|
155
|
+
CLAUDE_CODE_SESSION_ID: 'session-g',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
writeProcess({
|
|
159
|
+
pid: 208,
|
|
160
|
+
cmdline: argv('claude', '--name', issueUrl),
|
|
161
|
+
environ: {
|
|
162
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'token-g',
|
|
163
|
+
CLAUDE_CODE_SESSION_ID: 'session-g',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
168
|
+
procDirectory,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(repository.listInteractiveSessions()).toEqual([
|
|
172
|
+
{ token: 'token-g', sessionId: 'session-g', issueUrl },
|
|
173
|
+
{ token: 'token-g', sessionId: 'session-g', issueUrl },
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('returns an empty list when the proc directory does not exist', () => {
|
|
178
|
+
const repository = new ProcClaudeInteractiveSessionRepository(
|
|
179
|
+
path.join(procDirectory, 'missing'),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(repository.listInteractiveSessions()).toEqual([]);
|
|
183
|
+
});
|
|
184
|
+
});
|