@openagents-org/agent-launcher 0.2.161 → 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.
@@ -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
@@ -100,6 +100,36 @@ class Daemon {
100
100
  return roster;
101
101
  }
102
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
+
103
133
  /**
104
134
  * Detect, for every supported agent type, whether its runtime is installed and
105
135
  * logged-in/ready on this device. Runs `agn runtimes --json` in a CHILD process
@@ -129,6 +159,7 @@ class Daemon {
129
159
  const name = (args.name || '').trim();
130
160
  let ok = false;
131
161
  let message = '';
162
+ let data = null;
132
163
  try {
133
164
  if (action === 'create_agent') {
134
165
  const type = (args.type || '').trim();
@@ -192,6 +223,10 @@ class Daemon {
192
223
  this._nodeHeartbeat();
193
224
  ok = true;
194
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)`;
195
230
  } else {
196
231
  message = `Unknown action '${action}'`;
197
232
  }
@@ -200,7 +235,7 @@ class Daemon {
200
235
  message = e.message || String(e);
201
236
  }
202
237
  try {
203
- await this._nodeClient.nodeCommandResult(cmd.commandId, n.token, { ok, message });
238
+ await this._nodeClient.nodeCommandResult(cmd.commandId, n.token, { ok, message, data });
204
239
  } catch {
205
240
  // best-effort; the command stays 'running' if we can't report back
206
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
- const credsReady = this._checkCredsReady(checkReady);
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 WITHOUT reading its contents —
796
- * never loads a token/key into memory. Returns 'present' | 'unreadable' |
797
- * 'absent'. A non-empty readable file (or a non-empty directory) is 'present';
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';
@@ -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 data = await this._post(
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 data.data || data;
152
+ return resp.data || resp;
153
153
  }
154
154
 
155
155
  /**