@sleep2agi/agent-network 2.3.0-preview.4 → 2.3.0-preview.40
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 +35 -324
- package/dist/bin/anet.cjs +101 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +20 -1
- package/dist/bin/goal-wake-log-render.d.ts +31 -0
- package/dist/src/batch-workdir.d.ts +9 -0
- package/dist/src/bootstrap-password-db.d.ts +13 -0
- package/dist/src/channel-attachments.d.ts +29 -0
- package/dist/src/channel-task-trace.d.ts +10 -0
- package/dist/src/claude-vendor-env.d.ts +28 -0
- package/dist/src/cli-args.d.ts +8 -0
- package/dist/src/client-task-trace.d.ts +9 -0
- package/dist/src/client.js +1 -1
- package/dist/src/codex-model-default.d.ts +6 -0
- package/dist/src/controlled-upload.d.ts +83 -0
- package/dist/src/copresence-identity.d.ts +339 -0
- package/dist/src/dashboard-managed-process.d.ts +35 -0
- package/dist/src/environ-alias.d.ts +14 -0
- package/dist/src/grok-attach-client.d.ts +115 -0
- package/dist/src/grok-copresence-disclosure.d.ts +11 -0
- package/dist/src/grok-copresence-profile.d.ts +65 -0
- package/dist/src/im/feishu/adapter.d.ts +149 -1
- package/dist/src/im/feishu/bridge.d.ts +9 -0
- package/dist/src/im/feishu/config.d.ts +31 -0
- package/dist/src/im/feishu/hub-upload.d.ts +88 -0
- package/dist/src/im/feishu/markdown-image-renderer.d.ts +61 -0
- package/dist/src/im/feishu/outbound-marker.d.ts +140 -0
- package/dist/src/im/feishu/outbound-paths.d.ts +50 -0
- package/dist/src/im/feishu/outbound-route.d.ts +62 -0
- package/dist/src/im/feishu/worker-lifecycle.d.ts +13 -0
- package/dist/src/im/feishu/worker.js +391 -16
- package/dist/src/im/types.d.ts +38 -1
- package/dist/src/locale-diagnostic.d.ts +12 -0
- package/dist/src/mock-llm.d.ts +12 -0
- package/dist/src/node-server.js +7 -1
- package/dist/src/normalize-runtime.d.ts +14 -2
- package/dist/src/opencode-agent-node-pair.d.ts +14 -0
- package/dist/src/opencode-auth-login.d.ts +43 -0
- package/dist/src/opencode-launch-env.d.ts +13 -0
- package/dist/src/opencode-owner-mode.d.ts +6 -0
- package/dist/src/opencode-package-binary.d.ts +21 -0
- package/dist/src/opencode-pin.d.ts +44 -0
- package/dist/src/opencode-preset.d.ts +73 -0
- package/dist/src/opencode-runtime-binding.d.ts +30 -0
- package/dist/src/opencode-safe-root.d.ts +27 -0
- package/dist/src/opencode-smoke-env.d.ts +1 -0
- package/dist/src/outbound-tool-names.d.ts +1 -0
- package/dist/src/owner-env-file.d.ts +2 -0
- package/dist/src/package-mode-preflight.d.ts +31 -0
- package/dist/src/primary-network.d.ts +23 -0
- package/dist/src/private-state.d.ts +10 -0
- package/dist/src/project-key.d.ts +1 -0
- package/dist/src/secret-shell-guidance.d.ts +3 -0
- package/dist/src/task-trace.d.ts +33 -0
- package/dist/src/tmux-attach.d.ts +8 -0
- package/dist/src/tmux-capability.d.ts +58 -0
- package/dist/src/tmux-exact-target.d.ts +34 -0
- package/dist/src/tmux-pane-prompt.d.ts +22 -0
- package/dist/src/token-cli.d.ts +13 -0
- package/dist/src/unsafe-package-path-reason.d.ts +17 -0
- package/package.json +10 -6
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface WakeLogEntryShape {
|
|
2
|
+
ts?: string;
|
|
3
|
+
status?: string;
|
|
4
|
+
summary?: string;
|
|
5
|
+
task_id?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WakeLogGoalShape {
|
|
8
|
+
goal_id: string;
|
|
9
|
+
progress_log?: WakeLogEntryShape[];
|
|
10
|
+
}
|
|
11
|
+
export interface WakeLogRenderOpts {
|
|
12
|
+
/** Trim to the last N entries. undefined = all. */
|
|
13
|
+
tail?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Format progress_log as a JSON object suitable for `--json` output.
|
|
17
|
+
* Pure. Never reads/writes filesystem. Legacy goals without
|
|
18
|
+
* progress_log render as empty array (not error).
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderWakeLogJson(goal: WakeLogGoalShape, opts?: WakeLogRenderOpts): {
|
|
21
|
+
goal_id: string;
|
|
22
|
+
total: number;
|
|
23
|
+
returned: number;
|
|
24
|
+
entries: WakeLogEntryShape[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Format progress_log as a table for human console output.
|
|
28
|
+
* Column layout mirrors printGoalShow so the two commands read
|
|
29
|
+
* consistently. Empty log renders a single "(none)" line, not an error.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderWakeLogText(goal: WakeLogGoalShape, opts?: WakeLogRenderOpts): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve one batch workdir before any mkdir/chdir loop starts.
|
|
3
|
+
*
|
|
4
|
+
* Node does not expand shell tildes. Leaving `~/team` relative means every
|
|
5
|
+
* iteration can resolve it from the previous node's cwd and create paths such
|
|
6
|
+
* as `/work/~/team/~/team`. Only the current user's `~` form is supported;
|
|
7
|
+
* `~other` is rejected instead of being written as a literal directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeBatchWorkdir(input: string, cwd?: string, home?: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BootstrapPasswordUpdateInvocation {
|
|
2
|
+
argv: string[];
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
script: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the database selected for the local Hub into an explicit absolute
|
|
8
|
+
* path before launching the bootstrap helper. The helper must never infer a
|
|
9
|
+
* database from its own HOME/cwd: those can differ from the parent process.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveBootstrapDatabasePath(env: NodeJS.ProcessEnv, home: string, cwd: string): string;
|
|
12
|
+
export declare const BOOTSTRAP_PASSWORD_UPDATE_SCRIPT: string;
|
|
13
|
+
export declare function buildBootstrapPasswordUpdateInvocation(userId: string, dbPath: string, baseEnv?: NodeJS.ProcessEnv): BootstrapPasswordUpdateInvocation;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface AttachmentDescriptor {
|
|
2
|
+
type?: unknown;
|
|
3
|
+
file_id?: unknown;
|
|
4
|
+
path?: unknown;
|
|
5
|
+
mime?: unknown;
|
|
6
|
+
name?: unknown;
|
|
7
|
+
size?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface ChannelAttachmentDeps {
|
|
10
|
+
hubUrl: string;
|
|
11
|
+
authToken: string;
|
|
12
|
+
cacheDir: string;
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
maxBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface ChannelAttachmentResult {
|
|
17
|
+
paths: string[];
|
|
18
|
+
failures: Array<{
|
|
19
|
+
fileId: string;
|
|
20
|
+
code: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
export declare function readableAttachmentsFromInbox(message: any): AttachmentDescriptor[];
|
|
25
|
+
export declare function channelAttachmentCacheDir(home: string, alias: string): string;
|
|
26
|
+
export declare function downloadChannelAttachments(message: any, deps: ChannelAttachmentDeps): Promise<ChannelAttachmentResult>;
|
|
27
|
+
export declare const downloadChannelImageAttachments: typeof downloadChannelAttachments;
|
|
28
|
+
export declare function appendChannelAttachmentPaths(content: string, paths: readonly string[]): string;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function sendChannelTaskWithTrace(input: {
|
|
2
|
+
alias: string;
|
|
3
|
+
task: string;
|
|
4
|
+
priority?: string;
|
|
5
|
+
fromAlias: string;
|
|
6
|
+
networkId?: string | null;
|
|
7
|
+
}, deps: {
|
|
8
|
+
send: (args: Record<string, unknown>) => Promise<any>;
|
|
9
|
+
log: (line: string) => void;
|
|
10
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const CLAUDE_VENDOR_ENV_KEYS: readonly ["ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY"];
|
|
2
|
+
export type PlainSecretEnvRewrite = {
|
|
3
|
+
key: string;
|
|
4
|
+
refName: string;
|
|
5
|
+
value: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Plan the values that the node-create writer will move into its line-oriented
|
|
9
|
+
* `.env` file. This is deliberately side-effect free: callers can run it as a
|
|
10
|
+
* preflight before creating a node directory, changing process.env, or writing
|
|
11
|
+
* config.json. Keeping the line-break rejection here makes it cover every
|
|
12
|
+
* caller of the dotenv writer, including the no-name interactive wizard.
|
|
13
|
+
*/
|
|
14
|
+
export declare function planPlainSecretEnvRewrites(input: {
|
|
15
|
+
env: unknown;
|
|
16
|
+
nodeId: string;
|
|
17
|
+
}): PlainSecretEnvRewrite[];
|
|
18
|
+
/**
|
|
19
|
+
* Persist the exact Anthropic-compatible vendor environment used by an
|
|
20
|
+
* explicit claude-agent-sdk create. Explicit --env values retain precedence;
|
|
21
|
+
* ambient shell values only fill missing known keys. Other environment values
|
|
22
|
+
* are intentionally ignored so node create never snapshots the whole shell.
|
|
23
|
+
*/
|
|
24
|
+
export declare function collectClaudeVendorEnvForCreate(input: {
|
|
25
|
+
runtime: string;
|
|
26
|
+
explicitEnv: readonly string[];
|
|
27
|
+
shellEnv: Readonly<Record<string, string | undefined>>;
|
|
28
|
+
}): string[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ParsedCliOptions = {
|
|
2
|
+
[key: string]: string | string[];
|
|
3
|
+
_channels: string[];
|
|
4
|
+
_envs: string[];
|
|
5
|
+
};
|
|
6
|
+
export declare const BOOLEAN_FLAGS: Set<string>;
|
|
7
|
+
export declare function parseCliOptions(argv: string[]): ParsedCliOptions;
|
|
8
|
+
export declare function positionalArgs(argv: string[]): string[];
|
package/dist/src/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function a0_0x1b4a(_0x20b83e,_0x424c1c){_0x20b83e=_0x20b83e-0x1b9;const _0x1be9b5=a0_0x1be9();let _0x1b4afe=_0x1be9b5[_0x20b83e];return _0x1b4afe;}const a0_0x18f1eb=a0_0x1b4a;function a0_0x1be9(){const _0x53ac8f=['application/json','sdk-','processInbox','sdk','parse','body','ack_inbox','agent','text','POST','cwd','9FEzhgA','status','text/event-stream','send_reply','log','slice','min','connectSSE','new_task','new_message','tools/call','35826gtIMkF','report_status','reconnectDelay','send','205361RXDODK','match','type','messages','call','6315128hpwPVu','s...','heartbeat\x20failed:\x20','token','234GjDFCc','url','toTimeString','SSE\x20failed:\x20','alias','decode','pop','get_inbox','result','/mcp','57550NbZbyM','broadcast','SSE\x20connected','connect','connected','Authorization','12030630sgXszq','autoConnect','toString','getReader','data:\x20','sseAbort','content','signal','idle','error','emit',']\x20[commhub:','resumeId','4801734hWoLCt','message','getAllStatus','inbox\x20error:\x20','send_task','server','disconnected','reply','heartbeatInterval','from_session','Bearer\x20','2.0','send_message','2037516OMBPBZ','stringify','completed','registered','sleep','now','heartbeatTimer','AbortError','catch','running','118lrtnUT','offline'];a0_0x1be9=function(){return _0x53ac8f;};return a0_0x1be9();}(function(_0x1bbcb7,_0x59746d){const _0x17ce5b=a0_0x1b4a,_0x8590de=_0x1bbcb7();while(!![]){try{const _0x3684fa=-parseInt(_0x17ce5b(0x1ea))/0x1+parseInt(_0x17ce5b(0x1ce))/0x2*(-parseInt(_0x17ce5b(0x1e6))/0x3)+parseInt(_0x17ce5b(0x1c4))/0x4+parseInt(_0x17ce5b(0x1fd))/0x5*(-parseInt(_0x17ce5b(0x1f3))/0x6)+-parseInt(_0x17ce5b(0x210))/0x7+-parseInt(_0x17ce5b(0x1ef))/0x8*(-parseInt(_0x17ce5b(0x1db))/0x9)+parseInt(_0x17ce5b(0x203))/0xa;if(_0x3684fa===_0x59746d)break;else _0x8590de['push'](_0x8590de['shift']());}catch(_0x5b3a29){_0x8590de['push'](_0x8590de['shift']());}}}(a0_0x1be9,0x6f952));import{EventEmitter as a0_0x2ec4a1}from'events';import{hostname as a0_0xcedff1}from'os';class T extends a0_0x2ec4a1{[a0_0x18f1eb(0x1f4)];['alias'];[a0_0x18f1eb(0x1f2)];[a0_0x18f1eb(0x1d7)];[a0_0x18f1eb(0x20f)];['heartbeatInterval'];['reconnectDelay'];['heartbeatTimer'];[a0_0x18f1eb(0x208)];[a0_0x18f1eb(0x1cd)]=!0x1;constructor(_0x75a67){const _0x5655a=a0_0x18f1eb;super();if(this[_0x5655a(0x1f4)]=_0x75a67[_0x5655a(0x1f4)]['replace'](/\/$/,''),this['alias']=_0x75a67[_0x5655a(0x1f7)],this['token']=_0x75a67[_0x5655a(0x1f2)],this['agent']=_0x75a67[_0x5655a(0x1d7)]||_0x5655a(0x1d3),this['resumeId']=_0x5655a(0x1d1)+_0x75a67[_0x5655a(0x1f7)]+'-'+Date[_0x5655a(0x1c9)]()[_0x5655a(0x205)](0x24),this[_0x5655a(0x1bf)]=_0x75a67['heartbeatInterval']??0x2bf20,this['reconnectDelay']=_0x75a67[_0x5655a(0x1e8)]??0xbb8,_0x75a67[_0x5655a(0x204)]!==!0x1)this[_0x5655a(0x200)]();}[a0_0x18f1eb(0x1df)](_0x1239eb){const _0x408863=a0_0x18f1eb;console[_0x408863(0x1df)]('['+new Date()[_0x408863(0x1f5)]()['slice'](0x0,0x8)+_0x408863(0x20e)+this['alias']+']\x20'+_0x1239eb);}async[a0_0x18f1eb(0x1ee)](_0x28565e,_0x5c96aa){const _0x5347fc=a0_0x18f1eb;let _0x432bd8={'Content-Type':_0x5347fc(0x1d0),'Accept':'application/json,\x20text/event-stream'};if(this[_0x5347fc(0x1f2)])_0x432bd8[_0x5347fc(0x202)]=_0x5347fc(0x1c1)+this['token'];let _0x41a028=await(await fetch(this[_0x5347fc(0x1f4)]+_0x5347fc(0x1fc),{'method':_0x5347fc(0x1d9),'headers':_0x432bd8,'body':JSON[_0x5347fc(0x1c5)]({'jsonrpc':_0x5347fc(0x1c2),'id':Date[_0x5347fc(0x1c9)](),'method':_0x5347fc(0x1e5),'params':{'name':_0x28565e,'arguments':_0x5c96aa}})}))[_0x5347fc(0x1d8)](),_0x12da1a=_0x41a028[_0x5347fc(0x1eb)](/data: (.+)/),_0x2f5dbd=_0x12da1a?JSON[_0x5347fc(0x1d4)](_0x12da1a[0x1]):JSON[_0x5347fc(0x1d4)](_0x41a028),_0x438df3=_0x2f5dbd?.[_0x5347fc(0x1fb)]?.['content']?.[0x0]?.[_0x5347fc(0x1d8)];return _0x438df3?JSON[_0x5347fc(0x1d4)](_0x438df3):_0x2f5dbd;}async[a0_0x18f1eb(0x200)](){const _0x156b7e=a0_0x18f1eb;if(this['running'])return;this[_0x156b7e(0x1cd)]=!0x0,await this['status'](_0x156b7e(0x20b)),this[_0x156b7e(0x1df)](_0x156b7e(0x1c7)),this[_0x156b7e(0x1ca)]=setInterval(()=>{const _0xf9a1a2=_0x156b7e;this[_0xf9a1a2(0x1dc)](_0xf9a1a2(0x20b))[_0xf9a1a2(0x1cc)](_0x441651=>this[_0xf9a1a2(0x1df)](_0xf9a1a2(0x1f1)+_0x441651[_0xf9a1a2(0x211)]));},this[_0x156b7e(0x1bf)]),this[_0x156b7e(0x1e2)]();}async['disconnect'](){const _0x89c940=a0_0x18f1eb;if(this[_0x89c940(0x1cd)]=!0x1,this[_0x89c940(0x208)]?.['abort'](),this[_0x89c940(0x1ca)])clearInterval(this[_0x89c940(0x1ca)]);await this['status'](_0x89c940(0x1cf))[_0x89c940(0x1cc)](()=>{}),this[_0x89c940(0x1df)](_0x89c940(0x1bd));}async[a0_0x18f1eb(0x1e9)](_0x50240f,_0x5f075d,_0x540d03='normal'){const _0x504586=a0_0x18f1eb;return this['call'](_0x504586(0x1bb),{'alias':_0x50240f,'task':_0x5f075d,'priority':_0x540d03,'from_session':this['alias']});}async[a0_0x18f1eb(0x211)](_0x581196,_0x1e7262){const _0xd29dd9=a0_0x18f1eb;return this[_0xd29dd9(0x1ee)](_0xd29dd9(0x1c3),{'alias':_0x581196,'message':_0x1e7262,'from_session':this[_0xd29dd9(0x1f7)]});}async[a0_0x18f1eb(0x1be)](_0x480f4a,_0x408973,_0x1d4426=a0_0x18f1eb(0x1c6)){const _0x4f9f77=a0_0x18f1eb;return this[_0x4f9f77(0x1ee)](_0x4f9f77(0x1de),{'in_reply_to':_0x480f4a,'text':_0x408973,'status':_0x1d4426});}async[a0_0x18f1eb(0x1dc)](_0x141b38,_0x22c6d0){const _0x592027=a0_0x18f1eb;return this['call'](_0x592027(0x1e7),{'resume_id':this[_0x592027(0x20f)],'alias':this[_0x592027(0x1f7)],'status':_0x141b38,'server':a0_0xcedff1(),'hostname':a0_0xcedff1(),'agent':this[_0x592027(0x1d7)],'project_dir':process[_0x592027(0x1da)](),..._0x22c6d0});}async[a0_0x18f1eb(0x1b9)](){const _0x29fdbf=a0_0x18f1eb;return this[_0x29fdbf(0x1ee)]('get_all_status',{});}async[a0_0x18f1eb(0x1fe)](_0x2094b1,_0x138c23){const _0x221f79=a0_0x18f1eb;return this[_0x221f79(0x1ee)](_0x221f79(0x1fe),{'message':_0x2094b1,'filter_server':_0x138c23?.[_0x221f79(0x1bc)],'filter_status':_0x138c23?.[_0x221f79(0x1dc)]});}async[a0_0x18f1eb(0x1e2)](){const _0x5bd1b0=a0_0x18f1eb;let _0x99902=encodeURIComponent(this[_0x5bd1b0(0x1f7)]),_0x59f477=this[_0x5bd1b0(0x1f4)]+'/events/'+_0x99902,_0x1d1e41=this[_0x5bd1b0(0x1e8)];while(this[_0x5bd1b0(0x1cd)]){try{this[_0x5bd1b0(0x208)]=new AbortController();let _0x247406={'Accept':_0x5bd1b0(0x1dd)};if(this[_0x5bd1b0(0x1f2)])_0x247406[_0x5bd1b0(0x202)]='Bearer\x20'+this['token'];let _0x5afda3=await fetch(_0x59f477,{'headers':_0x247406,'signal':this[_0x5bd1b0(0x208)][_0x5bd1b0(0x20a)]});if(!_0x5afda3['ok']||!_0x5afda3[_0x5bd1b0(0x1d5)]){this[_0x5bd1b0(0x1df)](_0x5bd1b0(0x1f6)+_0x5afda3[_0x5bd1b0(0x1dc)]),await this[_0x5bd1b0(0x1c8)](_0x1d1e41),_0x1d1e41=Math[_0x5bd1b0(0x1e1)](_0x1d1e41*1.5,0xea60);continue;}_0x1d1e41=this[_0x5bd1b0(0x1e8)];let _0x1c50a0=_0x5afda3['body'][_0x5bd1b0(0x206)](),_0x22cd7d=new TextDecoder(),_0x3c7634='';while(this[_0x5bd1b0(0x1cd)]){let {done:_0x8ef69c,value:_0x58e2bf}=await _0x1c50a0['read']();if(_0x8ef69c)break;_0x3c7634+=_0x22cd7d[_0x5bd1b0(0x1f8)](_0x58e2bf,{'stream':!0x0});let _0x551170=_0x3c7634['split']('\x0a');_0x3c7634=_0x551170[_0x5bd1b0(0x1f9)]()||'';for(let _0x4581b3 of _0x551170){if(!_0x4581b3['startsWith'](_0x5bd1b0(0x207)))continue;try{let _0x320e27=JSON[_0x5bd1b0(0x1d4)](_0x4581b3[_0x5bd1b0(0x1e0)](0x6));if(_0x320e27[_0x5bd1b0(0x1ec)]==='connected'){this[_0x5bd1b0(0x1df)](_0x5bd1b0(0x1ff)),this['emit'](_0x5bd1b0(0x201));continue;}if(_0x320e27['type']===_0x5bd1b0(0x1e3)||_0x320e27['type']===_0x5bd1b0(0x1e4)||_0x320e27[_0x5bd1b0(0x1ec)]===_0x5bd1b0(0x1fe))await this[_0x5bd1b0(0x1d2)]();}catch{}}}}catch(_0x23760d){if(_0x23760d['name']===_0x5bd1b0(0x1cb))break;this[_0x5bd1b0(0x20d)](_0x5bd1b0(0x20c),_0x23760d),this[_0x5bd1b0(0x1df)]('SSE\x20error:\x20'+_0x23760d[_0x5bd1b0(0x211)]);}if(this[_0x5bd1b0(0x1cd)])this[_0x5bd1b0(0x20d)](_0x5bd1b0(0x1bd)),this[_0x5bd1b0(0x1df)]('SSE\x20reconnecting\x20in\x20'+_0x1d1e41/0x3e8+_0x5bd1b0(0x1f0)),await this['sleep'](_0x1d1e41),_0x1d1e41=Math[_0x5bd1b0(0x1e1)](_0x1d1e41*1.5,0xea60);}}async['processInbox'](){const _0x2decae=a0_0x18f1eb;try{let _0x19d6da=(await this[_0x2decae(0x1ee)](_0x2decae(0x1fa),{'alias':this[_0x2decae(0x1f7)],'limit':0xa}))?.[_0x2decae(0x1ed)]||[];for(let _0x4134f1 of _0x19d6da)await this[_0x2decae(0x1ee)](_0x2decae(0x1d6),{'alias':this['alias'],'message_id':_0x4134f1['id']}),this[_0x2decae(0x1df)]('←\x20'+_0x4134f1[_0x2decae(0x1c0)]+':\x20'+_0x4134f1[_0x2decae(0x209)]['slice'](0x0,0x3c)),this[_0x2decae(0x20d)]('task',_0x4134f1),this['emit']('message',_0x4134f1);}catch(_0x4e427b){this[_0x2decae(0x1df)](_0x2decae(0x1ba)+_0x4e427b[_0x2decae(0x211)]);}}['sleep'](_0x46346e){return new Promise(_0xc44451=>setTimeout(_0xc44451,_0x46346e));}}var $=T;export{$ as default,T as CommHub};
|
|
1
|
+
const a0_0x48272b=a0_0x4270;(function(_0x518421,_0x24257a){const _0x28eaa4=a0_0x4270,_0x3c9de8=_0x518421();while(!![]){try{const _0x586398=-parseInt(_0x28eaa4(0x242))/0x1+parseInt(_0x28eaa4(0x240))/0x2+parseInt(_0x28eaa4(0x229))/0x3*(parseInt(_0x28eaa4(0x24f))/0x4)+parseInt(_0x28eaa4(0x1ff))/0x5*(-parseInt(_0x28eaa4(0x263))/0x6)+parseInt(_0x28eaa4(0x22c))/0x7+parseInt(_0x28eaa4(0x214))/0x8*(parseInt(_0x28eaa4(0x262))/0x9)+-parseInt(_0x28eaa4(0x225))/0xa;if(_0x586398===_0x24257a)break;else _0x3c9de8['push'](_0x3c9de8['shift']());}catch(_0x51d780){_0x3c9de8['push'](_0x3c9de8['shift']());}}}(a0_0x4f75,0xd5184));import{EventEmitter as a0_0x5582f2}from'events';import{hostname as a0_0x56836f}from'os';var K=/(?:atok|ntok|utok|ghp|github_pat)_[A-Za-z0-9_-]+|Bearer\s+\S+/gi,L=(_0x552101,_0x16b042)=>(_0x552101??_0x16b042)[a0_0x48272b(0x230)](/[\r\n\x00-\x1f\x7f]/g,'\x20')['slice'](0x0,0xf0);function N(_0x53dcd9){const _0x2d194a=a0_0x48272b;return(_0x53dcd9 instanceof Error?_0x53dcd9['message']:String(_0x53dcd9??_0x2d194a(0x236)))[_0x2d194a(0x230)](K,_0x2d194a(0x258))[_0x2d194a(0x230)](/[\r\n\x00-\x1f\x7f]/g,'\x20')[_0x2d194a(0x260)](0x0,0xf0);}function O(_0x430111,_0x3b7071=process.env.ANET_TASK_TRACE_FORMAT===a0_0x48272b(0x23b)){const _0x37ee88=a0_0x48272b;if(_0x3b7071)return JSON[_0x37ee88(0x245)](_0x430111);let _0x4acdff=L(_0x430111['parent_task_id'],_0x37ee88(0x1ef)),_0x3add72=L(_0x430111[_0x37ee88(0x22e)],_0x37ee88(0x251)),_0x414faf=_0x430111['error_code']?_0x37ee88(0x219)+_0x430111['error_code']+':'+(_0x430111['error_message']??''):'';return _0x37ee88(0x228)+_0x430111[_0x37ee88(0x1fa)]+_0x37ee88(0x246)+L(_0x430111[_0x37ee88(0x22a)],_0x37ee88(0x244))+_0x37ee88(0x20b)+L(_0x430111[_0x37ee88(0x1f7)],_0x37ee88(0x244))+_0x37ee88(0x220)+_0x3add72+_0x37ee88(0x232)+_0x4acdff+_0x37ee88(0x1f5)+L(_0x430111['network_id'],_0x37ee88(0x244))+'\x20transport='+_0x430111[_0x37ee88(0x267)]+_0x37ee88(0x239)+_0x430111['duration_ms']+_0x37ee88(0x211)+_0x430111[_0x37ee88(0x204)]+_0x414faf;}function U(_0x1745a2){const _0x59ecd6=a0_0x48272b;return{'event':{'sending':_0x59ecd6(0x206),'delivered':_0x59ecd6(0x1f9),'failed':_0x59ecd6(0x1f2),'acked':_0x59ecd6(0x250),'started':_0x59ecd6(0x24e),'replied':'task.replied','expired':_0x59ecd6(0x1ee)}[_0x1745a2[_0x59ecd6(0x1fa)]],..._0x1745a2};}function a0_0x4f75(){const _0x3c7394=['name','heartbeatInterval','task.started','88qkXYlc','task.ack','<pending>','min','connectSSE','2.0','toString','getAllStatus','now','[REDACTED]','split','send_message','autoConnect','tools/call','catch','fromAlias','send','slice','application/json,\x20text/event-stream','27DpQXWL','541110YVYbgL','getReader','call','startsWith','transport','error','send_reply','result','task.expired','<missing>','idle','/mcp','task.send.failed','failed','completed','\x20network_id=','running','to_alias','log','task.send.delivered','status','type','resumeId','ack_inbox',']\x20[commhub:','80dPvtXV','task','sseAbort','read','lifecycleTracking','lifecycle_tracking','normal','task.send.start','alias','offline','message_id','report_status','\x20to=','send_rejected','CommHub\x20response\x20omitted\x20task_id','agent','broadcast','mcp_http','\x20lifecycle=','heartbeat\x20failed:\x20','Authorization','3354704AKYsOD','content','data:\x20','processInbox','toTimeString','\x20error=','send_task','new_message','connected','decode','SSE\x20failed:\x20','parentTaskId','\x20task_id=','disconnected','body','url','SSE\x20connected','1383540mYhJmK','connect','Bearer\x20','[commhub:task]\x20','41589tbyqgw','from_alias','token','5114235OYgWUU','signal','task_id','reconnectDelay','replace','get_inbox','\x20parent_task_id=','pop','heartbeatTimer','application/json','unknown\x20error','server','emit','\x20duration_ms=','delivered','json','networkId','sleep','inbox\x20error:\x20','get_all_status','518624iOrovF','text','98767TaVubW','/events/','<unknown>','stringify','\x20from=','parse','message','text/event-stream','sdk','toAlias'];a0_0x4f75=function(){return _0x3c7394;};return a0_0x4f75();}function j(_0xef2634){const _0xc62a7d=a0_0x48272b;let _0xcd4873=_0xef2634?.[_0xc62a7d(0x22e)]||_0xef2634?.[_0xc62a7d(0x209)]||_0xef2634?.['id'];if(_0xcd4873)return String(_0xcd4873);let _0x5cd0b5=_0xef2634?.[_0xc62a7d(0x215)]?.[0x0]?.['text'];if(typeof _0x5cd0b5!=='string')return null;try{let _0x36edf7=JSON['parse'](_0x5cd0b5);return _0x36edf7?.[_0xc62a7d(0x22e)]||_0x36edf7?.[_0xc62a7d(0x209)]||_0x36edf7?.['id']||null;}catch{return null;}}async function X(_0xfad233,_0x4feb00){const _0x4f15f1=a0_0x48272b;let _0x4f3335=_0x4feb00['now']??Date[_0x4f15f1(0x257)],_0x13b6c2=_0x4f3335(),_0x45e6fd=(_0xc4ef21,_0x5491ad,_0x9e9b6d,_0x10b7d8)=>{const _0x142824=_0x4f15f1;_0x4feb00[_0x142824(0x1f8)](O(U({'from_alias':_0xfad233[_0x142824(0x25e)],'to_alias':_0xfad233[_0x142824(0x24b)],'task_id':_0x5491ad,'parent_task_id':_0xfad233[_0x142824(0x21f)],'network_id':_0xfad233[_0x142824(0x23c)],'transport':_0xfad233[_0x142824(0x267)],'status':_0xc4ef21,'duration_ms':_0x4f3335()-_0x13b6c2,'lifecycle_tracking':_0xfad233[_0x142824(0x203)],..._0x9e9b6d!==void 0x0?{'error_code':_0x10b7d8||'send_failed','error_message':N(_0x9e9b6d)}:{}})));};_0x45e6fd('sending',null);try{let _0x26e393=await _0x4feb00[_0x4f15f1(0x25f)](),_0xc48db5=j(_0x26e393);if(_0xc48db5)_0x45e6fd(_0x4f15f1(0x23a),_0xc48db5);else{if(_0x26e393?.['ok']===!0x1||_0x26e393?.[_0x4f15f1(0x268)])_0x45e6fd(_0x4f15f1(0x1f3),_0xc48db5,_0x26e393?.[_0x4f15f1(0x268)]||'CommHub\x20rejected\x20task',_0x4f15f1(0x20c));else _0x45e6fd('failed',null,_0x4f15f1(0x20d),'missing_task_id');}return _0x26e393;}catch(_0x5e4fa3){throw _0x45e6fd(_0x4f15f1(0x1f3),null,_0x5e4fa3,'send_failed'),_0x5e4fa3;}}async function Y(_0x130bf6,_0x45cf15){const _0x8fe689=a0_0x48272b;return X({'fromAlias':_0x130bf6[_0x8fe689(0x25e)],'toAlias':_0x130bf6[_0x8fe689(0x207)],'parentTaskId':_0x130bf6[_0x8fe689(0x21f)]||null,'networkId':_0x130bf6[_0x8fe689(0x23c)]||null,'transport':_0x8fe689(0x210),'lifecycleTracking':'not_tracked'},_0x45cf15);}class $ extends a0_0x5582f2{['url'];[a0_0x48272b(0x207)];['token'];[a0_0x48272b(0x20e)];['resumeId'];[a0_0x48272b(0x24d)];[a0_0x48272b(0x22f)];[a0_0x48272b(0x234)];[a0_0x48272b(0x201)];[a0_0x48272b(0x1f6)]=!0x1;constructor(_0x9168e9){const _0x3cee55=a0_0x48272b;super();if(this['url']=_0x9168e9[_0x3cee55(0x223)][_0x3cee55(0x230)](/\/$/,''),this[_0x3cee55(0x207)]=_0x9168e9[_0x3cee55(0x207)],this[_0x3cee55(0x22b)]=_0x9168e9[_0x3cee55(0x22b)],this[_0x3cee55(0x20e)]=_0x9168e9[_0x3cee55(0x20e)]||_0x3cee55(0x24a),this[_0x3cee55(0x1fc)]='sdk-'+_0x9168e9[_0x3cee55(0x207)]+'-'+Date[_0x3cee55(0x257)]()[_0x3cee55(0x255)](0x24),this[_0x3cee55(0x24d)]=_0x9168e9[_0x3cee55(0x24d)]??0x2bf20,this[_0x3cee55(0x22f)]=_0x9168e9[_0x3cee55(0x22f)]??0xbb8,_0x9168e9[_0x3cee55(0x25b)]!==!0x1)this[_0x3cee55(0x226)]();}[a0_0x48272b(0x1f8)](_0x16c4a2){const _0x3f1fd5=a0_0x48272b;console[_0x3f1fd5(0x1f8)]('['+new Date()[_0x3f1fd5(0x218)]()['slice'](0x0,0x8)+_0x3f1fd5(0x1fe)+this['alias']+']\x20'+_0x16c4a2);}async[a0_0x48272b(0x265)](_0x3e61ec,_0x213d75){const _0x3a5cb0=a0_0x48272b;let _0x2fd48e={'Content-Type':_0x3a5cb0(0x235),'Accept':_0x3a5cb0(0x261)};if(this[_0x3a5cb0(0x22b)])_0x2fd48e['Authorization']=_0x3a5cb0(0x227)+this[_0x3a5cb0(0x22b)];let _0x103818=await(await fetch(this[_0x3a5cb0(0x223)]+_0x3a5cb0(0x1f1),{'method':'POST','headers':_0x2fd48e,'body':JSON[_0x3a5cb0(0x245)]({'jsonrpc':_0x3a5cb0(0x254),'id':Date[_0x3a5cb0(0x257)](),'method':_0x3a5cb0(0x25c),'params':{'name':_0x3e61ec,'arguments':_0x213d75}})}))[_0x3a5cb0(0x241)](),_0x5c0b84=_0x103818['match'](/data: (.+)/),_0x1831f2=_0x5c0b84?JSON[_0x3a5cb0(0x247)](_0x5c0b84[0x1]):JSON[_0x3a5cb0(0x247)](_0x103818),_0x2be163=_0x1831f2?.[_0x3a5cb0(0x1ed)]?.[_0x3a5cb0(0x215)]?.[0x0]?.[_0x3a5cb0(0x241)];return _0x2be163?JSON[_0x3a5cb0(0x247)](_0x2be163):_0x1831f2;}async[a0_0x48272b(0x226)](){const _0x4f17af=a0_0x48272b;if(this[_0x4f17af(0x1f6)])return;this[_0x4f17af(0x1f6)]=!0x0,await this[_0x4f17af(0x1fa)](_0x4f17af(0x1f0)),this['log']('registered'),this[_0x4f17af(0x234)]=setInterval(()=>{const _0x37bccc=_0x4f17af;this['status']('idle')['catch'](_0x52e170=>this[_0x37bccc(0x1f8)](_0x37bccc(0x212)+_0x52e170['message']));},this[_0x4f17af(0x24d)]),this[_0x4f17af(0x253)]();}async['disconnect'](){const _0x3164cc=a0_0x48272b;if(this[_0x3164cc(0x1f6)]=!0x1,this['sseAbort']?.['abort'](),this[_0x3164cc(0x234)])clearInterval(this['heartbeatTimer']);await this[_0x3164cc(0x1fa)](_0x3164cc(0x208))[_0x3164cc(0x25d)](()=>{}),this['log']('disconnected');}async[a0_0x48272b(0x25f)](_0x19d492,_0x51f329,_0x332591=a0_0x48272b(0x205)){const _0x6db59b=a0_0x48272b;return Y({'alias':_0x19d492,'fromAlias':this['alias']},{'log':_0x427ec5=>console[_0x6db59b(0x1f8)](_0x427ec5),'send':()=>this[_0x6db59b(0x265)](_0x6db59b(0x21a),{'alias':_0x19d492,'task':_0x51f329,'priority':_0x332591,'from_session':this[_0x6db59b(0x207)]})});}async['message'](_0x11f237,_0x527b95){const _0x4c312b=a0_0x48272b;return this['call'](_0x4c312b(0x25a),{'alias':_0x11f237,'message':_0x527b95,'from_session':this[_0x4c312b(0x207)]});}async['reply'](_0xb6822d,_0x4032f4,_0x55a2e0=a0_0x48272b(0x1f4)){const _0x20f505=a0_0x48272b;return this['call'](_0x20f505(0x269),{'in_reply_to':_0xb6822d,'text':_0x4032f4,'status':_0x55a2e0});}async[a0_0x48272b(0x1fa)](_0xdcd44d,_0xd200e7){const _0x16ca54=a0_0x48272b;return this[_0x16ca54(0x265)](_0x16ca54(0x20a),{'resume_id':this['resumeId'],'alias':this['alias'],'status':_0xdcd44d,'server':a0_0x56836f(),'hostname':a0_0x56836f(),'agent':this[_0x16ca54(0x20e)],'project_dir':process['cwd'](),..._0xd200e7});}async[a0_0x48272b(0x256)](){const _0x27abd0=a0_0x48272b;return this['call'](_0x27abd0(0x23f),{});}async[a0_0x48272b(0x20f)](_0x2f8faf,_0xd49d11){const _0x2136d8=a0_0x48272b;return this[_0x2136d8(0x265)]('broadcast',{'message':_0x2f8faf,'filter_server':_0xd49d11?.[_0x2136d8(0x237)],'filter_status':_0xd49d11?.[_0x2136d8(0x1fa)]});}async[a0_0x48272b(0x253)](){const _0x497fbb=a0_0x48272b;let _0x5f3279=encodeURIComponent(this[_0x497fbb(0x207)]),_0x19cb7d=this[_0x497fbb(0x223)]+_0x497fbb(0x243)+_0x5f3279,_0x3e2421=this[_0x497fbb(0x22f)];while(this[_0x497fbb(0x1f6)]){try{this[_0x497fbb(0x201)]=new AbortController();let _0x352782={'Accept':_0x497fbb(0x249)};if(this[_0x497fbb(0x22b)])_0x352782[_0x497fbb(0x213)]=_0x497fbb(0x227)+this['token'];let _0x30033=await fetch(_0x19cb7d,{'headers':_0x352782,'signal':this['sseAbort'][_0x497fbb(0x22d)]});if(!_0x30033['ok']||!_0x30033[_0x497fbb(0x222)]){this[_0x497fbb(0x1f8)](_0x497fbb(0x21e)+_0x30033['status']),await this[_0x497fbb(0x23d)](_0x3e2421),_0x3e2421=Math[_0x497fbb(0x252)](_0x3e2421*1.5,0xea60);continue;}_0x3e2421=this[_0x497fbb(0x22f)];let _0x2a0e20=_0x30033['body'][_0x497fbb(0x264)](),_0x189b3d=new TextDecoder(),_0x3f75e3='';while(this[_0x497fbb(0x1f6)]){let {done:_0x1a583b,value:_0x119e48}=await _0x2a0e20[_0x497fbb(0x202)]();if(_0x1a583b)break;_0x3f75e3+=_0x189b3d[_0x497fbb(0x21d)](_0x119e48,{'stream':!0x0});let _0x144efc=_0x3f75e3[_0x497fbb(0x259)]('\x0a');_0x3f75e3=_0x144efc[_0x497fbb(0x233)]()||'';for(let _0x5c2778 of _0x144efc){if(!_0x5c2778[_0x497fbb(0x266)](_0x497fbb(0x216)))continue;try{let _0x2d5dd3=JSON['parse'](_0x5c2778[_0x497fbb(0x260)](0x6));if(_0x2d5dd3['type']===_0x497fbb(0x21c)){this[_0x497fbb(0x1f8)](_0x497fbb(0x224)),this[_0x497fbb(0x238)](_0x497fbb(0x21c));continue;}if(_0x2d5dd3[_0x497fbb(0x1fb)]==='new_task'||_0x2d5dd3[_0x497fbb(0x1fb)]===_0x497fbb(0x21b)||_0x2d5dd3[_0x497fbb(0x1fb)]===_0x497fbb(0x20f))await this[_0x497fbb(0x217)]();}catch{}}}}catch(_0x234b32){if(_0x234b32[_0x497fbb(0x24c)]==='AbortError')break;this[_0x497fbb(0x238)](_0x497fbb(0x268),_0x234b32),this['log']('SSE\x20error:\x20'+_0x234b32[_0x497fbb(0x248)]);}if(this[_0x497fbb(0x1f6)])this[_0x497fbb(0x238)](_0x497fbb(0x221)),this[_0x497fbb(0x1f8)]('SSE\x20reconnecting\x20in\x20'+_0x3e2421/0x3e8+'s...'),await this[_0x497fbb(0x23d)](_0x3e2421),_0x3e2421=Math['min'](_0x3e2421*1.5,0xea60);}}async['processInbox'](){const _0x5473b0=a0_0x48272b;try{let _0x49d8ca=(await this[_0x5473b0(0x265)](_0x5473b0(0x231),{'alias':this[_0x5473b0(0x207)],'limit':0xa}))?.['messages']||[];for(let _0x5554a2 of _0x49d8ca)await this['call'](_0x5473b0(0x1fd),{'alias':this[_0x5473b0(0x207)],'message_id':_0x5554a2['id']}),this['log']('←\x20'+_0x5554a2['from_session']+':\x20'+_0x5554a2[_0x5473b0(0x215)][_0x5473b0(0x260)](0x0,0x3c)),this['emit'](_0x5473b0(0x200),_0x5554a2),this[_0x5473b0(0x238)](_0x5473b0(0x248),_0x5554a2);}catch(_0x2729c7){this['log'](_0x5473b0(0x23e)+_0x2729c7[_0x5473b0(0x248)]);}}['sleep'](_0x235230){return new Promise(_0x47d8e1=>setTimeout(_0x47d8e1,_0x235230));}}function a0_0x4270(_0x32b12a,_0x15415d){_0x32b12a=_0x32b12a-0x1ed;const _0x4f750b=a0_0x4f75();let _0x427085=_0x4f750b[_0x32b12a];return _0x427085;}var F=$;export{F as default,$ as CommHub};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #693 — token-bound node controlled local file → Hub /api/upload bridge.
|
|
3
|
+
*
|
|
4
|
+
* Security boundary (hard gates):
|
|
5
|
+
* 1. Cross-host: bytes travel over HTTP multipart; Hub never reads agent FS.
|
|
6
|
+
* 2. Identity: caller supplies the node's bearer token; ntok binds network.
|
|
7
|
+
* 3. Path safety: realpath + allowlisted roots; reject NUL, symlink, traversal.
|
|
8
|
+
* 4. Size ≤ 12 MiB enforced with same-fd fstat + bounded read (no full slurp).
|
|
9
|
+
* 5. Hub /api/upload is the sole write surface (atomic on hub side).
|
|
10
|
+
*
|
|
11
|
+
* Adversarial add-only (#694 DO-NOT-MERGE fixes):
|
|
12
|
+
* - TOCTOU: open O_NOFOLLOW → fstat(fd) → read(fd) only; no re-open by path.
|
|
13
|
+
* - Bounded read: never allocate/read past CONTROLLED_UPLOAD_MAX_BYTES.
|
|
14
|
+
* - NUL guard is live and reachable before any fs call.
|
|
15
|
+
*/
|
|
16
|
+
/** Mirrors server/src/uploads.ts MAX_UPLOAD_BYTES. */
|
|
17
|
+
export declare const CONTROLLED_UPLOAD_MAX_BYTES: number;
|
|
18
|
+
/** Chunk size for bounded streaming read from a single fd. */
|
|
19
|
+
export declare const CONTROLLED_UPLOAD_READ_CHUNK: number;
|
|
20
|
+
export type ControlledUploadErrorCode = "path_required" | "path_nul" | "path_not_found" | "path_untrusted" | "path_symlink" | "path_not_regular" | "path_empty" | "payload_too_large" | "read_failed" | "auth_required" | "hub_url_required" | "upload_failed" | "bad_response" | "missing_file_id";
|
|
21
|
+
export type ControlledUploadFail = {
|
|
22
|
+
ok: false;
|
|
23
|
+
error: ControlledUploadErrorCode;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
export type ControlledUploadOk = {
|
|
27
|
+
ok: true;
|
|
28
|
+
file_id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
mime: string;
|
|
31
|
+
size: number;
|
|
32
|
+
url?: string;
|
|
33
|
+
};
|
|
34
|
+
export type ControlledUploadResult = ControlledUploadOk | ControlledUploadFail;
|
|
35
|
+
export interface ControlledUploadDeps {
|
|
36
|
+
hubUrl: string;
|
|
37
|
+
authToken: string;
|
|
38
|
+
allowedRoots?: string[];
|
|
39
|
+
alias?: string;
|
|
40
|
+
nodeDir?: string;
|
|
41
|
+
fetch?: typeof fetch;
|
|
42
|
+
home?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Normalize a client filename: basename only, strip controls, cap 255. */
|
|
45
|
+
export declare function normalizeUploadName(input: string | undefined | null, fallback?: string): string;
|
|
46
|
+
export declare function defaultControlledUploadRoots(opts?: {
|
|
47
|
+
home?: string;
|
|
48
|
+
alias?: string;
|
|
49
|
+
nodeDir?: string;
|
|
50
|
+
cwd?: string;
|
|
51
|
+
env?: NodeJS.ProcessEnv;
|
|
52
|
+
}): string[];
|
|
53
|
+
/** True iff canonicalPath is strictly inside one of the allowlisted roots. */
|
|
54
|
+
export declare function isPathInsideAllowedRoots(canonicalPath: string, allowedRoots: string[]): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a user-supplied path to a canonical path under an allowlisted root.
|
|
57
|
+
* Does not open the file for reading — open+fstat+read is a separate same-fd step.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveControlledUploadPath(rawPath: string, allowedRoots: string[]): {
|
|
60
|
+
ok: true;
|
|
61
|
+
canonicalPath: string;
|
|
62
|
+
} | ControlledUploadFail;
|
|
63
|
+
export type ControlledFileBytes = {
|
|
64
|
+
ok: true;
|
|
65
|
+
bytes: Buffer;
|
|
66
|
+
size: number;
|
|
67
|
+
canonicalPath: string;
|
|
68
|
+
} | ControlledUploadFail;
|
|
69
|
+
/**
|
|
70
|
+
* Open → fstat(fd) → bounded read(fd) on the **same fd** (TOCTOU hard gate).
|
|
71
|
+
* Uses O_NOFOLLOW when available so a symlink swap at open is rejected.
|
|
72
|
+
* Never uses path-based readFileSync after the initial open.
|
|
73
|
+
*/
|
|
74
|
+
export declare function openFstatBoundedReadControlledFile(canonicalPath: string, allowedRoots: string[], maxBytes?: number): ControlledFileBytes;
|
|
75
|
+
/**
|
|
76
|
+
* Upload a controlled local file to Hub POST /api/upload.
|
|
77
|
+
* Returns file_id + metadata for send_reply attachments.
|
|
78
|
+
* Never falls back to sending a raw path to the Hub.
|
|
79
|
+
*/
|
|
80
|
+
export declare function uploadControlledLocalFile(rawPath: string, deps: ControlledUploadDeps, opts?: {
|
|
81
|
+
name?: string;
|
|
82
|
+
mime?: string;
|
|
83
|
+
}): Promise<ControlledUploadResult>;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
export interface SessionInfo {
|
|
2
|
+
tmux: string;
|
|
3
|
+
pid: number;
|
|
4
|
+
pgid: number;
|
|
5
|
+
starttime_jiffies: number;
|
|
6
|
+
}
|
|
7
|
+
export interface CopresenceMarker {
|
|
8
|
+
marker: string;
|
|
9
|
+
boot_id: string;
|
|
10
|
+
started_at_epoch_ms: number;
|
|
11
|
+
owner_uid: number;
|
|
12
|
+
sessions: {
|
|
13
|
+
appsrv?: SessionInfo;
|
|
14
|
+
bridge?: SessionInfo;
|
|
15
|
+
tui?: SessionInfo;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export type RefuseCause = "MISSING" | "SYMLINK" | "NOT_REGULAR" | "WRONG_MODE" | "OWNER_MISMATCH" | "PARSE_ERROR" | "SCHEMA_INVALID" | "STALE_BOOT_ID" | "PLATFORM_UNSUPPORTED";
|
|
19
|
+
export type ReadMarkerResult = {
|
|
20
|
+
kind: "ok";
|
|
21
|
+
marker: CopresenceMarker;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "refuse";
|
|
24
|
+
cause: RefuseCause;
|
|
25
|
+
detail: string;
|
|
26
|
+
};
|
|
27
|
+
export interface ProcStat {
|
|
28
|
+
pgid: number;
|
|
29
|
+
starttime_jiffies: number;
|
|
30
|
+
ppid: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Injectable primitive for /proc access. Real impl reads the filesystem;
|
|
34
|
+
* unit tests inject a mock so /proc-based logic is deterministically testable.
|
|
35
|
+
*
|
|
36
|
+
* Contract:
|
|
37
|
+
* - listAllPids: read /proc directory and return numeric-name entries.
|
|
38
|
+
* Throws on unrecoverable errors (permission denied on /proc itself).
|
|
39
|
+
* - readEnviron: read /proc/<pid>/environ as a raw string. Returns null
|
|
40
|
+
* if the pid has vanished (ENOENT) — that's a normal race, not a failure.
|
|
41
|
+
* Throws for permission errors etc.
|
|
42
|
+
* - readStat: read /proc/<pid>/stat and extract (pgid, starttime, ppid).
|
|
43
|
+
* Returns null on ENOENT (pid gone). Throws on other errors.
|
|
44
|
+
*/
|
|
45
|
+
export interface ProcessEnumerator {
|
|
46
|
+
listAllPids(): number[];
|
|
47
|
+
readEnviron(pid: number): string | null;
|
|
48
|
+
readStat(pid: number): ProcStat | null;
|
|
49
|
+
/**
|
|
50
|
+
* REAL uid of the process itself (`Uid:` line of /proc/<pid>/status,
|
|
51
|
+
* field 0). Used by the environ-EACCES discriminator to tell
|
|
52
|
+
* "other-user process (expected EACCES)" from "our own process we could
|
|
53
|
+
* not inspect (must be accounted for)".
|
|
54
|
+
*
|
|
55
|
+
* MUST NOT be implemented as `statSync('/proc/<pid>/environ').uid`.
|
|
56
|
+
* A process that called prctl(PR_SET_DUMPABLE, 0) keeps its real uid but
|
|
57
|
+
* its /proc/<pid>/{environ,mem,...} nodes flip to root:root 0400 (see
|
|
58
|
+
* proc(5) / kernel `task_dump_owner`). Deriving ownership from the
|
|
59
|
+
* environ inode therefore reports uid 0 for our OWN non-dumpable
|
|
60
|
+
* children, which made them invisible to the scan: not in `hits`, not in
|
|
61
|
+
* the unreadable list, so teardown reported success and deleted the
|
|
62
|
+
* marker while the orphan lived on (Defect A).
|
|
63
|
+
*
|
|
64
|
+
* Returns null if the pid is gone (ENOENT) or if even /proc/<pid>/status
|
|
65
|
+
* is blocked (hidepid / LSM) — in that case the process cannot be ours
|
|
66
|
+
* to reason about and is skipped.
|
|
67
|
+
*/
|
|
68
|
+
readOwnerUid(pid: number): number | null;
|
|
69
|
+
/**
|
|
70
|
+
* Single-character process state from /proc/<pid>/status (R/S/D/Z/T/...).
|
|
71
|
+
* `Z` means zombie — mm freed, environ returns EACCES even to owner, but
|
|
72
|
+
* the pid is going away shortly. Skip zombies during environ scan.
|
|
73
|
+
* Returns null if the pid is gone (ENOENT).
|
|
74
|
+
*/
|
|
75
|
+
readState(pid: number): string | null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Injectable primitive for sending signals. Real impl uses process.kill;
|
|
79
|
+
* tests inject a mock.
|
|
80
|
+
*/
|
|
81
|
+
export interface KillPrimitive {
|
|
82
|
+
killPgroup(pgid: number, signal: "TERM" | "KILL"): void;
|
|
83
|
+
/** Returns true if ANY process in the pgroup is still alive. */
|
|
84
|
+
pgroupAlive(pgid: number): boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare function markerFilePath(nodesDir: string, nodeId: string): string;
|
|
87
|
+
export declare function readBootId(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Write the marker file atomically.
|
|
90
|
+
*
|
|
91
|
+
* IMPORTANT (Blocker 1 fix): the uuid parameter is the SINGLE SOURCE OF
|
|
92
|
+
* TRUTH. Caller is expected to inject this same uuid into every tmux
|
|
93
|
+
* session's ANET_NODE_MARKER env var. writeMarker does NOT generate its
|
|
94
|
+
* own uuid — that was the exact bug in 9f2ec282.
|
|
95
|
+
*/
|
|
96
|
+
export declare function writeMarker(nodesDir: string, nodeId: string, uuid: string, sessions: CopresenceMarker["sessions"]): CopresenceMarker;
|
|
97
|
+
/**
|
|
98
|
+
* Read the marker file with structured fail-closed refuses.
|
|
99
|
+
*
|
|
100
|
+
* Never throws for expected refuse causes. Only throws if the OS itself
|
|
101
|
+
* is broken (e.g. EIO). Malformed JSON, wrong types, null bodies etc.
|
|
102
|
+
* all return { kind: "refuse", cause, detail }.
|
|
103
|
+
*/
|
|
104
|
+
export declare function readMarker(nodesDir: string, nodeId: string): ReadMarkerResult;
|
|
105
|
+
export declare function removeMarker(nodesDir: string, nodeId: string): void;
|
|
106
|
+
export declare function realEnumerator(): ProcessEnumerator;
|
|
107
|
+
export declare function realKiller(): KillPrimitive;
|
|
108
|
+
/**
|
|
109
|
+
* Scan /proc for all pids whose environ contains ANET_NODE_MARKER=<uuid>.
|
|
110
|
+
* This is the authoritative identity source — NOT the marker file's stored
|
|
111
|
+
* pids, which may be stale (main died, workers survived under new pgids).
|
|
112
|
+
*
|
|
113
|
+
* Bytes format of /proc/PID/environ: NUL-separated key=value pairs.
|
|
114
|
+
*
|
|
115
|
+
* Blocker 1 fix (independent audit 92d53c8f, 2026-07-29): /proc/<pid>/environ
|
|
116
|
+
* is 0400 owner-only. Blindly reading it will hit EACCES on every other-user
|
|
117
|
+
* process (pid 1 is systemd/root → guaranteed EACCES). The naive fix — wrap
|
|
118
|
+
* in try/catch and continue — would silently miss marker-carrying processes
|
|
119
|
+
* whose environ we can't read for some other reason, recreating Defect A
|
|
120
|
+
* ("nothing killed but report success"). Right fix:
|
|
121
|
+
*
|
|
122
|
+
* 1. Try readEnviron.
|
|
123
|
+
* 2. On EACCES, discriminate via readOwnerUid + readState:
|
|
124
|
+
* - Owner uid != ours → EXPECTED skip (can't be one of our procs).
|
|
125
|
+
* - Own uid, state = Z → EXPECTED skip (zombie, mm freed).
|
|
126
|
+
* - Own uid, not zombie → FAIL-CLOSED (unexplained EACCES on our own
|
|
127
|
+
* live process is a real problem, must throw).
|
|
128
|
+
*/
|
|
129
|
+
export interface ScanResult {
|
|
130
|
+
/** Pids whose environ we successfully read and matched the marker uuid. */
|
|
131
|
+
hits: number[];
|
|
132
|
+
/**
|
|
133
|
+
* Own-uid, live, environ-unreadable pids that are PLAUSIBLY PART OF THE
|
|
134
|
+
* COPRESENCE TREE (invariant 11 scope test). These might be marker-
|
|
135
|
+
* carrying without us being able to prove it, so the reap flow REFUSES
|
|
136
|
+
* marker removal while this list is non-empty — defense against Defect A
|
|
137
|
+
* (silently missing a marker-carrying process, then deleting the marker
|
|
138
|
+
* as if teardown had succeeded).
|
|
139
|
+
*/
|
|
140
|
+
unreadableOwnUid: number[];
|
|
141
|
+
/**
|
|
142
|
+
* Own-uid, live, environ-unreadable pids that failed the scope test:
|
|
143
|
+
* their pgid belongs to no marker-carrying group and their ppid chain
|
|
144
|
+
* reaches no marker carrier / validated marker-file session pid.
|
|
145
|
+
*
|
|
146
|
+
* INFORMATIONAL ONLY — never fail-closed. A machine routinely has such
|
|
147
|
+
* processes (a same-uid process whose primary GID differs from ours fails
|
|
148
|
+
* the kernel's __ptrace_may_access gid check and EACCESes on environ even
|
|
149
|
+
* though its real uid matches). v2 lumped these into unreadableOwnUid,
|
|
150
|
+
* which made reapMarkerGroups structurally unable to return success on
|
|
151
|
+
* any such host.
|
|
152
|
+
*
|
|
153
|
+
* KNOWN RESIDUAL GAP (stated rather than papered over): a marker-carrying
|
|
154
|
+
* process that is BOTH non-dumpable (environ unreadable) AND fully
|
|
155
|
+
* detached (setsid'd into its own pgroup with ppid=1, no live recorded
|
|
156
|
+
* session pid above it) lands here and is therefore not reaped and does
|
|
157
|
+
* not block marker removal. Nothing in /proc exposes the environment of a
|
|
158
|
+
* non-dumpable task to a non-root reader, so no scope widening can
|
|
159
|
+
* recover it — only running teardown as root could. Widening scope to
|
|
160
|
+
* "every same-uid unreadable process" is NOT an acceptable trade: that is
|
|
161
|
+
* exactly the v2 behaviour that made teardown never succeed. The
|
|
162
|
+
* realistic copresence shape (child of a recorded pane pid, or sharing a
|
|
163
|
+
* marker carrier's pgroup) IS covered — see the real-/proc test.
|
|
164
|
+
*/
|
|
165
|
+
unreadableOutOfScope: number[];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Marker-file session records used to anchor the invariant-11 scope test.
|
|
169
|
+
* Each entry must be re-validated (pid alive AND same starttime) before it
|
|
170
|
+
* is trusted — see validateAnchors.
|
|
171
|
+
*/
|
|
172
|
+
export interface ScanAnchors {
|
|
173
|
+
sessions?: Array<{
|
|
174
|
+
pid: number;
|
|
175
|
+
starttime_jiffies: number;
|
|
176
|
+
}>;
|
|
177
|
+
}
|
|
178
|
+
export declare function anchorsFromMarker(marker: CopresenceMarker): ScanAnchors;
|
|
179
|
+
/**
|
|
180
|
+
* Invariant 11 scope test: is `pid` plausibly part of the copresence tree?
|
|
181
|
+
*
|
|
182
|
+
* True when the pid IS an anchor, when its pgid belongs to a marker-carrying
|
|
183
|
+
* group (or a validated anchor's group), or when walking its ppid chain
|
|
184
|
+
* reaches an anchor / marker carrier / a pid inside a relevant pgroup.
|
|
185
|
+
*
|
|
186
|
+
* Everything else is out of scope and must not participate in a fail-closed
|
|
187
|
+
* decision, no matter how unreadable it is.
|
|
188
|
+
*/
|
|
189
|
+
export declare function isInCopresenceScope(enumer: ProcessEnumerator, pid: number, relevantPids: Set<number>, relevantPgids: Set<number>): boolean;
|
|
190
|
+
export declare function scanEnvironForMarker(enumer: ProcessEnumerator, markerUuid: string): number[];
|
|
191
|
+
export declare function scanEnvironForMarkerFull(enumer: ProcessEnumerator, markerUuid: string, anchors?: ScanAnchors): ScanResult;
|
|
192
|
+
export declare function groupPidsByPgid(enumer: ProcessEnumerator, pids: number[]): Map<number, number[]>;
|
|
193
|
+
export type HomogeneityResult = {
|
|
194
|
+
ok: true;
|
|
195
|
+
members: number[];
|
|
196
|
+
} | {
|
|
197
|
+
ok: false;
|
|
198
|
+
cause: "FOREIGN_MEMBER" | "ENUM_ERROR" | "EMPTY_GROUP";
|
|
199
|
+
foreignPids: number[];
|
|
200
|
+
unreadablePids: number[];
|
|
201
|
+
detail: string;
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* Verify that EVERY live member of `pgid` carries the marker.
|
|
205
|
+
*
|
|
206
|
+
* Blocker 2 fix: enumeration errors surface as ENUM_ERROR (fail-closed),
|
|
207
|
+
* never as an empty-list judged "safe". Any unreadable member also
|
|
208
|
+
* fails-closed. Empty group (no live members at all) also refuses — killing
|
|
209
|
+
* an empty pgroup can't be right and empty-list judged "ok" recreates the
|
|
210
|
+
* exact defect shape (independent audit 92d53c8f finding #6).
|
|
211
|
+
*
|
|
212
|
+
* Blocker 2 second half (zombie): a zombie same-uid process's environ
|
|
213
|
+
* returns EACCES even to owner (mm freed), but stat still exists with
|
|
214
|
+
* original pgid. Without discrimination, the zombie would be treated as
|
|
215
|
+
* "unreadable member" → ENUM_ERROR → entire group SKIPPED. Since re-verify
|
|
216
|
+
* happens post-SIGTERM (when zombies are most numerous), teardown would
|
|
217
|
+
* never escalate. Fix: apply the same owner-uid + state=Z discriminator as
|
|
218
|
+
* scanEnvironForMarker.
|
|
219
|
+
*
|
|
220
|
+
* Algorithm:
|
|
221
|
+
* 1. Enumerate all pids on the system.
|
|
222
|
+
* 2. Filter to pids whose /proc/PID/stat pgid == target pgid.
|
|
223
|
+
* 3. For each such pid, check its environ with EACCES discrimination:
|
|
224
|
+
* - Other-user EACCES → skip (can't be ours anyway).
|
|
225
|
+
* - Own-uid zombie → skip (expected, dying).
|
|
226
|
+
* - Own-uid live EACCES → truly unreadable → fail-closed.
|
|
227
|
+
* - Missing marker → foreign member → fail-closed.
|
|
228
|
+
*/
|
|
229
|
+
export declare function verifyGroupHomogeneity(enumer: ProcessEnumerator, pgid: number, markerUuid: string): HomogeneityResult;
|
|
230
|
+
/**
|
|
231
|
+
* Walk the caller's ancestry (self → PPID → grandparent → ...) and check
|
|
232
|
+
* whether any ancestor carries the target marker. If yes → caller is
|
|
233
|
+
* running inside the copresence tree we're about to kill, and would take
|
|
234
|
+
* itself down.
|
|
235
|
+
*/
|
|
236
|
+
export declare function callerCarriesMarker(enumer: ProcessEnumerator, markerUuid: string): {
|
|
237
|
+
self: boolean;
|
|
238
|
+
ancestorPid?: number;
|
|
239
|
+
};
|
|
240
|
+
export type ReapResult = {
|
|
241
|
+
kind: "success";
|
|
242
|
+
killedPgids: number[];
|
|
243
|
+
residualPids: number[];
|
|
244
|
+
unreadableOwnUid?: number[];
|
|
245
|
+
} | {
|
|
246
|
+
kind: "failed";
|
|
247
|
+
killedPgids: number[];
|
|
248
|
+
residualPids: number[];
|
|
249
|
+
skippedGroups: Array<{
|
|
250
|
+
pgid: number;
|
|
251
|
+
reason: string;
|
|
252
|
+
}>;
|
|
253
|
+
detail: string;
|
|
254
|
+
unreadableOwnUid?: number[];
|
|
255
|
+
};
|
|
256
|
+
export interface ReapOptions {
|
|
257
|
+
graceMs: number;
|
|
258
|
+
logger: (msg: string) => void;
|
|
259
|
+
/**
|
|
260
|
+
* Sleep primitive (grace period). Real code uses setTimeout; tests inject
|
|
261
|
+
* a fast/deterministic sleep so grace paths are actually covered. Prior
|
|
262
|
+
* impl was a busy-wait `while (Date.now() < end) {}` — audit 92d53c8f
|
|
263
|
+
* finding #3 flagged that as pinning one CPU core for graceMs and blocking
|
|
264
|
+
* the event loop; test coverage missed it because tests passed graceMs=0.
|
|
265
|
+
*/
|
|
266
|
+
sleep?: (ms: number) => Promise<void>;
|
|
267
|
+
/**
|
|
268
|
+
* Invariant-11 scope anchors, normally `anchorsFromMarker(marker)`.
|
|
269
|
+
*
|
|
270
|
+
* Without them the only anchors are the marker carriers the scan itself
|
|
271
|
+
* found, so a marker-carrying process we cannot READ (non-dumpable) but
|
|
272
|
+
* whose parent IS a recorded session pid would be judged out of scope and
|
|
273
|
+
* silently dropped. Passing the marker's recorded session pids closes that
|
|
274
|
+
* hole; each one is re-validated (alive + same starttime) before it can
|
|
275
|
+
* widen scope, so a stale/recycled pid cannot.
|
|
276
|
+
*/
|
|
277
|
+
anchors?: ScanAnchors;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Full reap flow with TOCTOU re-verification.
|
|
281
|
+
*
|
|
282
|
+
* Sequence:
|
|
283
|
+
* 1. Environ scan for marker → get all live pids carrying it.
|
|
284
|
+
* 2. Group by current PGID.
|
|
285
|
+
* 3. For each group: verify homogeneity. Skip on foreign/unreadable.
|
|
286
|
+
* 4. For each verified group: send SIGTERM to pgroup.
|
|
287
|
+
* 5. Wait grace.
|
|
288
|
+
* 6. Re-verify each group (marker still there, no new foreign) before SIGKILL.
|
|
289
|
+
* 7. Send SIGKILL to any group still alive.
|
|
290
|
+
* 8. Post-rescan: if any marker-carrying pid alive → preserve marker.
|
|
291
|
+
*/
|
|
292
|
+
export declare function reapMarkerGroups(enumer: ProcessEnumerator, killer: KillPrimitive, markerUuid: string, opts: ReapOptions): Promise<ReapResult>;
|
|
293
|
+
export interface PrepareStartDeps {
|
|
294
|
+
/** Usually () => readMarker(nodesDir, nodeId). */
|
|
295
|
+
readMarker(): ReadMarkerResult;
|
|
296
|
+
/** Usually (uuid, anchors) => reapMarkerGroups(realEnumerator(), realKiller(), uuid, {...anchors}). */
|
|
297
|
+
reap(uuid: string, anchors: ScanAnchors): Promise<ReapResult>;
|
|
298
|
+
/** Usually () => removeMarker(nodesDir, nodeId). */
|
|
299
|
+
removeMarker(): void;
|
|
300
|
+
/** Usually (uuid, sessions) => writeMarker(nodesDir, nodeId, uuid, sessions). */
|
|
301
|
+
writeMarker(uuid: string, sessions: CopresenceMarker["sessions"]): void;
|
|
302
|
+
logger(msg: string): void;
|
|
303
|
+
}
|
|
304
|
+
export type PrepareStartResult = {
|
|
305
|
+
kind: "ok";
|
|
306
|
+
reclaimedUuid?: string;
|
|
307
|
+
} | {
|
|
308
|
+
kind: "blocked";
|
|
309
|
+
cause: "STALE_TREE_ALIVE" | "UNUSABLE_MARKER";
|
|
310
|
+
detail: string;
|
|
311
|
+
remedy: string;
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Everything that must happen BEFORE the first `tmux new-session` of a
|
|
315
|
+
* copresence start. Extracted out of cli.ts so both blockers below are
|
|
316
|
+
* actually reachable by tests — the audit's point that the two fatal
|
|
317
|
+
* defects of the previous rounds both lived in an untested cli.ts seam.
|
|
318
|
+
*
|
|
319
|
+
* Blocker 5 — marker-before-tmux ordering.
|
|
320
|
+
* v2 wrote the marker only after the app-server had bound its port, i.e.
|
|
321
|
+
* after a 25s wait and after several `process.exit(1)` paths. A start that
|
|
322
|
+
* died in that window left a live, marker-carrying tmux session behind
|
|
323
|
+
* with NO marker file on disk: the exact unreclaimable-ghost this feature
|
|
324
|
+
* exists to prevent. The marker is now written here, before any session is
|
|
325
|
+
* created, with an empty `sessions` object — reap identity has always been
|
|
326
|
+
* the environ uuid, never the recorded pids, so an empty sessions object
|
|
327
|
+
* loses nothing but observability hints (which the start path fills in
|
|
328
|
+
* later, best-effort).
|
|
329
|
+
*
|
|
330
|
+
* Blocker 6 — never overwrite a preserved marker.
|
|
331
|
+
* A failed stop deliberately PRESERVES the marker so the next stop can
|
|
332
|
+
* retry idempotently. v2's start then overwrote it with a fresh uuid while
|
|
333
|
+
* only killing tmux sessions BY NAME, so the still-running subprocesses of
|
|
334
|
+
* the previous instance became permanently unreclaimable: their uuid was
|
|
335
|
+
* gone from disk forever. Now the old identity is reaped FIRST; if that
|
|
336
|
+
* reap does not fully succeed we refuse to start rather than destroy the
|
|
337
|
+
* only handle on those processes.
|
|
338
|
+
*/
|
|
339
|
+
export declare function prepareIdentityForStart(newUuid: string, deps: PrepareStartDeps): Promise<PrepareStartResult>;
|