loopengine 0.1.48 → 0.1.49

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.
@@ -17,6 +17,15 @@ export interface AbilityEnvDecl {
17
17
  * of a free-text field. Omit for anything that isn't genuinely a fixed
18
18
  * enum (an API key, a free-form model name, a path, ...). */
19
19
  options?: string[];
20
+ /** True for a value that's naturally multi-line (a pasted JSON key
21
+ * file, a PEM block, ...) — the Admin UI renders a <textarea> instead
22
+ * of a single-line <input>, which per the HTML spec's own value
23
+ * sanitization strips line breaks entirely and would otherwise mangle
24
+ * exactly this kind of value on paste, regardless of how correctly the
25
+ * .env file itself round-trips real embedded newlines (see
26
+ * env-admin.ts's own serializeEnvValue doc comment). Omit for anything
27
+ * genuinely single-line. */
28
+ multiline?: boolean;
20
29
  }
21
30
  /** `name`/`version` deliberately aren't declared here — they're read off
22
31
  * the ability's own sibling `package.json` instead (see readManifest),
@@ -1187,11 +1187,21 @@ export const agentsConfigPageHtml = `<!doctype html>
1187
1187
  // field, so there's nothing to mistype — the same "value" form field
1188
1188
  // name either element uses means wireEnvHandlers's own submit
1189
1189
  // handler doesn't need to know which one it's looking at.
1190
+ // A naturally multi-line value (AbilityEnvDecl.multiline, e.g. a
1191
+ // pasted JSON key file) gets a <textarea> — a single-line <input>
1192
+ // (even type="text") strips embedded line breaks from its value
1193
+ // entirely per the HTML spec's own value sanitization, mangling
1194
+ // exactly this kind of paste. No password-style masking for it
1195
+ // either — <textarea> has no such mode, and masking a large blob
1196
+ // secret buys little anyway compared to being able to paste it
1197
+ // correctly at all.
1190
1198
  var fieldHtml = v.options
1191
1199
  ? '<select name="value">' + v.options.map(function (opt) {
1192
1200
  return '<option value="' + escapeHtml(opt) + '"' + (v.value === opt ? ' selected' : '') + '>' + escapeHtml(opt) + '</option>';
1193
1201
  }).join('') + '</select>'
1194
- : '<input type="' + (v.secret ? 'password' : 'text') + '" name="value" placeholder="' + (v.set ? 'unchanged unless you type a new value' : 'value') + '" required>';
1202
+ : v.multiline
1203
+ ? '<textarea name="value" rows="8" placeholder="' + (v.set ? 'unchanged unless you paste a new value' : 'paste the full value') + '" required></textarea>'
1204
+ : '<input type="' + (v.secret ? 'password' : 'text') + '" name="value" placeholder="' + (v.set ? 'unchanged unless you type a new value' : 'value') + '" required>';
1195
1205
  return '<tr>' +
1196
1206
  '<td><code>' + escapeHtml(v.name) + '</code>' + sharedNote + '</td>' +
1197
1207
  '<td style="max-width:320px">' + escapeHtml(v.description || '') + '</td>' +
@@ -18,6 +18,11 @@ export interface DeclaredEnvVar {
18
18
  * only when the declaring ability actually named one; the Admin UI
19
19
  * renders a dropdown instead of a free-text field when this is set. */
20
20
  options?: string[];
21
+ /** True for a naturally multi-line value (see AbilityEnvDecl.multiline)
22
+ * — the Admin UI renders a <textarea> instead of a single-line
23
+ * <input>, which strips embedded line breaks entirely per the HTML
24
+ * spec's own value sanitization. */
25
+ multiline?: boolean;
21
26
  }
22
27
  /** Every env var any ability installed for `agentName` declared it
23
28
  * needs, merged by name. There's one .env per *project*, not per agent
@@ -66,6 +66,7 @@ export function listDeclaredEnvVars(agentName) {
66
66
  set: rawValue !== undefined,
67
67
  value: decl.secret === true ? undefined : rawValue,
68
68
  options: decl.options,
69
+ multiline: decl.multiline,
69
70
  });
70
71
  }
71
72
  }
@@ -81,13 +82,26 @@ function envFilePath() {
81
82
  return join(process.cwd(), '.env');
82
83
  }
83
84
  // Node's own --env-file parser (what bin/cli.ts's runTsx already passes
84
- // as --env-file-if-exists=.env) double-quotes a value containing
85
- // whitespace, '#', or a quote character — matched here so a value this
86
- // function writes reads back identically, not reinterpreted as a
87
- // comment or truncated at the first space.
85
+ // as --env-file-if-exists=.env) needs a value containing whitespace, '#',
86
+ // or a quote character quoted, so it reads back as one value instead of
87
+ // being reinterpreted as a comment or truncated at the first space.
88
+ // Single-quoting wins over double — confirmed live against Node's actual
89
+ // parser that a single-quoted value reads back fully literally, with no
90
+ // backslash-escape processing inside it at all: a real embedded newline,
91
+ // a literal double-quote, or a literal backslash (a JSON secret like
92
+ // GOOGLE_APPLICATION_CREDENTIALS_JSON has all three at once) all survive
93
+ // untouched. Double-quoting a value with an *escaped* embedded quote does
94
+ // NOT round-trip — confirmed live that Node's parser has no support for
95
+ // \" as an escaped literal quote inside a double-quoted value; it just
96
+ // ends the value there instead (`X="a\"b"` reads back as only `a\`,
97
+ // silently dropping `b"` entirely). The double-quote fallback below is
98
+ // only reached when the value itself contains a literal single quote —
99
+ // the one character single-quoting can't represent at all.
88
100
  function serializeEnvValue(value) {
89
101
  if (!/[\s#"'\\]/.test(value) && value !== '')
90
102
  return value;
103
+ if (!value.includes("'"))
104
+ return `'${value}'`;
91
105
  return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
92
106
  }
93
107
  /** Upserts `NAME=VALUE` into the project's `.env` file — preserving
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.48",
3
+ "version": "0.1.49",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",