@clone-ai/prompt-prediction 0.7.0-bootstrap.0 → 0.7.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/CHANGELOG.md +120 -0
- package/CONTRIBUTING.md +52 -0
- package/LICENSE +21 -0
- package/README.md +119 -1
- package/SECURITY.md +9 -0
- package/dist/assistant-ui.d.ts +5 -0
- package/dist/assistant-ui.js +26 -0
- package/dist/clone-mode.d.ts +64 -0
- package/dist/clone-mode.js +156 -0
- package/dist/controller.d.ts +61 -0
- package/dist/controller.js +155 -0
- package/dist/deadline.d.ts +2 -0
- package/dist/deadline.js +25 -0
- package/dist/feedback.d.ts +50 -0
- package/dist/feedback.js +118 -0
- package/dist/generated/api-types.d.ts +1663 -0
- package/dist/generated/api-types.js +5 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +35 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -0
- package/dist/react.d.ts +50 -0
- package/dist/react.js +147 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +93 -0
- package/dist/transport.d.ts +15 -0
- package/dist/transport.js +42 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/docs/agent-integration.md +122 -0
- package/docs/api.md +69 -0
- package/docs/billing.md +53 -0
- package/docs/browser-onboarding.md +69 -0
- package/docs/clone-mode.md +40 -0
- package/docs/context-mapping.md +28 -0
- package/docs/data-and-service.md +25 -0
- package/docs/developer-apps.md +87 -0
- package/docs/feedback.md +70 -0
- package/docs/pilot-validation.md +35 -0
- package/docs/releases.md +88 -0
- package/docs/reliability.md +33 -0
- package/docs/start.md +91 -0
- package/examples/react/composer-events.ts +3 -0
- package/examples/react/demo.tsx +195 -0
- package/examples/react/index.html +12 -0
- package/examples/react/mode-demo.tsx +95 -0
- package/examples/react/pilot-observations.ts +57 -0
- package/examples/react/vite.config.ts +123 -0
- package/openapi.json +2268 -0
- package/package.json +99 -4
- package/playwright.config.ts +13 -0
- package/release-manifest.json +63 -0
- package/scripts/create-example.mjs +31 -0
- package/scripts/pilot-metrics.mjs +91 -0
- package/tests/browser/clone-mode-options.tsx +38 -0
- package/tests/browser/clone-mode.spec.ts +68 -0
- package/tests/browser/composer.spec.ts +238 -0
package/package.json
CHANGED
|
@@ -1,13 +1,108 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clone-ai/prompt-prediction",
|
|
3
|
-
"version": "0.7.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Context-aware composer suggestions with optional presentation and explicitly started Clone mode.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"docs",
|
|
11
|
+
"openapi.json",
|
|
12
|
+
"release-manifest.json",
|
|
13
|
+
"scripts/create-example.mjs",
|
|
14
|
+
"scripts/pilot-metrics.mjs",
|
|
15
|
+
"playwright.config.ts",
|
|
16
|
+
"tests/browser",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"CONTRIBUTING.md",
|
|
20
|
+
"SECURITY.md",
|
|
21
|
+
"examples/react/*.ts",
|
|
22
|
+
"examples/react/*.tsx",
|
|
23
|
+
"examples/react/index.html"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./react": {
|
|
31
|
+
"types": "./dist/react.d.ts",
|
|
32
|
+
"import": "./dist/react.js"
|
|
33
|
+
},
|
|
34
|
+
"./assistant-ui": {
|
|
35
|
+
"types": "./dist/assistant-ui.d.ts",
|
|
36
|
+
"import": "./dist/assistant-ui.js"
|
|
37
|
+
},
|
|
38
|
+
"./server": {
|
|
39
|
+
"types": "./dist/server.d.ts",
|
|
40
|
+
"import": "./dist/server.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "node scripts/build.mjs",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:browser": "playwright test",
|
|
47
|
+
"dev": "vite --config examples/react/vite.config.ts",
|
|
48
|
+
"generate:types": "openapi-typescript openapi.json -o src/generated/api-types.ts --default-non-nullable false",
|
|
49
|
+
"check:public": "node scripts/check-public.mjs",
|
|
50
|
+
"prepack": "npm run build && node scripts/release-manifest.mjs",
|
|
51
|
+
"test:package": "node scripts/test-package.mjs",
|
|
52
|
+
"test:scripts": "node --test tests/scripts/*.test.mjs",
|
|
53
|
+
"check": "pnpm check:public && pnpm build && pnpm typecheck && pnpm test && pnpm test:scripts",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.test.json"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"react": "^18 || ^19",
|
|
58
|
+
"react-dom": "^18 || ^19",
|
|
59
|
+
"@assistant-ui/react": "0.15.21"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@assistant-ui/react": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"react": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"react-dom": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@assistant-ui/react": "0.15.21",
|
|
74
|
+
"@playwright/test": "1.60.0",
|
|
75
|
+
"@types/react": "19.2.14",
|
|
76
|
+
"@types/react-dom": "19.2.3",
|
|
77
|
+
"@types/node": "24.12.2",
|
|
78
|
+
"openapi-typescript": "7.13.0",
|
|
79
|
+
"react": "19.2.4",
|
|
80
|
+
"react-dom": "19.2.4",
|
|
81
|
+
"typescript": "5.9.3",
|
|
82
|
+
"vite": "8.0.16",
|
|
83
|
+
"vitest": "4.1.11"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22.13.0"
|
|
87
|
+
},
|
|
5
88
|
"license": "MIT",
|
|
6
89
|
"repository": {
|
|
7
90
|
"type": "git",
|
|
8
91
|
"url": "git+https://github.com/cloneisyou/clone-sdk.git"
|
|
9
92
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
93
|
+
"homepage": "https://github.com/cloneisyou/clone-sdk#readme",
|
|
94
|
+
"bugs": {
|
|
95
|
+
"url": "https://github.com/cloneisyou/clone-sdk/issues"
|
|
96
|
+
},
|
|
97
|
+
"publishConfig": {
|
|
98
|
+
"access": "public"
|
|
99
|
+
},
|
|
100
|
+
"packageManager": "pnpm@11.19.0",
|
|
101
|
+
"keywords": [
|
|
102
|
+
"react",
|
|
103
|
+
"tab-completion",
|
|
104
|
+
"composer",
|
|
105
|
+
"assistant-ui",
|
|
106
|
+
"clone"
|
|
12
107
|
]
|
|
13
108
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const port = Number(process.env.CLONE_SDK_TEST_PORT || 4317);
|
|
4
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Invalid CLONE_SDK_TEST_PORT');
|
|
5
|
+
const baseURL = `http://127.0.0.1:${port}`;
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
testDir: './tests/browser', fullyParallel: true, retries: 0,
|
|
9
|
+
use: { baseURL, trace: 'retain-on-failure' },
|
|
10
|
+
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
|
11
|
+
webServer: { command: `node node_modules/vite/bin/vite.js --config examples/react/vite.config.ts --host 127.0.0.1 --port ${port} --strictPort`,
|
|
12
|
+
url: baseURL, reuseExistingServer: false },
|
|
13
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.7.0",
|
|
3
|
+
"channel": "preview",
|
|
4
|
+
"assistantUiVersion": "0.15.21",
|
|
5
|
+
"generatedAt": "2026-10-02T08:43:57.345Z",
|
|
6
|
+
"sha256": {
|
|
7
|
+
"CHANGELOG.md": "e0a95196a481e881a778dfce172188492fe6a363553dd4081bb823defbb5cbb2",
|
|
8
|
+
"CONTRIBUTING.md": "ac8b335b3d9f5dc917f07c5276e635aefccc1982bef1e64274723ae65f5e940b",
|
|
9
|
+
"LICENSE": "bfdbf62032d1909e31ee825343d63df2acb35cf64c6309119979d34214c0210f",
|
|
10
|
+
"README.md": "9d979e45ae21702e9cff7155a95671cdd157159cc0ae09a71ae653f7bc852b9b",
|
|
11
|
+
"SECURITY.md": "fae4ea6d845b9cec05706bc50ced1b204d48f4980e9e6d49bbf34e032b795879",
|
|
12
|
+
"dist/assistant-ui.d.ts": "8a3322d7fb6f26428d18776366c6e7a2593308faeac9c8839eca9852a1aa201b",
|
|
13
|
+
"dist/assistant-ui.js": "60b32371f1243e8a22432ff263542540960c9840df0bfec7cfb311d3bfaa3166",
|
|
14
|
+
"dist/clone-mode.d.ts": "60b4b419ab686b45448c92337f974b270a4b3a72aaf207415ed431ba217b83b6",
|
|
15
|
+
"dist/clone-mode.js": "d17fc8ee77e152ed378a021ce04196c0f8c84fe0dac649194c59fa7348c4352a",
|
|
16
|
+
"dist/controller.d.ts": "412ad9c0aff86a1d3b80b83a6ab3452ee23f69528aed2d42d79a48832f6e83c6",
|
|
17
|
+
"dist/controller.js": "b11d5cba5b7301c0bc64c39e60f32ebba2e34660119c6b2395450f7812df2b2b",
|
|
18
|
+
"dist/deadline.d.ts": "9ae44702a2bdd5bcea20ede7f597e78cae4ae06a427f6c2b91251544b58faff6",
|
|
19
|
+
"dist/deadline.js": "1fd27ec740e7297eba12e476155242edf3c33fcd080a8667556642ea27a2feec",
|
|
20
|
+
"dist/feedback.d.ts": "2af2ab51506bea2deba97f14c9d64604cba3dec611b4da7b9df270fc8334a1e4",
|
|
21
|
+
"dist/feedback.js": "61562c68e212768aefe9ba982485099fb12e6b49c4e0044f561a737bf98123d0",
|
|
22
|
+
"dist/generated/api-types.d.ts": "963c70a5ba6dfb053ed0cdee23d75f763c4947924f46c5d7a227ca97c4b38237",
|
|
23
|
+
"dist/generated/api-types.js": "09137b30c7060495057e4acb073e6b8a218422d4bb96161d2b3a775a1030105f",
|
|
24
|
+
"dist/http.d.ts": "19f6887862ee258701caa20db241d95d9ed65a18d10a33014277b23eaca9cfca",
|
|
25
|
+
"dist/http.js": "a186e08d05379082b331f84134435a4d3aa2e24595389b4606d73e4fa77d904b",
|
|
26
|
+
"dist/index.d.ts": "760eda93b69ac06ab89c908030de0fabfa1c2bad2222f162284f0254f756952d",
|
|
27
|
+
"dist/index.js": "3a009a35e96144a41b8044eb44618c89688883c714030917ff0b0d5b9dcf1e6d",
|
|
28
|
+
"dist/react.d.ts": "6771b55a9aa2d18d3b99ca0d8c51a0444ea7948474125aa90741807215604e73",
|
|
29
|
+
"dist/react.js": "19c353953a7ae1969c14791a0725b150022559ce6c0fc5c3c689458019dfc85b",
|
|
30
|
+
"dist/server.d.ts": "4b87d0d482e1d5ed9d2afcefebcb976dfbb7c4ba79a333d65c8344646ea9dff8",
|
|
31
|
+
"dist/server.js": "e560f70bd4deef79598a8f3bbab051fc0e014a9023a3ebf8c823898e9f79a4f8",
|
|
32
|
+
"dist/transport.d.ts": "394b85a80c6490e6e6ca9a5f3be71b811412c8d0044628bcccf19a9521d59344",
|
|
33
|
+
"dist/transport.js": "f6095752c52cdb01fc1aeb357726b2b2b9ce28415b326df9c47bf0894cd722b9",
|
|
34
|
+
"dist/types.d.ts": "51b2b5f6ef86410aa1978d1f750b00d1be75db0bc46ca906b993168e2deaf57e",
|
|
35
|
+
"dist/types.js": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881",
|
|
36
|
+
"docs/agent-integration.md": "1dd86850621d9f96039e8e38ccfb184be8d82b247fc0df5df472ccdc747c3f50",
|
|
37
|
+
"docs/api.md": "49403830c6dd1937d05fa1d4c884376e3d52947fd1918264c5eb080eacde81ee",
|
|
38
|
+
"docs/billing.md": "ef0ea99efd2e278d8b7406f4748a21972117e5d3f418e19a493cdf2cbdd41083",
|
|
39
|
+
"docs/browser-onboarding.md": "189c80521e9f6b57387b54ee9b3608fcf02f476bc8c5eeef6eca74385168a97e",
|
|
40
|
+
"docs/clone-mode.md": "5042be93e57fc18d1e7cf03f073dfbef34052371e6f0e8f38413d33bf0b47da6",
|
|
41
|
+
"docs/context-mapping.md": "b05401f7eef95155f6bb6484789b073a37863e86d88714e59357b1da3ec6db1b",
|
|
42
|
+
"docs/data-and-service.md": "76069989449bd32b3eff66f54fcd5ab56e5f5ad02554e412c73848733e8b5068",
|
|
43
|
+
"docs/developer-apps.md": "2a078fe962894115d3b30cc7c2dc0c999e5217fef3b5c1912b2f5af93072e650",
|
|
44
|
+
"docs/feedback.md": "d759b2f5ffe0c6ab1e484a6cd908bb5fdf181dc30a0b885338a5b63c80c431e8",
|
|
45
|
+
"docs/pilot-validation.md": "81b3df159bc8715a9aae581dde740fd5a1b2db47cda55b051e1c72debfb733bf",
|
|
46
|
+
"docs/releases.md": "4e0f87d2c02739e584177f415eec4cb595ba3882cadfb4cc5266e546bffe1d91",
|
|
47
|
+
"docs/reliability.md": "1401ad877c2421a2fb60cafc65f8fd16242d7cbcf8d6781a36108cd292a29521",
|
|
48
|
+
"docs/start.md": "a65fb564f1c191a59b45d2c251930b5cf078748dfcdb20f2b1c434805ffbcefe",
|
|
49
|
+
"examples/react/composer-events.ts": "1d08d6d39416fd5f65a014f3b66675173c14fb1a88f7cad31e0c70eb5bb5f66e",
|
|
50
|
+
"examples/react/demo.tsx": "96c846a6a7d4fc822b2ba307c0571834503e0e4479292501ab179c72db43c678",
|
|
51
|
+
"examples/react/index.html": "63ee7716c011fb67a6c1687b9ede0bc2c551565f369ef0abeb9940ff042a4a68",
|
|
52
|
+
"examples/react/mode-demo.tsx": "0492e114dfbd36e04e54a07e4a7423e545b1739c9b3b22fce8c9176fa5eb658c",
|
|
53
|
+
"examples/react/pilot-observations.ts": "22f676c47b259c94f856d53d12a638e35844e8511df95405354785afba75eaed",
|
|
54
|
+
"examples/react/vite.config.ts": "4ef19a5b7463c308e187286fd8e1a34458edfabd5f7b243ebfe4aed3f08de80a",
|
|
55
|
+
"openapi.json": "1ebde326a7e6bf3eacd754bc2e40525df72e9afe49f11717db115690d81d1e8c",
|
|
56
|
+
"playwright.config.ts": "07f5515d7e50464f69f019c811c7b83b0b9e8b597b212a219bab175c1ee1fa5c",
|
|
57
|
+
"scripts/create-example.mjs": "3c4c45c64b40ccc4ca7efd3197463c7b1887442ea71138c847221946f25aa6d4",
|
|
58
|
+
"scripts/pilot-metrics.mjs": "c848d44ff477632477f76229cd91d18ed0acdf9faea792e251cb62cc0e281fc8",
|
|
59
|
+
"tests/browser/clone-mode-options.tsx": "0a4ed85d22b0f081e9ee9a185704f3eb4ed83688b0f69de4d63c0b4ba377f160",
|
|
60
|
+
"tests/browser/clone-mode.spec.ts": "a9fd2e03628eb16d188f38f74629833993a6f3119d63e84bab7931740d68ea5f",
|
|
61
|
+
"tests/browser/composer.spec.ts": "28a9f3b9ef01012c97d02433706abf44d8f16e595db2dbd3841679951322949d"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
5
|
+
const [destination, tarball] = process.argv.slice(2);
|
|
6
|
+
if (!destination || !tarball) throw new Error('Usage: node create-example.mjs /new/empty/directory /sdk.tgz');
|
|
7
|
+
const target = resolve(destination);
|
|
8
|
+
await mkdir(target); // Refuse to overwrite an existing project.
|
|
9
|
+
const files = ['scripts/pilot-metrics.mjs', 'examples/react/demo.tsx', 'examples/react/mode-demo.tsx', 'examples/react/composer-events.ts', 'examples/react/pilot-observations.ts', 'examples/react/index.html', 'examples/react/vite.config.ts', 'playwright.config.ts',
|
|
10
|
+
'tests/browser/composer.spec.ts', 'tests/browser/clone-mode.spec.ts', 'tests/browser/clone-mode-options.tsx'];
|
|
11
|
+
for (const file of files) {
|
|
12
|
+
let text = await readFile(resolve(root, file), 'utf8');
|
|
13
|
+
for (const [from, to] of Object.entries({
|
|
14
|
+
'../../src/react.js': '@clone-ai/prompt-prediction/react', '../../src/assistant-ui.js': '@clone-ai/prompt-prediction/assistant-ui',
|
|
15
|
+
'../../src/transport.js': '@clone-ai/prompt-prediction', '../../src/types.js': '@clone-ai/prompt-prediction',
|
|
16
|
+
'../../src/feedback.js': '@clone-ai/prompt-prediction',
|
|
17
|
+
'../../src/server.js': '@clone-ai/prompt-prediction/server',
|
|
18
|
+
})) text = text.replaceAll(from, to);
|
|
19
|
+
await mkdir(dirname(resolve(target, file)), { recursive: true });
|
|
20
|
+
await writeFile(resolve(target, file), text);
|
|
21
|
+
}
|
|
22
|
+
await writeFile(resolve(target, 'tsconfig.json'), JSON.stringify({ compilerOptions: {
|
|
23
|
+
target: 'ES2022', module: 'ESNext', moduleResolution: 'Bundler', jsx: 'react-jsx',
|
|
24
|
+
strict: true, noEmit: true, skipLibCheck: true, lib: ['DOM', 'ES2022'],
|
|
25
|
+
}, include: ['examples'] }, null, 2));
|
|
26
|
+
await writeFile(resolve(target, 'package.json'), JSON.stringify({ name: 'clone-tab-integration-example', private: true, type: 'module',
|
|
27
|
+
scripts: { dev: 'vite --config examples/react/vite.config.ts', build: 'tsc && vite build --config examples/react/vite.config.ts', test: 'playwright test' },
|
|
28
|
+
dependencies: { '@clone-ai/prompt-prediction': 'file:' + resolve(tarball), '@assistant-ui/react': '0.15.21', react: '19.2.4', 'react-dom': '19.2.4' },
|
|
29
|
+
devDependencies: { vite: '8.0.16', typescript: '5.9.3', '@playwright/test': '1.60.0', '@types/node': '24.12.2', '@types/react': '19.2.14', '@types/react-dom': '19.2.3' },
|
|
30
|
+
}, null, 2));
|
|
31
|
+
console.log('Created independent example at', target);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { appendFile, readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
export function summarize(records) {
|
|
6
|
+
const result = { sources: {}, onboarding: [], support_minutes: null, support_records: 0 };
|
|
7
|
+
const starts = new Map();
|
|
8
|
+
for (const row of records) {
|
|
9
|
+
if (row.kind === 'onboarding_started') starts.set(row.pilot, row.recorded_at);
|
|
10
|
+
if (row.kind === 'integration_verified' && starts.has(row.pilot)) {
|
|
11
|
+
result.onboarding.push({ pilot: row.pilot, minutes: (row.recorded_at - starts.get(row.pilot)) / 60_000 });
|
|
12
|
+
starts.delete(row.pilot);
|
|
13
|
+
}
|
|
14
|
+
if (row.kind === 'support' && Number.isFinite(row.minutes) && row.minutes >= 0) {
|
|
15
|
+
result.support_minutes = (result.support_minutes ?? 0) + row.minutes;
|
|
16
|
+
result.support_records++;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
for (const source of ['fixture', 'fault', 'live']) {
|
|
20
|
+
const rows = records.filter(row => row.source === source);
|
|
21
|
+
if (!rows.length) continue;
|
|
22
|
+
const metrics = rows.filter(row => row.kind === 'metric');
|
|
23
|
+
const durations = metrics.map(row => row.duration_ms).filter(Number.isFinite).sort((a, b) => a - b);
|
|
24
|
+
const quantile = fraction => durations.length ? durations[Math.ceil(durations.length * fraction) - 1] : null;
|
|
25
|
+
const events = new Map();
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
if (['accepted', 'submitted'].includes(row.kind) && row.delivery === 'recorded' && row.request_id) {
|
|
28
|
+
const request = events.get(row.request_id) ?? {};
|
|
29
|
+
// Exact ordered funnel uses client occurrence time. Missing timestamps
|
|
30
|
+
// and failed deliveries stay outside the measured denominator.
|
|
31
|
+
if (Number.isFinite(row.observed_at)) request[row.kind] = Math.min(request[row.kind] ?? Infinity, row.observed_at);
|
|
32
|
+
events.set(row.request_id, request);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const accepted = [...events.values()].filter(event => Number.isFinite(event.accepted));
|
|
36
|
+
const submitted = accepted.filter(event => Number.isFinite(event.submitted) && event.submitted >= event.accepted);
|
|
37
|
+
const decisions = new Map();
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
if (row.event_id && row.delivery === 'recorded' && ['rejected', 'feedback', 'outcome'].includes(row.kind)) {
|
|
40
|
+
decisions.set(row.event_id, { ...decisions.get(row.event_id), ...row });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const pilotSessions = new Map();
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
if (row.kind === 'session' && row.pilot && row.session_id) {
|
|
46
|
+
const sessions = pilotSessions.get(row.pilot) ?? new Set();
|
|
47
|
+
sessions.add(row.session_id); pilotSessions.set(row.pilot, sessions);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
result.sources[source] = {
|
|
51
|
+
sessions_observed: new Set(rows.filter(row => row.kind === 'session').map(row => row.session_id).filter(Boolean)).size,
|
|
52
|
+
pilots_with_repeat_sessions: [...pilotSessions.values()].filter(sessions => sessions.size > 1).length,
|
|
53
|
+
prediction_attempts_observed: rows.filter(row => row.kind === 'prediction').length,
|
|
54
|
+
distinct_prediction_requests: new Set(rows.filter(row => row.kind === 'prediction').map(row => row.request_id).filter(Boolean)).size,
|
|
55
|
+
timed_requests: metrics.length, p50_ms: quantile(0.5), p95_ms: quantile(0.95),
|
|
56
|
+
failures: metrics.filter(row => row.outcome === 'failed').length,
|
|
57
|
+
timeouts: metrics.filter(row => row.code === 'prediction_timeout').length,
|
|
58
|
+
cancellations: metrics.filter(row => row.outcome === 'cancelled').length,
|
|
59
|
+
accepted_with_timestamps: accepted.length, submitted_after_acceptance: submitted.length,
|
|
60
|
+
observed_accept_to_submit_rate: accepted.length ? submitted.length / accepted.length : null,
|
|
61
|
+
failed_event_deliveries: rows.filter(row => row.delivery === 'failed').length,
|
|
62
|
+
explicit_rejections: [...decisions.values()].filter(row => row.kind === 'rejected').length,
|
|
63
|
+
explicit_evaluations: [...decisions.values()].filter(row => row.kind === 'feedback').length,
|
|
64
|
+
host_reported_task_successes: [...decisions.values()].filter(row => row.task_outcome === 'succeeded').length,
|
|
65
|
+
host_reported_task_failures: [...decisions.values()].filter(row => row.task_outcome === 'failed').length,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
result.coverage = 'Local opt-in observations. Sessions are not returning customers. Missing telemetry is unknown, not rejection. Fixture/fault data is not customer adoption or provider capacity.';
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
73
|
+
const [command, file, pilot, value] = process.argv.slice(2);
|
|
74
|
+
if (!file) throw new Error('Usage: pilot-metrics.mjs report FILE | start/verified/support FILE PILOT [MINUTES]');
|
|
75
|
+
if (command === 'report') {
|
|
76
|
+
const rows = (await readFile(file, 'utf8')).trim().split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
77
|
+
console.log(JSON.stringify(summarize(rows), null, 2));
|
|
78
|
+
} else {
|
|
79
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(pilot ?? '')) throw new Error('Use a non-personal pilot label');
|
|
80
|
+
const kind = { start: 'onboarding_started', verified: 'integration_verified', support: 'support' }[command];
|
|
81
|
+
if (!kind) throw new Error('Unknown observation command');
|
|
82
|
+
const record = { kind, pilot, recorded_at: Date.now() };
|
|
83
|
+
if (command === 'support') {
|
|
84
|
+
const minutes = Number(value);
|
|
85
|
+
if (!Number.isFinite(minutes) || minutes < 0 || minutes > 10_000) throw new Error('Invalid support minutes');
|
|
86
|
+
record.minutes = minutes;
|
|
87
|
+
}
|
|
88
|
+
await appendFile(file, JSON.stringify(record) + '\n', { mode: 0o600 });
|
|
89
|
+
console.log('Recorded', kind);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StrictMode, useRef, useState } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { useCloneMode } from '../../src/react.js';
|
|
4
|
+
|
|
5
|
+
/** Isolated browser regression fixture; host receipts deliberately ignore abort. */
|
|
6
|
+
function OptionsFixture() {
|
|
7
|
+
const [reviewMs, setReviewMs] = useState(1000);
|
|
8
|
+
const [requestTimeoutMs, setRequestTimeoutMs] = useState(5000);
|
|
9
|
+
const [receipts, setReceipts] = useState(0);
|
|
10
|
+
const settle = useRef<((accepted: boolean) => void) | null>(null);
|
|
11
|
+
const mode = useCloneMode({
|
|
12
|
+
reviewMs, requestTimeoutMs,
|
|
13
|
+
input: { scopeId: 'fixture-user:thread', context: { session_id: 'thread', context_revision: '1', messages: [] },
|
|
14
|
+
draft: '', enabled: true, busy: false },
|
|
15
|
+
transport: async request => ({
|
|
16
|
+
request_id: request.request_id, prediction_id: 'fixture-prediction', session_id: request.session_id,
|
|
17
|
+
connection_id: request.connection_id ?? null, draft_revision: 0, context_revision: request.context_revision,
|
|
18
|
+
profile_revision: '', grant_revision: 0, status: 'suggested', completion: 'Shorten the introduction.',
|
|
19
|
+
expires_at: Date.now() / 1000 + 60, context_truncated: false, usage: { prediction_units: 1 },
|
|
20
|
+
}),
|
|
21
|
+
onSubmit: async () => {
|
|
22
|
+
setReceipts(count => count + 1);
|
|
23
|
+
return new Promise<boolean>(resolve => { settle.current = resolve; });
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return <section aria-label="Options fixture">
|
|
27
|
+
<button onClick={() => mode.start()}>Start fixture</button>
|
|
28
|
+
<button onClick={() => setReviewMs(2000)}>Change reviewMs</button>
|
|
29
|
+
<button onClick={() => setRequestTimeoutMs(6000)}>Change requestTimeoutMs</button>
|
|
30
|
+
<button onClick={() => settle.current?.(true)}>Settle host receipt</button>
|
|
31
|
+
<p data-testid="fixture-status">{mode.state.status}: {mode.state.reason}</p>
|
|
32
|
+
<p data-testid="fixture-receipts">{receipts}</p>
|
|
33
|
+
</section>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fixture = document.createElement('div');
|
|
37
|
+
document.body.append(fixture);
|
|
38
|
+
createRoot(fixture).render(<StrictMode><OptionsFixture /></StrictMode>);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
for (const setting of ['reviewMs', 'requestTimeoutMs']) {
|
|
5
|
+
test(`changing ${setting} retains the pending host send guard`, async ({ page }) => {
|
|
6
|
+
await page.goto('/');
|
|
7
|
+
await page.addScriptTag({ type: 'module', url: `/@fs/${resolve('tests/browser/clone-mode-options.tsx')}` });
|
|
8
|
+
const fixture = page.getByRole('region', { name: 'Options fixture' });
|
|
9
|
+
await fixture.getByRole('button', { name: 'Start fixture' }).click();
|
|
10
|
+
await expect(fixture.getByTestId('fixture-status')).toContainText('submitting');
|
|
11
|
+
await expect(fixture.getByTestId('fixture-receipts')).toHaveText('1');
|
|
12
|
+
await fixture.getByRole('button', { name: `Change ${setting}`, exact: true }).click();
|
|
13
|
+
await expect(fixture.getByTestId('fixture-status')).toHaveText('stopped: settings_changed');
|
|
14
|
+
await fixture.getByRole('button', { name: 'Start fixture' }).click();
|
|
15
|
+
await page.waitForTimeout(2100);
|
|
16
|
+
await expect(fixture.getByTestId('fixture-receipts')).toHaveText('1');
|
|
17
|
+
await expect(fixture.getByTestId('fixture-status')).toHaveText('stopped: settings_changed');
|
|
18
|
+
await fixture.getByRole('button', { name: 'Settle host receipt' }).click();
|
|
19
|
+
await fixture.getByRole('button', { name: 'Start fixture' }).click();
|
|
20
|
+
await expect(fixture.getByTestId('fixture-receipts')).toHaveText('2');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test(`changing ${setting} cancels an unsent preview`, async ({ page }) => {
|
|
24
|
+
await page.goto('/');
|
|
25
|
+
await page.addScriptTag({ type: 'module', url: `/@fs/${resolve('tests/browser/clone-mode-options.tsx')}` });
|
|
26
|
+
const fixture = page.getByRole('region', { name: 'Options fixture' });
|
|
27
|
+
await fixture.getByRole('button', { name: 'Start fixture' }).click();
|
|
28
|
+
await expect(fixture.getByTestId('fixture-status')).toContainText('reviewing');
|
|
29
|
+
await fixture.getByRole('button', { name: `Change ${setting}`, exact: true }).click();
|
|
30
|
+
await expect(fixture.getByTestId('fixture-status')).toHaveText('stopped: settings_changed');
|
|
31
|
+
await page.waitForTimeout(2100);
|
|
32
|
+
await expect(fixture.getByTestId('fixture-receipts')).toHaveText('0');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('Clone mode waits for a host completion, and Stop cancels the next preview', async ({ page }) => {
|
|
37
|
+
await page.goto('/?clone-mode=1');
|
|
38
|
+
await expect(page.getByRole('status')).toContainText('off');
|
|
39
|
+
await page.getByRole('button', { name: 'Start Clone mode', exact: true }).click();
|
|
40
|
+
await expect(page.getByTestId('clone-preview')).toHaveText('이 영상을 30초로 줄이고 핵심 장면부터 보여줘.');
|
|
41
|
+
await expect(page.getByRole('heading', { name: 'Host send receipts: 0' })).toBeVisible();
|
|
42
|
+
await expect(page.getByRole('status')).toContainText('waiting', { timeout: 6000 });
|
|
43
|
+
await expect(page.getByRole('heading', { name: 'Host send receipts: 1' })).toBeVisible();
|
|
44
|
+
await page.waitForTimeout(3300);
|
|
45
|
+
await expect(page.getByRole('heading', { name: 'Host send receipts: 1' })).toBeVisible();
|
|
46
|
+
await page.getByRole('button', { name: 'Complete fixture agent turn' }).click();
|
|
47
|
+
await expect(page.getByRole('status')).toContainText('reviewing');
|
|
48
|
+
await page.getByRole('button', { name: 'Stop', exact: true }).click();
|
|
49
|
+
await expect(page.getByTestId('clone-preview')).toHaveCount(0);
|
|
50
|
+
await page.waitForTimeout(3300);
|
|
51
|
+
await expect(page.getByRole('heading', { name: 'Host send receipts: 1' })).toBeVisible();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('typing cancels automatic sending; a hung prediction never blocks manual send', async ({ page }) => {
|
|
55
|
+
await page.goto('/?clone-mode=1');
|
|
56
|
+
await page.getByRole('button', { name: 'Start Clone mode', exact: true }).click();
|
|
57
|
+
await expect(page.getByRole('status')).toContainText('reviewing');
|
|
58
|
+
const input = page.getByRole('textbox', { name: 'Instruction' });
|
|
59
|
+
await input.fill('직접 수정');
|
|
60
|
+
await expect(page.getByRole('status')).toContainText('user_input');
|
|
61
|
+
await page.getByLabel('Prediction service').selectOption('pending');
|
|
62
|
+
await input.fill('서버 장애 중에도 전송');
|
|
63
|
+
await page.waitForTimeout(1700);
|
|
64
|
+
await input.press('Enter');
|
|
65
|
+
await expect(page.getByRole('heading', { name: 'Host send receipts: 1' })).toBeVisible();
|
|
66
|
+
await expect(page.getByText('human: 서버 장애 중에도 전송')).toBeVisible();
|
|
67
|
+
await expect(page.getByTestId('clone-preview')).toHaveCount(0);
|
|
68
|
+
});
|