@nikelyh/offscript 1.5.0 → 1.6.1
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/README.md +4 -4
- package/dist/packages/browser/browser-manager.js +4 -0
- package/dist/packages/browser/session.js +3 -1
- package/dist/packages/contracts/simulation-execution.js +9 -2
- package/dist/packages/orchestration/browser-exploration-factory.js +1 -1
- package/dist/packages/orchestration/investigation-reaction.js +7 -1
- package/dist/packages/runtime/execution-coordinator.js +2 -1
- package/dist/packages/runtime/local-host.js +2 -2
- package/dist/packages/server/configuration-routes.js +2 -2
- package/dist/scripts/server/run.js +9 -1
- package/dist/studio/assets/index-7aoMMH1S.js +9 -0
- package/dist/studio/assets/index-CmzdwSZ_.css +1 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/participants/badger.png +0 -0
- package/dist/studio/participants/compass.png +0 -0
- package/dist/studio/participants/fox.png +0 -0
- package/dist/studio/participants/owl.png +0 -0
- package/dist/studio/participants/raven.png +0 -0
- package/dist/studio/participants/sparrow.png +0 -0
- package/package.json +1 -1
- package/dist/studio/assets/index-Bo99ldFK.css +0 -1
- package/dist/studio/assets/index-CDlYw4OG.js +0 -9
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
**Your users don't follow a script. Neither do ours.**
|
|
8
8
|
|
|
9
|
-
Current stable release: **v1.0
|
|
9
|
+
Current stable release: **v1.7.0** on [npm](https://www.npmjs.com/package/@nikelyh/offscript).
|
|
10
10
|
Online documentation: [offscript.nikelyh.tech](https://offscript.nikelyh.tech).
|
|
11
11
|
|
|
12
12
|
OFFSCRIPT brings multi-agent software exploration into a local developer workflow: configure a target and goal, choose artificial users, run a bounded session, and inspect observations, investigations and visual evidence in Studio or the terminal.
|
|
@@ -36,7 +36,7 @@ npm install -g @nikelyh/offscript
|
|
|
36
36
|
Or run directly with `npx`:
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
npx --yes @nikelyh/offscript
|
|
39
|
+
npx --yes @nikelyh/offscript studio
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
For browser exploration, install the required Chromium binary:
|
|
@@ -110,10 +110,10 @@ From the OFFSCRIPT checkout:
|
|
|
110
110
|
npm pack
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
This builds `nikelyh-offscript
|
|
113
|
+
This builds `nikelyh-offscript-*.tgz`. From your target project's directory:
|
|
114
114
|
|
|
115
115
|
```sh
|
|
116
|
-
npm exec --yes --package="/absolute/path/nikelyh-offscript
|
|
116
|
+
npm exec --yes --package="/absolute/path/nikelyh-offscript-*.tgz" -- offscript studio
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
The current directory becomes the project root. Use a real absolute archive path; Windows paths are supported. npm installs dependencies in its execution cache without adding OFFSCRIPT to the target's package manifest. The first run can need network access and native dependency setup.
|
|
@@ -22,6 +22,7 @@ export class BrowserManager {
|
|
|
22
22
|
try {
|
|
23
23
|
const browser = await chromium.launch({
|
|
24
24
|
headless: this.headless,
|
|
25
|
+
args: this.headless ? [] : ["--start-maximized"],
|
|
25
26
|
});
|
|
26
27
|
if (this.closed) {
|
|
27
28
|
await browser.close().catch(() => { });
|
|
@@ -50,6 +51,9 @@ export class BrowserManager {
|
|
|
50
51
|
if (options.storageState) {
|
|
51
52
|
contextOptions.storageState = options.storageState;
|
|
52
53
|
}
|
|
54
|
+
if (!this.headless) {
|
|
55
|
+
contextOptions.viewport = null;
|
|
56
|
+
}
|
|
53
57
|
const context = await browser.newContext(contextOptions);
|
|
54
58
|
const session = await BrowserUserSession.create(context, options);
|
|
55
59
|
this.sessions.add(session);
|
|
@@ -60,8 +60,10 @@ export class BrowserUserSession {
|
|
|
60
60
|
try {
|
|
61
61
|
await this.page.goto(resolvedUrl, {
|
|
62
62
|
timeout: this.navigationTimeoutMs,
|
|
63
|
-
waitUntil: "
|
|
63
|
+
waitUntil: "load",
|
|
64
64
|
});
|
|
65
|
+
// Allow client-side frameworks (e.g. Next.js, React) to settle initial hydration
|
|
66
|
+
await this.page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 50))).catch(() => { });
|
|
65
67
|
return { success: true, action: "navigate", url: this.page.url() };
|
|
66
68
|
}
|
|
67
69
|
catch (cause) {
|
|
@@ -13,9 +13,16 @@ export const simulationExecutionPhases = [
|
|
|
13
13
|
"interrupted",
|
|
14
14
|
];
|
|
15
15
|
export function parseStartSimulationRequest(value) {
|
|
16
|
-
const input = record(value, "startRequest", ["mode"]);
|
|
16
|
+
const input = record(value, "startRequest", ["mode", "headless"]);
|
|
17
17
|
const mode = oneOf(input.mode, simulationExecutionModes, "startRequest.mode");
|
|
18
|
-
|
|
18
|
+
let headless;
|
|
19
|
+
if (input.headless !== undefined) {
|
|
20
|
+
if (typeof input.headless !== "boolean") {
|
|
21
|
+
throw new ContractValidationError("startRequest.headless", "Expected a boolean.");
|
|
22
|
+
}
|
|
23
|
+
headless = input.headless;
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze({ mode, ...(headless !== undefined ? { headless } : {}) });
|
|
19
26
|
}
|
|
20
27
|
export function parseSimulationExecutionStatus(value) {
|
|
21
28
|
const input = record(value, "executionStatus", [
|
|
@@ -43,7 +43,7 @@ export function createBrowserExplorationSessionFactory(options = {}) {
|
|
|
43
43
|
}
|
|
44
44
|
const targetUrl = resolvedTargetUrl;
|
|
45
45
|
const browserManager = createBrowserManager({
|
|
46
|
-
headless: options.headless ?? true,
|
|
46
|
+
headless: context?.headless ?? options.headless ?? true,
|
|
47
47
|
});
|
|
48
48
|
const sessions = new Map();
|
|
49
49
|
function getSession(id) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { executeAssignInvestigation } from "../application/index.js";
|
|
2
|
+
import { ApplicationError, executeAssignInvestigation } from "../application/index.js";
|
|
3
3
|
import { decodeEvent, offscriptEventType } from "./event-bridge.js";
|
|
4
4
|
import { situation } from "./situations.js";
|
|
5
5
|
export function investigationReaction({ state, id, maxTurns, store, startTurn }) {
|
|
@@ -41,6 +41,12 @@ export function investigationReaction({ state, id, maxTurns, store, startTurn })
|
|
|
41
41
|
}
|
|
42
42
|
catch (cause) {
|
|
43
43
|
state.reactions.delete(key);
|
|
44
|
+
if (cause instanceof ApplicationError && cause.code === "INVESTIGATION_CONFLICT") {
|
|
45
|
+
const reloaded = await store.findInvestigation(decoded.payload.investigation.id).catch(() => undefined);
|
|
46
|
+
if (reloaded && reloaded.investigation.lifecycle.status !== "requested") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
44
50
|
state.fail("investigation-assignment", id, cause);
|
|
45
51
|
}
|
|
46
52
|
finally {
|
|
@@ -89,7 +89,7 @@ export function executionCoordinator(dependencies) {
|
|
|
89
89
|
await finalizeExecution(record, outcome, reason);
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
async function start(id, mode) {
|
|
92
|
+
async function start(id, mode, options = {}) {
|
|
93
93
|
if (closed)
|
|
94
94
|
throw new ApplicationError("EXECUTION_START_FAILED", "Host is closing.");
|
|
95
95
|
if (!factory)
|
|
@@ -137,6 +137,7 @@ export function executionCoordinator(dependencies) {
|
|
|
137
137
|
mode,
|
|
138
138
|
targetUrl: config.targetUrl,
|
|
139
139
|
evidenceStore,
|
|
140
|
+
...(options.headless !== undefined ? { headless: options.headless } : {}),
|
|
140
141
|
}));
|
|
141
142
|
record.session = session;
|
|
142
143
|
record.phase = "running";
|
|
@@ -197,12 +197,12 @@ export function createLocalHost(options) {
|
|
|
197
197
|
deleting.delete(id);
|
|
198
198
|
}
|
|
199
199
|
},
|
|
200
|
-
async startSimulation(id, mode) {
|
|
200
|
+
async startSimulation(id, mode, options = {}) {
|
|
201
201
|
if (phase !== "ready" || closing)
|
|
202
202
|
throw new Error("Host is not accepting execution commands.");
|
|
203
203
|
if (!coordinator)
|
|
204
204
|
throw new Error("No execution session factory configured on this host.");
|
|
205
|
-
return coordinator.start(simulationId(id), mode);
|
|
205
|
+
return coordinator.start(simulationId(id), mode, options);
|
|
206
206
|
},
|
|
207
207
|
async stopSimulation(id) {
|
|
208
208
|
if (phase !== "ready" || closing)
|
|
@@ -44,8 +44,8 @@ export function configurationRoutes(server, services) {
|
|
|
44
44
|
return value;
|
|
45
45
|
});
|
|
46
46
|
server.post("/api/simulations/:id/start", { bodyLimit: 4096 }, async (request, reply) => {
|
|
47
|
-
const { mode } = parseStartSimulationRequest(request.body);
|
|
48
|
-
const status = await services.startSimulation(simulationId(request.params.id), mode);
|
|
47
|
+
const { mode, headless } = parseStartSimulationRequest(request.body);
|
|
48
|
+
const status = await services.startSimulation(simulationId(request.params.id), mode, { headless });
|
|
49
49
|
return reply.code(200).send(status);
|
|
50
50
|
});
|
|
51
51
|
server.get("/api/simulations/:id/execution", async (request) => {
|
|
@@ -7,10 +7,16 @@ import { startupError } from "./startup-error.js";
|
|
|
7
7
|
process.env.MOZAIK_API_KEY ??= "";
|
|
8
8
|
const originalWarn = console.warn;
|
|
9
9
|
console.warn = (...args) => {
|
|
10
|
-
if (typeof args[0] === "string" && args[0].includes("mozaik cloud
|
|
10
|
+
if (typeof args[0] === "string" && args[0].includes("mozaik cloud"))
|
|
11
11
|
return;
|
|
12
12
|
originalWarn.apply(console, args);
|
|
13
13
|
};
|
|
14
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
15
|
+
process.stderr.write = ((chunk, ...rest) => {
|
|
16
|
+
if (typeof chunk === "string" && chunk.includes("mozaik cloud"))
|
|
17
|
+
return true;
|
|
18
|
+
return originalStderrWrite(chunk, ...rest);
|
|
19
|
+
});
|
|
14
20
|
let requestStop = () => { };
|
|
15
21
|
const stopping = new Promise(resolve => { requestStop = resolve; });
|
|
16
22
|
let interrupted = false;
|
|
@@ -47,6 +53,8 @@ try {
|
|
|
47
53
|
});
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
console.log("AI Mode: Local Autonomous Runner (offline, no API key required).");
|
|
57
|
+
console.log("Browser exploration requires Chromium: npx playwright install chromium");
|
|
50
58
|
console.log("Press Ctrl+C to stop. No exploration starts automatically.");
|
|
51
59
|
await stopping;
|
|
52
60
|
}
|