indusagi-coding-agent 0.2.8 → 0.2.10
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 +7 -0
- package/dist/entry.js +4 -4
- package/dist/index.js +4 -4
- package/package.json +3 -2
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import vm from "node:vm";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { parseWorkflowScript, type WorkflowMeta } from "./parse.js";
|
|
4
|
+
|
|
5
|
+
export interface WorkflowAgentActivity {
|
|
6
|
+
tokensIn: number;
|
|
7
|
+
tokensOut: number;
|
|
8
|
+
toolCount: number;
|
|
9
|
+
currentTool?: string;
|
|
10
|
+
lastTool?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WorkflowAgentRunOptions {
|
|
14
|
+
label: string;
|
|
15
|
+
phase?: string;
|
|
16
|
+
schema?: unknown;
|
|
17
|
+
instructions?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
agentType?: string;
|
|
20
|
+
isolation?: "worktree";
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
onActivity?: (metrics: WorkflowAgentActivity) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WorkflowAgentRunner {
|
|
26
|
+
run(prompt: string, options: WorkflowAgentRunOptions): Promise<unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AgentOptions {
|
|
30
|
+
label?: string;
|
|
31
|
+
phase?: string;
|
|
32
|
+
schema?: unknown;
|
|
33
|
+
model?: string;
|
|
34
|
+
isolation?: "worktree";
|
|
35
|
+
agentType?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface WorkflowRunOptions {
|
|
39
|
+
agentRunner: WorkflowAgentRunner;
|
|
40
|
+
args?: unknown;
|
|
41
|
+
cwd?: string;
|
|
42
|
+
concurrency?: number;
|
|
43
|
+
tokenBudget?: number | null;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
onLog?: (message: string) => void;
|
|
46
|
+
onPhase?: (title: string) => void;
|
|
47
|
+
onAgentStart?: (event: { id: number; label: string; phase?: string; prompt: string }) => void;
|
|
48
|
+
onAgentEnd?: (event: { id: number; label: string; phase?: string; result: unknown }) => void;
|
|
49
|
+
onAgentActivity?: (id: number, metrics: WorkflowAgentActivity) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorkflowRunResult<T = unknown> {
|
|
53
|
+
meta: WorkflowMeta;
|
|
54
|
+
result: T;
|
|
55
|
+
logs: string[];
|
|
56
|
+
phases: string[];
|
|
57
|
+
agentCount: number;
|
|
58
|
+
durationMs: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const WORKFLOW_RUNTIME_BUDGET_MS = 1000;
|
|
62
|
+
|
|
63
|
+
interface EngineState {
|
|
64
|
+
logs: string[];
|
|
65
|
+
phases: string[];
|
|
66
|
+
agentCount: number;
|
|
67
|
+
spent: number;
|
|
68
|
+
currentPhase?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function runWorkflow<T = unknown>(script: string, options: WorkflowRunOptions): Promise<WorkflowRunResult<T>> {
|
|
72
|
+
const started = Date.now();
|
|
73
|
+
const { meta, body } = parseWorkflowScript(script);
|
|
74
|
+
const state: EngineState = { logs: [], phases: [], agentCount: 0, spent: 0 };
|
|
75
|
+
const { agentRunner } = options;
|
|
76
|
+
if (!agentRunner || typeof agentRunner.run !== "function") {
|
|
77
|
+
throw new TypeError("runWorkflow requires an agentRunner with a run() method");
|
|
78
|
+
}
|
|
79
|
+
const concurrency = Math.max(1, Math.min(options.concurrency ?? Math.max(1, defaultConcurrency() - 2), 16));
|
|
80
|
+
const limiter = createLimiter(concurrency);
|
|
81
|
+
const pendingAgentRuns = new Set<Promise<unknown>>();
|
|
82
|
+
const log = (message: unknown) => {
|
|
83
|
+
const text = String(message);
|
|
84
|
+
state.logs.push(text);
|
|
85
|
+
options.onLog?.(text);
|
|
86
|
+
};
|
|
87
|
+
const phase = (title: unknown) => {
|
|
88
|
+
const text = requireString(title, "phase title");
|
|
89
|
+
state.currentPhase = text;
|
|
90
|
+
if (!state.phases.includes(text)) state.phases.push(text);
|
|
91
|
+
options.onPhase?.(text);
|
|
92
|
+
};
|
|
93
|
+
const budget = Object.freeze({
|
|
94
|
+
total: options.tokenBudget ?? null,
|
|
95
|
+
spent: () => state.spent,
|
|
96
|
+
remaining: () => options.tokenBudget == null ? Infinity : Math.max(0, options.tokenBudget - state.spent),
|
|
97
|
+
});
|
|
98
|
+
const throwIfAborted = () => {
|
|
99
|
+
if (options.signal?.aborted) throw new Error("workflow aborted");
|
|
100
|
+
};
|
|
101
|
+
const agent = async (prompt: unknown, agentOptions: unknown = {}) => {
|
|
102
|
+
throwIfAborted();
|
|
103
|
+
if (budget.total !== null && budget.remaining() <= 0) throw new Error("workflow token budget exhausted");
|
|
104
|
+
const taskPrompt = requireString(prompt, "agent prompt");
|
|
105
|
+
const normalizedOptions = normalizeAgentOptions(agentOptions);
|
|
106
|
+
const assignedPhase = normalizedOptions.phase ?? state.currentPhase;
|
|
107
|
+
const requestedLabel = normalizedOptions.label?.trim();
|
|
108
|
+
const run = limiter(async () => {
|
|
109
|
+
const id = ++state.agentCount;
|
|
110
|
+
const label = requestedLabel || defaultAgentLabel(assignedPhase, id);
|
|
111
|
+
options.onAgentStart?.({ id, label, phase: assignedPhase, prompt: taskPrompt });
|
|
112
|
+
try {
|
|
113
|
+
throwIfAborted();
|
|
114
|
+
const result = await agentRunner.run(taskPrompt, {
|
|
115
|
+
label,
|
|
116
|
+
phase: assignedPhase,
|
|
117
|
+
schema: normalizedOptions.schema,
|
|
118
|
+
model: normalizedOptions.model,
|
|
119
|
+
agentType: normalizedOptions.agentType,
|
|
120
|
+
isolation: normalizedOptions.isolation,
|
|
121
|
+
signal: options.signal,
|
|
122
|
+
instructions: buildAgentInstructions(assignedPhase, normalizedOptions),
|
|
123
|
+
onActivity: options.onAgentActivity ? (metrics) => options.onAgentActivity?.(id, metrics) : undefined,
|
|
124
|
+
});
|
|
125
|
+
throwIfAborted();
|
|
126
|
+
state.spent += estimateTokens(result);
|
|
127
|
+
options.onAgentEnd?.({ id, label, phase: assignedPhase, result });
|
|
128
|
+
return result;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (options.signal?.aborted) throw error;
|
|
131
|
+
log(`agent ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null });
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
pendingAgentRuns.add(run);
|
|
137
|
+
run.then(() => pendingAgentRuns.delete(run), () => pendingAgentRuns.delete(run));
|
|
138
|
+
return run;
|
|
139
|
+
};
|
|
140
|
+
const parallel = async (thunks: unknown[]) => {
|
|
141
|
+
throwIfAborted();
|
|
142
|
+
if (!Array.isArray(thunks)) throw new TypeError("parallel() expects an array of functions");
|
|
143
|
+
if (thunks.some((thunk) => typeof thunk !== "function")) {
|
|
144
|
+
throw new TypeError("parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)");
|
|
145
|
+
}
|
|
146
|
+
return Promise.all(thunks.map(async (thunk, index) => {
|
|
147
|
+
try {
|
|
148
|
+
return await (thunk as () => unknown)();
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (options.signal?.aborted) throw error;
|
|
151
|
+
log(`parallel[${index}] failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}));
|
|
155
|
+
};
|
|
156
|
+
const pipeline = async (items: unknown[], ...stages: Array<(value: unknown, item: unknown, index: number) => unknown>) => {
|
|
157
|
+
throwIfAborted();
|
|
158
|
+
if (!Array.isArray(items)) throw new TypeError("pipeline() expects an array as the first argument");
|
|
159
|
+
if (stages.some((stage) => typeof stage !== "function")) {
|
|
160
|
+
throw new TypeError("pipeline() stages must be functions: pipeline(items, item => ..., result => ...)");
|
|
161
|
+
}
|
|
162
|
+
return Promise.all(items.map(async (item, index) => {
|
|
163
|
+
let value = item;
|
|
164
|
+
for (const stage of stages) {
|
|
165
|
+
try {
|
|
166
|
+
throwIfAborted();
|
|
167
|
+
value = await stage(value, item, index);
|
|
168
|
+
throwIfAborted();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (options.signal?.aborted) throw error;
|
|
171
|
+
log(`pipeline[${index}] failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}));
|
|
177
|
+
};
|
|
178
|
+
const resolvedCwd = options.cwd ?? process.cwd();
|
|
179
|
+
const context = vm.createContext({
|
|
180
|
+
agent,
|
|
181
|
+
parallel,
|
|
182
|
+
pipeline,
|
|
183
|
+
log,
|
|
184
|
+
phase,
|
|
185
|
+
args: options.args,
|
|
186
|
+
cwd: resolvedCwd,
|
|
187
|
+
process: Object.freeze({ cwd: () => resolvedCwd }),
|
|
188
|
+
budget,
|
|
189
|
+
console: Object.freeze({
|
|
190
|
+
log,
|
|
191
|
+
info: log,
|
|
192
|
+
warn: (message: unknown) => log(`[warn] ${String(message)}`),
|
|
193
|
+
error: (message: unknown) => log(`[error] ${String(message)}`),
|
|
194
|
+
}),
|
|
195
|
+
JSON,
|
|
196
|
+
Math,
|
|
197
|
+
Array,
|
|
198
|
+
Object,
|
|
199
|
+
String,
|
|
200
|
+
Number,
|
|
201
|
+
Boolean,
|
|
202
|
+
Set,
|
|
203
|
+
Map,
|
|
204
|
+
Promise,
|
|
205
|
+
});
|
|
206
|
+
const wrapped = `(async () => {\n${body}\n})()`;
|
|
207
|
+
const result = await new vm.Script(wrapped, { filename: `${meta.name || "workflow"}.js` }).runInContext(context);
|
|
208
|
+
await Promise.allSettled([...pendingAgentRuns]);
|
|
209
|
+
assertStructuredCloneable(result, "workflow result");
|
|
210
|
+
return {
|
|
211
|
+
meta,
|
|
212
|
+
result: result as T,
|
|
213
|
+
logs: state.logs,
|
|
214
|
+
phases: state.phases,
|
|
215
|
+
agentCount: state.agentCount,
|
|
216
|
+
durationMs: Date.now() - started,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function defaultConcurrency(): number {
|
|
221
|
+
const count = os.cpus()?.length;
|
|
222
|
+
return typeof count === "number" && count > 0 ? count : 8;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function createLimiter(limit: number): <T>(fn: () => Promise<T>) => Promise<T> {
|
|
226
|
+
let active = 0;
|
|
227
|
+
const queue: Array<() => void> = [];
|
|
228
|
+
const next = () => {
|
|
229
|
+
active--;
|
|
230
|
+
queue.shift()?.();
|
|
231
|
+
};
|
|
232
|
+
return async <T>(fn: () => Promise<T>): Promise<T> => {
|
|
233
|
+
if (active >= limit) await new Promise<void>((resolve) => queue.push(resolve));
|
|
234
|
+
active++;
|
|
235
|
+
try {
|
|
236
|
+
return await fn();
|
|
237
|
+
} finally {
|
|
238
|
+
next();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function requireString(value: unknown, name: string): string {
|
|
244
|
+
if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function optionalString(value: unknown, name: string): string | undefined {
|
|
249
|
+
if (value === undefined) return undefined;
|
|
250
|
+
return requireString(value, name);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function normalizeAgentOptions(value: unknown): AgentOptions {
|
|
254
|
+
if (!value || typeof value !== "object") throw new TypeError("agent options must be an object");
|
|
255
|
+
const options = value as Record<string, unknown>;
|
|
256
|
+
return {
|
|
257
|
+
...options,
|
|
258
|
+
label: optionalString(options.label, "agent label"),
|
|
259
|
+
phase: optionalString(options.phase, "agent phase"),
|
|
260
|
+
model: optionalString(options.model, "agent model"),
|
|
261
|
+
isolation: options.isolation as "worktree" | undefined,
|
|
262
|
+
agentType: optionalString(options.agentType, "agent type"),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function assertStructuredCloneable(value: unknown, name: string): void {
|
|
267
|
+
try {
|
|
268
|
+
structuredClone(value);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
271
|
+
throw new Error(`${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function defaultAgentLabel(phase: string | undefined, index: number): string {
|
|
276
|
+
return phase ? `${phase} agent ${index}` : `agent ${index}`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function buildAgentInstructions(phase: string | undefined, options: AgentOptions): string | undefined {
|
|
280
|
+
const lines: string[] = [];
|
|
281
|
+
if (phase) lines.push(`Workflow phase: ${phase}`);
|
|
282
|
+
if (options.agentType) lines.push(`Act as workflow subagent type: ${options.agentType}`);
|
|
283
|
+
if (options.isolation) lines.push(`Requested isolation: ${options.isolation}`);
|
|
284
|
+
if (options.model) lines.push(`Requested model: ${options.model}`);
|
|
285
|
+
return lines.length ? lines.join("\n") : undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function estimateTokens(value: unknown): number {
|
|
289
|
+
return Math.ceil(JSON.stringify(value ?? "").length / 4);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function conductorFault(message: string): Error {
|
|
293
|
+
return new Error(message);
|
|
294
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Type declarations recovered from dist/types (no runtime body)
|
|
3
|
+
/**
|
|
4
|
+
* workflow-engine — the pure, network-free orchestration core for the `workflow`
|
|
5
|
+
* dynamic-workflow tool.
|
|
6
|
+
*
|
|
7
|
+
* This barrel exposes the engine subsystem only: the deterministic script
|
|
8
|
+
* parser, the `structured_output` capture tool, the progress snapshot model +
|
|
9
|
+
* text renderer, and the `runWorkflow` engine with its injectable
|
|
10
|
+
* {@link WorkflowAgentRunner} spawn seam. The card and console wiring (the real
|
|
11
|
+
* runner backed by a fresh framework `Agent`, the `tool_update` product signal,
|
|
12
|
+
* and `APP_NOVEL_CARDS` registration) live in stage 2 and consume these exports.
|
|
13
|
+
*/
|
|
14
|
+
export type { ParsedWorkflow, WorkflowMeta, WorkflowMetaPhase, } from "./parse.js";
|
|
15
|
+
export { NONDETERMINISM_ERROR, parseWorkflowScript } from "./parse.js";
|
|
16
|
+
export type { StructuredOutputCapture, StructuredOutputToolOptions, } from "./structured-output.js";
|
|
17
|
+
export { createStructuredOutputTool } from "./structured-output.js";
|
|
18
|
+
export type { WorkflowAgentSnapshot, WorkflowAgentStatus, WorkflowDisplayOptions, WorkflowSnapshot, } from "./display.js";
|
|
19
|
+
export { createWorkflowSnapshot, preview, recomputeWorkflowSnapshot, renderWorkflowLines, renderWorkflowText, } from "./display.js";
|
|
20
|
+
export type { AgentOptions, WorkflowAgentActivity, WorkflowAgentRunner, WorkflowAgentRunOptions, WorkflowRunOptions, WorkflowRunResult, } from "./engine.js";
|
|
21
|
+
export { runWorkflow } from "./engine.js";
|
|
22
|
+
export type { WorkflowAgentRunnerOptions, WorkflowSubAgent, WorkflowSpawnContext, } from "./agent-runner.js";
|
|
23
|
+
export { createWorkflowAgentRunner } from "./agent-runner.js";
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import vm from "node:vm";
|
|
2
|
+
|
|
3
|
+
export interface WorkflowMetaPhase {
|
|
4
|
+
title: string;
|
|
5
|
+
detail?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WorkflowMeta {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
whenToUse?: string;
|
|
13
|
+
phases?: WorkflowMetaPhase[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ParsedWorkflow {
|
|
17
|
+
meta: WorkflowMeta;
|
|
18
|
+
body: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const NONDETERMINISM_ERROR =
|
|
22
|
+
"Workflow scripts must be deterministic: Date.now()/Math.random()/new Date() are unavailable";
|
|
23
|
+
|
|
24
|
+
export const NONDETERMINISTIC_PATTERNS = [
|
|
25
|
+
/\bDate\s*\.\s*now\s*\(/,
|
|
26
|
+
/\bMath\s*\.\s*random\s*\(/,
|
|
27
|
+
/\bnew\s+Date\b/,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function parseWorkflowScript(script: string): ParsedWorkflow {
|
|
31
|
+
if (typeof script !== "string") throw new TypeError("workflow script must be a string");
|
|
32
|
+
assertDeterministicSource(script);
|
|
33
|
+
const located = locateMetaExport(script);
|
|
34
|
+
const meta = evaluateMetaLiteral(located.literal);
|
|
35
|
+
validateMeta(meta);
|
|
36
|
+
return { meta, body: script.slice(0, located.start) + script.slice(located.end) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertDeterministicSource(script: string): void {
|
|
40
|
+
for (const pattern of NONDETERMINISTIC_PATTERNS) {
|
|
41
|
+
if (pattern.test(script)) throw new Error(NONDETERMINISM_ERROR);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function locateMetaExport(script: string): { start: number; end: number; literal: string } {
|
|
46
|
+
const firstToken = skipTrivia(script, 0);
|
|
47
|
+
const header = /export\s+const\s+meta\s*=\s*/y;
|
|
48
|
+
header.lastIndex = firstToken;
|
|
49
|
+
const match = header.exec(script);
|
|
50
|
+
if (!match || match.index !== firstToken) {
|
|
51
|
+
throw new Error("`export const meta = { name, description }` must be the first statement in the script");
|
|
52
|
+
}
|
|
53
|
+
const braceStart = header.lastIndex;
|
|
54
|
+
if (script[braceStart] !== "{") {
|
|
55
|
+
throw new Error("meta export must be assigned a plain object literal: `export const meta = { … }`");
|
|
56
|
+
}
|
|
57
|
+
const braceEnd = matchBraces(script, braceStart);
|
|
58
|
+
const literal = script.slice(braceStart, braceEnd);
|
|
59
|
+
let end = braceEnd;
|
|
60
|
+
const afterBrace = skipTrivia(script, braceEnd);
|
|
61
|
+
if (script[afterBrace] === ";") end = afterBrace + 1;
|
|
62
|
+
return { start: firstToken, end, literal };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function skipTrivia(source: string, from: number): number {
|
|
66
|
+
let i = from;
|
|
67
|
+
while (i < source.length) {
|
|
68
|
+
const ch = source[i];
|
|
69
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v") {
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
74
|
+
i += 2;
|
|
75
|
+
while (i < source.length && source[i] !== "\n") i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
79
|
+
i += 2;
|
|
80
|
+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i++;
|
|
81
|
+
i += 2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
return i;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function matchBraces(source: string, open: number): number {
|
|
90
|
+
let depth = 0;
|
|
91
|
+
let i = open;
|
|
92
|
+
while (i < source.length) {
|
|
93
|
+
const ch = source[i];
|
|
94
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
95
|
+
i += 2;
|
|
96
|
+
while (i < source.length && source[i] !== "\n") i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
100
|
+
i += 2;
|
|
101
|
+
while (i < source.length && !(source[i] === "*" && source[i + 1] === "/")) i++;
|
|
102
|
+
i += 2;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
106
|
+
i = skipStringLike(source, i, ch);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === "{") depth++;
|
|
110
|
+
else if (ch === "}") {
|
|
111
|
+
depth--;
|
|
112
|
+
if (depth === 0) return i + 1;
|
|
113
|
+
}
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
throw new Error("meta object literal is not closed: missing `}`");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function skipStringLike(source: string, start: number, quote: string): number {
|
|
120
|
+
let i = start + 1;
|
|
121
|
+
while (i < source.length) {
|
|
122
|
+
const ch = source[i];
|
|
123
|
+
if (ch === "\\") {
|
|
124
|
+
i += 2;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (quote === "`" && ch === "$" && source[i + 1] === "{") {
|
|
128
|
+
let depth = 1;
|
|
129
|
+
i += 2;
|
|
130
|
+
while (i < source.length && depth > 0) {
|
|
131
|
+
if (source[i] === "{") depth++;
|
|
132
|
+
else if (source[i] === "}") depth--;
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (ch === quote) return i + 1;
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
throw new Error("unterminated string in meta object literal");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function evaluateMetaLiteral(literal: string): unknown {
|
|
144
|
+
const context = vm.createContext(Object.create(null));
|
|
145
|
+
try {
|
|
146
|
+
return new vm.Script(`(${literal})`, { filename: "workflow.meta.js" }).runInContext(context, { timeout: 1000 });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const detail = error instanceof Error ? `: ${error.message}` : "";
|
|
149
|
+
throw new Error(`meta export must be a plain object literal${detail}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function validateMeta(meta: unknown): asserts meta is WorkflowMeta {
|
|
154
|
+
if (!meta || typeof meta !== "object") throw new Error("meta must be an object");
|
|
155
|
+
const value = meta as Record<string, unknown>;
|
|
156
|
+
if (typeof value.name !== "string" || !value.name.trim()) throw new Error("meta.name must be a non-empty string");
|
|
157
|
+
if (typeof value.description !== "string" || !value.description.trim()) {
|
|
158
|
+
throw new Error("meta.description must be a non-empty string");
|
|
159
|
+
}
|
|
160
|
+
if (value.whenToUse !== undefined && typeof value.whenToUse !== "string") throw new Error("meta.whenToUse must be a string");
|
|
161
|
+
if (value.phases !== undefined) {
|
|
162
|
+
if (!Array.isArray(value.phases)) throw new Error("meta.phases must be an array");
|
|
163
|
+
for (const phase of value.phases) {
|
|
164
|
+
if (!phase || typeof phase !== "object" || typeof phase.title !== "string") {
|
|
165
|
+
throw new Error("each meta phase must have a title string");
|
|
166
|
+
}
|
|
167
|
+
if (phase.detail !== undefined && typeof phase.detail !== "string") {
|
|
168
|
+
throw new Error("meta phase detail must be a string");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Static, TSchema } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentTool } from "../capability-deck/index.js";
|
|
3
|
+
|
|
4
|
+
export interface StructuredOutputCapture<T = unknown> {
|
|
5
|
+
value: T | undefined;
|
|
6
|
+
called: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface StructuredOutputToolOptions<TSchemaDef extends TSchema> {
|
|
10
|
+
schema: TSchemaDef;
|
|
11
|
+
capture: StructuredOutputCapture<Static<TSchemaDef>>;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createStructuredOutputTool<TSchemaDef extends TSchema>({
|
|
16
|
+
schema,
|
|
17
|
+
capture,
|
|
18
|
+
name = "structured_output",
|
|
19
|
+
}: StructuredOutputToolOptions<TSchemaDef>): AgentTool<TSchemaDef, Static<TSchemaDef>> {
|
|
20
|
+
return {
|
|
21
|
+
name,
|
|
22
|
+
label: "Structured Output",
|
|
23
|
+
description: "Return the final machine-readable result for this subagent task.",
|
|
24
|
+
readOnly: true,
|
|
25
|
+
parameters: schema,
|
|
26
|
+
async execute(_toolCallId, params) {
|
|
27
|
+
capture.value = params;
|
|
28
|
+
capture.called = true;
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: "Structured output received." }],
|
|
31
|
+
details: params,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand record — the single source of truth for every identity literal.
|
|
3
|
+
*
|
|
4
|
+
* One frozen {@link Brand} value. Nothing else in the app hard-codes the
|
|
5
|
+
* product name, the profile-directory leaf, the bin names, the env-var
|
|
6
|
+
* namespace, or the share-viewer origin.
|
|
7
|
+
*/
|
|
8
|
+
import type { Brand } from "../boot/contract.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The concrete indusagi identity.
|
|
12
|
+
*/
|
|
13
|
+
export const BRAND: Brand = Object.freeze({
|
|
14
|
+
name: "indusagi",
|
|
15
|
+
label: "indusagi",
|
|
16
|
+
profileDirName: ".indusagi",
|
|
17
|
+
stateDirName: "agent",
|
|
18
|
+
binNames: ["indus", "indusagi"],
|
|
19
|
+
envPrefix: "INDUSAGI",
|
|
20
|
+
envProfileDir: "INDUSAGI_CODING_AGENT_DIR",
|
|
21
|
+
envDebug: "INDUSAGI_DEBUG",
|
|
22
|
+
envShareViewer: "INDUSAGI_SHARE_VIEWER_URL",
|
|
23
|
+
shareViewerUrl: "https://buildwithindusagi.ai/session/",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The product version — printed by `--version` and shown on the startup banner.
|
|
28
|
+
*/
|
|
29
|
+
export const VERSION = "0.2.10";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Type declarations recovered from dist/types (no runtime body)
|
|
3
|
+
/**
|
|
4
|
+
* Workspace subsystem — public barrel.
|
|
5
|
+
*
|
|
6
|
+
* One import site for the keyed Workspace Locator, the concrete {@link BRAND}
|
|
7
|
+
* record, and the runtime/packaging detection. Boot stages depend on this
|
|
8
|
+
* module rather than reaching into the individual files.
|
|
9
|
+
*/
|
|
10
|
+
export { BRAND, VERSION } from "./brand.js";
|
|
11
|
+
export { createWorkspace, ensureDirs, type WorkspaceOverrides } from "./locator.js";
|
|
12
|
+
export {
|
|
13
|
+
isPackagedBinary,
|
|
14
|
+
resolvePackageRoot,
|
|
15
|
+
detectPackagingFromModuleUrl,
|
|
16
|
+
type Packaging,
|
|
17
|
+
} from "./runtime-detect.js";
|
|
18
|
+
export type { Brand, Workspace } from "../boot/contract.js";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Locator — turns the active {@link Brand} plus the environment into a
|
|
3
|
+
* single, fully-resolved {@link Workspace} record of absolute paths.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
8
|
+
import type { Brand, Workspace } from "../boot/contract.js";
|
|
9
|
+
import { BRAND } from "./brand.js";
|
|
10
|
+
|
|
11
|
+
export interface WorkspaceOverrides {
|
|
12
|
+
/** User home directory; defaults to {@link homedir}. */
|
|
13
|
+
readonly home?: string;
|
|
14
|
+
/** Working directory the resolution is anchored to; defaults to `process.cwd()`. */
|
|
15
|
+
readonly cwd?: string;
|
|
16
|
+
/** Environment to read the profile-dir override from; defaults to {@link process.env}. */
|
|
17
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
18
|
+
/** Brand whose directory names drive the layout; defaults to {@link BRAND}. */
|
|
19
|
+
readonly brand?: Brand;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const LAYOUT: Record<keyof Workspace, string[]> = {
|
|
23
|
+
profileDir: [],
|
|
24
|
+
settingsPath: ["settings.json"],
|
|
25
|
+
authPath: ["auth.json"],
|
|
26
|
+
sessionsDir: ["sessions"],
|
|
27
|
+
modelsPath: ["models.json"],
|
|
28
|
+
toolsDir: ["bin"],
|
|
29
|
+
binDir: ["bin"],
|
|
30
|
+
promptsDir: ["prompts"],
|
|
31
|
+
themesDir: ["themes"],
|
|
32
|
+
exportTemplateDir: ["export-template"],
|
|
33
|
+
debugLogPath: ["debug.log"],
|
|
34
|
+
mcpConfigPath: ["mcp-servers.json"],
|
|
35
|
+
memoryConfigPath: ["memory.json"],
|
|
36
|
+
composioConfigPath: ["composio.json"],
|
|
37
|
+
zohoConfigPath: ["zoho.json"],
|
|
38
|
+
memoryDbPath: ["memory.db"],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DIRECTORY_KEYS: Array<keyof Workspace> = [
|
|
42
|
+
"profileDir",
|
|
43
|
+
"sessionsDir",
|
|
44
|
+
"toolsDir",
|
|
45
|
+
"binDir",
|
|
46
|
+
"promptsDir",
|
|
47
|
+
"themesDir",
|
|
48
|
+
"exportTemplateDir",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function resolveProfileRoot(
|
|
52
|
+
brand: Brand,
|
|
53
|
+
home: string,
|
|
54
|
+
cwd: string,
|
|
55
|
+
env: NodeJS.ProcessEnv,
|
|
56
|
+
): string {
|
|
57
|
+
const override = env[brand.envProfileDir]?.trim();
|
|
58
|
+
if (override) {
|
|
59
|
+
return expandHome(override, home, cwd);
|
|
60
|
+
}
|
|
61
|
+
return join(home, brand.profileDirName, brand.stateDirName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function expandHome(rawPath: string, home: string, cwd: string): string {
|
|
65
|
+
if (rawPath === "~") {
|
|
66
|
+
return home;
|
|
67
|
+
}
|
|
68
|
+
if (rawPath.startsWith("~/") || rawPath.startsWith("~\\")) {
|
|
69
|
+
return join(home, rawPath.slice(2));
|
|
70
|
+
}
|
|
71
|
+
if (isAbsolute(rawPath)) {
|
|
72
|
+
return rawPath;
|
|
73
|
+
}
|
|
74
|
+
return resolve(cwd, rawPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build the fully-resolved {@link Workspace} for the active brand and
|
|
79
|
+
* environment.
|
|
80
|
+
*/
|
|
81
|
+
export function createWorkspace(overrides: WorkspaceOverrides = {}): Workspace {
|
|
82
|
+
const brand = overrides.brand ?? BRAND;
|
|
83
|
+
const home = overrides.home ?? homedir();
|
|
84
|
+
const cwd = overrides.cwd ?? process.cwd();
|
|
85
|
+
const env = overrides.env ?? process.env;
|
|
86
|
+
const profileRoot = resolveProfileRoot(brand, home, cwd, env);
|
|
87
|
+
const entries = Object.keys(LAYOUT).map((key) => {
|
|
88
|
+
const segments = LAYOUT[key as keyof Workspace];
|
|
89
|
+
const absolute = segments.length === 0 ? profileRoot : join(profileRoot, ...segments);
|
|
90
|
+
return [key, absolute];
|
|
91
|
+
});
|
|
92
|
+
return Object.freeze(Object.fromEntries(entries)) as Workspace;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create every directory the workspace expects, idempotently.
|
|
97
|
+
*/
|
|
98
|
+
export function ensureDirs(workspace: Workspace): Workspace {
|
|
99
|
+
for (const key of DIRECTORY_KEYS) {
|
|
100
|
+
mkdirSync(workspace[key], { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
return workspace;
|
|
103
|
+
}
|