@openagents-org/agent-launcher 0.2.160 → 0.2.162
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/icons/pi.svg +1 -0
- package/package.json +1 -1
- package/registry.json +144 -0
- package/src/adapters/index.js +4 -1
- package/src/adapters/pi-launcher-provider.mjs +116 -0
- package/src/adapters/pi-stream.js +922 -0
- package/src/adapters/pi.js +1492 -0
- package/src/adapters/workspace-prompt.js +21 -0
- package/src/daemon.js +73 -3
- package/src/installer.js +20 -4
- package/src/workspace-client.js +4 -4
|
@@ -869,6 +869,26 @@ function buildOpenCodeSystemPrompt({ agentName, workspaceId, channelName, endpoi
|
|
|
869
869
|
return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Build the system prompt appended to Pi's own prompt (`--append-system-prompt`).
|
|
874
|
+
*
|
|
875
|
+
* Shape mirrors buildOpenCodeSystemPrompt: identity + browser directive +
|
|
876
|
+
* collaboration + mode + the REST "skills" block, because Pi is NOT wired to an
|
|
877
|
+
* MCP server (only the Claude adapter uses --mcp-config). Pi reaches workspace
|
|
878
|
+
* APIs through its built-in `bash` tool + curl, exactly like OpenCode/Amp.
|
|
879
|
+
*
|
|
880
|
+
* NOTE: this is APPENDED, never passed via `--system-prompt` — that flag would
|
|
881
|
+
* REPLACE Pi's own coding-assistant prompt and strip its tool instructions.
|
|
882
|
+
*/
|
|
883
|
+
function buildPiSystemPrompt({ agentName, workspaceId, channelName, endpoint, token, mode = 'execute', disabledModules, browserEnabled = false }) {
|
|
884
|
+
const identity = buildWorkspaceIdentity(agentName, workspaceId, channelName, mode, 'skills');
|
|
885
|
+
const directive = buildBrowserDirective(browserEnabled);
|
|
886
|
+
const collab = buildCollaborationPrompt('skills', workspaceSkillName(agentName));
|
|
887
|
+
const modePrompt = buildModePrompt(mode);
|
|
888
|
+
const api = buildApiSkillsPrompt({ endpoint, workspaceId, token, agentName, channelName, disabledModules, mode });
|
|
889
|
+
return identity + directive + '\n' + collab + '\n' + modePrompt + '\n' + api + '\n' + buildGuardrails();
|
|
890
|
+
}
|
|
891
|
+
|
|
872
892
|
/**
|
|
873
893
|
* Build workspace skill markdown for OpenCode (written to .opencode/skills/).
|
|
874
894
|
*/
|
|
@@ -972,6 +992,7 @@ module.exports = {
|
|
|
972
992
|
buildOpenclawSkillMd,
|
|
973
993
|
buildOpenCodeSystemPrompt,
|
|
974
994
|
buildOpenCodeSkillMd,
|
|
995
|
+
buildPiSystemPrompt,
|
|
975
996
|
buildClaudeSkillMd,
|
|
976
997
|
buildCursorSkillMd,
|
|
977
998
|
};
|
package/src/daemon.js
CHANGED
|
@@ -51,7 +51,7 @@ class Daemon {
|
|
|
51
51
|
if (!this._nodeClient) {
|
|
52
52
|
this._nodeClient = new WorkspaceClient(n.endpoint);
|
|
53
53
|
}
|
|
54
|
-
const info = { ...nodeCfg.gatherDeviceInfo(), agents: this._buildRoster(), runtimes: this._runtimes };
|
|
54
|
+
const info = { ...nodeCfg.gatherDeviceInfo(), agents: this._buildRoster(), runtimes: this._runtimes, fs: this._buildFs() };
|
|
55
55
|
const resp = await this._nodeClient.nodeHeartbeat(n.node_id, n.token, info);
|
|
56
56
|
// The heartbeat response is our push channel: run any queued remote
|
|
57
57
|
// agent-management commands the workspace enqueued for this node. Fire
|
|
@@ -80,7 +80,19 @@ class Daemon {
|
|
|
80
80
|
try {
|
|
81
81
|
for (const a of this.config.getAgents()) {
|
|
82
82
|
const proc = this._processes[a.name];
|
|
83
|
-
|
|
83
|
+
// Model: per-agent env override, else the type-level saved model. Working
|
|
84
|
+
// dir: the agent's configured path. Both power the workspace agent cards.
|
|
85
|
+
let model = (a.env && a.env.LLM_MODEL) || null;
|
|
86
|
+
if (!model) {
|
|
87
|
+
try { model = (this.envManager.load(a.type) || {}).LLM_MODEL || null; } catch { model = null; }
|
|
88
|
+
}
|
|
89
|
+
roster.push({
|
|
90
|
+
name: a.name,
|
|
91
|
+
type: a.type || 'unknown',
|
|
92
|
+
status: (proc && proc.state) || 'stopped',
|
|
93
|
+
model: model || null,
|
|
94
|
+
workingDir: a.path || null,
|
|
95
|
+
});
|
|
84
96
|
}
|
|
85
97
|
} catch {
|
|
86
98
|
// best-effort
|
|
@@ -88,6 +100,36 @@ class Daemon {
|
|
|
88
100
|
return roster;
|
|
89
101
|
}
|
|
90
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Filesystem hint for the working-directory picker: home + its immediate
|
|
105
|
+
* (non-hidden) subfolders, so the workspace can show real folders instantly.
|
|
106
|
+
*/
|
|
107
|
+
_buildFs() {
|
|
108
|
+
try {
|
|
109
|
+
const home = os.homedir();
|
|
110
|
+
const dirs = fs.readdirSync(home, { withFileTypes: true })
|
|
111
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('.'))
|
|
112
|
+
.map((d) => d.name)
|
|
113
|
+
.sort((a, b) => a.localeCompare(b))
|
|
114
|
+
.slice(0, 100);
|
|
115
|
+
return { home, dirs };
|
|
116
|
+
} catch {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** List the (non-hidden) subfolders of a directory, for on-demand browsing. */
|
|
122
|
+
_listDir(dir) {
|
|
123
|
+
const target = dir && String(dir).trim() ? String(dir) : os.homedir();
|
|
124
|
+
const dirs = fs.readdirSync(target, { withFileTypes: true })
|
|
125
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith('.'))
|
|
126
|
+
.map((d) => d.name)
|
|
127
|
+
.sort((a, b) => a.localeCompare(b))
|
|
128
|
+
.slice(0, 500);
|
|
129
|
+
const parent = path.dirname(target);
|
|
130
|
+
return { path: target, parent: parent === target ? null : parent, dirs };
|
|
131
|
+
}
|
|
132
|
+
|
|
91
133
|
/**
|
|
92
134
|
* Detect, for every supported agent type, whether its runtime is installed and
|
|
93
135
|
* logged-in/ready on this device. Runs `agn runtimes --json` in a CHILD process
|
|
@@ -117,6 +159,7 @@ class Daemon {
|
|
|
117
159
|
const name = (args.name || '').trim();
|
|
118
160
|
let ok = false;
|
|
119
161
|
let message = '';
|
|
162
|
+
let data = null;
|
|
120
163
|
try {
|
|
121
164
|
if (action === 'create_agent') {
|
|
122
165
|
const type = (args.type || '').trim();
|
|
@@ -150,6 +193,29 @@ class Daemon {
|
|
|
150
193
|
const r = await this._runAgn(['remove', name]);
|
|
151
194
|
ok = r.code === 0;
|
|
152
195
|
message = ok ? `Agent '${name}' removed` : (r.stderr || r.stdout || 'remove failed');
|
|
196
|
+
} else if (action === 'configure_agent') {
|
|
197
|
+
// Reconfigure an existing agent: update model/key (type-level env), then
|
|
198
|
+
// restart it so the change takes effect. Working-dir changes require a
|
|
199
|
+
// recreate (agn has no in-place path change) at the new path.
|
|
200
|
+
const type = (args.type || '').trim();
|
|
201
|
+
if (args.apiKey) await this._runAgn(['env', type, '--set', `LLM_API_KEY=${args.apiKey}`]);
|
|
202
|
+
if (args.model !== undefined) {
|
|
203
|
+
await this._runAgn(['env', type, '--set', `LLM_MODEL=${String(args.model || '')}`]);
|
|
204
|
+
}
|
|
205
|
+
const newDir = (args.workingDir || '').trim();
|
|
206
|
+
if (newDir && newDir !== (args.currentWorkingDir || '')) {
|
|
207
|
+
try { fs.mkdirSync(newDir, { recursive: true }); } catch {}
|
|
208
|
+
await this._runAgn(['remove', name]);
|
|
209
|
+
const rc = await this._runAgn(['create', name, '--type', type, '--install', '--path', newDir]);
|
|
210
|
+
if (rc.code !== 0) throw new Error(rc.stderr || rc.stdout || 'recreate failed');
|
|
211
|
+
const rc2 = await this._runAgn(['connect', name, n.token, '--endpoint', n.endpoint]);
|
|
212
|
+
if (rc2.code !== 0) throw new Error(rc2.stderr || rc2.stdout || 'connect failed');
|
|
213
|
+
} else {
|
|
214
|
+
await this._runAgn(['stop', name]);
|
|
215
|
+
await this._runAgn(['start', name]);
|
|
216
|
+
}
|
|
217
|
+
ok = true;
|
|
218
|
+
message = `Agent '${name}' reconfigured`;
|
|
153
219
|
} else if (action === 'detect_runtimes') {
|
|
154
220
|
await this._refreshRuntimes();
|
|
155
221
|
// Push the fresh detection right away rather than waiting for the next
|
|
@@ -157,6 +223,10 @@ class Daemon {
|
|
|
157
223
|
this._nodeHeartbeat();
|
|
158
224
|
ok = true;
|
|
159
225
|
message = `Detected ${this._runtimes.length} runtime(s)`;
|
|
226
|
+
} else if (action === 'list_dir') {
|
|
227
|
+
data = this._listDir(args.path);
|
|
228
|
+
ok = true;
|
|
229
|
+
message = `Listed ${data.dirs.length} folder(s)`;
|
|
160
230
|
} else {
|
|
161
231
|
message = `Unknown action '${action}'`;
|
|
162
232
|
}
|
|
@@ -165,7 +235,7 @@ class Daemon {
|
|
|
165
235
|
message = e.message || String(e);
|
|
166
236
|
}
|
|
167
237
|
try {
|
|
168
|
-
await this._nodeClient.nodeCommandResult(cmd.commandId, n.token, { ok, message });
|
|
238
|
+
await this._nodeClient.nodeCommandResult(cmd.commandId, n.token, { ok, message, data });
|
|
169
239
|
} catch {
|
|
170
240
|
// best-effort; the command stays 'running' if we can't report back
|
|
171
241
|
}
|
package/src/installer.js
CHANGED
|
@@ -665,7 +665,12 @@ class Installer {
|
|
|
665
665
|
const directReady = directEnv || directSaved;
|
|
666
666
|
const envAnyReady = this._hasAnyValue(process.env, checkReady.env_vars);
|
|
667
667
|
const savedAnyReady = !!(checkReady.saved_env_key && savedEnv[checkReady.saved_env_key]);
|
|
668
|
-
|
|
668
|
+
// A registry entry may require a non-empty JSON object rather than mere
|
|
669
|
+
// parseability. In that mode the stricter _evaluateCredsFile path below is
|
|
670
|
+
// authoritative; the legacy parser treats an empty {} as ready.
|
|
671
|
+
const credsReady = checkReady.creds_json_has_entries
|
|
672
|
+
? false
|
|
673
|
+
: this._checkCredsReady(checkReady);
|
|
669
674
|
// Content-free credential probes (opt-in via check_ready). Detect agents
|
|
670
675
|
// whose sign-in is a local OAuth/credential FILE (e.g. Gemini's
|
|
671
676
|
// ~/.gemini/oauth_creds.json) or a service-account file path, WITHOUT
|
|
@@ -792,9 +797,12 @@ class Installer {
|
|
|
792
797
|
}
|
|
793
798
|
|
|
794
799
|
/**
|
|
795
|
-
* Existence/readability of a credential file
|
|
796
|
-
*
|
|
797
|
-
*
|
|
800
|
+
* Existence/readability of a credential file. Returns 'present' |
|
|
801
|
+
* 'unreadable' | 'absent'. A non-empty readable file (or a non-empty
|
|
802
|
+
* directory) is normally 'present'. Entries with creds_json_has_entries
|
|
803
|
+
* additionally require at least one top-level JSON entry, allowing an empty
|
|
804
|
+
* auto-created {} file to remain signed out. Credential values are never
|
|
805
|
+
* logged or returned.
|
|
798
806
|
* a path that exists but cannot be statted/read is 'unreadable'. Resolves "~"
|
|
799
807
|
* against the running user's home (the daemon/launcher user), not the
|
|
800
808
|
* renderer/browser user.
|
|
@@ -809,6 +817,14 @@ class Installer {
|
|
|
809
817
|
const st = fs.statSync(p);
|
|
810
818
|
if (st.isDirectory()) return fs.readdirSync(p).length > 0 ? 'present' : 'absent';
|
|
811
819
|
fs.accessSync(p, fs.constants.R_OK);
|
|
820
|
+
if (checkReady.creds_json_has_entries) {
|
|
821
|
+
const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
822
|
+
if (Array.isArray(parsed)) return parsed.length > 0 ? 'present' : 'absent';
|
|
823
|
+
if (parsed && typeof parsed === 'object') {
|
|
824
|
+
return Object.keys(parsed).length > 0 ? 'present' : 'absent';
|
|
825
|
+
}
|
|
826
|
+
return 'absent';
|
|
827
|
+
}
|
|
812
828
|
return st.size > 0 ? 'present' : 'absent';
|
|
813
829
|
} catch {
|
|
814
830
|
return 'unreadable';
|
package/src/workspace-client.js
CHANGED
|
@@ -143,13 +143,13 @@ class WorkspaceClient {
|
|
|
143
143
|
* Report the outcome of a remote node command via
|
|
144
144
|
* POST /v1/nodes/commands/{id}/result (authenticated by the workspace token).
|
|
145
145
|
*/
|
|
146
|
-
async nodeCommandResult(commandId, token, { ok, message } = {}) {
|
|
147
|
-
const
|
|
146
|
+
async nodeCommandResult(commandId, token, { ok, message, data } = {}) {
|
|
147
|
+
const resp = await this._post(
|
|
148
148
|
`/v1/nodes/commands/${commandId}/result`,
|
|
149
|
-
{ ok: !!ok, message: message || null },
|
|
149
|
+
{ ok: !!ok, message: message || null, data: data ?? null },
|
|
150
150
|
this._wsHeaders(token),
|
|
151
151
|
);
|
|
152
|
-
return
|
|
152
|
+
return resp.data || resp;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|