takomi 2.1.32 → 2.1.34

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 (54) hide show
  1. package/.pi/extensions/oauth-router/config.ts +14 -5
  2. package/.pi/extensions/oauth-router/index.ts +130 -130
  3. package/.pi/extensions/oauth-router/provider.ts +29 -0
  4. package/.pi/extensions/oauth-router/state.ts +372 -372
  5. package/.pi/extensions/oauth-router/types.ts +1 -1
  6. package/.pi/extensions/takomi-runtime/command-text.ts +2 -4
  7. package/.pi/extensions/takomi-runtime/commands.ts +15 -21
  8. package/.pi/extensions/takomi-runtime/index.ts +127 -53
  9. package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +296 -296
  10. package/.pi/extensions/takomi-runtime/ui.ts +18 -11
  11. package/.pi/extensions/takomi-subagents/index.ts +2 -0
  12. package/.pi/extensions/takomi-subagents/native-render.ts +27 -5
  13. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +1 -1
  14. package/.pi/extensions/takomi-subagents/tool-runner.ts +1 -0
  15. package/assets/.agent/skills/photo-book-builder/SKILL.md +96 -0
  16. package/assets/.agent/skills/photo-book-builder/references/layout_templates.md +72 -0
  17. package/assets/.agent/skills/photo-book-builder/scripts/create_full_bleed_layouts.py +212 -0
  18. package/assets/.agent/skills/photo-book-builder/scripts/organize_photos.py +99 -0
  19. package/assets/.agent/skills/photo-book-builder/scripts/revert_organization.py +61 -0
  20. package/assets/.agent/skills/photo-book-builder/scripts/upscale_covers.py +47 -0
  21. package/assets/.agent/skills/youtube-pipeline/SKILL.md +73 -62
  22. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Application.md +1 -0
  23. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Phase 1.md +86 -0
  24. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Phase 2.md +106 -0
  25. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Phase 3.md +112 -0
  26. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Phase 4.md +90 -0
  27. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Phased Outline.md +58 -0
  28. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Repurposing.md +1 -0
  29. package/assets/.agent/skills/youtube-pipeline/resources/knowledge/Research.md +438 -0
  30. package/assets/.agent/skills/youtube-pipeline/resources/prompts/Shorts Bridge Protocol.md +159 -0
  31. package/assets/.agent/skills/youtube-pipeline/resources/prompts/Title Thumbnail Picker Prompt.md +144 -0
  32. package/assets/.agent/skills/youtube-pipeline/resources/prompts/Transcription Extraction Prompt v2.md +190 -0
  33. package/assets/.agent/skills/youtube-pipeline/resources/prompts/Transcription Extraction Prompt.md +156 -0
  34. package/assets/.agent/skills/youtube-pipeline/resources/prompts/Video QA Prompt.md +133 -0
  35. package/assets/.agent/skills/youtube-pipeline/resources/youtube-phase1-strategy.md +28 -18
  36. package/assets/.agent/skills/youtube-pipeline/resources/youtube-phase2-packaging.md +2 -2
  37. package/assets/.agent/skills/youtube-pipeline/resources/youtube-phase3-scripting.md +2 -2
  38. package/assets/.agent/skills/youtube-pipeline/resources/youtube-phase3.5-shorts.md +2 -2
  39. package/assets/.agent/skills/youtube-pipeline/resources/youtube-phase4-production.md +2 -2
  40. package/assets/.agent/skills/youtube-pipeline/resources/youtube-pipeline.md +15 -15
  41. package/assets/.agent/skills/youtube-pipeline/scripts/google-trends/package.json +17 -0
  42. package/assets/.agent/skills/youtube-pipeline/scripts/google-trends/pnpm-lock.yaml +31 -0
  43. package/assets/.agent/skills/youtube-pipeline/scripts/google-trends/search.js +168 -0
  44. package/package.json +1 -1
  45. package/src/cli.js +13 -4
  46. package/src/pi-harness.js +36 -14
  47. package/src/pi-optional-features.js +190 -190
  48. package/src/postinstall.js +27 -27
  49. package/src/skills-catalog.js +245 -245
  50. package/src/skills-installer.js +244 -244
  51. package/src/skills-selection-tui.js +200 -200
  52. package/src/store.js +418 -418
  53. package/src/takomi-stats.d.ts +3 -3
  54. package/src/takomi-stats.js +442 -35
@@ -9,6 +9,10 @@ export const CONFIG_PATH = join(DATA_ROOT, "config.json");
9
9
  export const CREDENTIALS_PATH = join(DATA_ROOT, "credentials.json");
10
10
  export const STATE_PATH = join(DATA_ROOT, "state.json");
11
11
 
12
+ const LEGACY_CODEX_CONTEXT_WINDOW = 272000;
13
+ const SAFE_CODEX_CONTEXT_WINDOW = 240000;
14
+ const CODEX_MODEL_IDS = new Set(["gpt-5.1", "gpt-5.4", "gpt-5.4-mini", "gpt-5.5"]);
15
+
12
16
  const DEFAULT_MODELS: RouterModelConfig[] = [
13
17
  {
14
18
  id: "gpt-4o",
@@ -43,7 +47,7 @@ const DEFAULT_MODELS: RouterModelConfig[] = [
43
47
  reasoning: true,
44
48
  input: ["text", "image"],
45
49
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
46
- contextWindow: 272000,
50
+ contextWindow: SAFE_CODEX_CONTEXT_WINDOW,
47
51
  maxTokens: 128000,
48
52
  },
49
53
  {
@@ -52,7 +56,7 @@ const DEFAULT_MODELS: RouterModelConfig[] = [
52
56
  reasoning: true,
53
57
  input: ["text", "image"],
54
58
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
55
- contextWindow: 272000,
59
+ contextWindow: SAFE_CODEX_CONTEXT_WINDOW,
56
60
  maxTokens: 128000,
57
61
  },
58
62
  {
@@ -61,7 +65,7 @@ const DEFAULT_MODELS: RouterModelConfig[] = [
61
65
  reasoning: true,
62
66
  input: ["text", "image"],
63
67
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
64
- contextWindow: 272000,
68
+ contextWindow: SAFE_CODEX_CONTEXT_WINDOW,
65
69
  maxTokens: 128000,
66
70
  },
67
71
  {
@@ -70,7 +74,7 @@ const DEFAULT_MODELS: RouterModelConfig[] = [
70
74
  reasoning: true,
71
75
  input: ["text", "image"],
72
76
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
73
- contextWindow: 272000,
77
+ contextWindow: SAFE_CODEX_CONTEXT_WINDOW,
74
78
  maxTokens: 128000,
75
79
  },
76
80
  ];
@@ -210,7 +214,12 @@ function mergeModelConfigs(candidateModels: RouterModelConfig[] | undefined): Ro
210
214
  const previous = merged.get(model.id) ?? ({} as RouterModelConfig);
211
215
  merged.set(model.id, { ...previous, ...deepClone(model), id: model.id });
212
216
  }
213
- return Array.from(merged.values());
217
+ return Array.from(merged.values()).map((model) => {
218
+ if (CODEX_MODEL_IDS.has(model.id) && model.contextWindow === LEGACY_CODEX_CONTEXT_WINDOW) {
219
+ return { ...model, contextWindow: SAFE_CODEX_CONTEXT_WINDOW };
220
+ }
221
+ return model;
222
+ });
214
223
  }
215
224
 
216
225
  function mergeUpstreamConfigs(candidateUpstreams: RouterUpstreamConfig[] | undefined): RouterUpstreamConfig[] {
@@ -1,130 +1,130 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { registerRouterCommands, formatStatusReport } from "./commands.ts";
3
- import { registerRouterProvider, RouterRuntime } from "./provider.ts";
4
- import type { RouterUiEvent } from "./types.ts";
5
-
6
- function getHealthSummary(runtime: RouterRuntime) {
7
- const rows = runtime.getStatusRows();
8
- const healthy = rows.filter((row) => {
9
- if (!row.enabled) return false;
10
- if (row.authHealth !== "ok") return false;
11
- if (row.cooldownUntil && row.cooldownUntil > Date.now()) return false;
12
- if (row.penaltyUntil && row.penaltyUntil > Date.now()) return false;
13
- return true;
14
- }).length;
15
-
16
- return `oauth-router ${healthy}/${rows.length || 0} healthy | ${runtime.getPolicy()}`;
17
- }
18
-
19
- function formatDuration(ms?: number): string {
20
- if (!ms || ms <= 0) return "now";
21
- if (ms < 1_000) return `${Math.round(ms)}ms`;
22
- return `${Math.round(ms / 1_000)}s`;
23
- }
24
-
25
- function truncate(text: string | undefined, max = 110): string {
26
- if (!text) return "";
27
- const clean = text.replace(/\s+/g, " ").trim();
28
- return clean.length > max ? `${clean.slice(0, Math.max(0, max - 1))}…` : clean;
29
- }
30
-
31
- function formatRoute(event: RouterUiEvent): string {
32
- const parts = [event.accountLabel || event.accountId, event.upstreamId].filter(Boolean);
33
- return parts.length ? parts.join(" @ ") : "router";
34
- }
35
-
36
- function formatActivityStatus(event: RouterUiEvent): string {
37
- const route = formatRoute(event);
38
- const model = event.modelId ? `${event.modelId} ` : "";
39
- const reason = truncate(event.message, 70);
40
-
41
- switch (event.phase) {
42
- case "attempt": {
43
- const retry = event.retryAttempt && event.maxRetries
44
- ? ` retry ${event.retryAttempt}/${event.maxRetries}`
45
- : "";
46
- return `oauth-router contacting ${model}via ${route}${retry}`;
47
- }
48
- case "retry":
49
- return `oauth-router retry ${event.retryAttempt}/${event.maxRetries} in ${formatDuration(event.delayMs)} | ${reason || event.failureKind || "network error"}`;
50
- case "failover":
51
- return `oauth-router failover from ${route} | ${reason || event.failureKind || "upstream error"}`;
52
- case "success":
53
- return `oauth-router ok: ${model}via ${route}`;
54
- case "error":
55
- return `oauth-router error | ${reason || event.failureKind || "request failed"}`;
56
- }
57
- }
58
-
59
- function notifyActivity(ctx: ExtensionContext, event: RouterUiEvent) {
60
- if (event.phase === "attempt" || event.phase === "success") return;
61
-
62
- const message = formatActivityStatus(event);
63
- const level = event.phase === "error" ? "error" : "info";
64
- ctx.ui.notify(message, level);
65
- }
66
-
67
- function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyOnLoad = false) {
68
- let activeCtx: ExtensionContext | undefined;
69
-
70
- const setBaseStatus = (ctx: ExtensionContext) => {
71
- activeCtx = ctx;
72
- if (ctx.hasUI) ctx.ui.setStatus("oauth-router", getHealthSummary(runtime));
73
- };
74
-
75
- runtime.setUiReporter((event) => {
76
- const ctx = activeCtx;
77
- if (!ctx?.hasUI) return;
78
- try {
79
- ctx.ui.setStatus("oauth-router", formatActivityStatus(event));
80
- notifyActivity(ctx, event);
81
- } catch {
82
- // Best-effort UI only; never disturb provider streaming.
83
- }
84
- });
85
-
86
- pi.on("session_start", async (_event, ctx) => {
87
- setBaseStatus(ctx);
88
- if (notifyOnLoad) ctx.ui.notify("oauth-router loaded", "info");
89
- });
90
-
91
- pi.on("agent_start", async (_event, ctx) => {
92
- setBaseStatus(ctx);
93
- });
94
-
95
- pi.on("turn_start", async (_event, ctx) => {
96
- setBaseStatus(ctx);
97
- });
98
-
99
- pi.on("turn_end", async (_event, ctx) => {
100
- setBaseStatus(ctx);
101
- });
102
-
103
- pi.on("agent_end", async (_event, ctx) => {
104
- setBaseStatus(ctx);
105
- });
106
-
107
- pi.on("session_shutdown", async () => {
108
- activeCtx = undefined;
109
- });
110
- }
111
-
112
- export default function (pi: ExtensionAPI) {
113
- const runtime = new RouterRuntime();
114
-
115
- registerRouterProvider(pi, runtime);
116
- registerRouterCommands(pi, runtime);
117
- installRouterUiBridge(pi, runtime, false);
118
-
119
- pi.registerCommand("router-debug-report", {
120
- description: "Emit a detailed oauth-router report",
121
- handler: async () => {
122
- pi.sendMessage({
123
- customType: "oauth-router",
124
- content: formatStatusReport(runtime),
125
- display: true,
126
- details: { source: "oauth-router", debug: true },
127
- });
128
- },
129
- });
130
- }
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { registerRouterCommands, formatStatusReport } from "./commands.ts";
3
+ import { registerRouterProvider, RouterRuntime } from "./provider.ts";
4
+ import type { RouterUiEvent } from "./types.ts";
5
+
6
+ function getHealthSummary(runtime: RouterRuntime) {
7
+ const rows = runtime.getStatusRows();
8
+ const healthy = rows.filter((row) => {
9
+ if (!row.enabled) return false;
10
+ if (row.authHealth !== "ok") return false;
11
+ if (row.cooldownUntil && row.cooldownUntil > Date.now()) return false;
12
+ if (row.penaltyUntil && row.penaltyUntil > Date.now()) return false;
13
+ return true;
14
+ }).length;
15
+
16
+ return `oauth-router ${healthy}/${rows.length || 0} healthy | ${runtime.getPolicy()}`;
17
+ }
18
+
19
+ function formatDuration(ms?: number): string {
20
+ if (!ms || ms <= 0) return "now";
21
+ if (ms < 1_000) return `${Math.round(ms)}ms`;
22
+ return `${Math.round(ms / 1_000)}s`;
23
+ }
24
+
25
+ function truncate(text: string | undefined, max = 110): string {
26
+ if (!text) return "";
27
+ const clean = text.replace(/\s+/g, " ").trim();
28
+ return clean.length > max ? `${clean.slice(0, Math.max(0, max - 1))}…` : clean;
29
+ }
30
+
31
+ function formatRoute(event: RouterUiEvent): string {
32
+ const parts = [event.accountLabel || event.accountId, event.upstreamId].filter(Boolean);
33
+ return parts.length ? parts.join(" @ ") : "router";
34
+ }
35
+
36
+ function formatActivityStatus(event: RouterUiEvent): string {
37
+ const route = formatRoute(event);
38
+ const model = event.modelId ? `${event.modelId} ` : "";
39
+ const reason = truncate(event.message, 70);
40
+
41
+ switch (event.phase) {
42
+ case "attempt": {
43
+ const retry = event.retryAttempt && event.maxRetries
44
+ ? ` retry ${event.retryAttempt}/${event.maxRetries}`
45
+ : "";
46
+ return `oauth-router contacting ${model}via ${route}${retry}`;
47
+ }
48
+ case "retry":
49
+ return `oauth-router retry ${event.retryAttempt}/${event.maxRetries} in ${formatDuration(event.delayMs)} | ${reason || event.failureKind || "network error"}`;
50
+ case "failover":
51
+ return `oauth-router failover from ${route} | ${reason || event.failureKind || "upstream error"}`;
52
+ case "success":
53
+ return `oauth-router ok: ${model}via ${route}`;
54
+ case "error":
55
+ return `oauth-router error | ${reason || event.failureKind || "request failed"}`;
56
+ }
57
+ }
58
+
59
+ function notifyActivity(ctx: ExtensionContext, event: RouterUiEvent) {
60
+ if (event.phase === "attempt" || event.phase === "success") return;
61
+
62
+ const message = formatActivityStatus(event);
63
+ const level = event.phase === "error" ? "error" : "info";
64
+ ctx.ui.notify(message, level);
65
+ }
66
+
67
+ function installRouterUiBridge(pi: ExtensionAPI, runtime: RouterRuntime, notifyOnLoad = false) {
68
+ let activeCtx: ExtensionContext | undefined;
69
+
70
+ const setBaseStatus = (ctx: ExtensionContext) => {
71
+ activeCtx = ctx;
72
+ if (ctx.hasUI) ctx.ui.setStatus("oauth-router", getHealthSummary(runtime));
73
+ };
74
+
75
+ runtime.setUiReporter((event) => {
76
+ const ctx = activeCtx;
77
+ if (!ctx?.hasUI) return;
78
+ try {
79
+ ctx.ui.setStatus("oauth-router", formatActivityStatus(event));
80
+ notifyActivity(ctx, event);
81
+ } catch {
82
+ // Best-effort UI only; never disturb provider streaming.
83
+ }
84
+ });
85
+
86
+ pi.on("session_start", async (_event, ctx) => {
87
+ setBaseStatus(ctx);
88
+ if (notifyOnLoad) ctx.ui.notify("oauth-router loaded", "info");
89
+ });
90
+
91
+ pi.on("agent_start", async (_event, ctx) => {
92
+ setBaseStatus(ctx);
93
+ });
94
+
95
+ pi.on("turn_start", async (_event, ctx) => {
96
+ setBaseStatus(ctx);
97
+ });
98
+
99
+ pi.on("turn_end", async (_event, ctx) => {
100
+ setBaseStatus(ctx);
101
+ });
102
+
103
+ pi.on("agent_end", async (_event, ctx) => {
104
+ setBaseStatus(ctx);
105
+ });
106
+
107
+ pi.on("session_shutdown", async () => {
108
+ activeCtx = undefined;
109
+ });
110
+ }
111
+
112
+ export default function (pi: ExtensionAPI) {
113
+ const runtime = new RouterRuntime();
114
+
115
+ registerRouterProvider(pi, runtime);
116
+ registerRouterCommands(pi, runtime);
117
+ installRouterUiBridge(pi, runtime, false);
118
+
119
+ pi.registerCommand("router-debug-report", {
120
+ description: "Emit a detailed oauth-router report",
121
+ handler: async () => {
122
+ pi.sendMessage({
123
+ customType: "oauth-router",
124
+ content: formatStatusReport(runtime),
125
+ display: true,
126
+ details: { source: "oauth-router", debug: true },
127
+ });
128
+ },
129
+ });
130
+ }
@@ -124,6 +124,20 @@ function isClientNetworkFailure(lower: string): boolean {
124
124
  ].some((token) => lower.includes(token));
125
125
  }
126
126
 
127
+ function isContextOverflowFailure(lower: string): boolean {
128
+ return [
129
+ "context_length_exceeded",
130
+ "context length exceeded",
131
+ "context window exceeded",
132
+ "exceeds the context window",
133
+ "exceeds the model's maximum context length",
134
+ "exceeds model's maximum context length",
135
+ "maximum context length",
136
+ "too many tokens",
137
+ "token limit exceeded",
138
+ ].some((token) => lower.includes(token));
139
+ }
140
+
127
141
  function classifyFailure(
128
142
  status: number | undefined,
129
143
  headers: Record<string, string>,
@@ -137,6 +151,10 @@ function classifyFailure(
137
151
  return { kind: "rate-limit", status: status ?? 429, retryAfterMs, message };
138
152
  }
139
153
 
154
+ if (isContextOverflowFailure(lower)) {
155
+ return { kind: "context-overflow", status, message };
156
+ }
157
+
140
158
  if (status === 401 || status === 403 || lower.includes("unauthorized") || lower.includes("forbidden")) {
141
159
  return { kind: "auth", status: status ?? 401, message };
142
160
  }
@@ -461,6 +479,11 @@ export class RouterRuntime {
461
479
  this.state.recordClientNetworkFailure(accountId, failure.status, failure.message);
462
480
  }
463
481
  break;
482
+ case "context-overflow":
483
+ // Context-size failures are request-level, not account-health failures.
484
+ // Do not cool down or quarantine accounts; Pi needs the overflow error surfaced
485
+ // directly so its built-in compaction recovery can compact and retry.
486
+ break;
464
487
  case "fatal":
465
488
  this.state.markTransientFailure(accountId, this.config.transientPenaltyMs, failure.status ?? 500, failure.message);
466
489
  break;
@@ -567,6 +590,12 @@ export class RouterRuntime {
567
590
  }
568
591
 
569
592
  const failure = classifyFailure(responseStatus, responseHeaders, message, this.config);
593
+
594
+ if (failure.kind === "context-overflow") {
595
+ outerStream.push(event);
596
+ return { completed: true, emittedMeaningfulOutput, failure };
597
+ }
598
+
570
599
  this.markFailure(selection.account.id, failure);
571
600
 
572
601
  if (!emittedMeaningfulOutput) {