@pi-unipi/subagents 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-manager.d.ts +69 -0
- package/dist/agent-manager.d.ts.map +1 -0
- package/dist/agent-manager.js +240 -0
- package/dist/agent-manager.js.map +1 -0
- package/dist/agent-runner.d.ts +50 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +238 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/custom-agents.d.ts +14 -0
- package/dist/custom-agents.d.ts.map +1 -0
- package/dist/custom-agents.js +94 -0
- package/dist/custom-agents.js.map +1 -0
- package/dist/file-lock.d.ts +42 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +91 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +31 -0
- package/dist/prompts.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/widget.d.ts +22 -0
- package/dist/widget.d.ts.map +1 -0
- package/dist/widget.js +108 -0
- package/dist/widget.js.map +1 -0
- package/package.json +30 -0
- package/src/agent-manager.ts +302 -0
- package/src/agent-runner.ts +306 -0
- package/src/config.ts +128 -0
- package/src/custom-agents.ts +106 -0
- package/src/file-lock.ts +102 -0
- package/src/index.ts +323 -0
- package/src/prompts.ts +39 -0
- package/src/skills/explore/SKILL.md +32 -0
- package/src/skills/work/SKILL.md +40 -0
- package/src/types.ts +86 -0
- package/src/widget.ts +123 -0
- package/tsconfig.json +19 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Config management
|
|
3
|
+
*
|
|
4
|
+
* Loads config from ~/.unipc/config/subagents.json (global)
|
|
5
|
+
* and <workspace>/.unipi/config/subagents.json (override).
|
|
6
|
+
* Auto-generates on first run. Repairs corrupted files.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
maxConcurrent: 4,
|
|
13
|
+
enabled: true,
|
|
14
|
+
types: {
|
|
15
|
+
explore: { enabled: true },
|
|
16
|
+
work: { enabled: true },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
/** Get global config path: ~/.unipi/config/subagents.json */
|
|
20
|
+
function getGlobalConfigPath() {
|
|
21
|
+
return join(homedir(), ".unipi", "config", "subagents.json");
|
|
22
|
+
}
|
|
23
|
+
/** Get workspace config path: <cwd>/.unipi/config/subagents.json */
|
|
24
|
+
function getWorkspaceConfigPath(cwd) {
|
|
25
|
+
return join(cwd, ".unipi", "config", "subagents.json");
|
|
26
|
+
}
|
|
27
|
+
/** Ensure directory exists. */
|
|
28
|
+
function ensureDir(filePath) {
|
|
29
|
+
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Write config atomically (write then rename). */
|
|
35
|
+
function writeConfigAtomic(filePath, config) {
|
|
36
|
+
const tmpPath = filePath + ".tmp";
|
|
37
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2), "utf-8");
|
|
38
|
+
renameSync(tmpPath, filePath);
|
|
39
|
+
}
|
|
40
|
+
/** Load and parse config from a path. Returns null on failure. */
|
|
41
|
+
function loadConfigFromPath(filePath) {
|
|
42
|
+
if (!existsSync(filePath))
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(filePath, "utf-8");
|
|
46
|
+
const parsed = JSON.parse(content);
|
|
47
|
+
// Basic validation
|
|
48
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
49
|
+
return null;
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Repair corrupted config: rename to .bak and generate fresh. */
|
|
57
|
+
function repairCorrupted(filePath) {
|
|
58
|
+
const backupPath = filePath + ".bak";
|
|
59
|
+
try {
|
|
60
|
+
renameSync(filePath, backupPath);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// If rename fails, just overwrite
|
|
64
|
+
}
|
|
65
|
+
writeConfigAtomic(filePath, DEFAULT_CONFIG);
|
|
66
|
+
return DEFAULT_CONFIG;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Initialize config on extension start.
|
|
70
|
+
* - If missing: generate with defaults
|
|
71
|
+
* - If corrupted: rename to .bak, generate fresh
|
|
72
|
+
* - If valid: load
|
|
73
|
+
*/
|
|
74
|
+
export function initConfig(cwd) {
|
|
75
|
+
const globalPath = getGlobalConfigPath();
|
|
76
|
+
// Ensure global config dir exists
|
|
77
|
+
ensureDir(globalPath);
|
|
78
|
+
// Load or create global config
|
|
79
|
+
let globalConfig = loadConfigFromPath(globalPath);
|
|
80
|
+
if (globalConfig === null) {
|
|
81
|
+
globalConfig = repairCorrupted(globalPath);
|
|
82
|
+
}
|
|
83
|
+
// Load workspace override if exists
|
|
84
|
+
const workspacePath = getWorkspaceConfigPath(cwd);
|
|
85
|
+
const workspaceConfig = loadConfigFromPath(workspacePath);
|
|
86
|
+
if (workspaceConfig) {
|
|
87
|
+
// Merge: workspace overrides global on any field present
|
|
88
|
+
return {
|
|
89
|
+
...globalConfig,
|
|
90
|
+
...workspaceConfig,
|
|
91
|
+
types: {
|
|
92
|
+
...globalConfig.types,
|
|
93
|
+
...workspaceConfig.types,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return globalConfig;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Save global config.
|
|
101
|
+
*/
|
|
102
|
+
export function saveGlobalConfig(config) {
|
|
103
|
+
const globalPath = getGlobalConfigPath();
|
|
104
|
+
ensureDir(globalPath);
|
|
105
|
+
writeConfigAtomic(globalPath, config);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Save workspace config.
|
|
109
|
+
*/
|
|
110
|
+
export function saveWorkspaceConfig(cwd, config) {
|
|
111
|
+
const workspacePath = getWorkspaceConfigPath(cwd);
|
|
112
|
+
ensureDir(workspacePath);
|
|
113
|
+
writeConfigAtomic(workspacePath, config);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,MAAM,cAAc,GAAoB;IACtC,aAAa,EAAE,CAAC;IAChB,OAAO,EAAE,IAAI;IACb,KAAK,EAAE;QACL,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;QAC1B,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;KACxB;CACF,CAAC;AAEF,6DAA6D;AAC7D,SAAS,mBAAmB;IAC1B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AAC/D,CAAC;AAED,oEAAoE;AACpE,SAAS,sBAAsB,CAAC,GAAW;IACzC,OAAO,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AACzD,CAAC;AAED,+BAA+B;AAC/B,SAAS,SAAS,CAAC,QAAgB;IACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,SAAS,iBAAiB,CAAC,QAAgB,EAAE,MAAuB;IAClE,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACjE,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED,kEAAkE;AAClE,SAAS,kBAAkB,CAAC,QAAgB;IAC1C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,mBAAmB;QACnB,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC/D,OAAO,MAAyB,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,IAAI,CAAC;QACH,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,kCAAkC;IACpC,CAAC;IACD,iBAAiB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC5C,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IAEzC,kCAAkC;IAClC,SAAS,CAAC,UAAU,CAAC,CAAC;IAEtB,+BAA+B;IAC/B,IAAI,YAAY,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,YAAY,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;IAED,oCAAoC;IACpC,MAAM,aAAa,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,eAAe,GAAG,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAE1D,IAAI,eAAe,EAAE,CAAC;QACpB,yDAAyD;QACzD,OAAO;YACL,GAAG,YAAY;YACf,GAAG,eAAe;YAClB,KAAK,EAAE;gBACL,GAAG,YAAY,CAAC,KAAK;gBACrB,GAAG,eAAe,CAAC,KAAK;aACzB;SACF,CAAC;IACJ,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAuB;IACtD,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IACzC,SAAS,CAAC,UAAU,CAAC,CAAC;IACtB,iBAAiB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW,EAAE,MAAuB;IACtE,MAAM,aAAa,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAClD,SAAS,CAAC,aAAa,CAAC,CAAC;IACzB,iBAAiB,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Custom agent loader
|
|
3
|
+
*
|
|
4
|
+
* Discovers agent types from:
|
|
5
|
+
* - <workspace>/.unipc/config/agents/*.md (project, highest priority)
|
|
6
|
+
* - ~/.unipc/config/agents/*.md (global)
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentConfig } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Load all custom agents from project and global directories.
|
|
11
|
+
* Project agents override global agents with the same name.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadCustomAgents(cwd: string): Map<string, AgentConfig>;
|
|
14
|
+
//# sourceMappingURL=custom-agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-agents.d.ts","sourceRoot":"","sources":["../src/custom-agents.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6D9C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CA4BtE"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Custom agent loader
|
|
3
|
+
*
|
|
4
|
+
* Discovers agent types from:
|
|
5
|
+
* - <workspace>/.unipc/config/agents/*.md (project, highest priority)
|
|
6
|
+
* - ~/.unipc/config/agents/*.md (global)
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
12
|
+
/** Get project agents directory. */
|
|
13
|
+
function getProjectAgentsDir(cwd) {
|
|
14
|
+
return join(cwd, ".unipc", "config", "agents");
|
|
15
|
+
}
|
|
16
|
+
/** Get global agents directory. */
|
|
17
|
+
function getGlobalAgentsDir() {
|
|
18
|
+
return join(homedir(), ".unipc", "config", "agents");
|
|
19
|
+
}
|
|
20
|
+
/** All known built-in tool names. */
|
|
21
|
+
const BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
22
|
+
/**
|
|
23
|
+
* Load a single agent from a .md file.
|
|
24
|
+
*/
|
|
25
|
+
function loadAgentFromFile(filePath, source) {
|
|
26
|
+
try {
|
|
27
|
+
const content = readFileSync(filePath, "utf-8");
|
|
28
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
29
|
+
if (!frontmatter || typeof frontmatter !== "object") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const name = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "unknown";
|
|
33
|
+
// Parse tools from comma-separated string
|
|
34
|
+
const toolsStr = frontmatter.tools;
|
|
35
|
+
const builtinToolNames = toolsStr
|
|
36
|
+
? toolsStr.split(",").map((t) => t.trim()).filter((t) => BUILTIN_TOOL_NAMES.includes(t))
|
|
37
|
+
: [...BUILTIN_TOOL_NAMES];
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
displayName: frontmatter.display_name,
|
|
41
|
+
description: frontmatter.description ?? `${name} agent`,
|
|
42
|
+
builtinToolNames,
|
|
43
|
+
disallowedTools: frontmatter.disallowed_tools
|
|
44
|
+
?.split(",")
|
|
45
|
+
.map((t) => t.trim()),
|
|
46
|
+
extensions: frontmatter.extensions !== false,
|
|
47
|
+
skills: frontmatter.skills !== false,
|
|
48
|
+
model: frontmatter.model,
|
|
49
|
+
thinking: frontmatter.thinking,
|
|
50
|
+
maxTurns: frontmatter.max_turns,
|
|
51
|
+
systemPrompt: body.trim(),
|
|
52
|
+
promptMode: frontmatter.prompt_mode ?? "replace",
|
|
53
|
+
inheritContext: frontmatter.inherit_context,
|
|
54
|
+
runInBackground: frontmatter.run_in_background,
|
|
55
|
+
isolated: frontmatter.isolated,
|
|
56
|
+
enabled: frontmatter.enabled !== false,
|
|
57
|
+
source,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load all custom agents from project and global directories.
|
|
66
|
+
* Project agents override global agents with the same name.
|
|
67
|
+
*/
|
|
68
|
+
export function loadCustomAgents(cwd) {
|
|
69
|
+
const agents = new Map();
|
|
70
|
+
// Load global agents first
|
|
71
|
+
const globalDir = getGlobalAgentsDir();
|
|
72
|
+
if (existsSync(globalDir)) {
|
|
73
|
+
const files = readdirSync(globalDir).filter((f) => f.endsWith(".md"));
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const agent = loadAgentFromFile(join(globalDir, file), "global");
|
|
76
|
+
if (agent) {
|
|
77
|
+
agents.set(agent.name, agent);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Load project agents (overrides global)
|
|
82
|
+
const projectDir = getProjectAgentsDir(cwd);
|
|
83
|
+
if (existsSync(projectDir)) {
|
|
84
|
+
const files = readdirSync(projectDir).filter((f) => f.endsWith(".md"));
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
const agent = loadAgentFromFile(join(projectDir, file), "project");
|
|
87
|
+
if (agent) {
|
|
88
|
+
agents.set(agent.name, agent);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return agents;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=custom-agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-agents.js","sourceRoot":"","sources":["../src/custom-agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Per-file transparent locking
|
|
3
|
+
*
|
|
4
|
+
* Agents never see lock errors. Write tool queues internally.
|
|
5
|
+
* Per-file granularity: locking src/auth.ts doesn't block src/login.ts.
|
|
6
|
+
*/
|
|
7
|
+
export declare class FileLock {
|
|
8
|
+
/** Active locks by file path. */
|
|
9
|
+
private locks;
|
|
10
|
+
/** Queue of waiting acquires per file path. */
|
|
11
|
+
private queues;
|
|
12
|
+
/**
|
|
13
|
+
* Acquire a lock on a file. Blocks until available.
|
|
14
|
+
* Returns a release function.
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - Absolute path to the file
|
|
17
|
+
* @param agentId - ID of the agent requesting the lock
|
|
18
|
+
* @returns Release function — call when done writing
|
|
19
|
+
*/
|
|
20
|
+
acquire(filePath: string, agentId: string): Promise<() => void>;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a file is currently locked.
|
|
23
|
+
*/
|
|
24
|
+
isLocked(filePath: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Get the agent that holds a lock on a file.
|
|
27
|
+
*/
|
|
28
|
+
getHolder(filePath: string): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Get count of locked files.
|
|
31
|
+
*/
|
|
32
|
+
get lockCount(): number;
|
|
33
|
+
/**
|
|
34
|
+
* Release all locks held by an agent (on abort).
|
|
35
|
+
*/
|
|
36
|
+
releaseAll(agentId: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clear all locks (on shutdown).
|
|
39
|
+
*/
|
|
40
|
+
clear(): void;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=file-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,qBAAa,QAAQ;IACnB,iCAAiC;IACjC,OAAO,CAAC,KAAK,CAAoC;IACjD,+CAA+C;IAC/C,OAAO,CAAC,MAAM,CAAwC;IAEtD;;;;;;;OAOG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAoCrE;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI/C;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAQjC;;OAEG;IACH,KAAK,IAAI,IAAI;CAOd"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Per-file transparent locking
|
|
3
|
+
*
|
|
4
|
+
* Agents never see lock errors. Write tool queues internally.
|
|
5
|
+
* Per-file granularity: locking src/auth.ts doesn't block src/login.ts.
|
|
6
|
+
*/
|
|
7
|
+
export class FileLock {
|
|
8
|
+
/** Active locks by file path. */
|
|
9
|
+
locks = new Map();
|
|
10
|
+
/** Queue of waiting acquires per file path. */
|
|
11
|
+
queues = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* Acquire a lock on a file. Blocks until available.
|
|
14
|
+
* Returns a release function.
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - Absolute path to the file
|
|
17
|
+
* @param agentId - ID of the agent requesting the lock
|
|
18
|
+
* @returns Release function — call when done writing
|
|
19
|
+
*/
|
|
20
|
+
async acquire(filePath, agentId) {
|
|
21
|
+
// Wait for existing lock
|
|
22
|
+
while (this.locks.has(filePath)) {
|
|
23
|
+
await new Promise((resolve) => {
|
|
24
|
+
const queue = this.queues.get(filePath) ?? [];
|
|
25
|
+
queue.push(resolve);
|
|
26
|
+
this.queues.set(filePath, queue);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Create lock entry
|
|
30
|
+
let releaseFn;
|
|
31
|
+
const promise = new Promise((resolve) => {
|
|
32
|
+
releaseFn = () => {
|
|
33
|
+
this.locks.delete(filePath);
|
|
34
|
+
resolve();
|
|
35
|
+
// Wake next waiter
|
|
36
|
+
const queue = this.queues.get(filePath);
|
|
37
|
+
if (queue && queue.length > 0) {
|
|
38
|
+
const next = queue.shift();
|
|
39
|
+
next();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const entry = {
|
|
44
|
+
agentId,
|
|
45
|
+
filePath,
|
|
46
|
+
promise,
|
|
47
|
+
release: releaseFn,
|
|
48
|
+
};
|
|
49
|
+
this.locks.set(filePath, entry);
|
|
50
|
+
return releaseFn;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a file is currently locked.
|
|
54
|
+
*/
|
|
55
|
+
isLocked(filePath) {
|
|
56
|
+
return this.locks.has(filePath);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the agent that holds a lock on a file.
|
|
60
|
+
*/
|
|
61
|
+
getHolder(filePath) {
|
|
62
|
+
return this.locks.get(filePath)?.agentId;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get count of locked files.
|
|
66
|
+
*/
|
|
67
|
+
get lockCount() {
|
|
68
|
+
return this.locks.size;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Release all locks held by an agent (on abort).
|
|
72
|
+
*/
|
|
73
|
+
releaseAll(agentId) {
|
|
74
|
+
for (const [filePath, entry] of this.locks) {
|
|
75
|
+
if (entry.agentId === agentId) {
|
|
76
|
+
entry.release();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Clear all locks (on shutdown).
|
|
82
|
+
*/
|
|
83
|
+
clear() {
|
|
84
|
+
for (const entry of this.locks.values()) {
|
|
85
|
+
entry.release();
|
|
86
|
+
}
|
|
87
|
+
this.locks.clear();
|
|
88
|
+
this.queues.clear();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,OAAO,QAAQ;IACnB,iCAAiC;IACzB,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IACjD,+CAA+C;IACvC,MAAM,GAAG,IAAI,GAAG,EAA6B,CAAC;IAEtD;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,OAAe;QAC7C,yBAAyB;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAC9C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;QACL,CAAC;QAED,oBAAoB;QACpB,IAAI,SAAqB,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC5C,SAAS,GAAG,GAAG,EAAE;gBACf,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC5B,OAAO,EAAE,CAAC;gBACV,mBAAmB;gBACnB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACxC,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;oBAC5B,IAAI,EAAE,CAAC;gBACT,CAAC;YACH,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAkB;YAC3B,OAAO;YACP,QAAQ;YACR,OAAO;YACP,OAAO,EAAE,SAAU;SACpB,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,SAAU,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,QAAgB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,QAAgB;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,OAAe;QACxB,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3C,IAAI,KAAK,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;gBAC9B,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK;QACH,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Extension entry
|
|
3
|
+
*
|
|
4
|
+
* Tools: Agent, get_result
|
|
5
|
+
* ESC propagation: all children abort on parent ESC
|
|
6
|
+
*/
|
|
7
|
+
import { type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
export default function (pi: ExtensionAPI): void;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAc,KAAK,YAAY,EAAyB,MAAM,+BAA+B,CAAC;AA2BrG,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAgSxC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — Extension entry
|
|
3
|
+
*
|
|
4
|
+
* Tools: Agent, get_result
|
|
5
|
+
* ESC propagation: all children abort on parent ESC
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import { AgentManager } from "./agent-manager.js";
|
|
10
|
+
import { initConfig } from "./config.js";
|
|
11
|
+
import { BUILTIN_TYPES } from "./types.js";
|
|
12
|
+
import { AgentWidget } from "./widget.js";
|
|
13
|
+
/** Format tokens safely. */
|
|
14
|
+
function safeFormatTokens(session) {
|
|
15
|
+
if (!session)
|
|
16
|
+
return "";
|
|
17
|
+
try {
|
|
18
|
+
const stats = session.getSessionStats();
|
|
19
|
+
const total = stats.tokens?.total ?? 0;
|
|
20
|
+
if (total >= 1_000_000)
|
|
21
|
+
return `${(total / 1_000_000).toFixed(1)}M`;
|
|
22
|
+
if (total >= 1_000)
|
|
23
|
+
return `${(total / 1_000).toFixed(1)}k`;
|
|
24
|
+
return `${total}`;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Build result text. */
|
|
31
|
+
function textResult(msg, details) {
|
|
32
|
+
return { content: [{ type: "text", text: msg }], details };
|
|
33
|
+
}
|
|
34
|
+
export default function (pi) {
|
|
35
|
+
// Initialize config
|
|
36
|
+
const config = initConfig(process.cwd());
|
|
37
|
+
if (!config.enabled)
|
|
38
|
+
return;
|
|
39
|
+
// Activity tracking for widget
|
|
40
|
+
const agentActivity = new Map();
|
|
41
|
+
// Create manager with completion callback
|
|
42
|
+
const manager = new AgentManager((record) => {
|
|
43
|
+
// On complete: clean up activity, emit event
|
|
44
|
+
agentActivity.delete(record.id);
|
|
45
|
+
widget.markFinished(record.id);
|
|
46
|
+
widget.update();
|
|
47
|
+
pi.events.emit("subagents:completed", {
|
|
48
|
+
id: record.id,
|
|
49
|
+
type: record.type,
|
|
50
|
+
description: record.description,
|
|
51
|
+
status: record.status,
|
|
52
|
+
result: record.result,
|
|
53
|
+
error: record.error,
|
|
54
|
+
});
|
|
55
|
+
}, config.maxConcurrent, (record) => {
|
|
56
|
+
// On start: emit event
|
|
57
|
+
pi.events.emit("subagents:started", {
|
|
58
|
+
id: record.id,
|
|
59
|
+
type: record.type,
|
|
60
|
+
description: record.description,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// Create widget
|
|
64
|
+
const widget = new AgentWidget(manager, agentActivity);
|
|
65
|
+
// ESC propagation: abort all agents on session shutdown
|
|
66
|
+
pi.on("session_shutdown", async () => {
|
|
67
|
+
manager.abortAll();
|
|
68
|
+
manager.dispose();
|
|
69
|
+
});
|
|
70
|
+
// Wire UI context for widget
|
|
71
|
+
pi.on("tool_execution_start", async (_event, ctx) => {
|
|
72
|
+
widget.setUICtx(ctx.ui);
|
|
73
|
+
widget.update();
|
|
74
|
+
});
|
|
75
|
+
// Create activity tracker
|
|
76
|
+
function createActivityTracker(maxTurns) {
|
|
77
|
+
const state = {
|
|
78
|
+
activeTools: new Map(),
|
|
79
|
+
toolUses: 0,
|
|
80
|
+
turnCount: 1,
|
|
81
|
+
maxTurns,
|
|
82
|
+
tokens: "",
|
|
83
|
+
responseText: "",
|
|
84
|
+
};
|
|
85
|
+
const callbacks = {
|
|
86
|
+
onToolActivity: (activity) => {
|
|
87
|
+
if (activity.type === "start") {
|
|
88
|
+
state.activeTools.set(activity.toolName + "_" + Date.now(), activity.toolName);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
for (const [key, name] of state.activeTools) {
|
|
92
|
+
if (name === activity.toolName) {
|
|
93
|
+
state.activeTools.delete(key);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
state.toolUses++;
|
|
98
|
+
}
|
|
99
|
+
widget.update();
|
|
100
|
+
},
|
|
101
|
+
onTextDelta: (_delta, fullText) => {
|
|
102
|
+
state.responseText = fullText;
|
|
103
|
+
widget.update();
|
|
104
|
+
},
|
|
105
|
+
onTurnEnd: (turnCount) => {
|
|
106
|
+
state.turnCount = turnCount;
|
|
107
|
+
widget.update();
|
|
108
|
+
},
|
|
109
|
+
onSessionCreated: (session) => {
|
|
110
|
+
state.session = session;
|
|
111
|
+
state.tokens = safeFormatTokens(session);
|
|
112
|
+
widget.update();
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
return { state, callbacks };
|
|
116
|
+
}
|
|
117
|
+
// ---- Agent tool ----
|
|
118
|
+
const builtinTypes = BUILTIN_TYPES.join(", ");
|
|
119
|
+
pi.registerTool(defineTool({
|
|
120
|
+
name: "Agent",
|
|
121
|
+
label: "Agent",
|
|
122
|
+
description: `Launch a sub-agent for parallel work.
|
|
123
|
+
|
|
124
|
+
Available agent types: ${builtinTypes}
|
|
125
|
+
Custom types can be defined in .unipi/config/agents/<name>.md
|
|
126
|
+
|
|
127
|
+
Guidelines:
|
|
128
|
+
- Use "explore" for parallel file reads
|
|
129
|
+
- Use "work" for parallel file writes (transparent locking)
|
|
130
|
+
- Use run_in_background for work you don't need immediately
|
|
131
|
+
- ESC kills all running agents immediately
|
|
132
|
+
- Agents inherit the parent model by default`,
|
|
133
|
+
parameters: Type.Object({
|
|
134
|
+
type: Type.String({
|
|
135
|
+
description: `Agent type: ${builtinTypes}, or custom type from .unipc/config/agents/*.md`,
|
|
136
|
+
}),
|
|
137
|
+
prompt: Type.String({
|
|
138
|
+
description: "The task for the agent to perform.",
|
|
139
|
+
}),
|
|
140
|
+
description: Type.String({
|
|
141
|
+
description: "A short (3-5 word) description of the task.",
|
|
142
|
+
}),
|
|
143
|
+
run_in_background: Type.Optional(Type.Boolean({
|
|
144
|
+
description: "Run in background. Returns agent ID immediately.",
|
|
145
|
+
})),
|
|
146
|
+
max_turns: Type.Optional(Type.Number({
|
|
147
|
+
description: "Max agentic turns before stopping.",
|
|
148
|
+
minimum: 1,
|
|
149
|
+
})),
|
|
150
|
+
}),
|
|
151
|
+
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
|
152
|
+
widget.setUICtx(ctx.ui);
|
|
153
|
+
const type = params.type;
|
|
154
|
+
const prompt = params.prompt;
|
|
155
|
+
const description = params.description;
|
|
156
|
+
const runInBackground = params.run_in_background;
|
|
157
|
+
const maxTurns = params.max_turns;
|
|
158
|
+
// Create activity tracker
|
|
159
|
+
const { state: bgState, callbacks: bgCallbacks } = createActivityTracker(maxTurns);
|
|
160
|
+
if (runInBackground) {
|
|
161
|
+
// Background execution
|
|
162
|
+
const id = manager.spawn(pi, ctx, type, prompt, {
|
|
163
|
+
description,
|
|
164
|
+
maxTurns,
|
|
165
|
+
isBackground: true,
|
|
166
|
+
...bgCallbacks,
|
|
167
|
+
});
|
|
168
|
+
agentActivity.set(id, bgState);
|
|
169
|
+
widget.ensureTimer();
|
|
170
|
+
widget.update();
|
|
171
|
+
const record = manager.getRecord(id);
|
|
172
|
+
const isQueued = record?.status === "queued";
|
|
173
|
+
return textResult(`Agent ${isQueued ? "queued" : "started"} in background.\n` +
|
|
174
|
+
`ID: ${id}\n` +
|
|
175
|
+
`Type: ${type}\n` +
|
|
176
|
+
`Description: ${description}\n` +
|
|
177
|
+
(isQueued ? `Position: queued (max ${manager.getMaxConcurrent()} concurrent)\n` : "") +
|
|
178
|
+
`\nYou will be notified when this agent completes.\n` +
|
|
179
|
+
`Use get_result to retrieve full results.`, { status: "background", agentId: id });
|
|
180
|
+
}
|
|
181
|
+
// Foreground execution
|
|
182
|
+
let spinnerFrame = 0;
|
|
183
|
+
const startedAt = Date.now();
|
|
184
|
+
let fgId;
|
|
185
|
+
const streamUpdate = () => {
|
|
186
|
+
onUpdate?.({
|
|
187
|
+
content: [{ type: "text", text: `${bgState.toolUses} tool uses...` }],
|
|
188
|
+
details: {
|
|
189
|
+
status: "running",
|
|
190
|
+
toolUses: bgState.toolUses,
|
|
191
|
+
tokens: bgState.tokens,
|
|
192
|
+
turnCount: bgState.turnCount,
|
|
193
|
+
maxTurns: bgState.maxTurns,
|
|
194
|
+
durationMs: Date.now() - startedAt,
|
|
195
|
+
activity: bgState.responseText
|
|
196
|
+
? bgState.responseText.split("\n").pop()?.trim().slice(0, 60)
|
|
197
|
+
: "thinking…",
|
|
198
|
+
spinnerFrame: spinnerFrame % 10,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
const spinnerInterval = setInterval(() => {
|
|
203
|
+
spinnerFrame++;
|
|
204
|
+
streamUpdate();
|
|
205
|
+
}, 80);
|
|
206
|
+
streamUpdate();
|
|
207
|
+
const record = await manager.spawnAndWait(pi, ctx, type, prompt, {
|
|
208
|
+
description,
|
|
209
|
+
maxTurns,
|
|
210
|
+
...bgCallbacks,
|
|
211
|
+
});
|
|
212
|
+
clearInterval(spinnerInterval);
|
|
213
|
+
if (fgId) {
|
|
214
|
+
agentActivity.delete(fgId);
|
|
215
|
+
widget.markFinished(fgId);
|
|
216
|
+
}
|
|
217
|
+
const tokenText = safeFormatTokens(bgState.session);
|
|
218
|
+
const durationMs = (record.completedAt ?? Date.now()) - record.startedAt;
|
|
219
|
+
if (record.status === "error") {
|
|
220
|
+
return textResult(`Agent failed: ${record.error}`);
|
|
221
|
+
}
|
|
222
|
+
return textResult(`Agent completed in ${(durationMs / 1000).toFixed(1)}s (${record.toolUses} tool uses${tokenText ? `, ${tokenText} tokens` : ""}).\n\n` +
|
|
223
|
+
(record.result?.trim() || "No output."));
|
|
224
|
+
},
|
|
225
|
+
}));
|
|
226
|
+
// ---- get_result tool ----
|
|
227
|
+
pi.registerTool(defineTool({
|
|
228
|
+
name: "get_result",
|
|
229
|
+
label: "Get Agent Result",
|
|
230
|
+
description: "Check status and retrieve results from a background agent.",
|
|
231
|
+
parameters: Type.Object({
|
|
232
|
+
agent_id: Type.String({
|
|
233
|
+
description: "The agent ID to check.",
|
|
234
|
+
}),
|
|
235
|
+
wait: Type.Optional(Type.Boolean({
|
|
236
|
+
description: "Wait for completion. Default: false.",
|
|
237
|
+
})),
|
|
238
|
+
}),
|
|
239
|
+
execute: async (_toolCallId, params) => {
|
|
240
|
+
const record = manager.getRecord(params.agent_id);
|
|
241
|
+
if (!record) {
|
|
242
|
+
return textResult(`Agent not found: "${params.agent_id}". It may have been cleaned up.`);
|
|
243
|
+
}
|
|
244
|
+
if (params.wait && record.status === "running" && record.promise) {
|
|
245
|
+
record.resultConsumed = true;
|
|
246
|
+
await record.promise;
|
|
247
|
+
}
|
|
248
|
+
const duration = record.completedAt
|
|
249
|
+
? `${((record.completedAt - record.startedAt) / 1000).toFixed(1)}s`
|
|
250
|
+
: "running";
|
|
251
|
+
let output = `Agent: ${record.id}\n` +
|
|
252
|
+
`Type: ${record.type} | Status: ${record.status} | Tool uses: ${record.toolUses} | Duration: ${duration}\n` +
|
|
253
|
+
`Description: ${record.description}\n\n`;
|
|
254
|
+
if (record.status === "running") {
|
|
255
|
+
output += "Agent is still running. Use wait: true or check back later.";
|
|
256
|
+
}
|
|
257
|
+
else if (record.status === "error") {
|
|
258
|
+
output += `Error: ${record.error}`;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
output += record.result?.trim() || "No output.";
|
|
262
|
+
}
|
|
263
|
+
if (record.status !== "running" && record.status !== "queued") {
|
|
264
|
+
record.resultConsumed = true;
|
|
265
|
+
}
|
|
266
|
+
return textResult(output);
|
|
267
|
+
},
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=index.js.map
|