@trim21/personal-pi-extensions 0.1.542 → 0.1.544

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/gh-readonly.ts +38 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.1.542",
3
+ "version": "0.1.544",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -425,6 +425,11 @@ export function stepsDetail(
425
425
  // 缓存检查避免重复网络请求。闭包状态不与其他扩展共享,key 无需全局前缀。
426
426
  const seq = createSeqState();
427
427
 
428
+ /** Absolute path of the raw job log cache file written by `getJobLog`. */
429
+ export function jobLogPath(runId: string, jobId: number): string {
430
+ return join(homedir(), ".cache", "pi", "ci-logs", runId, `${jobId}.log`);
431
+ }
432
+
428
433
  async function getJobLog(
429
434
  runId: string,
430
435
  jobId: number,
@@ -433,8 +438,8 @@ async function getJobLog(
433
438
  cwd: string | undefined,
434
439
  input?: unknown,
435
440
  ): Promise<string> {
436
- const cacheDir = join(homedir(), ".cache", "pi", "ci-logs", runId);
437
- const cacheFile = join(cacheDir, `${jobId}.log`);
441
+ const cacheFile = jobLogPath(runId, jobId);
442
+ const cacheDir = dirname(cacheFile);
438
443
 
439
444
  // 同一 runId:jobId 的请求串行执行:后一个进入时缓存已写入,直接命中缓存,
440
445
  // 不会重复发网络请求;串行也保证不会有两个并发写同一 cache 文件。
@@ -682,6 +687,15 @@ export function stripAnsi(text: string): string {
682
687
  return text.replace(/^\uFEFF/, "").replaceAll(ANSI_RE, "");
683
688
  }
684
689
 
690
+ /**
691
+ * Appended to `read-github-ci-logs` content so the raw log file path reaches
692
+ * the model: tool `details` is not part of the LLM context, so the path has to
693
+ * live in the returned text.
694
+ */
695
+ function rawLogNotice(path: string): string {
696
+ return `[Raw job log (whole job): ${path}]`;
697
+ }
698
+
685
699
  export interface StepLogParams {
686
700
  runId: string;
687
701
  job?: string;
@@ -772,6 +786,7 @@ export async function renderStepLog(
772
786
  });
773
787
 
774
788
  const rawLog = await fetchJobLog(targetJob.id);
789
+ const rawLogFile = jobLogPath(params.runId, targetJob.id);
775
790
 
776
791
  const stepLog = extractStepFromLog(rawLog, stepNum, targetJob.steps);
777
792
  if (stepLog === null) {
@@ -779,10 +794,10 @@ export async function renderStepLog(
779
794
  content: [
780
795
  {
781
796
  type: "text",
782
- text: `Could not extract step ${stepNum} from job "${targetJob.name}" logs. The log may be malformed or empty. Try fetching without \`step\` to see the full job log.`,
797
+ text: `Could not extract step ${stepNum} from job "${targetJob.name}" logs. The log may be malformed or empty — read the raw file instead.\n\n${rawLogNotice(rawLogFile)}`,
783
798
  },
784
799
  ],
785
- details: {},
800
+ details: { rawLogFile },
786
801
  };
787
802
  }
788
803
 
@@ -792,11 +807,12 @@ export async function renderStepLog(
792
807
  if (full) {
793
808
  const fullLines = clean.split("\n").length;
794
809
  return {
795
- content: [{ type: "text", text: clean }],
810
+ content: [{ type: "text", text: `${clean}\n\n${rawLogNotice(rawLogFile)}` }],
796
811
  details: {
797
812
  summary: `Step ${stepNum} — ${targetJob.name} / ${found.name}: complete output (${fullLines} lines)`,
798
813
  truncated: false,
799
814
  full: true,
815
+ rawLogFile,
800
816
  job: {
801
817
  name: targetJob.name,
802
818
  conclusion: targetJob.conclusion,
@@ -818,10 +834,10 @@ export async function renderStepLog(
818
834
  content: [
819
835
  {
820
836
  type: "text",
821
- text: `Offset ${offset} exceeds step log length (${totalLines} lines).`,
837
+ text: `Offset ${offset} exceeds step log length (${totalLines} lines).\n\n${rawLogNotice(rawLogFile)}`,
822
838
  },
823
839
  ],
824
- details: {},
840
+ details: { rawLogFile },
825
841
  };
826
842
  }
827
843
  logToShow = clean
@@ -837,10 +853,11 @@ export async function renderStepLog(
837
853
  const shownLines = text.split("\n").length;
838
854
 
839
855
  return {
840
- content: [{ type: "text", text }],
856
+ content: [{ type: "text", text: `${text}\n\n${rawLogNotice(rawLogFile)}` }],
841
857
  details: {
842
858
  summary: `Step ${stepNum} — ${targetJob.name} / ${found.name}: ${shownLines} of ${totalLines} lines${tr ? " (truncated)" : ""}`,
843
859
  truncated: tr,
860
+ rawLogFile,
844
861
  job: {
845
862
  name: targetJob.name,
846
863
  conclusion: targetJob.conclusion,
@@ -869,15 +886,18 @@ export interface JobLogsStep {
869
886
 
870
887
  export interface JobLogsOutput {
871
888
  name: string;
889
+ /** Absolute path of this job's raw log on disk, set when its log was fetched. */
890
+ log_file?: string;
872
891
  steps: JobLogsStep[];
873
892
  }
874
893
 
875
894
  /**
876
895
  * Render the result of `read-github-ci-logs` without a `step`: a JSON array of
877
- * jobs `[{ name, steps: [{ name, output? }] }]`. Every step is listed by name;
878
- * only failed steps carry an `output` (their log as plain text). `job` is an
879
- * optional filter; `offset`/`limit` control the size of each `output` text.
880
- * Pure — no network, no `gh`.
896
+ * jobs `[{ name, log_file?, steps: [{ name, output? }] }]`. Every step is listed
897
+ * by name; only failed steps carry an `output` (their log as plain text).
898
+ * `log_file` is the raw on-disk log path for jobs whose log was fetched. `job`
899
+ * is an optional filter; `offset`/`limit` control the size of each `output`
900
+ * text. Pure — no network, no `gh`.
881
901
  */
882
902
  export async function renderJobLogs(
883
903
  params: JobLogsParams,
@@ -961,7 +981,11 @@ export async function renderJobLogs(
961
981
  }
962
982
  }
963
983
 
964
- output.push({ name: j.name, steps });
984
+ output.push({
985
+ name: j.name,
986
+ ...(rawLog !== null && { log_file: jobLogPath(params.runId, j.id) }),
987
+ steps,
988
+ });
965
989
  }
966
990
 
967
991
  const totalJobs = output.length;
@@ -1856,7 +1880,7 @@ export default function ghReadonlyTools(pi: ExtensionAPI) {
1856
1880
  name: "read-github-ci-logs",
1857
1881
  label: "GitHub CI Logs",
1858
1882
  description:
1859
- "Get CI logs from a GitHub Actions workflow run. Without step: returns a JSON array of jobs [{name, steps:[{name, output?}]}] where every step is listed by name and failed steps carry their log as plain text in `output`. With step (requires job): returns that step's complete log as plain text. offset/limit control the size of every expanded output. Use run_id from list-github-workflow-runs. Note: queued jobs have no logs yet; use watch-github-run to wait for completion. Set full=true for complete untruncated outputs (every step when step is omitted; caution: very large outputs consume a lot of LLM context). Set output_file=/path to write the complete log to a file instead of returning it (requires job when the run has multiple jobs); the tool returns the file path to read.",
1883
+ "Get CI logs from a GitHub Actions workflow run. Without step: returns a JSON array of jobs [{name, log_file?, steps:[{name, output?}]}] where every step is listed by name and failed steps carry their log as plain text in `output`. With step (requires job): returns that step's complete log as plain text. Every fetched job log is also saved raw (timestamps and ANSI kept) to a local file whose path is reported — `log_file` on the job, or a trailing `[Raw job log (whole job): <path>]` line with step — so read/grep that file for the complete, untruncated log. offset/limit control the size of every expanded output. Use run_id from list-github-workflow-runs. Note: queued jobs have no logs yet; use watch-github-run to wait for completion. Set full=true for complete untruncated outputs (every step when step is omitted; caution: very large outputs consume a lot of LLM context). Set output_file=/path to write the complete cleaned log to this file instead of returning it (requires job when the run has multiple jobs); the tool returns the file path to read.",
1860
1884
  promptSnippet: "Read GitHub CI logs",
1861
1885
  parameters: Type.Object({
1862
1886
  run_id: Type.Union([Type.Number(), Type.String()], { description: "Workflow run ID" }),