gsd-pi 2.37.0 → 2.37.1-dev.49503be
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/README.md +21 -20
- package/dist/onboarding.js +1 -0
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +54 -1
- package/dist/resources/extensions/gsd/auto-loop.js +18 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +55 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +19 -1
- package/dist/resources/extensions/gsd/auto.js +42 -5
- package/dist/resources/extensions/gsd/commands.js +80 -33
- package/dist/resources/extensions/gsd/files.js +41 -0
- package/dist/resources/extensions/gsd/git-service.js +9 -1
- package/dist/resources/extensions/gsd/history.js +2 -1
- package/dist/resources/extensions/gsd/metrics.js +4 -2
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/session-lock.js +26 -6
- package/dist/resources/extensions/shared/format-utils.js +5 -41
- package/dist/resources/extensions/shared/layout-utils.js +46 -0
- package/dist/resources/extensions/shared/mod.js +2 -1
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +78 -0
- package/src/resources/extensions/gsd/auto-loop.ts +24 -6
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +68 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +18 -0
- package/src/resources/extensions/gsd/auto.ts +56 -5
- package/src/resources/extensions/gsd/commands.ts +85 -31
- package/src/resources/extensions/gsd/files.ts +45 -0
- package/src/resources/extensions/gsd/git-service.ts +12 -1
- package/src/resources/extensions/gsd/history.ts +2 -1
- package/src/resources/extensions/gsd/metrics.ts +4 -2
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/session-lock.ts +41 -6
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +37 -1
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +367 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
- package/src/resources/extensions/gsd/types.ts +41 -0
- package/src/resources/extensions/shared/format-utils.ts +5 -44
- package/src/resources/extensions/shared/layout-utils.ts +49 -0
- package/src/resources/extensions/shared/mod.ts +7 -4
- package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
|
@@ -349,10 +349,18 @@ export class GitServiceImpl {
|
|
|
349
349
|
}
|
|
350
350
|
const wtName = detectWorktreeName(this.basePath);
|
|
351
351
|
if (wtName) {
|
|
352
|
+
// Auto-mode worktrees use milestone/<MID> branches (wtName = milestone ID)
|
|
353
|
+
const milestoneBranch = `milestone/${wtName}`;
|
|
354
|
+
const currentBranch = nativeGetCurrentBranch(this.basePath);
|
|
355
|
+
// If we're on a milestone/<MID> branch, use it (auto-mode case)
|
|
356
|
+
if (currentBranch.startsWith("milestone/")) {
|
|
357
|
+
return currentBranch;
|
|
358
|
+
}
|
|
359
|
+
// Otherwise check for manual worktree branch (worktree/<name>)
|
|
352
360
|
const wtBranch = `worktree/${wtName}`;
|
|
353
361
|
if (nativeBranchExists(this.basePath, wtBranch))
|
|
354
362
|
return wtBranch;
|
|
355
|
-
return
|
|
363
|
+
return currentBranch;
|
|
356
364
|
}
|
|
357
365
|
// Repo-level default detection: origin/HEAD → main → master → current branch.
|
|
358
366
|
// Native path uses libgit2 (single call), fallback spawns multiple git processes.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// GSD Extension — Session History View
|
|
2
2
|
// Human-readable display of past auto-mode unit executions.
|
|
3
|
-
import { formatDuration,
|
|
3
|
+
import { formatDuration, truncateWithEllipsis } from "../shared/format-utils.js";
|
|
4
|
+
import { padRight } from "../shared/layout-utils.js";
|
|
4
5
|
import { getLedger, getProjectTotals, formatCost, formatTokenCount, aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk, } from "./metrics.js";
|
|
5
6
|
/**
|
|
6
7
|
* Show recent unit execution history with cost, tokens, and duration.
|
|
@@ -17,8 +17,10 @@ import { gsdRoot } from "./paths.js";
|
|
|
17
17
|
import { getAndClearSkills } from "./skill-telemetry.js";
|
|
18
18
|
import { loadJsonFile, loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
19
19
|
import { parseUnitId } from "./unit-id.js";
|
|
20
|
-
// Re-export from shared —
|
|
21
|
-
|
|
20
|
+
// Re-export from shared — import directly from format-utils to avoid pulling
|
|
21
|
+
// in the full barrel (mod.js → ui.js → @gsd/pi-tui) which breaks when loaded
|
|
22
|
+
// outside jiti's alias resolution (e.g. dynamic import in auto-loop reports).
|
|
23
|
+
export { formatTokenCount } from "../shared/format-utils.js";
|
|
22
24
|
export function classifyUnitPhase(unitType) {
|
|
23
25
|
switch (unitType) {
|
|
24
26
|
case "research-milestone":
|
|
@@ -65,11 +65,12 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
|
|
|
65
65
|
"compression_strategy",
|
|
66
66
|
"context_selection",
|
|
67
67
|
"widget_mode",
|
|
68
|
+
"reactive_execution",
|
|
68
69
|
]);
|
|
69
70
|
/** Canonical list of all dispatch unit types. */
|
|
70
71
|
export const KNOWN_UNIT_TYPES = [
|
|
71
72
|
"research-milestone", "plan-milestone", "research-slice", "plan-slice",
|
|
72
|
-
"execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
|
|
73
|
+
"execute-task", "reactive-execute", "complete-slice", "replan-slice", "reassess-roadmap",
|
|
73
74
|
"run-uat", "complete-milestone",
|
|
74
75
|
];
|
|
75
76
|
export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
@@ -500,6 +500,48 @@ export function validatePreferences(preferences) {
|
|
|
500
500
|
validated.parallel = parallel;
|
|
501
501
|
}
|
|
502
502
|
}
|
|
503
|
+
// ─── Reactive Execution ─────────────────────────────────────────────────
|
|
504
|
+
if (preferences.reactive_execution !== undefined) {
|
|
505
|
+
if (typeof preferences.reactive_execution === "object" && preferences.reactive_execution !== null) {
|
|
506
|
+
const re = preferences.reactive_execution;
|
|
507
|
+
const validRe = {};
|
|
508
|
+
if (re.enabled !== undefined) {
|
|
509
|
+
if (typeof re.enabled === "boolean")
|
|
510
|
+
validRe.enabled = re.enabled;
|
|
511
|
+
else
|
|
512
|
+
errors.push("reactive_execution.enabled must be a boolean");
|
|
513
|
+
}
|
|
514
|
+
if (re.max_parallel !== undefined) {
|
|
515
|
+
const mp = typeof re.max_parallel === "number" ? re.max_parallel : Number(re.max_parallel);
|
|
516
|
+
if (Number.isFinite(mp) && mp >= 1 && mp <= 8) {
|
|
517
|
+
validRe.max_parallel = Math.floor(mp);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
errors.push("reactive_execution.max_parallel must be a number between 1 and 8");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (re.isolation_mode !== undefined) {
|
|
524
|
+
if (re.isolation_mode === "same-tree") {
|
|
525
|
+
validRe.isolation_mode = "same-tree";
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
errors.push('reactive_execution.isolation_mode must be "same-tree"');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const knownReKeys = new Set(["enabled", "max_parallel", "isolation_mode"]);
|
|
532
|
+
for (const key of Object.keys(re)) {
|
|
533
|
+
if (!knownReKeys.has(key)) {
|
|
534
|
+
warnings.push(`unknown reactive_execution key "${key}" — ignored`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (Object.keys(validRe).length > 0) {
|
|
538
|
+
validated.reactive_execution = validRe;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
errors.push("reactive_execution must be an object");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
503
545
|
// ─── Verification Preferences ───────────────────────────────────────────
|
|
504
546
|
if (preferences.verification_commands !== undefined) {
|
|
505
547
|
if (Array.isArray(preferences.verification_commands)) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Reactive Task Execution — Parallel Dispatch
|
|
2
|
+
|
|
3
|
+
**Working directory:** `{{workingDirectory}}`
|
|
4
|
+
**Milestone:** {{milestoneId}} — {{milestoneTitle}}
|
|
5
|
+
**Slice:** {{sliceId}} — {{sliceTitle}}
|
|
6
|
+
|
|
7
|
+
## Mission
|
|
8
|
+
|
|
9
|
+
You are executing **multiple tasks in parallel** for this slice. The task graph below shows which tasks are ready for simultaneous execution based on their input/output dependencies.
|
|
10
|
+
|
|
11
|
+
**Critical rule:** Use the `subagent` tool in **parallel mode** to dispatch all ready tasks simultaneously. Each subagent gets a self-contained execute-task prompt. After all subagents return, verify each task's outputs and write summaries.
|
|
12
|
+
|
|
13
|
+
## Task Dependency Graph
|
|
14
|
+
|
|
15
|
+
{{graphContext}}
|
|
16
|
+
|
|
17
|
+
## Ready Tasks for Parallel Dispatch
|
|
18
|
+
|
|
19
|
+
{{readyTaskCount}} tasks are ready for parallel execution:
|
|
20
|
+
|
|
21
|
+
{{readyTaskList}}
|
|
22
|
+
|
|
23
|
+
## Execution Protocol
|
|
24
|
+
|
|
25
|
+
1. **Dispatch all ready tasks** using `subagent` in parallel mode. Each subagent prompt is provided below.
|
|
26
|
+
2. **Wait for all subagents** to complete.
|
|
27
|
+
3. **Verify each task's outputs** — check that expected files were created/modified and that verification commands pass.
|
|
28
|
+
4. **Write task summaries** for each completed task using the task-summary template.
|
|
29
|
+
5. **Mark completed tasks** as done in the slice plan (checkbox `[x]`).
|
|
30
|
+
6. **Commit** all changes with a clear message covering the parallel batch.
|
|
31
|
+
|
|
32
|
+
If any subagent fails:
|
|
33
|
+
- Write a summary for the failed task with `blocker_discovered: true`
|
|
34
|
+
- Continue marking the successful tasks as done
|
|
35
|
+
- The orchestrator will handle re-dispatch on the next iteration
|
|
36
|
+
|
|
37
|
+
## Subagent Prompts
|
|
38
|
+
|
|
39
|
+
{{subagentPrompts}}
|
|
40
|
+
|
|
41
|
+
{{inlinedTemplates}}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive Task Graph — derives dependency edges from task plan IO signatures.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that build a DAG from task IO intersections and resolve
|
|
5
|
+
* which tasks are currently ready for parallel dispatch. Used by the
|
|
6
|
+
* reactive-execute dispatch path (ADR-004).
|
|
7
|
+
*
|
|
8
|
+
* Graph derivation and resolution functions are pure (no filesystem access).
|
|
9
|
+
* The `loadSliceTaskIO` loader at the bottom is the only async/IO function.
|
|
10
|
+
*/
|
|
11
|
+
import { loadFile, parsePlan, parseTaskPlanIO } from "./files.js";
|
|
12
|
+
import { resolveTasksDir, resolveTaskFiles } from "./paths.js";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
15
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
16
|
+
// ─── Graph Construction ───────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Build a dependency graph from task IO signatures.
|
|
19
|
+
*
|
|
20
|
+
* A task T_b depends on T_a when any of T_b's inputFiles appear in T_a's
|
|
21
|
+
* outputFiles. Self-references are excluded.
|
|
22
|
+
*
|
|
23
|
+
* Tasks are returned in the same order as the input array.
|
|
24
|
+
*/
|
|
25
|
+
export function deriveTaskGraph(tasks) {
|
|
26
|
+
// Build output → producer lookup
|
|
27
|
+
const outputToProducer = new Map();
|
|
28
|
+
for (const task of tasks) {
|
|
29
|
+
for (const outFile of task.outputFiles) {
|
|
30
|
+
const existing = outputToProducer.get(outFile);
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.push(task.id);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
outputToProducer.set(outFile, [task.id]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return tasks.map((task) => {
|
|
40
|
+
const deps = new Set();
|
|
41
|
+
for (const inFile of task.inputFiles) {
|
|
42
|
+
const producers = outputToProducer.get(inFile);
|
|
43
|
+
if (producers) {
|
|
44
|
+
for (const pid of producers) {
|
|
45
|
+
if (pid !== task.id)
|
|
46
|
+
deps.add(pid);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...task,
|
|
52
|
+
dependsOn: [...deps].sort(),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ─── Ready Set Resolution ─────────────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Return task IDs whose dependencies are all in `completed`.
|
|
59
|
+
* Excludes tasks that are already done or in-flight.
|
|
60
|
+
*/
|
|
61
|
+
export function getReadyTasks(graph, completed, inFlight) {
|
|
62
|
+
return graph
|
|
63
|
+
.filter((node) => {
|
|
64
|
+
if (node.done || completed.has(node.id) || inFlight.has(node.id))
|
|
65
|
+
return false;
|
|
66
|
+
return node.dependsOn.every((dep) => completed.has(dep));
|
|
67
|
+
})
|
|
68
|
+
.map((node) => node.id);
|
|
69
|
+
}
|
|
70
|
+
// ─── Conflict-Free Subset Selection ──────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Greedy selection of non-conflicting tasks up to `maxParallel`.
|
|
73
|
+
*
|
|
74
|
+
* Two tasks conflict if they share any outputFile. We also exclude tasks
|
|
75
|
+
* whose outputs overlap with `inFlightOutputs` (files being written by
|
|
76
|
+
* tasks currently in progress).
|
|
77
|
+
*/
|
|
78
|
+
export function chooseNonConflictingSubset(readyIds, graph, maxParallel, inFlightOutputs) {
|
|
79
|
+
const nodeMap = new Map(graph.map((n) => [n.id, n]));
|
|
80
|
+
const claimed = new Set(inFlightOutputs);
|
|
81
|
+
const selected = [];
|
|
82
|
+
for (const id of readyIds) {
|
|
83
|
+
if (selected.length >= maxParallel)
|
|
84
|
+
break;
|
|
85
|
+
const node = nodeMap.get(id);
|
|
86
|
+
if (!node)
|
|
87
|
+
continue;
|
|
88
|
+
// Check for output overlap with already-selected or in-flight
|
|
89
|
+
const conflicts = node.outputFiles.some((f) => claimed.has(f));
|
|
90
|
+
if (conflicts)
|
|
91
|
+
continue;
|
|
92
|
+
// Claim this task's outputs
|
|
93
|
+
for (const f of node.outputFiles)
|
|
94
|
+
claimed.add(f);
|
|
95
|
+
selected.push(id);
|
|
96
|
+
}
|
|
97
|
+
return selected;
|
|
98
|
+
}
|
|
99
|
+
// ─── Graph Quality Checks ─────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Returns true if any incomplete task has 0 inputFiles AND 0 outputFiles.
|
|
102
|
+
*
|
|
103
|
+
* An ambiguous graph means IO annotations are too sparse to derive reliable
|
|
104
|
+
* edges — the dispatcher should fall back to sequential execution.
|
|
105
|
+
*/
|
|
106
|
+
export function isGraphAmbiguous(graph) {
|
|
107
|
+
return graph.some((node) => !node.done &&
|
|
108
|
+
node.inputFiles.length === 0 &&
|
|
109
|
+
node.outputFiles.length === 0);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detect deadlock: no tasks are ready and none are in-flight, yet incomplete
|
|
113
|
+
* tasks remain. This indicates a circular dependency or impossible state.
|
|
114
|
+
*/
|
|
115
|
+
export function detectDeadlock(graph, completed, inFlight) {
|
|
116
|
+
const incomplete = graph.filter((n) => !n.done && !completed.has(n.id) && !inFlight.has(n.id));
|
|
117
|
+
if (incomplete.length === 0)
|
|
118
|
+
return false; // all done
|
|
119
|
+
if (inFlight.size > 0)
|
|
120
|
+
return false; // something is running, wait for it
|
|
121
|
+
// Nothing in flight, but incomplete tasks remain — check if any are ready
|
|
122
|
+
const ready = getReadyTasks(graph, completed, inFlight);
|
|
123
|
+
return ready.length === 0;
|
|
124
|
+
}
|
|
125
|
+
// ─── Graph Metrics ────────────────────────────────────────────────────────
|
|
126
|
+
/** Compute summary metrics for logging. */
|
|
127
|
+
export function graphMetrics(graph) {
|
|
128
|
+
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
129
|
+
const ready = getReadyTasks(graph, completed, new Set());
|
|
130
|
+
const edgeCount = graph.reduce((sum, n) => sum + n.dependsOn.length, 0);
|
|
131
|
+
return {
|
|
132
|
+
taskCount: graph.length,
|
|
133
|
+
edgeCount,
|
|
134
|
+
readySetSize: ready.length,
|
|
135
|
+
ambiguous: isGraphAmbiguous(graph),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// ─── IO Loader (async, filesystem) ────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Load TaskIO for all tasks in a slice by reading the slice plan (for done
|
|
141
|
+
* status and task IDs) and individual task plan files (for IO sections).
|
|
142
|
+
*
|
|
143
|
+
* Returns [] when the slice plan or tasks directory doesn't exist.
|
|
144
|
+
*/
|
|
145
|
+
export async function loadSliceTaskIO(basePath, mid, sid) {
|
|
146
|
+
const { resolveSliceFile } = await import("./paths.js");
|
|
147
|
+
const slicePlanPath = resolveSliceFile(basePath, mid, sid, "PLAN");
|
|
148
|
+
const planContent = slicePlanPath ? await loadFile(slicePlanPath) : null;
|
|
149
|
+
if (!planContent)
|
|
150
|
+
return [];
|
|
151
|
+
const plan = parsePlan(planContent);
|
|
152
|
+
const tDir = resolveTasksDir(basePath, mid, sid);
|
|
153
|
+
if (!tDir)
|
|
154
|
+
return [];
|
|
155
|
+
const results = [];
|
|
156
|
+
for (const taskEntry of plan.tasks) {
|
|
157
|
+
const planFiles = resolveTaskFiles(tDir, "PLAN");
|
|
158
|
+
const taskFileName = planFiles.find((f) => f.toUpperCase().startsWith(taskEntry.id.toUpperCase() + "-"));
|
|
159
|
+
if (!taskFileName) {
|
|
160
|
+
// Task plan file missing — include with empty IO (will trigger ambiguous)
|
|
161
|
+
results.push({
|
|
162
|
+
id: taskEntry.id,
|
|
163
|
+
title: taskEntry.title,
|
|
164
|
+
inputFiles: [],
|
|
165
|
+
outputFiles: [],
|
|
166
|
+
done: taskEntry.done,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const taskContent = await loadFile(join(tDir, taskFileName));
|
|
171
|
+
if (!taskContent) {
|
|
172
|
+
results.push({
|
|
173
|
+
id: taskEntry.id,
|
|
174
|
+
title: taskEntry.title,
|
|
175
|
+
inputFiles: [],
|
|
176
|
+
outputFiles: [],
|
|
177
|
+
done: taskEntry.done,
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const io = parseTaskPlanIO(taskContent);
|
|
182
|
+
results.push({
|
|
183
|
+
id: taskEntry.id,
|
|
184
|
+
title: taskEntry.title,
|
|
185
|
+
inputFiles: io.inputFiles,
|
|
186
|
+
outputFiles: io.outputFiles,
|
|
187
|
+
done: taskEntry.done,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
// ─── State Persistence ────────────────────────────────────────────────────
|
|
193
|
+
function reactiveStatePath(basePath, mid, sid) {
|
|
194
|
+
return join(basePath, ".gsd", "runtime", `${mid}-${sid}-reactive.json`);
|
|
195
|
+
}
|
|
196
|
+
function isReactiveState(data) {
|
|
197
|
+
if (!data || typeof data !== "object")
|
|
198
|
+
return false;
|
|
199
|
+
const d = data;
|
|
200
|
+
return typeof d.sliceId === "string" && Array.isArray(d.completed);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Load persisted reactive execution state for a slice.
|
|
204
|
+
* Returns null when no state file exists or the file is invalid.
|
|
205
|
+
*/
|
|
206
|
+
export function loadReactiveState(basePath, mid, sid) {
|
|
207
|
+
return loadJsonFileOrNull(reactiveStatePath(basePath, mid, sid), isReactiveState);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Save reactive execution state to disk.
|
|
211
|
+
*/
|
|
212
|
+
export function saveReactiveState(basePath, mid, sid, state) {
|
|
213
|
+
saveJsonFile(reactiveStatePath(basePath, mid, sid), state);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Remove the reactive state file when a slice completes.
|
|
217
|
+
*/
|
|
218
|
+
export function clearReactiveState(basePath, mid, sid) {
|
|
219
|
+
const path = reactiveStatePath(basePath, mid, sid);
|
|
220
|
+
try {
|
|
221
|
+
if (existsSync(path))
|
|
222
|
+
unlinkSync(path);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Non-fatal
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -320,7 +320,7 @@ export function updateSessionLock(basePath, unitType, unitId, completedUnits, se
|
|
|
320
320
|
*
|
|
321
321
|
* This is called periodically during the dispatch loop.
|
|
322
322
|
*/
|
|
323
|
-
export function
|
|
323
|
+
export function getSessionLockStatus(basePath) {
|
|
324
324
|
// Lock was compromised by proper-lockfile (mtime drift from sleep, stall, etc.)
|
|
325
325
|
if (_lockCompromised) {
|
|
326
326
|
// Recovery gate (#1512): Before declaring the lock lost, check if the lock
|
|
@@ -335,27 +335,47 @@ export function validateSessionLock(basePath) {
|
|
|
335
335
|
const result = acquireSessionLock(basePath);
|
|
336
336
|
if (result.acquired) {
|
|
337
337
|
process.stderr.write(`[gsd] Lock recovered after onCompromised — lock file PID matched, re-acquired.\n`);
|
|
338
|
-
return true;
|
|
338
|
+
return { valid: true, recovered: true };
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
catch {
|
|
342
342
|
// Re-acquisition failed — fall through to return false
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
|
-
return
|
|
345
|
+
return {
|
|
346
|
+
valid: false,
|
|
347
|
+
failureReason: "compromised",
|
|
348
|
+
existingPid: existing?.pid,
|
|
349
|
+
expectedPid: process.pid,
|
|
350
|
+
};
|
|
346
351
|
}
|
|
347
352
|
// If we have an OS-level lock, we're still the owner
|
|
348
353
|
if (_releaseFunction && _lockedPath === basePath) {
|
|
349
|
-
return true;
|
|
354
|
+
return { valid: true };
|
|
350
355
|
}
|
|
351
356
|
// Fallback: check the lock file PID
|
|
352
357
|
const lp = lockPath(basePath);
|
|
353
358
|
const existing = readExistingLockData(lp);
|
|
354
359
|
if (!existing) {
|
|
355
360
|
// Lock file was deleted — we lost ownership
|
|
356
|
-
return
|
|
361
|
+
return {
|
|
362
|
+
valid: false,
|
|
363
|
+
failureReason: "missing-metadata",
|
|
364
|
+
expectedPid: process.pid,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (existing.pid !== process.pid) {
|
|
368
|
+
return {
|
|
369
|
+
valid: false,
|
|
370
|
+
failureReason: "pid-mismatch",
|
|
371
|
+
existingPid: existing.pid,
|
|
372
|
+
expectedPid: process.pid,
|
|
373
|
+
};
|
|
357
374
|
}
|
|
358
|
-
return
|
|
375
|
+
return { valid: true };
|
|
376
|
+
}
|
|
377
|
+
export function validateSessionLock(basePath) {
|
|
378
|
+
return getSessionLockStatus(basePath).valid;
|
|
359
379
|
}
|
|
360
380
|
/**
|
|
361
381
|
* Release the session lock. Called on clean stop/pause.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared formatting
|
|
2
|
+
* Shared pure formatting utilities — no @gsd/pi-tui dependency.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* ANSI-aware layout helpers (padRight, joinColumns, centerLine, fitColumns)
|
|
5
|
+
* live in layout-utils.ts to avoid pulling @gsd/pi-tui into modules that
|
|
6
|
+
* run outside jiti's alias resolution (e.g. HTML report generation via
|
|
7
|
+
* dynamic import in auto-loop).
|
|
6
8
|
*/
|
|
7
|
-
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
8
9
|
// ─── Duration Formatting ──────────────────────────────────────────────────────
|
|
9
10
|
/** Format a millisecond duration as a compact human-readable string. */
|
|
10
11
|
export function formatDuration(ms) {
|
|
@@ -30,43 +31,6 @@ export function formatTokenCount(count) {
|
|
|
30
31
|
return `${(count / 1000).toFixed(1)}k`;
|
|
31
32
|
return `${(count / 1_000_000).toFixed(2)}M`;
|
|
32
33
|
}
|
|
33
|
-
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
|
34
|
-
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
|
35
|
-
export function padRight(content, width) {
|
|
36
|
-
const vis = visibleWidth(content);
|
|
37
|
-
return content + " ".repeat(Math.max(0, width - vis));
|
|
38
|
-
}
|
|
39
|
-
/** Build a line with left-aligned and right-aligned content. */
|
|
40
|
-
export function joinColumns(left, right, width) {
|
|
41
|
-
const leftW = visibleWidth(left);
|
|
42
|
-
const rightW = visibleWidth(right);
|
|
43
|
-
if (leftW + rightW + 2 > width) {
|
|
44
|
-
return truncateToWidth(`${left} ${right}`, width);
|
|
45
|
-
}
|
|
46
|
-
return left + " ".repeat(width - leftW - rightW) + right;
|
|
47
|
-
}
|
|
48
|
-
/** Center content within `width` (ANSI-aware). */
|
|
49
|
-
export function centerLine(content, width) {
|
|
50
|
-
const vis = visibleWidth(content);
|
|
51
|
-
if (vis >= width)
|
|
52
|
-
return truncateToWidth(content, width);
|
|
53
|
-
const leftPad = Math.floor((width - vis) / 2);
|
|
54
|
-
return " ".repeat(leftPad) + content;
|
|
55
|
-
}
|
|
56
|
-
/** Join as many parts as fit within `width`, separated by `separator`. */
|
|
57
|
-
export function fitColumns(parts, width, separator = " ") {
|
|
58
|
-
const filtered = parts.filter(Boolean);
|
|
59
|
-
if (filtered.length === 0)
|
|
60
|
-
return "";
|
|
61
|
-
let result = filtered[0];
|
|
62
|
-
for (let i = 1; i < filtered.length; i++) {
|
|
63
|
-
const candidate = `${result}${separator}${filtered[i]}`;
|
|
64
|
-
if (visibleWidth(candidate) > width)
|
|
65
|
-
break;
|
|
66
|
-
result = candidate;
|
|
67
|
-
}
|
|
68
|
-
return truncateToWidth(result, width);
|
|
69
|
-
}
|
|
70
34
|
// ─── Text Truncation ─────────────────────────────────────────────────────────
|
|
71
35
|
/** Truncate a string to `maxLength` characters, replacing the last character with an ellipsis if needed. */
|
|
72
36
|
export function truncateWithEllipsis(text, maxLength) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI-aware TUI layout utilities that depend on @gsd/pi-tui.
|
|
3
|
+
*
|
|
4
|
+
* Separated from format-utils.ts so that modules needing only pure
|
|
5
|
+
* formatting (e.g. HTML report generation) can import format-utils
|
|
6
|
+
* without pulling in the @gsd/pi-tui dependency — which fails when
|
|
7
|
+
* loaded outside jiti's alias resolution context.
|
|
8
|
+
*/
|
|
9
|
+
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
10
|
+
// ─── Layout Helpers ───────────────────────────────────────────────────────────
|
|
11
|
+
/** Pad a string with trailing spaces to fill `width` (ANSI-aware). */
|
|
12
|
+
export function padRight(content, width) {
|
|
13
|
+
const vis = visibleWidth(content);
|
|
14
|
+
return content + " ".repeat(Math.max(0, width - vis));
|
|
15
|
+
}
|
|
16
|
+
/** Build a line with left-aligned and right-aligned content. */
|
|
17
|
+
export function joinColumns(left, right, width) {
|
|
18
|
+
const leftW = visibleWidth(left);
|
|
19
|
+
const rightW = visibleWidth(right);
|
|
20
|
+
if (leftW + rightW + 2 > width) {
|
|
21
|
+
return truncateToWidth(`${left} ${right}`, width);
|
|
22
|
+
}
|
|
23
|
+
return left + " ".repeat(width - leftW - rightW) + right;
|
|
24
|
+
}
|
|
25
|
+
/** Center content within `width` (ANSI-aware). */
|
|
26
|
+
export function centerLine(content, width) {
|
|
27
|
+
const vis = visibleWidth(content);
|
|
28
|
+
if (vis >= width)
|
|
29
|
+
return truncateToWidth(content, width);
|
|
30
|
+
const leftPad = Math.floor((width - vis) / 2);
|
|
31
|
+
return " ".repeat(leftPad) + content;
|
|
32
|
+
}
|
|
33
|
+
/** Join as many parts as fit within `width`, separated by `separator`. */
|
|
34
|
+
export function fitColumns(parts, width, separator = " ") {
|
|
35
|
+
const filtered = parts.filter(Boolean);
|
|
36
|
+
if (filtered.length === 0)
|
|
37
|
+
return "";
|
|
38
|
+
let result = filtered[0];
|
|
39
|
+
for (let i = 1; i < filtered.length; i++) {
|
|
40
|
+
const candidate = `${result}${separator}${filtered[i]}`;
|
|
41
|
+
if (visibleWidth(candidate) > width)
|
|
42
|
+
break;
|
|
43
|
+
result = candidate;
|
|
44
|
+
}
|
|
45
|
+
return truncateToWidth(result, width);
|
|
46
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Barrel file — re-exports consumed by external modules
|
|
2
2
|
export { makeUI, GLYPH, INDENT, STATUS_GLYPH, STATUS_COLOR, } from "./ui.js";
|
|
3
|
-
export { stripAnsi, formatTokenCount, formatDuration,
|
|
3
|
+
export { stripAnsi, formatTokenCount, formatDuration, sparkline, normalizeStringArray, fileLink, } from "./format-utils.js";
|
|
4
|
+
export { padRight, joinColumns, centerLine, fitColumns, } from "./layout-utils.js";
|
|
4
5
|
export { shortcutDesc } from "./terminal.js";
|
|
5
6
|
export { toPosixPath } from "./path-display.js";
|
|
6
7
|
export { showInterviewRound } from "./interview-ui.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-pi",
|
|
3
|
-
"version": "2.37.
|
|
3
|
+
"version": "2.37.1-dev.49503be",
|
|
4
4
|
"description": "GSD — Get Shit Done coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
86
|
"@anthropic-ai/sdk": "^0.73.0",
|
|
87
|
+
"@anthropic-ai/vertex-sdk": "^0.14.4",
|
|
87
88
|
"@aws-sdk/client-bedrock-runtime": "^3.983.0",
|
|
88
89
|
"@clack/prompts": "^1.1.0",
|
|
89
90
|
"@google/genai": "^1.40.0",
|
|
@@ -53,6 +53,19 @@ export function getEnvApiKey(provider) {
|
|
|
53
53
|
if (provider === "anthropic") {
|
|
54
54
|
return process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;
|
|
55
55
|
}
|
|
56
|
+
// Anthropic on Vertex AI uses Application Default Credentials.
|
|
57
|
+
// Detected via ANTHROPIC_VERTEX_PROJECT_ID (same env var as Claude Code).
|
|
58
|
+
if (provider === "anthropic-vertex") {
|
|
59
|
+
const hasProject = !!process.env.ANTHROPIC_VERTEX_PROJECT_ID;
|
|
60
|
+
if (hasProject) {
|
|
61
|
+
return "<authenticated>";
|
|
62
|
+
}
|
|
63
|
+
// Fall back to Google Cloud project env vars
|
|
64
|
+
const hasGoogleProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);
|
|
65
|
+
if (hasGoogleProject && hasVertexAdcCredentials()) {
|
|
66
|
+
return "<authenticated>";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
56
69
|
// Vertex AI uses Application Default Credentials, not API keys.
|
|
57
70
|
// Auth is configured via `gcloud auth application-default login`.
|
|
58
71
|
if (provider === "google-vertex") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-api-keys.js","sourceRoot":"","sources":["../src/env-api-keys.ts"],"names":[],"mappings":"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","sourcesContent":["// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _existsSync: typeof import(\"node:fs\").existsSync | null = null;\nlet _homedir: typeof import(\"node:os\").homedir | null = null;\nlet _join: typeof import(\"node:path\").join | null = null;\n\ntype DynamicImport = (specifier: string) => Promise<unknown>;\n\nconst dynamicImport: DynamicImport = (specifier) => import(specifier);\nconst NODE_FS_SPECIFIER = \"node:\" + \"fs\";\nconst NODE_OS_SPECIFIER = \"node:\" + \"os\";\nconst NODE_PATH_SPECIFIER = \"node:\" + \"path\";\n\n// Eagerly load in Node.js/Bun environment only\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\tdynamicImport(NODE_FS_SPECIFIER).then((m) => {\n\t\t_existsSync = (m as typeof import(\"node:fs\")).existsSync;\n\t});\n\tdynamicImport(NODE_OS_SPECIFIER).then((m) => {\n\t\t_homedir = (m as typeof import(\"node:os\")).homedir;\n\t});\n\tdynamicImport(NODE_PATH_SPECIFIER).then((m) => {\n\t\t_join = (m as typeof import(\"node:path\")).join;\n\t});\n}\n\nimport type { KnownProvider } from \"./types.js\";\n\nlet cachedVertexAdcCredentialsExists: boolean | null = null;\n\nfunction hasVertexAdcCredentials(): boolean {\n\tif (cachedVertexAdcCredentialsExists === null) {\n\t\t// If node modules haven't loaded yet (async import race at startup),\n\t\t// return false WITHOUT caching so the next call retries once they're ready.\n\t\t// Only cache false permanently in a browser environment where fs is never available.\n\t\tif (!_existsSync || !_homedir || !_join) {\n\t\t\tconst isNode = typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun);\n\t\t\tif (!isNode) {\n\t\t\t\t// Definitively in a browser — safe to cache false permanently\n\t\t\t\tcachedVertexAdcCredentialsExists = false;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)\n\t\tconst gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n\t\tif (gacPath) {\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(gacPath);\n\t\t} else {\n\t\t\t// Fall back to default ADC path (lazy evaluation)\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(\n\t\t\t\t_join(_homedir(), \".config\", \"gcloud\", \"application_default_credentials.json\"),\n\t\t\t);\n\t\t}\n\t}\n\treturn cachedVertexAdcCredentialsExists;\n}\n\n/**\n * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.\n *\n * Will not return API keys for providers that require OAuth tokens.\n */\nexport function getEnvApiKey(provider: KnownProvider): string | undefined;\nexport function getEnvApiKey(provider: string): string | undefined;\nexport function getEnvApiKey(provider: any): string | undefined {\n\t// Fall back to environment variables\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\n\t// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\n\t// Vertex AI uses Application Default Credentials, not API keys.\n\t// Auth is configured via `gcloud auth application-default login`.\n\tif (provider === \"google-vertex\") {\n\t\tconst hasCredentials = hasVertexAdcCredentials();\n\t\tconst hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);\n\t\tconst hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;\n\n\t\tif (hasCredentials && hasProject && hasLocation) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tif (provider === \"amazon-bedrock\") {\n\t\t// Amazon Bedrock supports multiple credential sources:\n\t\t// 1. AWS_PROFILE - named profile from ~/.aws/credentials\n\t\t// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys\n\t\t// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)\n\t\t// 4. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - ECS task roles\n\t\t// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)\n\t\t// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)\n\t\tif (\n\t\t\tprocess.env.AWS_PROFILE ||\n\t\t\t(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||\n\t\t\tprocess.env.AWS_BEARER_TOKEN_BEDROCK ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||\n\t\t\tprocess.env.AWS_WEB_IDENTITY_TOKEN_FILE\n\t\t) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tconst envMap: Record<string, string> = {\n\t\topenai: \"OPENAI_API_KEY\",\n\t\t\"azure-openai-responses\": \"AZURE_OPENAI_API_KEY\",\n\t\tgoogle: \"GEMINI_API_KEY\",\n\t\tgroq: \"GROQ_API_KEY\",\n\t\tcerebras: \"CEREBRAS_API_KEY\",\n\t\txai: \"XAI_API_KEY\",\n\t\topenrouter: \"OPENROUTER_API_KEY\",\n\t\t\"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n\t\tzai: \"ZAI_API_KEY\",\n\t\tmistral: \"MISTRAL_API_KEY\",\n\t\tminimax: \"MINIMAX_API_KEY\",\n\t\t\"minimax-cn\": \"MINIMAX_CN_API_KEY\",\n\t\thuggingface: \"HF_TOKEN\",\n\t\topencode: \"OPENCODE_API_KEY\",\n\t\t\"opencode-go\": \"OPENCODE_API_KEY\",\n\t\t\"kimi-coding\": \"KIMI_API_KEY\",\n\t\t\"alibaba-coding-plan\": \"ALIBABA_API_KEY\",\n\t\t\"ollama-cloud\": \"OLLAMA_API_KEY\",\n\t\t\"custom-openai\": \"CUSTOM_OPENAI_API_KEY\",\n\t};\n\n\tconst envVar = envMap[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"env-api-keys.js","sourceRoot":"","sources":["../src/env-api-keys.ts"],"names":[],"mappings":"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","sourcesContent":["// NEVER convert to top-level imports - breaks browser/Vite builds (web-ui)\nlet _existsSync: typeof import(\"node:fs\").existsSync | null = null;\nlet _homedir: typeof import(\"node:os\").homedir | null = null;\nlet _join: typeof import(\"node:path\").join | null = null;\n\ntype DynamicImport = (specifier: string) => Promise<unknown>;\n\nconst dynamicImport: DynamicImport = (specifier) => import(specifier);\nconst NODE_FS_SPECIFIER = \"node:\" + \"fs\";\nconst NODE_OS_SPECIFIER = \"node:\" + \"os\";\nconst NODE_PATH_SPECIFIER = \"node:\" + \"path\";\n\n// Eagerly load in Node.js/Bun environment only\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\tdynamicImport(NODE_FS_SPECIFIER).then((m) => {\n\t\t_existsSync = (m as typeof import(\"node:fs\")).existsSync;\n\t});\n\tdynamicImport(NODE_OS_SPECIFIER).then((m) => {\n\t\t_homedir = (m as typeof import(\"node:os\")).homedir;\n\t});\n\tdynamicImport(NODE_PATH_SPECIFIER).then((m) => {\n\t\t_join = (m as typeof import(\"node:path\")).join;\n\t});\n}\n\nimport type { KnownProvider } from \"./types.js\";\n\nlet cachedVertexAdcCredentialsExists: boolean | null = null;\n\nfunction hasVertexAdcCredentials(): boolean {\n\tif (cachedVertexAdcCredentialsExists === null) {\n\t\t// If node modules haven't loaded yet (async import race at startup),\n\t\t// return false WITHOUT caching so the next call retries once they're ready.\n\t\t// Only cache false permanently in a browser environment where fs is never available.\n\t\tif (!_existsSync || !_homedir || !_join) {\n\t\t\tconst isNode = typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun);\n\t\t\tif (!isNode) {\n\t\t\t\t// Definitively in a browser — safe to cache false permanently\n\t\t\t\tcachedVertexAdcCredentialsExists = false;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check GOOGLE_APPLICATION_CREDENTIALS env var first (standard way)\n\t\tconst gacPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;\n\t\tif (gacPath) {\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(gacPath);\n\t\t} else {\n\t\t\t// Fall back to default ADC path (lazy evaluation)\n\t\t\tcachedVertexAdcCredentialsExists = _existsSync(\n\t\t\t\t_join(_homedir(), \".config\", \"gcloud\", \"application_default_credentials.json\"),\n\t\t\t);\n\t\t}\n\t}\n\treturn cachedVertexAdcCredentialsExists;\n}\n\n/**\n * Get API key for provider from known environment variables, e.g. OPENAI_API_KEY.\n *\n * Will not return API keys for providers that require OAuth tokens.\n */\nexport function getEnvApiKey(provider: KnownProvider): string | undefined;\nexport function getEnvApiKey(provider: string): string | undefined;\nexport function getEnvApiKey(provider: any): string | undefined {\n\t// Fall back to environment variables\n\tif (provider === \"github-copilot\") {\n\t\treturn process.env.COPILOT_GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;\n\t}\n\n\t// ANTHROPIC_OAUTH_TOKEN takes precedence over ANTHROPIC_API_KEY\n\tif (provider === \"anthropic\") {\n\t\treturn process.env.ANTHROPIC_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY;\n\t}\n\n\t// Anthropic on Vertex AI uses Application Default Credentials.\n\t// Detected via ANTHROPIC_VERTEX_PROJECT_ID (same env var as Claude Code).\n\tif (provider === \"anthropic-vertex\") {\n\t\tconst hasProject = !!process.env.ANTHROPIC_VERTEX_PROJECT_ID;\n\t\tif (hasProject) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t\t// Fall back to Google Cloud project env vars\n\t\tconst hasGoogleProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);\n\t\tif (hasGoogleProject && hasVertexAdcCredentials()) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\t// Vertex AI uses Application Default Credentials, not API keys.\n\t// Auth is configured via `gcloud auth application-default login`.\n\tif (provider === \"google-vertex\") {\n\t\tconst hasCredentials = hasVertexAdcCredentials();\n\t\tconst hasProject = !!(process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT);\n\t\tconst hasLocation = !!process.env.GOOGLE_CLOUD_LOCATION;\n\n\t\tif (hasCredentials && hasProject && hasLocation) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tif (provider === \"amazon-bedrock\") {\n\t\t// Amazon Bedrock supports multiple credential sources:\n\t\t// 1. AWS_PROFILE - named profile from ~/.aws/credentials\n\t\t// 2. AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY - standard IAM keys\n\t\t// 3. AWS_BEARER_TOKEN_BEDROCK - Bedrock API keys (bearer token)\n\t\t// 4. AWS_CONTAINER_CREDENTIALS_RELATIVE_URI - ECS task roles\n\t\t// 5. AWS_CONTAINER_CREDENTIALS_FULL_URI - ECS task roles (full URI)\n\t\t// 6. AWS_WEB_IDENTITY_TOKEN_FILE - IRSA (IAM Roles for Service Accounts)\n\t\tif (\n\t\t\tprocess.env.AWS_PROFILE ||\n\t\t\t(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||\n\t\t\tprocess.env.AWS_BEARER_TOKEN_BEDROCK ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||\n\t\t\tprocess.env.AWS_CONTAINER_CREDENTIALS_FULL_URI ||\n\t\t\tprocess.env.AWS_WEB_IDENTITY_TOKEN_FILE\n\t\t) {\n\t\t\treturn \"<authenticated>\";\n\t\t}\n\t}\n\n\tconst envMap: Record<string, string> = {\n\t\topenai: \"OPENAI_API_KEY\",\n\t\t\"azure-openai-responses\": \"AZURE_OPENAI_API_KEY\",\n\t\tgoogle: \"GEMINI_API_KEY\",\n\t\tgroq: \"GROQ_API_KEY\",\n\t\tcerebras: \"CEREBRAS_API_KEY\",\n\t\txai: \"XAI_API_KEY\",\n\t\topenrouter: \"OPENROUTER_API_KEY\",\n\t\t\"vercel-ai-gateway\": \"AI_GATEWAY_API_KEY\",\n\t\tzai: \"ZAI_API_KEY\",\n\t\tmistral: \"MISTRAL_API_KEY\",\n\t\tminimax: \"MINIMAX_API_KEY\",\n\t\t\"minimax-cn\": \"MINIMAX_CN_API_KEY\",\n\t\thuggingface: \"HF_TOKEN\",\n\t\topencode: \"OPENCODE_API_KEY\",\n\t\t\"opencode-go\": \"OPENCODE_API_KEY\",\n\t\t\"kimi-coding\": \"KIMI_API_KEY\",\n\t\t\"alibaba-coding-plan\": \"ALIBABA_API_KEY\",\n\t\t\"ollama-cloud\": \"OLLAMA_API_KEY\",\n\t\t\"custom-openai\": \"CUSTOM_OPENAI_API_KEY\",\n\t};\n\n\tconst envVar = envMap[provider];\n\treturn envVar ? process.env[envVar] : undefined;\n}\n"]}
|