@ishlabs/cli 0.17.7 → 0.19.0
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 +54 -54
- package/dist/commands/ask.d.ts +4 -4
- package/dist/commands/ask.js +66 -66
- package/dist/commands/chat.js +10 -10
- package/dist/commands/config.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/iteration.js +57 -57
- package/dist/commands/mcp.d.ts +23 -0
- package/dist/commands/mcp.js +676 -0
- package/dist/commands/person.d.ts +5 -0
- package/dist/commands/{profile.js → person.js} +197 -162
- package/dist/commands/source.d.ts +6 -2
- package/dist/commands/source.js +35 -30
- package/dist/commands/study-analyze.d.ts +1 -1
- package/dist/commands/study-analyze.js +3 -3
- package/dist/commands/study-participant.d.ts +8 -0
- package/dist/commands/{study-tester.js → study-participant.js} +50 -50
- package/dist/commands/study-run.d.ts +6 -6
- package/dist/commands/study-run.js +341 -290
- package/dist/commands/study.js +106 -72
- package/dist/commands/workspace.js +13 -13
- package/dist/connect.js +5 -5
- package/dist/index.js +6 -4
- package/dist/lib/accessibility-profile.d.ts +1 -1
- package/dist/lib/accessibility-profile.js +1 -1
- package/dist/lib/alias-hydrate.js +4 -4
- package/dist/lib/alias-store.d.ts +5 -5
- package/dist/lib/alias-store.js +8 -8
- package/dist/lib/api-client.d.ts +1 -1
- package/dist/lib/api-client.js +1 -1
- package/dist/lib/billing.d.ts +11 -11
- package/dist/lib/billing.js +16 -16
- package/dist/lib/chat-endpoint-templates.js +1 -1
- package/dist/lib/command-helpers.d.ts +18 -18
- package/dist/lib/command-helpers.js +49 -37
- package/dist/lib/docs.js +570 -387
- package/dist/lib/enums.d.ts +2 -2
- package/dist/lib/enums.js +2 -2
- package/dist/lib/local-sim/browser.d.ts +1 -1
- package/dist/lib/local-sim/browser.js +1 -1
- package/dist/lib/local-sim/debug-report.d.ts +2 -2
- package/dist/lib/local-sim/debug-report.js +3 -3
- package/dist/lib/local-sim/loop.d.ts +5 -5
- package/dist/lib/local-sim/loop.js +38 -38
- package/dist/lib/local-sim/types.d.ts +12 -12
- package/dist/lib/mcp-clients.d.ts +51 -0
- package/dist/lib/mcp-clients.js +175 -0
- package/dist/lib/modality.d.ts +10 -10
- package/dist/lib/modality.js +46 -46
- package/dist/lib/output.d.ts +16 -15
- package/dist/lib/output.js +291 -226
- package/dist/lib/profile-sources.d.ts +64 -16
- package/dist/lib/profile-sources.js +91 -30
- package/dist/lib/skill-content.js +216 -168
- package/dist/lib/study-events.d.ts +3 -3
- package/dist/lib/study-events.js +1 -1
- package/dist/lib/study-inputs.d.ts +11 -1
- package/dist/lib/study-inputs.js +68 -17
- package/dist/lib/study-participants.d.ts +32 -0
- package/dist/lib/study-participants.js +12 -0
- package/dist/lib/types.d.ts +104 -34
- package/package.json +1 -1
- package/dist/commands/profile.d.ts +0 -5
- package/dist/commands/study-tester.d.ts +0 -8
package/dist/lib/enums.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Agents (and humans) reach for hyphen-style values on the command line —
|
|
5
5
|
* `--screen-format mobile-portrait`, `--kind text-file`, `--chat-mode
|
|
6
|
-
*
|
|
6
|
+
* participant-pair` — even when the canonical backend value is underscored (or
|
|
7
7
|
* vice versa for the ask-question `type` field, which is hyphenated). Rather
|
|
8
8
|
* than fail with a 422, each parse site funnels the raw value through
|
|
9
9
|
* `normalizeEnumValue` and gets back the canonical form (or `null` for a
|
|
@@ -32,7 +32,7 @@ export type ScreenFormat = typeof SCREEN_FORMATS[number];
|
|
|
32
32
|
export declare const QUESTION_TYPES: readonly ["text", "slider", "likert", "single-choice", "multiple-choice", "number"];
|
|
33
33
|
export type QuestionType = typeof QUESTION_TYPES[number];
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Person structured enums (profile-enums.v1.json). Values are
|
|
36
36
|
* snake_case and match the spec byte-for-byte; agents pass them verbatim
|
|
37
37
|
* via CLI flags.
|
|
38
38
|
*/
|
package/dist/lib/enums.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Agents (and humans) reach for hyphen-style values on the command line —
|
|
5
5
|
* `--screen-format mobile-portrait`, `--kind text-file`, `--chat-mode
|
|
6
|
-
*
|
|
6
|
+
* participant-pair` — even when the canonical backend value is underscored (or
|
|
7
7
|
* vice versa for the ask-question `type` field, which is hyphenated). Rather
|
|
8
8
|
* than fail with a 422, each parse site funnels the raw value through
|
|
9
9
|
* `normalizeEnumValue` and gets back the canonical form (or `null` for a
|
|
@@ -46,7 +46,7 @@ export const QUESTION_TYPES = [
|
|
|
46
46
|
"number",
|
|
47
47
|
];
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* Person structured enums (profile-enums.v1.json). Values are
|
|
50
50
|
* snake_case and match the spec byte-for-byte; agents pass them verbatim
|
|
51
51
|
* via CLI flags.
|
|
52
52
|
*/
|
|
@@ -23,7 +23,7 @@ export declare function launchSharedBrowser(opts: LocalSimBrowserOptions): Promi
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function createTab(browser: Browser, opts: LocalSimBrowserOptions): Promise<BrowserSession>;
|
|
25
25
|
/**
|
|
26
|
-
* Launch a standalone browser session (single
|
|
26
|
+
* Launch a standalone browser session (single participant, owns the browser).
|
|
27
27
|
*/
|
|
28
28
|
export declare function launchBrowser(opts: LocalSimBrowserOptions): Promise<BrowserSession>;
|
|
29
29
|
export interface ObservationData {
|
|
@@ -76,7 +76,7 @@ export async function createTab(browser, opts) {
|
|
|
76
76
|
return { browser, context, page };
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Launch a standalone browser session (single
|
|
79
|
+
* Launch a standalone browser session (single participant, owns the browser).
|
|
80
80
|
*/
|
|
81
81
|
export async function launchBrowser(opts) {
|
|
82
82
|
const browser = await launchSharedBrowser(opts);
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
import type { DebugStep } from "./loop.js";
|
|
8
8
|
export type { DebugStep };
|
|
9
9
|
export interface DebugReportMeta {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
participantId: string;
|
|
11
|
+
participantName: string;
|
|
12
12
|
url: string;
|
|
13
13
|
screenFormat: string;
|
|
14
14
|
finalStatus: string;
|
|
@@ -28,7 +28,7 @@ export function generateDebugReport(steps, meta) {
|
|
|
28
28
|
const dir = join(homedir(), ".ish", "debug");
|
|
29
29
|
mkdirSync(dir, { recursive: true });
|
|
30
30
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
31
|
-
const shortId = meta.
|
|
31
|
+
const shortId = meta.participantId.slice(0, 12);
|
|
32
32
|
const fileName = `sim-${shortId}-${ts}.html`;
|
|
33
33
|
const filePath = join(dir, fileName);
|
|
34
34
|
const totalSteps = steps.length;
|
|
@@ -119,7 +119,7 @@ export function generateDebugReport(steps, meta) {
|
|
|
119
119
|
<html lang="en">
|
|
120
120
|
<head>
|
|
121
121
|
<meta charset="utf-8"/>
|
|
122
|
-
<title>Local Sim Debug — ${escapeHtml(meta.
|
|
122
|
+
<title>Local Sim Debug — ${escapeHtml(meta.participantName)}</title>
|
|
123
123
|
<style>
|
|
124
124
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
125
125
|
body { background: #1a1a2e; color: #e0e0e0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace; padding: 20px; }
|
|
@@ -169,7 +169,7 @@ export function generateDebugReport(steps, meta) {
|
|
|
169
169
|
<div class="header">
|
|
170
170
|
<h1>Local Sim Debug Report</h1>
|
|
171
171
|
<div class="header-meta">
|
|
172
|
-
<span><strong>
|
|
172
|
+
<span><strong>Participant:</strong> ${escapeHtml(meta.participantName)} (${escapeHtml(meta.participantId.slice(0, 12))})</span>
|
|
173
173
|
<span><strong>URL:</strong> ${escapeHtml(meta.url)}</span>
|
|
174
174
|
<span><strong>Format:</strong> ${escapeHtml(meta.screenFormat)}</span>
|
|
175
175
|
<span><strong>Status:</strong> <span class="badge" style="background:${meta.finalStatus === "completed" ? "#4caf50" : meta.finalStatus === "failed" ? "#f44336" : "#ff9800"}">${escapeHtml(meta.finalStatus)}</span></span>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Local simulation loop orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Runs the observe → reason (remote) → act (local) loop for each
|
|
5
|
-
*
|
|
5
|
+
* participant against a local Playwright browser.
|
|
6
6
|
*/
|
|
7
7
|
import type { ApiClient } from "../api-client.js";
|
|
8
8
|
export interface DebugStep {
|
|
@@ -39,8 +39,8 @@ export interface LocalSimRunOptions {
|
|
|
39
39
|
workspaceId: string;
|
|
40
40
|
studyId: string;
|
|
41
41
|
iterationId: string;
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
participantIds: string[];
|
|
43
|
+
participantNames: Map<string, string>;
|
|
44
44
|
url?: string;
|
|
45
45
|
screenFormat?: "desktop" | "mobile_portrait";
|
|
46
46
|
locale?: string;
|
|
@@ -54,7 +54,7 @@ export interface LocalSimRunOptions {
|
|
|
54
54
|
parallel?: number;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* Run local simulations — parallel when multiple
|
|
58
|
-
* Use --parallel <n> to control concurrency (default: number of
|
|
57
|
+
* Run local simulations — parallel when multiple participants, sequential by default.
|
|
58
|
+
* Use --parallel <n> to control concurrency (default: number of participants).
|
|
59
59
|
*/
|
|
60
60
|
export declare function runLocalSimulations(client: ApiClient, opts: LocalSimRunOptions): Promise<void>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Local simulation loop orchestrator.
|
|
3
3
|
*
|
|
4
4
|
* Runs the observe → reason (remote) → act (local) loop for each
|
|
5
|
-
*
|
|
5
|
+
* participant against a local Playwright browser.
|
|
6
6
|
*/
|
|
7
7
|
import { launchBrowser, launchSharedBrowser, createTab, captureObservation, takeScreenshot, takeScreenshotJpeg, navigateWithRetry, closeBrowser } from "./browser.js";
|
|
8
8
|
import { uploadScreenshot } from "./upload.js";
|
|
@@ -71,8 +71,8 @@ const SENTIMENT_ICONS = {
|
|
|
71
71
|
Frustrated: "!", Confused: "?", Delighted: "*",
|
|
72
72
|
};
|
|
73
73
|
/**
|
|
74
|
-
* Run local simulations — parallel when multiple
|
|
75
|
-
* Use --parallel <n> to control concurrency (default: number of
|
|
74
|
+
* Run local simulations — parallel when multiple participants, sequential by default.
|
|
75
|
+
* Use --parallel <n> to control concurrency (default: number of participants).
|
|
76
76
|
*/
|
|
77
77
|
export async function runLocalSimulations(client, opts) {
|
|
78
78
|
const log = (msg) => { if (!opts.quiet || opts.debug)
|
|
@@ -89,29 +89,29 @@ export async function runLocalSimulations(client, opts) {
|
|
|
89
89
|
log("\nCancelling after current step...");
|
|
90
90
|
};
|
|
91
91
|
process.on("SIGINT", onSigint);
|
|
92
|
-
const concurrency = opts.parallel ?? opts.
|
|
92
|
+
const concurrency = opts.parallel ?? opts.participantIds.length;
|
|
93
93
|
try {
|
|
94
|
-
if (concurrency <= 1 || opts.
|
|
95
|
-
// Sequential execution — each
|
|
96
|
-
for (const
|
|
94
|
+
if (concurrency <= 1 || opts.participantIds.length <= 1) {
|
|
95
|
+
// Sequential execution — each participant owns its own browser
|
|
96
|
+
for (const participantId of opts.participantIds) {
|
|
97
97
|
if (cancelled)
|
|
98
98
|
break;
|
|
99
|
-
const
|
|
100
|
-
log(`\nStarting local simulation for ${
|
|
99
|
+
const participantName = opts.participantNames.get(participantId) ?? participantId;
|
|
100
|
+
log(`\nStarting local simulation for ${participantName}...`);
|
|
101
101
|
try {
|
|
102
|
-
const
|
|
103
|
-
await runSingleSimulation(client,
|
|
104
|
-
log(`Completed: ${
|
|
102
|
+
const participantLog = (msg) => log(`[${participantName}] ${msg}`);
|
|
103
|
+
await runSingleSimulation(client, participantId, participantName, opts, participantLog, () => cancelled);
|
|
104
|
+
log(`Completed: ${participantName}`);
|
|
105
105
|
}
|
|
106
106
|
catch (err) {
|
|
107
107
|
const msg = err instanceof Error ? err.message : String(err);
|
|
108
|
-
log(`Failed: ${
|
|
108
|
+
log(`Failed: ${participantName} — ${msg}`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
// Parallel execution — shared browser, one tab per
|
|
114
|
-
log(`\nRunning ${opts.
|
|
113
|
+
// Parallel execution — shared browser, one tab per participant
|
|
114
|
+
log(`\nRunning ${opts.participantIds.length} simulations in parallel (concurrency: ${concurrency})...`);
|
|
115
115
|
const sharedBrowserOpts = {
|
|
116
116
|
headed: opts.headed,
|
|
117
117
|
slowMo: opts.slowMo,
|
|
@@ -123,23 +123,23 @@ export async function runLocalSimulations(client, opts) {
|
|
|
123
123
|
const sharedBrowser = await launchSharedBrowser(sharedBrowserOpts);
|
|
124
124
|
try {
|
|
125
125
|
const batches = [];
|
|
126
|
-
for (let i = 0; i < opts.
|
|
127
|
-
batches.push(opts.
|
|
126
|
+
for (let i = 0; i < opts.participantIds.length; i += concurrency) {
|
|
127
|
+
batches.push(opts.participantIds.slice(i, i + concurrency));
|
|
128
128
|
}
|
|
129
129
|
for (const batch of batches) {
|
|
130
130
|
if (cancelled)
|
|
131
131
|
break;
|
|
132
|
-
const promises = batch.map(async (
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
132
|
+
const promises = batch.map(async (participantId) => {
|
|
133
|
+
const participantName = opts.participantNames.get(participantId) ?? participantId;
|
|
134
|
+
const participantLog = (msg) => log(`[${participantName}] ${msg}`);
|
|
135
|
+
participantLog("Starting...");
|
|
136
136
|
try {
|
|
137
|
-
await runSingleSimulation(client,
|
|
138
|
-
|
|
137
|
+
await runSingleSimulation(client, participantId, participantName, opts, participantLog, () => cancelled, sharedBrowser);
|
|
138
|
+
participantLog("Completed");
|
|
139
139
|
}
|
|
140
140
|
catch (err) {
|
|
141
141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
142
|
-
|
|
142
|
+
participantLog(`Failed — ${msg}`);
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
await Promise.allSettled(promises);
|
|
@@ -154,10 +154,10 @@ export async function runLocalSimulations(client, opts) {
|
|
|
154
154
|
process.off("SIGINT", onSigint);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
async function runSingleSimulation(client,
|
|
157
|
+
async function runSingleSimulation(client, participantId, participantName, opts, log, isCancelled, sharedBrowser) {
|
|
158
158
|
// Step 1: Initialize session
|
|
159
159
|
const initResponse = await client.localSimInit({
|
|
160
|
-
|
|
160
|
+
participant_id: participantId,
|
|
161
161
|
study_id: opts.studyId,
|
|
162
162
|
product_id: opts.workspaceId,
|
|
163
163
|
iteration_id: opts.iterationId,
|
|
@@ -172,12 +172,12 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
172
172
|
const locale = opts.locale ?? iterDetails?.locale;
|
|
173
173
|
// Cache session state for per-step requests
|
|
174
174
|
const session = {
|
|
175
|
-
|
|
175
|
+
participant_id: initResponse.participant_id,
|
|
176
176
|
study_id: initResponse.study_id,
|
|
177
177
|
product_id: initResponse.product_id,
|
|
178
178
|
assignments: initResponse.assignments,
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
participant_background: initResponse.participant_background,
|
|
180
|
+
participant_language: initResponse.participant_language,
|
|
181
181
|
context_values: initResponse.context_values,
|
|
182
182
|
max_interactions: initResponse.max_interactions,
|
|
183
183
|
agent_model: initResponse.agent_model,
|
|
@@ -251,7 +251,7 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
251
251
|
let stepResponse;
|
|
252
252
|
try {
|
|
253
253
|
const stepReqBody = {
|
|
254
|
-
|
|
254
|
+
participant_id: session.participant_id,
|
|
255
255
|
product_id: session.product_id,
|
|
256
256
|
assignment_name: assignment.name,
|
|
257
257
|
assignment_instructions: assignment.instructions,
|
|
@@ -263,8 +263,8 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
263
263
|
interaction_count: step,
|
|
264
264
|
history,
|
|
265
265
|
forwards,
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
participant_background: session.participant_background,
|
|
267
|
+
participant_language: session.participant_language,
|
|
268
268
|
context_values: stepContextValues,
|
|
269
269
|
agent_model: session.agent_model,
|
|
270
270
|
dom_model: session.dom_model,
|
|
@@ -279,7 +279,7 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
279
279
|
await page.waitForTimeout(2000);
|
|
280
280
|
try {
|
|
281
281
|
const stepReqBody = {
|
|
282
|
-
|
|
282
|
+
participant_id: session.participant_id,
|
|
283
283
|
product_id: session.product_id,
|
|
284
284
|
assignment_name: assignment.name,
|
|
285
285
|
assignment_instructions: assignment.instructions,
|
|
@@ -291,8 +291,8 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
291
291
|
interaction_count: step,
|
|
292
292
|
history,
|
|
293
293
|
forwards,
|
|
294
|
-
|
|
295
|
-
|
|
294
|
+
participant_background: session.participant_background,
|
|
295
|
+
participant_language: session.participant_language,
|
|
296
296
|
context_values: stepContextValues,
|
|
297
297
|
agent_model: session.agent_model,
|
|
298
298
|
dom_model: session.dom_model,
|
|
@@ -516,8 +516,8 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
516
516
|
try {
|
|
517
517
|
const { generateDebugReport } = await import("./debug-report.js");
|
|
518
518
|
generateDebugReport(debugSteps, {
|
|
519
|
-
|
|
520
|
-
|
|
519
|
+
participantId: session.participant_id,
|
|
520
|
+
participantName,
|
|
521
521
|
url: navigationUrl,
|
|
522
522
|
screenFormat,
|
|
523
523
|
finalStatus,
|
|
@@ -531,7 +531,7 @@ async function runSingleSimulation(client, testerId, testerName, opts, log, isCa
|
|
|
531
531
|
}
|
|
532
532
|
try {
|
|
533
533
|
await client.localSimRecord({
|
|
534
|
-
|
|
534
|
+
participant_id: session.participant_id,
|
|
535
535
|
product_id: session.product_id,
|
|
536
536
|
interactions,
|
|
537
537
|
final_status: finalStatus,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* receives actions with node_ids, resolves elements locally via CDP.
|
|
7
7
|
*/
|
|
8
8
|
export interface LocalSimInitRequest {
|
|
9
|
-
|
|
9
|
+
participant_id: string;
|
|
10
10
|
study_id: string;
|
|
11
11
|
product_id: string;
|
|
12
12
|
iteration_id: string;
|
|
@@ -18,12 +18,12 @@ export interface IterationDetails {
|
|
|
18
18
|
locale?: string;
|
|
19
19
|
}
|
|
20
20
|
export interface LocalSimInitResponse {
|
|
21
|
-
|
|
21
|
+
participant_id: string;
|
|
22
22
|
study_id: string;
|
|
23
23
|
product_id: string;
|
|
24
24
|
assignments: LocalSimAssignment[];
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
participant_background: Record<string, unknown> | null;
|
|
26
|
+
participant_language: string | null;
|
|
27
27
|
config: Record<string, unknown>;
|
|
28
28
|
context_values: ContextValue[];
|
|
29
29
|
max_interactions: number;
|
|
@@ -64,7 +64,7 @@ export interface LocalTabInfo {
|
|
|
64
64
|
opener_id: string | null;
|
|
65
65
|
}
|
|
66
66
|
export interface LocalSimStepRequest {
|
|
67
|
-
|
|
67
|
+
participant_id: string;
|
|
68
68
|
product_id: string;
|
|
69
69
|
assignment_name: string;
|
|
70
70
|
assignment_instructions: string;
|
|
@@ -76,8 +76,8 @@ export interface LocalSimStepRequest {
|
|
|
76
76
|
interaction_count: number;
|
|
77
77
|
history: HistoryEntry[];
|
|
78
78
|
forwards: ForwardEntry[];
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
participant_background: Record<string, unknown> | null;
|
|
80
|
+
participant_language: string | null;
|
|
81
81
|
context_values: ContextValue[];
|
|
82
82
|
agent_model: string | null;
|
|
83
83
|
dom_model: string | null;
|
|
@@ -190,14 +190,14 @@ export interface AssignmentStatusUpdate {
|
|
|
190
190
|
step_count: number;
|
|
191
191
|
}
|
|
192
192
|
export interface LocalSimRecordRequest {
|
|
193
|
-
|
|
193
|
+
participant_id: string;
|
|
194
194
|
product_id: string;
|
|
195
195
|
interactions: RecordInteraction[];
|
|
196
196
|
final_status: string;
|
|
197
197
|
assignment_statuses: AssignmentStatusUpdate[];
|
|
198
198
|
}
|
|
199
199
|
export interface LocalSimRecordResponse {
|
|
200
|
-
|
|
200
|
+
participant_id: string;
|
|
201
201
|
interactions_created: number;
|
|
202
202
|
credits_consumed: number;
|
|
203
203
|
}
|
|
@@ -233,12 +233,12 @@ export interface TreeData {
|
|
|
233
233
|
* Avoids DB lookups on the backend hot path.
|
|
234
234
|
*/
|
|
235
235
|
export interface SessionState {
|
|
236
|
-
|
|
236
|
+
participant_id: string;
|
|
237
237
|
study_id: string;
|
|
238
238
|
product_id: string;
|
|
239
239
|
assignments: LocalSimAssignment[];
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
participant_background: Record<string, unknown> | null;
|
|
241
|
+
participant_language: string | null;
|
|
242
242
|
context_values: ContextValue[];
|
|
243
243
|
max_interactions: number;
|
|
244
244
|
agent_model: string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client targets — the five clients `ish mcp add` knows how to wire.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the SkillTargetSpec shape in `skill-content.ts` (a per-client
|
|
5
|
+
* table with config-path resolver + detection predicate). Each entry
|
|
6
|
+
* captures the surface required to round-trip the client's config file:
|
|
7
|
+
*
|
|
8
|
+
* - `configPath()` — absolute path to the client's MCP config (per-OS).
|
|
9
|
+
* - `detect()` — does the per-client config directory exist?
|
|
10
|
+
* Accepts false-negatives on never-launched apps;
|
|
11
|
+
* we'd rather skip than write into a bogus dir.
|
|
12
|
+
* - `serverKey` — which top-level object holds servers in this
|
|
13
|
+
* client's config (`mcpServers` for most;
|
|
14
|
+
* `servers` for VS Code).
|
|
15
|
+
* - `renderServer()` — the per-client server-block shape (the actual
|
|
16
|
+
* JSON value placed under `<serverKey>.<name>`).
|
|
17
|
+
*
|
|
18
|
+
* Server URL is sourced from `ISH_MCP_URL` (env override) or the public
|
|
19
|
+
* `https://mcp.ishlabs.io/mcp` default. Honors `--dev` indirectly: callers
|
|
20
|
+
* can flip the env var before invoking; the hosted MCP server handles
|
|
21
|
+
* OAuth on first connect so we never embed credentials in client files.
|
|
22
|
+
*/
|
|
23
|
+
/** Public default. Override with `ISH_MCP_URL` (used by `--dev` workflows). */
|
|
24
|
+
export declare const ISH_MCP_URL: string;
|
|
25
|
+
/** Server name placed under `<serverKey>` in each client config. */
|
|
26
|
+
export declare const ISH_SERVER_NAME = "ish";
|
|
27
|
+
export type McpClientKey = "cursor" | "vscode" | "claude-code" | "claude-desktop" | "windsurf";
|
|
28
|
+
export type McpServerKey = "mcpServers" | "servers";
|
|
29
|
+
export interface McpClientSpec {
|
|
30
|
+
key: McpClientKey;
|
|
31
|
+
displayName: string;
|
|
32
|
+
/** Top-level object that holds MCP servers in this client's config. */
|
|
33
|
+
serverKey: McpServerKey;
|
|
34
|
+
/** Absolute path to this client's MCP config file (per-OS). */
|
|
35
|
+
configPath: () => string;
|
|
36
|
+
/** Cheap detection: does the per-client config dir exist on disk? */
|
|
37
|
+
detect: () => boolean;
|
|
38
|
+
/** Per-client server-block shape (the value at `<serverKey>.<name>`). */
|
|
39
|
+
renderServer: (url: string) => Record<string, unknown>;
|
|
40
|
+
/** True when this client can't be wired on the current OS. */
|
|
41
|
+
unsupportedOnThisOs?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Per-client targets. Order matters only for human output (listed in this
|
|
45
|
+
* order in `mcp list` and `mcp add` dry-run plans).
|
|
46
|
+
*/
|
|
47
|
+
export declare const MCP_CLIENT_TARGETS: McpClientSpec[];
|
|
48
|
+
/** Lookup by key (returns undefined for unknown keys). */
|
|
49
|
+
export declare function getClientSpec(key: string): McpClientSpec | undefined;
|
|
50
|
+
/** Convenience: every valid client key, for usage-error messages. */
|
|
51
|
+
export declare function allClientKeys(): McpClientKey[];
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client targets — the five clients `ish mcp add` knows how to wire.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the SkillTargetSpec shape in `skill-content.ts` (a per-client
|
|
5
|
+
* table with config-path resolver + detection predicate). Each entry
|
|
6
|
+
* captures the surface required to round-trip the client's config file:
|
|
7
|
+
*
|
|
8
|
+
* - `configPath()` — absolute path to the client's MCP config (per-OS).
|
|
9
|
+
* - `detect()` — does the per-client config directory exist?
|
|
10
|
+
* Accepts false-negatives on never-launched apps;
|
|
11
|
+
* we'd rather skip than write into a bogus dir.
|
|
12
|
+
* - `serverKey` — which top-level object holds servers in this
|
|
13
|
+
* client's config (`mcpServers` for most;
|
|
14
|
+
* `servers` for VS Code).
|
|
15
|
+
* - `renderServer()` — the per-client server-block shape (the actual
|
|
16
|
+
* JSON value placed under `<serverKey>.<name>`).
|
|
17
|
+
*
|
|
18
|
+
* Server URL is sourced from `ISH_MCP_URL` (env override) or the public
|
|
19
|
+
* `https://mcp.ishlabs.io/mcp` default. Honors `--dev` indirectly: callers
|
|
20
|
+
* can flip the env var before invoking; the hosted MCP server handles
|
|
21
|
+
* OAuth on first connect so we never embed credentials in client files.
|
|
22
|
+
*/
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
/** Public default. Override with `ISH_MCP_URL` (used by `--dev` workflows). */
|
|
27
|
+
export const ISH_MCP_URL = process.env.ISH_MCP_URL ?? "https://mcp.ishlabs.io/mcp";
|
|
28
|
+
/** Server name placed under `<serverKey>` in each client config. */
|
|
29
|
+
export const ISH_SERVER_NAME = "ish";
|
|
30
|
+
function homedir() {
|
|
31
|
+
return os.homedir();
|
|
32
|
+
}
|
|
33
|
+
function appData() {
|
|
34
|
+
return process.env.APPDATA ?? path.join(homedir(), "AppData", "Roaming");
|
|
35
|
+
}
|
|
36
|
+
function dirExists(p) {
|
|
37
|
+
try {
|
|
38
|
+
return fs.statSync(p).isDirectory();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// --- Cursor ---------------------------------------------------------------
|
|
45
|
+
//
|
|
46
|
+
// Cross-platform: `~/.cursor/mcp.json`.
|
|
47
|
+
function cursorConfigPath() {
|
|
48
|
+
return path.join(homedir(), ".cursor", "mcp.json");
|
|
49
|
+
}
|
|
50
|
+
// --- VS Code --------------------------------------------------------------
|
|
51
|
+
//
|
|
52
|
+
// macOS: ~/Library/Application Support/Code/User/mcp.json
|
|
53
|
+
// Linux: ~/.config/Code/User/mcp.json
|
|
54
|
+
// Windows: %APPDATA%\Code\User\mcp.json
|
|
55
|
+
function vscodeConfigDir() {
|
|
56
|
+
const plat = os.platform();
|
|
57
|
+
if (plat === "darwin") {
|
|
58
|
+
return path.join(homedir(), "Library", "Application Support", "Code", "User");
|
|
59
|
+
}
|
|
60
|
+
if (plat === "win32") {
|
|
61
|
+
return path.join(appData(), "Code", "User");
|
|
62
|
+
}
|
|
63
|
+
return path.join(homedir(), ".config", "Code", "User");
|
|
64
|
+
}
|
|
65
|
+
function vscodeConfigPath() {
|
|
66
|
+
return path.join(vscodeConfigDir(), "mcp.json");
|
|
67
|
+
}
|
|
68
|
+
// --- Claude Code (user-scope) --------------------------------------------
|
|
69
|
+
//
|
|
70
|
+
// `~/.claude.json` is Claude Code's user-scope MCP config. (`--scope project`
|
|
71
|
+
// writes `.mcp.json` in the project root — we don't drive that here.)
|
|
72
|
+
function claudeCodeConfigPath() {
|
|
73
|
+
return path.join(homedir(), ".claude.json");
|
|
74
|
+
}
|
|
75
|
+
// --- Claude Desktop ------------------------------------------------------
|
|
76
|
+
//
|
|
77
|
+
// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
78
|
+
// Windows: %APPDATA%\Claude\claude_desktop_config.json
|
|
79
|
+
// Linux: not supported by Anthropic at the time of writing.
|
|
80
|
+
function claudeDesktopConfigDir() {
|
|
81
|
+
const plat = os.platform();
|
|
82
|
+
if (plat === "darwin") {
|
|
83
|
+
return path.join(homedir(), "Library", "Application Support", "Claude");
|
|
84
|
+
}
|
|
85
|
+
if (plat === "win32") {
|
|
86
|
+
return path.join(appData(), "Claude");
|
|
87
|
+
}
|
|
88
|
+
// Linux: no official desktop release. Report a sentinel path; the spec
|
|
89
|
+
// also flips `unsupportedOnThisOs` so callers can skip without writing
|
|
90
|
+
// a config under a dir Claude Desktop won't ever read.
|
|
91
|
+
return path.join(homedir(), ".config", "Claude");
|
|
92
|
+
}
|
|
93
|
+
function claudeDesktopConfigPath() {
|
|
94
|
+
return path.join(claudeDesktopConfigDir(), "claude_desktop_config.json");
|
|
95
|
+
}
|
|
96
|
+
// --- Windsurf -------------------------------------------------------------
|
|
97
|
+
//
|
|
98
|
+
// `~/.codeium/windsurf/mcp_config.json` — same on every platform.
|
|
99
|
+
function windsurfConfigPath() {
|
|
100
|
+
return path.join(homedir(), ".codeium", "windsurf", "mcp_config.json");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Per-client targets. Order matters only for human output (listed in this
|
|
104
|
+
* order in `mcp list` and `mcp add` dry-run plans).
|
|
105
|
+
*/
|
|
106
|
+
export const MCP_CLIENT_TARGETS = [
|
|
107
|
+
{
|
|
108
|
+
key: "cursor",
|
|
109
|
+
displayName: "Cursor",
|
|
110
|
+
serverKey: "mcpServers",
|
|
111
|
+
configPath: cursorConfigPath,
|
|
112
|
+
detect: () => dirExists(path.join(homedir(), ".cursor")),
|
|
113
|
+
renderServer: (url) => ({ url }),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "vscode",
|
|
117
|
+
displayName: "VS Code",
|
|
118
|
+
serverKey: "servers",
|
|
119
|
+
configPath: vscodeConfigPath,
|
|
120
|
+
detect: () => dirExists(vscodeConfigDir()),
|
|
121
|
+
renderServer: (url) => ({ type: "http", url }),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: "claude-code",
|
|
125
|
+
displayName: "Claude Code",
|
|
126
|
+
serverKey: "mcpServers",
|
|
127
|
+
configPath: claudeCodeConfigPath,
|
|
128
|
+
// Claude Code's user-scope config is a single file at `~/.claude.json`.
|
|
129
|
+
// Detect by file existence (rather than dir existence) so users who
|
|
130
|
+
// have only ever installed Claude Code via npm — and never created a
|
|
131
|
+
// `~/.claude/` dir — still get the entry surfaced.
|
|
132
|
+
detect: () => {
|
|
133
|
+
try {
|
|
134
|
+
return fs.existsSync(claudeCodeConfigPath()) || dirExists(path.join(homedir(), ".claude"));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
renderServer: (url) => ({ type: "http", url }),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
key: "claude-desktop",
|
|
144
|
+
displayName: "Claude Desktop",
|
|
145
|
+
serverKey: "mcpServers",
|
|
146
|
+
configPath: claudeDesktopConfigPath,
|
|
147
|
+
detect: () => {
|
|
148
|
+
// No Linux release yet — surface as undetected on linux to keep the
|
|
149
|
+
// `--all` sweep honest. Users can still target it explicitly via
|
|
150
|
+
// `--client claude-desktop` if they have a config dir there.
|
|
151
|
+
if (os.platform() === "linux")
|
|
152
|
+
return false;
|
|
153
|
+
return dirExists(claudeDesktopConfigDir());
|
|
154
|
+
},
|
|
155
|
+
renderServer: (url) => ({ type: "http", url }),
|
|
156
|
+
unsupportedOnThisOs: os.platform() === "linux",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
key: "windsurf",
|
|
160
|
+
displayName: "Windsurf",
|
|
161
|
+
serverKey: "mcpServers",
|
|
162
|
+
configPath: windsurfConfigPath,
|
|
163
|
+
detect: () => dirExists(path.join(homedir(), ".codeium", "windsurf"))
|
|
164
|
+
|| dirExists(path.join(homedir(), ".codeium")),
|
|
165
|
+
renderServer: (url) => ({ serverUrl: url }),
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
/** Lookup by key (returns undefined for unknown keys). */
|
|
169
|
+
export function getClientSpec(key) {
|
|
170
|
+
return MCP_CLIENT_TARGETS.find((c) => c.key === key);
|
|
171
|
+
}
|
|
172
|
+
/** Convenience: every valid client key, for usage-error messages. */
|
|
173
|
+
export function allClientKeys() {
|
|
174
|
+
return MCP_CLIENT_TARGETS.map((c) => c.key);
|
|
175
|
+
}
|
package/dist/lib/modality.d.ts
CHANGED
|
@@ -29,11 +29,11 @@ export declare function describeRequiredContentFlag(modality: string, chatMode?:
|
|
|
29
29
|
* the read helpers below fall back to that legacy shape and treat it as
|
|
30
30
|
* `external_chatbot`.
|
|
31
31
|
*/
|
|
32
|
-
declare const CHAT_MODES: readonly ["external_chatbot", "
|
|
32
|
+
declare const CHAT_MODES: readonly ["external_chatbot", "participant_pair"];
|
|
33
33
|
export type ChatMode = typeof CHAT_MODES[number];
|
|
34
34
|
/**
|
|
35
|
-
* Normalise user-supplied `--chat-mode` values. Accepts both `
|
|
36
|
-
* (canonical) and `
|
|
35
|
+
* Normalise user-supplied `--chat-mode` values. Accepts both `participant_pair`
|
|
36
|
+
* (canonical) and `participant-pair` (hyphenated — matches CLI flag convention
|
|
37
37
|
* elsewhere in `ish`); same for `external-chatbot`. Returns `null` for
|
|
38
38
|
* anything unrecognised so callers can throw a clean ValidationError.
|
|
39
39
|
*/
|
|
@@ -50,14 +50,14 @@ export declare function readExternalChatbotEndpoint(details: unknown): {
|
|
|
50
50
|
chatbot_endpoint_id?: string;
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
* Extract the
|
|
54
|
-
* `mode_details.mode !== "
|
|
53
|
+
* Extract the participant-pair payload from chat details. Returns `undefined` if
|
|
54
|
+
* `mode_details.mode !== "participant_pair"`. Does not validate equal lengths or
|
|
55
55
|
* non-empty scenarios — that's the caller's job (see `iterationHasContent`
|
|
56
56
|
* and `validateIterationDetails`).
|
|
57
57
|
*/
|
|
58
|
-
export declare function
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
export declare function readParticipantPairConfig(details: unknown): {
|
|
59
|
+
group_a: string[];
|
|
60
|
+
group_b: string[];
|
|
61
61
|
scenario_a: string;
|
|
62
62
|
scenario_b: string;
|
|
63
63
|
initiator_side: "a" | "b";
|
|
@@ -65,8 +65,8 @@ export declare function readTesterPairConfig(details: unknown): {
|
|
|
65
65
|
role_criteria_b?: Record<string, unknown>;
|
|
66
66
|
} | undefined;
|
|
67
67
|
/**
|
|
68
|
-
* RoleCriteria — persona-first filter that resolves a
|
|
69
|
-
* for one side of a
|
|
68
|
+
* RoleCriteria — persona-first filter that resolves a person pool
|
|
69
|
+
* for one side of a participant_pair iteration. Mirrors the backend Pydantic
|
|
70
70
|
* shape in `app/api/models/iterations.py:RoleCriteria` (swift-charm plan).
|
|
71
71
|
* All fields optional; an empty object is treated as "no filter".
|
|
72
72
|
*/
|