sync: update from private repo (ce93095)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-10 03:52:37 +00:00
parent eb35e32f7a
commit 9f8958c4a2
27 changed files with 261 additions and 1582 deletions
+5 -5
View File
@@ -11,7 +11,7 @@ describe('user-folder/paths', () => {
it('creates the standard subdirs on first ensure', () => {
ensureUserFolder(root, 'user-abc');
for (const sub of ['scripts', 'browser-macros', 'templates', 'recordings', 'trash', 'memory', 'pets']) {
for (const sub of ['browser-macros', 'recordings', 'trash', 'memory', 'pets', 'notes']) {
expect(existsSync(join(root, 'user-abc', sub))).toBe(true);
}
});
@@ -35,17 +35,17 @@ describe('user-folder/paths', () => {
});
it('resolves subdir paths under the owner root', () => {
const p = resolveUserSubdir(root, 'user-abc', 'scripts', 'foo.js');
expect(p).toBe(join(root, 'user-abc', 'scripts', 'foo.js'));
const p = resolveUserSubdir(root, 'user-abc', 'browser-macros', 'foo.js');
expect(p).toBe(join(root, 'user-abc', 'browser-macros', 'foo.js'));
});
it('rejects path traversal in the relative segment', () => {
expect(() => resolveUserSubdir(root, 'user-abc', 'scripts', '../../etc/passwd'))
expect(() => resolveUserSubdir(root, 'user-abc', 'browser-macros', '../../etc/passwd'))
.toThrow(/outside owner folder/);
});
it('rejects empty relPath', () => {
expect(() => resolveUserSubdir(root, 'user-abc', 'scripts', ''))
expect(() => resolveUserSubdir(root, 'user-abc', 'browser-macros', ''))
.toThrow(/relPath must not be empty/);
});
+4 -1
View File
@@ -6,7 +6,10 @@ export const LOCAL_SYSTEM_OWNER_ID = 'local';
const USER_AGENTS_MAX_BYTES = 64 * 1024;
export const USER_SUBDIRS = ['scripts', 'browser-macros', 'templates', 'recordings', 'trash', 'memory', 'pets', 'notes'] as const;
// 'scripts' and 'templates' were retired in 2026-06 (superseded by Skills +
// the Bash tool). Existing files stay on disk but are no longer created,
// listed, or resolvable through the user-folder API / tools.
export const USER_SUBDIRS = ['browser-macros', 'recordings', 'trash', 'memory', 'pets', 'notes'] as const;
export type UserSubdir = typeof USER_SUBDIRS[number];
export function userRoot(rootDir: string, ownerId: string): string {
+24 -38
View File
@@ -1,17 +1,21 @@
/**
* script-orchestrator.ts
*
* Shared "resolve a user script by name + run it" helper. Used by both:
* Shared "resolve a browser-macro by name + run it" helper. Used by both:
* - The LLM-facing RunUserScript tool (engine/tools/user-folder.ts).
* - The scheduler's script kind (scheduler.ts), so a periodic script run
* - The scheduler's script kind (scheduler.ts), so a periodic macro run
* does not need to spin up an LLM agent loop.
*
* Note: plain-Node `scripts/` and `templates/` were retired in 2026-06
* (superseded by Skills + the Bash tool). The only user-script runtime left
* is Playwright browser-macros.
*
* Responsibilities:
* - Path resolution under data/users/{userId}/{scripts,browser-macros}/ with
* - Path resolution under data/users/{userId}/browser-macros/ with
* traversal protection (delegated to resolveUserSubdir).
* - Frontmatter parsing for browser-macros to find session_profile_id.
* - Frontmatter parsing to find session_profile_id.
* - Decrypting + loading Playwright storageState for the owning user.
* - Calling runUserScript() with the right runtime.
* - Calling runUserScript() with the playwright runtime.
*
* Intentionally NOT responsible for:
* - Config gating (tools.user_scripts_enabled) — callers decide.
@@ -27,9 +31,9 @@ import { runUserScript } from './script-runner.js';
import { loadSessionStateForUser } from './session-loader.js';
import type { BrowserSessionRepo } from '../db/browser-session-repo.js';
export type ScriptKind = 'script' | 'browser-macro';
export type ScriptSubdir = 'scripts' | 'browser-macros';
export type ScriptRuntime = 'plain' | 'playwright';
export type ScriptKind = 'browser-macro';
export type ScriptSubdir = 'browser-macros';
export type ScriptRuntime = 'playwright';
export interface ResolveScriptResult {
scriptPath: string;
@@ -37,49 +41,31 @@ export interface ResolveScriptResult {
runtime: ScriptRuntime;
}
/**
* Resolve a script name to its on-disk path. When kind is omitted, scripts/
* is searched first, then browser-macros/.
*/
/** Resolve a macro name to its on-disk path under browser-macros/. */
export function resolveScriptForKind(
rootDir: string,
userId: string,
scriptName: string,
kind: ScriptKind | undefined,
_kind?: ScriptKind,
): ResolveScriptResult | { error: string } {
const tryOne = (sd: ScriptSubdir): string | null => {
try {
const p = resolveUserSubdir(rootDir, userId, sd, scriptName);
return existsSync(p) ? p : null;
} catch {
return null;
}
};
if (kind === 'script') {
const p = tryOne('scripts');
if (!p) return { error: `script not found: scripts/${basename(scriptName)}` };
return { scriptPath: p, subdir: 'scripts', runtime: 'plain' };
let p: string | null;
try {
const full = resolveUserSubdir(rootDir, userId, 'browser-macros', scriptName);
p = existsSync(full) ? full : null;
} catch {
p = null;
}
if (kind === 'browser-macro') {
const p = tryOne('browser-macros');
if (!p) return { error: `browser-macro not found: browser-macros/${basename(scriptName)}` };
return { scriptPath: p, subdir: 'browser-macros', runtime: 'playwright' };
}
const sp = tryOne('scripts');
if (sp) return { scriptPath: sp, subdir: 'scripts', runtime: 'plain' };
const bp = tryOne('browser-macros');
if (bp) return { scriptPath: bp, subdir: 'browser-macros', runtime: 'playwright' };
return { error: `script not found: ${scriptName} (searched scripts/ and browser-macros/)` };
if (!p) return { error: `browser-macro not found: browser-macros/${basename(scriptName)}` };
return { scriptPath: p, subdir: 'browser-macros', runtime: 'playwright' };
}
export interface ResolveAndRunOptions {
rootDir: string;
userId: string;
/** Script name with or without `.js` extension. */
/** Macro name with or without `.js` extension. */
name: string;
params: Record<string, unknown>;
/** If omitted, scripts/ is tried first, then browser-macros/. */
/** Kept for call-site compatibility; the only kind is 'browser-macro'. */
kind?: ScriptKind;
/** Required only when the resolved script is a browser-macro that declares session_profile_id. */
sessRepo?: BrowserSessionRepo;
-32
View File
@@ -1,32 +0,0 @@
/**
* template-renderer.ts
*
* Simple {{var}} substitution for user templates. Intentionally minimal:
* - No conditionals, no loops, no helpers. If those become needed,
* graduate to Handlebars in a follow-up.
* - Unknown placeholders (var not declared in frontmatter.params) are
* left literal — so README-style templates with prose like "use {{x}}"
* don't blow up when there's no x param.
*
* Param semantics (type-check + defaults) are shared with scripts via
* validateAndApplyDefaults from script-runner.ts; templates use the same
* frontmatter.params schema as scripts/browser-macros.
*/
import type { ParamSpec } from './frontmatter.js';
import { validateAndApplyDefaults } from './script-runner.js';
/**
* Replaces {{name}} with the corresponding param value, but only for params
* that appear in `declared` (the validated set). Unknown {{xxx}} stays literal.
*/
export function renderTemplate(
body: string,
paramSpec: ParamSpec[],
rawParams: Record<string, unknown>,
): string {
const resolved = validateAndApplyDefaults(paramSpec, rawParams);
return body.replace(/\{\{(\w+)\}\}/g, (match, name) =>
Object.prototype.hasOwnProperty.call(resolved, name) ? String(resolved[name]) : match,
);
}