super-dsh 0.1.11 → 0.1.12
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 -6
- package/agent-agy/dist/agent.d.ts +4 -0
- package/agent-agy/dist/agent.js +41 -7
- package/agent-agy/dist/agy-cli-client.d.ts +12 -0
- package/agent-agy/dist/agy-cli-client.js +81 -13
- package/agent-agy/dist/index.js +13 -7
- package/agent-agy/package.json +5 -1
- package/agent-claude/dist/agent.js +67 -4
- package/agent-claude/dist/claude-client.js +46 -1
- package/agent-claude/dist/index.js +3 -0
- package/agent-claude/dist/sdk-runtime.js +2 -2
- package/agent-codex/dist/agent.js +33 -8
- package/agent-codex/dist/app-server-client.js +26 -3
- package/agent-codex/dist/app-server-protocol.js +6 -2
- package/agent-codex/dist/app-server-wire.js +23 -6
- package/agent-codex/dist/codex-native-config.js +6 -1
- package/agent-codex/dist/codex-runtime-fetch.js +293 -0
- package/agent-codex/dist/codex-runtime.js +12 -12
- package/agent-codex/dist/index.js +10 -4
- package/agent-codex/package.json +8 -3
- package/agent-codex-sdk/dist/sdk-runtime.js +1 -1
- package/agent-hermes/dist/hermes-client.js +44 -5
- package/agent-hermes/dist/index.js +10 -5
- package/agent-omp/dist/agent.js +36 -5
- package/agent-omp/dist/index.js +29 -6
- package/agent-omp/dist/lazy-rpc.js +11 -2
- package/agent-omp/dist/rpc-client.js +6 -1
- package/agent-pi/dist/agent.js +36 -9
- package/agent-pi/dist/index.js +6 -2
- package/agent-pi/dist/pi-client.js +13 -3
- package/cordis.patch.yml +10 -9
- package/package.json +6 -4
- package/superd-hub/dist/a2a-row.js +20 -3
- package/superd-hub/dist/connection-trust.d.ts +8 -0
- package/superd-hub/dist/connection-trust.js +16 -0
- package/superd-hub/dist/foreign-agy.d.ts +3 -0
- package/superd-hub/dist/foreign-agy.js +110 -0
- package/superd-hub/dist/foreign-claude.d.ts +4 -0
- package/superd-hub/dist/foreign-claude.js +160 -0
- package/superd-hub/dist/foreign-extension.d.ts +10 -0
- package/superd-hub/dist/foreign-extension.js +27 -0
- package/superd-hub/dist/foreign-hub.d.ts +27 -0
- package/superd-hub/dist/foreign-hub.js +243 -0
- package/superd-hub/dist/foreign-native.d.ts +4 -0
- package/superd-hub/dist/foreign-native.js +115 -0
- package/superd-hub/dist/web-trust.d.ts +14 -42
- package/superd-hub/dist/web-trust.js +18 -43
- package/superd-hub/package.json +14 -2
- package/agent-codex/README.md +0 -191
- package/agent-codex-sdk/README.md +0 -79
- package/agent-hermes/README.md +0 -145
- package/agent-omp/README.md +0 -94
- package/agent-omp-sdk/README.md +0 -103
- package/agent-pi/README.md +0 -67
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/** Native plugin delivery. No prompt hooks; all identity lives in the Hub binding. */
|
|
2
|
+
import { mkdir, readFile, readdir, writeFile, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
const owner = 'super-dsh:foreign-hub:v1\n';
|
|
6
|
+
const preparing = new Map();
|
|
7
|
+
async function serialized(home, work) {
|
|
8
|
+
const key = resolve(home);
|
|
9
|
+
const previous = preparing.get(key);
|
|
10
|
+
const current = (previous ?? Promise.resolve()).catch(() => { }).then(work);
|
|
11
|
+
preparing.set(key, current);
|
|
12
|
+
try {
|
|
13
|
+
return await current;
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
if (preparing.get(key) === current)
|
|
17
|
+
preparing.delete(key);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function atomicWrite(path, content) {
|
|
21
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
22
|
+
try {
|
|
23
|
+
await writeFile(temporary, content, { mode: 0o600, flag: 'wx' });
|
|
24
|
+
await rename(temporary, path);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await rm(temporary, { force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Called by the Hub host at mount, independently of any agent/tool use. */
|
|
31
|
+
export async function refreshHermesNativeEndpoint(endpoint, nativeHome) {
|
|
32
|
+
await serialized(nativeHome, async () => {
|
|
33
|
+
const directory = join(nativeHome, 'plugins', 'superd-hub');
|
|
34
|
+
if (await readFile(join(directory, '.superd-owner'), 'utf8').catch(() => '') !== owner)
|
|
35
|
+
return;
|
|
36
|
+
const config = JSON.parse(await readFile(join(directory, 'connection.json'), 'utf8'));
|
|
37
|
+
const url = await endpoint();
|
|
38
|
+
if (url !== undefined)
|
|
39
|
+
await atomicWrite(join(directory, 'connection.json'), JSON.stringify({ url, definition: config.definition }) + '\n');
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// A normal Hermes backend plugin, using only its documented registration seam.
|
|
43
|
+
// Public connection.json lets native sessions reach the post office's address
|
|
44
|
+
// error; the capability exists ONLY in the Super D child's environment.
|
|
45
|
+
const hermesModule = String.raw `import json
|
|
46
|
+
import os
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
from urllib.request import Request, urlopen
|
|
49
|
+
from urllib.error import HTTPError, URLError
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _public():
|
|
53
|
+
return json.loads((Path(__file__).parent / "connection.json").read_text())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _call(args, **kwargs):
|
|
57
|
+
try:
|
|
58
|
+
private = os.environ.get("SUPERD_HUB_SESSION")
|
|
59
|
+
config = json.loads(private) if private else _public()
|
|
60
|
+
url = config["url"]
|
|
61
|
+
if not url.startswith("http://127.0.0.1:"):
|
|
62
|
+
raise ValueError("Super D Hub requires a same-machine loopback URL")
|
|
63
|
+
headers = {"Content-Type": "application/json"}
|
|
64
|
+
if private:
|
|
65
|
+
headers["Authorization"] = "Bearer " + config["token"]
|
|
66
|
+
request = Request(url, data=json.dumps(args).encode(), headers=headers, method="POST")
|
|
67
|
+
try:
|
|
68
|
+
with urlopen(request, timeout=120) as response:
|
|
69
|
+
envelope = json.loads(response.read())
|
|
70
|
+
except HTTPError as error:
|
|
71
|
+
envelope = json.loads(error.read())
|
|
72
|
+
if "error" in envelope:
|
|
73
|
+
return json.dumps({"error": envelope["error"]}, ensure_ascii=False)
|
|
74
|
+
return json.dumps(envelope["result"], ensure_ascii=False)
|
|
75
|
+
except (OSError, URLError, ValueError, KeyError) as error:
|
|
76
|
+
return json.dumps({"error": "[E_HUB_UNAVAILABLE] " + str(error) + ". Open or resume this conversation through Super D."})
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def register(ctx):
|
|
80
|
+
definition = _public()["definition"]
|
|
81
|
+
ctx.register_tool(
|
|
82
|
+
name=definition["name"], toolset="superd_hub",
|
|
83
|
+
schema={"name": definition["name"], "description": definition["description"], "parameters": definition["inputSchema"]},
|
|
84
|
+
handler=_call,
|
|
85
|
+
)
|
|
86
|
+
`;
|
|
87
|
+
export async function prepareHermesNativePlugin(binding, nativeHome, enable) {
|
|
88
|
+
const transport = await binding.transport();
|
|
89
|
+
if (transport === undefined)
|
|
90
|
+
return undefined;
|
|
91
|
+
return serialized(nativeHome, async () => {
|
|
92
|
+
const directory = join(nativeHome, 'plugins', 'superd-hub');
|
|
93
|
+
let entries;
|
|
94
|
+
try {
|
|
95
|
+
entries = await readdir(directory);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error.code !== 'ENOENT')
|
|
99
|
+
throw error;
|
|
100
|
+
entries = [];
|
|
101
|
+
}
|
|
102
|
+
if (entries.length > 0) {
|
|
103
|
+
const marker = await readFile(join(directory, '.superd-owner'), 'utf8').catch(() => '');
|
|
104
|
+
if (marker !== owner)
|
|
105
|
+
throw new Error(`Refusing to overwrite unowned native plugin directory: ${directory}`);
|
|
106
|
+
}
|
|
107
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
108
|
+
await atomicWrite(join(directory, 'connection.json'), JSON.stringify({ url: transport.url, definition: transport.definition }) + '\n');
|
|
109
|
+
await atomicWrite(join(directory, '__init__.py'), hermesModule);
|
|
110
|
+
await atomicWrite(join(directory, 'plugin.yaml'), 'name: superd-hub\nversion: 1.0.0\ndescription: Substrate Hub facility for Super D conversations\nkind: backend\nprovides_tools:\n - superd-hub\n');
|
|
111
|
+
await atomicWrite(join(directory, '.superd-owner'), owner);
|
|
112
|
+
await enable('superd-hub');
|
|
113
|
+
return JSON.stringify(transport);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -1,41 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Why: when the Web UI is served on an operator-declared non-loopback
|
|
7
|
-
* authority (Caddy domain, LAN IP), the server side accepts the Host through
|
|
8
|
-
* `trustedHosts`, but the BROWSER-side connection client computes
|
|
9
|
-
* `ctx.connection.isLoopback` from `location.hostname` — non-loopback → the
|
|
10
|
-
* page degrades into the describe-mirror shape (Host settings persistence
|
|
11
|
-
* reports terminal `memory` unavailability). This component injects one
|
|
12
|
-
* inline `<head>` script through the webserver's public
|
|
13
|
-
* `webserver/index-inject` event (every index render re-emits; row data is
|
|
14
|
-
* read fresh at emit time, so a config reload needs no listener re-wiring):
|
|
15
|
-
* when the page's hostname is one the operator declared their own, it sets
|
|
16
|
-
* `window.__DSH_TRANSPORT__ = { ownsHost: true }` BEFORE any application
|
|
17
|
-
* bundle materializes — the connection client's only whole-repo consumer —
|
|
18
|
-
* restoring the upstream-documented shape for trusted authorities.
|
|
19
|
-
*
|
|
20
|
-
* The object deliberately carries NO fetch/openStream: the connection
|
|
21
|
-
* client's optional chaining then builds the default HTTP/WS transport, so
|
|
22
|
-
* the flag's only effect is the loopback verdict. An existing
|
|
23
|
-
* `__DSH_TRANSPORT__` (a worker shell that owns a real transport) is never
|
|
24
|
-
* overwritten.
|
|
25
|
-
*
|
|
26
|
-
* On/off (two independent levels, both standard):
|
|
27
|
-
* - row level: a profile layer with `- id: superd-web-trust / disabled: true`
|
|
28
|
-
* removes the component entirely;
|
|
29
|
-
* - config level: `trustedPageAuthorities: []` keeps the row mounted but
|
|
30
|
-
* injects nothing (fully inert).
|
|
31
|
-
*
|
|
32
|
-
* Composed on ctx0 only (since path-separation T-D): the `super-dsh/web-trust`
|
|
33
|
-
* row in the composite bundle's insert list. The per-world row retired with
|
|
34
|
-
* the world pages themselves (T-D disabled the world webui rows — no world
|
|
35
|
-
* page renders, so no world-local injection exists); `/hub` is the sole
|
|
36
|
-
* foreign entry and it serves ctx0's index, which this ctx0 row covers.
|
|
37
|
-
*
|
|
38
|
-
* @module @pgmi-builds/superd-hub/web-trust
|
|
2
|
+
* Browser ownership and opt-in authentication-only server admission.
|
|
3
|
+
* trustAllHosts skips Host/Origin restrictions while retaining native cookie
|
|
4
|
+
* authentication. Existing transports and declared-authority mode are preserved.
|
|
5
|
+
* Mounted on ctx0, whose connection also gates the /hub routes.
|
|
39
6
|
*/
|
|
40
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
41
8
|
import z from '@deepseek-ai/schemastery';
|
|
@@ -70,19 +37,23 @@ export interface BootScriptRow {
|
|
|
70
37
|
* inert-when-empty, hostile-string escaping).
|
|
71
38
|
*
|
|
72
39
|
* @param trustedPageAuthorities - the operator-declared hostnames.
|
|
73
|
-
* @
|
|
74
|
-
*
|
|
40
|
+
* @param trustAllHosts - enable browser ownership for every hostname.
|
|
41
|
+
* @returns script text, or undefined for an empty declared-authority list.
|
|
75
42
|
*/
|
|
76
|
-
export declare function buildTrustScript(trustedPageAuthorities: readonly string[] | undefined): string | undefined;
|
|
43
|
+
export declare function buildTrustScript(trustedPageAuthorities: readonly string[] | undefined, trustAllHosts?: boolean): string | undefined;
|
|
77
44
|
/** Row config schema. */
|
|
78
45
|
export interface TrustConfig {
|
|
46
|
+
/** Skip address fences on server and browser; native authentication remains. */
|
|
47
|
+
trustAllHosts?: boolean;
|
|
79
48
|
/** Hostnames this operator declares their own devices' pages run on. */
|
|
80
49
|
trustedPageAuthorities?: readonly string[];
|
|
81
50
|
}
|
|
82
51
|
/** Row config schema (schema-level default derives from the trusted-hosts env). */
|
|
83
52
|
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
53
|
+
trustAllHosts: z<boolean, boolean, "defined">;
|
|
84
54
|
trustedPageAuthorities: z<string[], string[], "defined">;
|
|
85
55
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
56
|
+
trustAllHosts: z<boolean, boolean, "defined">;
|
|
86
57
|
trustedPageAuthorities: z<string[], string[], "defined">;
|
|
87
58
|
}>>, "plain">;
|
|
88
59
|
/** Cordis plugin name. */
|
|
@@ -93,8 +64,7 @@ export declare const name = "superd-web-trust";
|
|
|
93
64
|
*/
|
|
94
65
|
export declare const inject: string[];
|
|
95
66
|
/**
|
|
96
|
-
* Mount
|
|
97
|
-
* pushing the rendered row.
|
|
67
|
+
* Mount browser ownership and, when opted in, native authentication-only admission.
|
|
98
68
|
* @param ctx - host plugin context.
|
|
99
69
|
* @param config - the row config.
|
|
100
70
|
*/
|
|
@@ -103,8 +73,10 @@ declare const _default: {
|
|
|
103
73
|
name: string;
|
|
104
74
|
inject: string[];
|
|
105
75
|
Config: z<Schemastery.ObjectS<NoInfer<{
|
|
76
|
+
trustAllHosts: z<boolean, boolean, "defined">;
|
|
106
77
|
trustedPageAuthorities: z<string[], string[], "defined">;
|
|
107
78
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
79
|
+
trustAllHosts: z<boolean, boolean, "defined">;
|
|
108
80
|
trustedPageAuthorities: z<string[], string[], "defined">;
|
|
109
81
|
}>>, "plain">;
|
|
110
82
|
apply: typeof apply;
|
|
@@ -1,43 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Why: when the Web UI is served on an operator-declared non-loopback
|
|
7
|
-
* authority (Caddy domain, LAN IP), the server side accepts the Host through
|
|
8
|
-
* `trustedHosts`, but the BROWSER-side connection client computes
|
|
9
|
-
* `ctx.connection.isLoopback` from `location.hostname` — non-loopback → the
|
|
10
|
-
* page degrades into the describe-mirror shape (Host settings persistence
|
|
11
|
-
* reports terminal `memory` unavailability). This component injects one
|
|
12
|
-
* inline `<head>` script through the webserver's public
|
|
13
|
-
* `webserver/index-inject` event (every index render re-emits; row data is
|
|
14
|
-
* read fresh at emit time, so a config reload needs no listener re-wiring):
|
|
15
|
-
* when the page's hostname is one the operator declared their own, it sets
|
|
16
|
-
* `window.__DSH_TRANSPORT__ = { ownsHost: true }` BEFORE any application
|
|
17
|
-
* bundle materializes — the connection client's only whole-repo consumer —
|
|
18
|
-
* restoring the upstream-documented shape for trusted authorities.
|
|
19
|
-
*
|
|
20
|
-
* The object deliberately carries NO fetch/openStream: the connection
|
|
21
|
-
* client's optional chaining then builds the default HTTP/WS transport, so
|
|
22
|
-
* the flag's only effect is the loopback verdict. An existing
|
|
23
|
-
* `__DSH_TRANSPORT__` (a worker shell that owns a real transport) is never
|
|
24
|
-
* overwritten.
|
|
25
|
-
*
|
|
26
|
-
* On/off (two independent levels, both standard):
|
|
27
|
-
* - row level: a profile layer with `- id: superd-web-trust / disabled: true`
|
|
28
|
-
* removes the component entirely;
|
|
29
|
-
* - config level: `trustedPageAuthorities: []` keeps the row mounted but
|
|
30
|
-
* injects nothing (fully inert).
|
|
31
|
-
*
|
|
32
|
-
* Composed on ctx0 only (since path-separation T-D): the `super-dsh/web-trust`
|
|
33
|
-
* row in the composite bundle's insert list. The per-world row retired with
|
|
34
|
-
* the world pages themselves (T-D disabled the world webui rows — no world
|
|
35
|
-
* page renders, so no world-local injection exists); `/hub` is the sole
|
|
36
|
-
* foreign entry and it serves ctx0's index, which this ctx0 row covers.
|
|
37
|
-
*
|
|
38
|
-
* @module @pgmi-builds/superd-hub/web-trust
|
|
2
|
+
* Browser ownership and opt-in authentication-only server admission.
|
|
3
|
+
* trustAllHosts skips Host/Origin restrictions while retaining native cookie
|
|
4
|
+
* authentication. Existing transports and declared-authority mode are preserved.
|
|
5
|
+
* Mounted on ctx0, whose connection also gates the /hub routes.
|
|
39
6
|
*/
|
|
40
7
|
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
import { installAuthenticationOnlyAdmission } from './connection-trust.js';
|
|
41
9
|
/**
|
|
42
10
|
* The schema-level default for `trustedPageAuthorities` (better-dsh v0.2.2a
|
|
43
11
|
* shape): derive from the trusted-hosts environment — the SAME single source
|
|
@@ -76,10 +44,12 @@ function frame(body) {
|
|
|
76
44
|
* inert-when-empty, hostile-string escaping).
|
|
77
45
|
*
|
|
78
46
|
* @param trustedPageAuthorities - the operator-declared hostnames.
|
|
79
|
-
* @
|
|
80
|
-
*
|
|
47
|
+
* @param trustAllHosts - enable browser ownership for every hostname.
|
|
48
|
+
* @returns script text, or undefined for an empty declared-authority list.
|
|
81
49
|
*/
|
|
82
|
-
export function buildTrustScript(trustedPageAuthorities) {
|
|
50
|
+
export function buildTrustScript(trustedPageAuthorities, trustAllHosts = false) {
|
|
51
|
+
if (trustAllHosts)
|
|
52
|
+
return frame(['if(!window.__DSH_TRANSPORT__)window.__DSH_TRANSPORT__={ownsHost:true};']);
|
|
83
53
|
const authorities = [...(trustedPageAuthorities ?? [])];
|
|
84
54
|
if (authorities.length === 0)
|
|
85
55
|
return undefined;
|
|
@@ -98,6 +68,7 @@ function trustedHostsEnv() {
|
|
|
98
68
|
}
|
|
99
69
|
/** Row config schema (schema-level default derives from the trusted-hosts env). */
|
|
100
70
|
export const Config = z.object({
|
|
71
|
+
trustAllHosts: z.boolean().default(false),
|
|
101
72
|
trustedPageAuthorities: z.array(String).default(deriveDefaultPageAuthorities(trustedHostsEnv())),
|
|
102
73
|
});
|
|
103
74
|
/** Cordis plugin name. */
|
|
@@ -108,16 +79,20 @@ export const name = 'superd-web-trust';
|
|
|
108
79
|
*/
|
|
109
80
|
export const inject = [];
|
|
110
81
|
/**
|
|
111
|
-
* Mount
|
|
112
|
-
* pushing the rendered row.
|
|
82
|
+
* Mount browser ownership and, when opted in, native authentication-only admission.
|
|
113
83
|
* @param ctx - host plugin context.
|
|
114
84
|
* @param config - the row config.
|
|
115
85
|
*/
|
|
116
86
|
export function apply(ctx, config) {
|
|
87
|
+
if (config?.trustAllHosts) {
|
|
88
|
+
ctx.inject(['connection'], owner => {
|
|
89
|
+
owner.effect(() => installAuthenticationOnlyAdmission(owner.connection), 'superd-web-trust: authentication-only admission');
|
|
90
|
+
});
|
|
91
|
+
}
|
|
117
92
|
const authorities = config?.trustedPageAuthorities;
|
|
118
93
|
ctx.inject(['webServer'], () => {
|
|
119
94
|
ctx.on('webserver/index-inject', (table) => {
|
|
120
|
-
const text = buildTrustScript(authorities);
|
|
95
|
+
const text = buildTrustScript(authorities, config?.trustAllHosts);
|
|
121
96
|
if (text !== undefined)
|
|
122
97
|
table.push({ kind: 'script', placement: 'head', text });
|
|
123
98
|
});
|
package/superd-hub/package.json
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
"./world": "./dist/world-entry.js",
|
|
10
10
|
"./join": "./dist/world-join.js",
|
|
11
11
|
"./web-trust": "./dist/web-trust.js",
|
|
12
|
-
"./a2a": "./dist/a2a-row.js"
|
|
12
|
+
"./a2a": "./dist/a2a-row.js",
|
|
13
|
+
"./foreign-hub": "./dist/foreign-hub.js",
|
|
14
|
+
"./foreign-native": "./dist/foreign-native.js",
|
|
15
|
+
"./foreign-claude": "./dist/foreign-claude.js",
|
|
16
|
+
"./foreign-agy": "./dist/foreign-agy.js"
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"dist",
|
|
@@ -38,14 +42,19 @@
|
|
|
38
42
|
"ws": "^8.21.0"
|
|
39
43
|
},
|
|
40
44
|
"peerDependencies": {
|
|
45
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
41
46
|
"@deepseek-ai/cordis": "*",
|
|
42
47
|
"@deepseek-ai/dsh-api-gateway": ">=0.1.5-0 <0.3.0-0",
|
|
43
48
|
"@deepseek-ai/dsh-home-paths": ">=0.1.5-0 <0.3.0-0",
|
|
44
49
|
"@deepseek-ai/dsh-host-webserver": ">=0.1.5-0 <0.3.0-0",
|
|
45
50
|
"@deepseek-ai/dsh-tools": ">=0.1.5-0 <0.3.0-0",
|
|
46
|
-
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0"
|
|
51
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.1.5-0 <0.3.0-0",
|
|
52
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.5-0 <0.3.0-0"
|
|
47
53
|
},
|
|
48
54
|
"peerDependenciesMeta": {
|
|
55
|
+
"@earendil-works/pi-coding-agent": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
49
58
|
"@deepseek-ai/cordis": {
|
|
50
59
|
"optional": true
|
|
51
60
|
},
|
|
@@ -63,6 +72,9 @@
|
|
|
63
72
|
},
|
|
64
73
|
"@deepseek-ai/dsh-typert-protocol": {
|
|
65
74
|
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@deepseek-ai/dsh-client-connection": {
|
|
77
|
+
"optional": true
|
|
66
78
|
}
|
|
67
79
|
},
|
|
68
80
|
"devDependencies": {
|
package/agent-codex/README.md
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# @pgmi-builds/agent-codex
|
|
2
|
-
|
|
3
|
-
superd 外壳下的 **Codex adapter**(super-dsh 线):把 Codex CLI 经由 **`codex app-server`**
|
|
4
|
-
接进 DSH —— 会话、事件、模型目录、审批、持久化全部按 DSH 语义呈现,WebUI 消费的是 DSH
|
|
5
|
-
服务提供的数据面。
|
|
6
|
-
|
|
7
|
-
> 通用规则(定位 / 两形态 / 测试准则 / 接线域 / per-agent 定制权 / home 与 profile 布局)
|
|
8
|
-
> 见上级目录的 `agent-adapter-dev-rules.md` —— 改本包前先读它。
|
|
9
|
-
> 迁移计划与实证:`docs/superpowers/plans/2026-10-03-agent-codex-app-server-migration.md`。
|
|
10
|
-
|
|
11
|
-
## 它是什么
|
|
12
|
-
|
|
13
|
-
- 一个**普通 dsh plugin bundle**:`package.json` 的 `dsh.bundle.patch` 指向本目录的
|
|
14
|
-
`cordis.patch.yml`(挂 codex provider、关掉它替代的原生行、重写权限三档表、pin 应用内目录选择器)。
|
|
15
|
-
- 一个 **AgentFactory**:`src/index.ts` 里的 `CodexProvider` 在 `ctx.agents` 注册工厂,
|
|
16
|
-
**每个会话一个 `codex app-server` 子进程**(`src/app-server-client.ts`);
|
|
17
|
-
`src/agent.ts` 把 app-server 通知流投影成 DSH 会话事件。
|
|
18
|
-
- **不包含** 任何 hub 耦合:世界挂载由 hub 侧驱动。本包自己就足以点亮一个完整的 web app。
|
|
19
|
-
|
|
20
|
-
## 线别(2026-10-03 迁移)
|
|
21
|
-
|
|
22
|
-
**app-server 线**是主路线(SDK 依赖已从本包与主 pack 移除)。SDK 另存为默认关闭的 `agent-codex-sdk`,拥有独立的按需 runtime 安装。
|
|
23
|
-
SDK 线在 adapter 里登记的一整排降级被 app-server 协议面直接消掉:
|
|
24
|
-
|
|
25
|
-
| 能力 | SDK 可选线 | app-server 主路线 |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| 打断 | 只有 AbortSignal(掐断本轮 stream) | **`turn/interrupt`** |
|
|
28
|
-
| steer | 无 → 排队 followUp 近似 | **`turn/steer`**(注入进行中的 turn) |
|
|
29
|
-
| 运行时换模型 | 无 → 下一轮 `resumeThread` | **`turn/start.model`**(下一轮起生效,线程不重建) |
|
|
30
|
-
| 运行时审批档 | 无 → launch-only 预设 | **`turn/start.approvalPolicy`**(折叠 `approval/policy`,`/permission` 可用) |
|
|
31
|
-
| 运行时沙箱档 | 无 → launch-only | **`turn/start.sandboxPolicy`**(折叠 `sandbox/mode`) |
|
|
32
|
-
| 审批人机回路 | 无(`sendRaw` 是 no-op) | **`*/requestApproval` → DSH `ctx.approval`**(Web UI ApprovalPanel) |
|
|
33
|
-
| 用户消息回显 | 无(SDK item 无 `user_message`) | `item/started userMessage` |
|
|
34
|
-
| token usage | 只在本轮 `turn/completed`,滞后 | `thread/tokenUsage/updated`(本轮 `turn_end` 附着) |
|
|
35
|
-
|
|
36
|
-
**未接(登记 backlog)**:`thread/compact/start`(`/compact` 仍显式拒绝)、
|
|
37
|
-
`thread/list` 会话清单(DSH 日志仍是权威)、`skills/list` / `mcpServerStatus/list` 面板、
|
|
38
|
-
`thread/fork`、`requestUserInput` / `mcpServer/elicitation`(fail-closed 应答)、
|
|
39
|
-
collab/subagent 项。
|
|
40
|
-
|
|
41
|
-
## 单独跑起来(首选开发/验收形态)
|
|
42
|
-
|
|
43
|
-
场景级验证唯一入口 = **整包 pack 线**(`super-dsh/test/start-4999.sh`)+ dev3 目标机实装
|
|
44
|
-
(2026-09-24「场景退役」裁决);本包不再有 standalone 启动脚本。
|
|
45
|
-
|
|
46
|
-
## Home 与 profile 布局(user 裁定)
|
|
47
|
-
|
|
48
|
-
| 位置 | 内容 |
|
|
49
|
-
|---|---|
|
|
50
|
-
| `$DSH_HOME`(测试 = `<repo>/.tests`) | 测试 DSH home |
|
|
51
|
-
| 原生 `~/.codex`(`CODEX_HOME` 才重定向) | **Codex 原生 app home**:`config.toml` / `auth.json` / 模型 catalog / rollout / state。adapter **绝不重定向**(红线 §5),**从不写入**(单向阀) |
|
|
52
|
-
| `$DSH_HOME/agents/codex` | 该 world 的 **DSH home**:DSH 会话日志副本、storages、`dsh-sessions.json` 映射 |
|
|
53
|
-
| `$DSH_HOME/profiles/superd-pub` | 整包线的 dsh profile(tarball 物理安装) |
|
|
54
|
-
|
|
55
|
-
## 关键实现点
|
|
56
|
-
|
|
57
|
-
- **lazy spawn(硬门)**:`CodexAppServerClient.create()` 零 IO、零子进程;`ensureStarted()`
|
|
58
|
-
在**首个真实 prompt** 才 spawn + `initialize` + `thread/start|resume`。查看/创建/resume
|
|
59
|
-
一个会话不产生任何 native 进程。单测直接钉住这条不变量(transport factory 调用计数 = 0)。
|
|
60
|
-
- **进程回收**:子进程 `detached: true`(自有进程组),close 时先 `SIGTERM` 进程组、
|
|
61
|
-
宽限后 `SIGKILL`,再 `child.kill()` 兜底——stdio MCP 后代 `setsid` 逃离时仍能被扫到。
|
|
62
|
-
- **session 身份 = 一对一映射**:DSH session id 是权威;Codex thread id 在**线程 attach**
|
|
63
|
-
时经 `onThreadAttached` 写入 `<dshHome>/dsh-sessions.json`(`src/session-map.ts`)。
|
|
64
|
-
resume 只经该映射重挂线程,查不到即 fail-closed。
|
|
65
|
-
- **DSH-native 持久化**:工厂按上游 agent-loop 的方式持有写通道(create `persistence.create` /
|
|
66
|
-
resume `persistence.open(id,'write')` → `read` → `interruptedTurnClosers` → `prepare`),
|
|
67
|
-
发布点刷未存后缀,dispose 时 `handle.close()` 排空。
|
|
68
|
-
- **每会话路由钉住**:create/resume 时同步钉住本会话路由(追加 `model/selection` +
|
|
69
|
-
`installModelSelection`),**不依赖全局默认模型的落盘时序**。
|
|
70
|
-
- **每轮权限折叠**:`agent.ts` 在每次 `turn/start` 前把会话日志的
|
|
71
|
-
`approval/policy` / `sandbox/mode`(无则由 preset 表兜底)折成 codex 覆盖
|
|
72
|
-
(`permission.ts` 的 `turnPolicyFromEvents`);`CODEX_APPROVAL_MODE` 只覆盖审批半边。
|
|
73
|
-
- **审批桥 fail-closed**:`app-server-approvals.ts` 把 server-request 映射到
|
|
74
|
-
`ctx.approval.request()`;无 answerer / 抛错 / 超时(`CODEX_APPROVAL_TIMEOUT_MS`,默认 5 分钟)
|
|
75
|
-
一律 **decline**,绝不让 codex 挂死。
|
|
76
|
-
- **事件映射**:`system/message`(rollout HEAD `base_instructions`)、`request/context`、
|
|
77
|
-
`assistant/message`(reasoning 用 `{type:'reasoning',text}`)、
|
|
78
|
-
`tool/call`(原始 JSON 串)/`tool/result`、`step/start|end`、`turn/start|end`、`todo/write`。
|
|
79
|
-
|
|
80
|
-
## 环境变量
|
|
81
|
-
|
|
82
|
-
| 变量 | 作用 |
|
|
83
|
-
|---|---|
|
|
84
|
-
| `CODEX_TRACE=1` | provider/agent/app-server 追踪(stderr) |
|
|
85
|
-
| `CODEX_BIN` | 显式覆盖 Codex 核心或 JS 入口;默认依序发现 desktop 核心、用户 CLI、包内 exact-pin fallback |
|
|
86
|
-
| `CODEX_HOME` | Codex 原生 home 重定向(**测试专用**;生产路径零重定向) |
|
|
87
|
-
| `CODEX_APPROVAL_MODE` | 覆盖审批档(`never`/`on-request`/`on-failure`/`untrusted`;非法值忽略) |
|
|
88
|
-
| `CODEX_APPROVAL_TIMEOUT_MS` | 审批应答超时;`0` 关闭;默认 300000 |
|
|
89
|
-
| `CODEX_IDLE_EXIT_MS` | 会话空闲后拆客户端;`0` 关闭;默认 600000 |
|
|
90
|
-
|
|
91
|
-
## Binary 与官方 OAuth
|
|
92
|
-
|
|
93
|
-
核心发现顺序是显式 `CODEX_BIN` → 已安装 desktop 核心 → 用户 CLI → 包内 `@openai/codex@0.160.0`。
|
|
94
|
-
发现和启动仅发生在真正 prompt 时。JS 入口经 Node 启动,原生核心直接执行;认证或生成失败不会静默换 provider。
|
|
95
|
-
desktop 优先选择核心版本,不代表复制 desktop 宿主:独立 app-server 使用 Super D 自有 clientInfo,
|
|
96
|
-
不借用父任务的 desktop 工具 pipe。browser/computer use 仍需要对应宿主服务与工具接线。
|
|
97
|
-
|
|
98
|
-
本地 `auth.json` 有 ChatGPT OAuth 时,模型选择器增加只读的 **OpenAI OAuth** provider。
|
|
99
|
-
adapter 正本仅存其 native 身份,token 留在 Codex home;选择该 route 时通过子进程配置覆盖 builtin `openai`,
|
|
100
|
-
不会改写用户的 config/auth。官方模型读取原生 `models_cache.json`,并保留原生配置的官方默认模型;第三方自定义目录不会当作官方目录。
|
|
101
|
-
没有原生目录冲突时不强制注入静态 catalog,让实际选中的 binary 按版本、身份与有效期维护 cache;有目录冲突时仅注入可消费的官方 cache,缺失时明确报错。原生默认模型的 ID 不代表当前 binary 的访问权限。OAuth logout 后显式选择必须失败,不能落到本地第三方默认配置。
|
|
102
|
-
|
|
103
|
-
## 测试
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
node super-dsh/scripts/pack.mjs # 仓根执行,唯一构建打包入口
|
|
107
|
-
DSH_HOME=<repo>/.tests \
|
|
108
|
-
SUPERD_DSH_ANCHOR=<repo>/upstream/deepseek-harness/apps/cli/package.json \
|
|
109
|
-
node --test super-dsh/agent-codex/test/*.test.mjs
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
单测全部是 fake:`test/fixtures/fake-app-server.mjs` 是一个**可编程的 app-server 替身**
|
|
113
|
-
(真 JSONL JSON-RPC 分帧、真子进程),wire / client 测试用真管道驱动它;投影器与审批桥是纯函数。
|
|
114
|
-
**不 spawn 真 codex、不打模型。**
|
|
115
|
-
|
|
116
|
-
## 已知边界(app-server 线 v1)
|
|
117
|
-
|
|
118
|
-
- `thread/compact/start` 未接 → `/compact` 仍以显式错误拒绝;
|
|
119
|
-
- 会话清单仍由 DSH 日志提供(不接 `thread/list`,遵守 mapping-only);
|
|
120
|
-
- `requestUserInput` / `mcpServer/elicitation` / `permissions/requestApproval` 一律 fail-closed;
|
|
121
|
-
- collab / subagent / `thread/fork` / skills / MCP 面板未接;
|
|
122
|
-
- 内存镜像 `getMessages()` 仅供诊断,权威转录是 DSH 会话日志。
|
|
123
|
-
|
|
124
|
-
## 多 provider 面(Hub + DSH,2026-10-04 重做)
|
|
125
|
-
|
|
126
|
-
Codex 是**单 provider** harness:它自己的 `config.toml` 只能写一个 `model_provider`。provider 的**清单**
|
|
127
|
-
在 Codex 家里无处安放——那正是 cc-switch 存在的理由;而「这个会话现在跑在哪个 provider 上」在 DSH
|
|
128
|
-
里已有公认答案(`session/selectModel` + 持久的 `model/selection`)。所以本线**只做清单**,选择交给 DSH:
|
|
129
|
-
|
|
130
|
-
- adapter 的 store 是**正本**(canonical),原生 `config.toml` 是**来源**(source),单向读入;
|
|
131
|
-
- UI 不再有「active provider」概念——界面里切换 provider/model 就是 DSH 自带模型选择器,与本仓其它
|
|
132
|
-
harness 完全一致;
|
|
133
|
-
- provider 由**进程参数**注入子进程(`-c model_providers.<id>.*`),**不写任何原生文件**。
|
|
134
|
-
|
|
135
|
-
### 分账
|
|
136
|
-
|
|
137
|
-
| 位置 | 内容 |
|
|
138
|
-
|---|---|
|
|
139
|
-
| `$DSH_HOME/agents/codex/codex-providers.json`(0600) | **正本**:provider 清单 + 被显式删除的 endpoint 墓碑(含 API key) |
|
|
140
|
-
| `$DSH_HOME/agents/codex/codex-catalogs/<id>.json` | 本 adapter 自有的 per-provider 模型目录;经 `-c model_catalog_json=<绝对路径>` 注入 |
|
|
141
|
-
| `~/.codex/config.toml` | 用户原生:**只读**。启动时 + 每 10 分钟读一次,按 endpoint URL 去重折进正本 |
|
|
142
|
-
| `~/.codex/auth.json` | 用户原生:**只读**。`apiKey` 形态的 key 走子进程环境,不走这里 |
|
|
143
|
-
|
|
144
|
-
### 同步(2026-10-04 user 裁决)
|
|
145
|
-
|
|
146
|
-
`syncFromNative()`:启动时 + 每 10 分钟(`SYNC_INTERVAL_MS = 600_000`)读 `config.toml` 的**全部**
|
|
147
|
-
`[model_providers.<id>]` 表,按 endpoint URL 去重折进正本。新 endpoint 在库里没有名字,**就用 URL 端点
|
|
148
|
-
当名字**(`label = baseUrl`)——Codex 的 catalog 是「当前在役 provider」专用的,所以非在役 provider 的
|
|
149
|
-
模型清单**确实无从得知**,此时存 `models: []`(面板显示「no models yet」,该 provider 不注册路由),
|
|
150
|
-
而不是编一个。同步只**增/刷**、永不删;唯一的例外是用户显式删除留下的**墓碑**(endpoint key),
|
|
151
|
-
否则「删除」会在十分钟后自己撤销。
|
|
152
|
-
|
|
153
|
-
### 选择与切换
|
|
154
|
-
|
|
155
|
-
- **路由 = DSH 的**:每个有模型的 provider 注册一条 `ctx.llm` 路由,于是 `session/modelCatalog` 天然
|
|
156
|
-
按 provider 分组,`session/selectModel` 天然持久(`model/selection` + `modelSelection` 投影);
|
|
157
|
-
- **turn 边界判定 = DSH 的**:agent 每轮按 `pending ?? request/header ?? agentDefaultModel ?? options`
|
|
158
|
-
解析路由(`codex-route.ts`),并回写 `request/header`——于是投影的 `lastUsed`/`next`、
|
|
159
|
-
`consumeSelection`、以及「会话上次跑在哪个 provider」全部由上游自洽,UI 重放自然准确;
|
|
160
|
-
- **中途切换不用打断**:`turn/start` 的 `model` 覆盖「本轮及后续轮」,所以换**模型**是原生的;换
|
|
161
|
-
**provider** 必须换子进程——`turn/start` 没有 provider 字段,而打给**已加载**线程的 `thread/resume`
|
|
162
|
-
是「接受但忽略」(2026-10-03 实测:要 `zhipu-cn` 回 `deepseek`)。故 agent 在**下一轮冷启动**时
|
|
163
|
-
kill 掉旧 child、用新 `-c` 起新 child、`thread/resume` **同一个 thread id**,历史与上下文完整保留。
|
|
164
|
-
turn 中发起的切换落在下一轮(DSH 的 pending 语义),**不打断在跑的 turn**。
|
|
165
|
-
|
|
166
|
-
### 启动注入(零原生写入)
|
|
167
|
-
|
|
168
|
-
`codex-launch.ts` 生成 argv/env;三条实测约束写死在注释里:
|
|
169
|
-
|
|
170
|
-
1. hyphenated id 在 `-c` 点路径里合法(`model_providers.zhipu-cn.…`)⇒ 原生表名可原样作 provider id;
|
|
171
|
-
2. `model_catalog_json` 作为 override 时按**进程 cwd** 解析 ⇒ 路径必须绝对(相对路径实测 `ENOENT`);
|
|
172
|
-
3. `env_key` 是唯一不经过 auth manager 的形态 ⇒ 库里的 key 以合成变量名(`SUPERD_CODEX_KEY_<ID>`)
|
|
173
|
-
经**子进程环境**交给 codex,`auth.json` 完全不碰。**这也顺带解决了 ChatGPT 登录遮蔽 keyed provider
|
|
174
|
-
的老问题**(`requires_openai_auth` 才会被 `auth_mode=chatgpt` 遮蔽);只有「seeded」形态
|
|
175
|
-
(`apiKey` 且 value 为空 = key 在 auth.json 里)才回落到 `requires_openai_auth`,面板仍会告警。
|
|
176
|
-
|
|
177
|
-
### RPC
|
|
178
|
-
|
|
179
|
-
`TypertRemoteService`,namespace `codexProviders`(hub 经 `/hub/api?agent=codex` 原样转发):
|
|
180
|
-
`get` / `save` / `remove`。**API key 只写不读**——wire 上只有 `hasKey`,绝不回传值。
|
|
181
|
-
没有 `select`:选择是 DSH 的 per-session 状态。
|
|
182
|
-
|
|
183
|
-
### 模型目录必须克隆模板
|
|
184
|
-
|
|
185
|
-
codex 0.154 对 catalog 条目要求 `slug`/`display_name`/`supported_reasoning_levels`/`shell_type`/
|
|
186
|
-
`visibility`/`supported_in_api`/`priority`/`support_verbosity` 以及**嵌套 struct** `truncation_policy`…
|
|
187
|
-
字段链未终止,故只从既有条目克隆并覆盖身份字段。目录是**替换式**:一份目录只写它那个 provider 的模型
|
|
188
|
-
(2026-10-03 修正:早期「吸收且永不丢弃」让 Chat 模型清单只增不减)。
|
|
189
|
-
|
|
190
|
-
记录:`docs/superpowers/plans/2026-10-03-codex-multi-provider-hub.md` +
|
|
191
|
-
`docs/test-reports/2026-10-04-codex-multi-provider-onthefly.md`。
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# @pgmi-builds/agent-codex-sdk
|
|
2
|
-
|
|
3
|
-
superd 外壳下的 **Codex adapter**(super-dsh 线,AW-E):把 Codex CLI 经由
|
|
4
|
-
`@openai/codex-sdk` 接进 DSH —— 会话、事件、模型目录、持久化全部按 DSH 语义呈现,
|
|
5
|
-
WebUI 消费的是 DSH 服务提供的数据面。
|
|
6
|
-
|
|
7
|
-
> 通用规则(定位 / 两形态 / 测试准则 / 接线域 / per-agent 定制权 / home 与 profile 布局)
|
|
8
|
-
> 见上级目录的 `agent-adapter-dev-rules.md` —— 改本包前先读它。
|
|
9
|
-
|
|
10
|
-
## 它是什么
|
|
11
|
-
|
|
12
|
-
- 一个**普通 dsh plugin bundle**:`package.json` 的 `dsh.bundle.patch` 指向本目录的
|
|
13
|
-
`cordis.patch.yml`(挂 codex provider、关掉它替代的原生行、重写权限三档表、pin 应用内目录选择器)。
|
|
14
|
-
- 一个 **AgentFactory**:`src/index.ts` 里的 `CodexProvider` 在 `ctx.agents` 注册工厂,
|
|
15
|
-
每个会话一个 `CodexSdkClient`(真 `@openai/codex-sdk` 线程);`src/agent.ts` 把 Codex 线程事件
|
|
16
|
-
投影成 DSH 会话事件。
|
|
17
|
-
- **带自己的 world 入口**:`src/world-plugin.ts`(`./world`,world key `codex-sdk`)把它作为
|
|
18
|
-
superd 世界挂载(`spawnWorld` + `worldMountPatches`),需要 hub 的 `@pgmi-builds/superd-hub`;
|
|
19
|
-
同一份 adapter 代码也可作为普通 bundle 点亮一个完整 web app(两形态一条代码路径)。
|
|
20
|
-
|
|
21
|
-
## 零声明依赖与运行时自取(2026-10-04 user 裁决)
|
|
22
|
-
|
|
23
|
-
本包 **不声明任何运行时依赖** —— `@openai/codex-sdk` / `@openai/codex` / `zod` 既不在
|
|
24
|
-
`dependencies`、也不在 `optionalDependencies`、也不在 `peerDependencies`(`dependencies` 恒为
|
|
25
|
-
`{}`),安装期零下载、零 postinstall。SDK 树在**首个真 prompt** 时才被 fetch 进
|
|
26
|
-
`<worldHome>/sdk-runtime/`(`src/sdk-runtime.ts`):
|
|
27
|
-
|
|
28
|
-
- marker `<worldHome>/sdk-runtime.json`(**最后写**:写不到就不算成功)、锁 `.lock`(mkdir 原子,
|
|
29
|
-
30 min 判陈旧可夺)、失败留 `.error.log`;
|
|
30
|
-
- marker 身份 = 本包版本 + 两个 pin,任一变化即重取;命中且 entry 存在则零网络短路;
|
|
31
|
-
- 装完立刻 smoke `import()` entry 并要求 `Codex` 构造器存在 —— 坏树在 fetch 时报错,不留到首 prompt;
|
|
32
|
-
- 预热(dev/运维):`npm run fetch-sdk-runtime [<home>]`(只接受仓内 `.tests` 下的 home)。
|
|
33
|
-
|
|
34
|
-
## 跑起来
|
|
35
|
-
|
|
36
|
-
本包默认 **disabled**(composite 行 `agent-codex-sdk`,打开该行即启用):行为级验证只走整包
|
|
37
|
-
pack 线(`super-dsh/test/start-4999.sh` + dev3 实装),不另起 per-agent 场景 rig(2026-09-24
|
|
38
|
-
场景退役,见 `agent-adapter-dev-rules.md` §2)。`CODEX_TRACE=1` 开 provider/agent 追踪。
|
|
39
|
-
## Home 与 profile 布局(user 裁定)
|
|
40
|
-
|
|
41
|
-
| 位置 | 内容 |
|
|
42
|
-
|---|---|
|
|
43
|
-
| `$DSH_HOME`(测试 = `<repo>/.tests`) | 测试 DSH home |
|
|
44
|
-
| `<DSH_HOME>/agents/codex-sdk` | 该世界的 **world DSH home**(`dsh-sessions.json` 映射等 adapter 态)+ **自取 SDK 运行时** `<worldHome>/sdk-runtime/`;codex app home 仍是原生 `~/.codex`(绝不重定向) |
|
|
45
|
-
| `$DSH_HOME/profiles/codex-sdk` | 该世界的 dsh profile(bundles + `cordis.patch.yml`) |
|
|
46
|
-
| `$DSH_HOME/sessions/...` | **DSH 会话日志**(上游 `session-persistence-jsonl`,zstd)——list/replay 都由此提供 |
|
|
47
|
-
|
|
48
|
-
**禁止**自造 per-app home(如 `.tests/codex-app`)。
|
|
49
|
-
|
|
50
|
-
## 关键实现点
|
|
51
|
-
|
|
52
|
-
- **session 身份 = 一对一映射**:DSH session id 是权威;Codex thread id 在其物化后写入
|
|
53
|
-
`<worldHome>/dsh-sessions.json`(`src/session-map.ts`)。resume 只经该映射重挂线程,查不到即
|
|
54
|
-
fail-closed。**不自建 rollout 扫描做 list,也不自解析 JSONL 做 replay。**
|
|
55
|
-
- **DSH-native 持久化**:工厂按上游 agent-loop 的方式持有写通道(create `persistence.create` /
|
|
56
|
-
resume `persistence.open(id,'write')` → `read` → `interruptedTurnClosers` → `prepare`),
|
|
57
|
-
发布点刷未存后缀,dispose 时 `handle.close()` 排空。
|
|
58
|
-
- **每会话路由钉住**:工厂在 create/resume 时同步钉住本会话路由(追加 `model/selection` +
|
|
59
|
-
`installModelSelection`),**不依赖全局默认模型的落盘时序**(共享 test home 里 settings 默认可能属别线)。
|
|
60
|
-
- **事件映射**:`system/message`(Codex 系统提示,rollout HEAD `base_instructions`)、
|
|
61
|
-
`request/context`(provider/model/contextWindow)、`assistant/message`(reasoning 用
|
|
62
|
-
`{type:'reasoning',text}`)、`tool/call`(原始 JSON 串)/`tool/result`、`step/start|end`、`turn/start|end`。
|
|
63
|
-
|
|
64
|
-
## 测试
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
npm run build # tsc strict
|
|
68
|
-
DSH_HOME=<repo>/.tests \
|
|
69
|
-
SUPERD_DSH_ANCHOR=<repo>/upstream/deepseek-harness/package.json \
|
|
70
|
-
node --test test/*.test.mjs
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## 已知边界(V1)
|
|
74
|
-
|
|
75
|
-
- 审批仅 launch-only 预设(SDK 无运行时审批;app-server 线才有 `*/requestApproval`);
|
|
76
|
-
- 无 steer / 无运行时换模型(下一轮生效)/ usage 滞后一轮 / 无 fork;
|
|
77
|
-
- `compaction/*`、`todo/write`、`subagent/*` 未接(见规则文档 §9.4 backlog);
|
|
78
|
-
- **world 入口**:`src/world-plugin.ts`(world key `codex-sdk`)已接线 `spawnWorld`;roster 条目
|
|
79
|
-
fiber-scoped、行关闭时 dispose 世界(2026-10-03 omp-sdk 验收踩到的两条卸载契约)。
|