@agimon-ai/doompi-computer-use 0.0.1-alpha.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/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/adapters/computerUseApi.cjs +2 -0
- package/dist/adapters/computerUseApi.cjs.map +1 -0
- package/dist/adapters/computerUseApi.d.cts +37 -0
- package/dist/adapters/computerUseApi.d.cts.map +1 -0
- package/dist/adapters/computerUseApi.d.mts +37 -0
- package/dist/adapters/computerUseApi.d.mts.map +1 -0
- package/dist/adapters/computerUseApi.mjs +2 -0
- package/dist/adapters/computerUseApi.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +3 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +12 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +12 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +3 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pi/sessionApiClient.cjs +2 -0
- package/dist/adapters/pi/sessionApiClient.cjs.map +1 -0
- package/dist/adapters/pi/sessionApiClient.d.cts +26 -0
- package/dist/adapters/pi/sessionApiClient.d.cts.map +1 -0
- package/dist/adapters/pi/sessionApiClient.d.mts +26 -0
- package/dist/adapters/pi/sessionApiClient.d.mts.map +1 -0
- package/dist/adapters/pi/sessionApiClient.mjs +2 -0
- package/dist/adapters/pi/sessionApiClient.mjs.map +1 -0
- package/dist/adapters/webComputerUseAvailability.cjs +2 -0
- package/dist/adapters/webComputerUseAvailability.cjs.map +1 -0
- package/dist/adapters/webComputerUseAvailability.mjs +2 -0
- package/dist/adapters/webComputerUseAvailability.mjs.map +1 -0
- package/dist/adapters/webComputerUseChannel.cjs +2 -0
- package/dist/adapters/webComputerUseChannel.cjs.map +1 -0
- package/dist/adapters/webComputerUseChannel.d.cts +10 -0
- package/dist/adapters/webComputerUseChannel.d.cts.map +1 -0
- package/dist/adapters/webComputerUseChannel.d.mts +10 -0
- package/dist/adapters/webComputerUseChannel.d.mts.map +1 -0
- package/dist/adapters/webComputerUseChannel.mjs +2 -0
- package/dist/adapters/webComputerUseChannel.mjs.map +1 -0
- package/dist/commands/computerUseCommand.cjs +2 -0
- package/dist/commands/computerUseCommand.cjs.map +1 -0
- package/dist/commands/computerUseCommand.mjs +2 -0
- package/dist/commands/computerUseCommand.mjs.map +1 -0
- package/dist/container/index.cjs +2 -0
- package/dist/container/index.cjs.map +1 -0
- package/dist/container/index.d.cts +6 -0
- package/dist/container/index.d.cts.map +1 -0
- package/dist/container/index.d.mts +6 -0
- package/dist/container/index.d.mts.map +1 -0
- package/dist/container/index.mjs +2 -0
- package/dist/container/index.mjs.map +1 -0
- package/dist/extensions/pi.cjs +1 -0
- package/dist/extensions/pi.d.cts +2 -0
- package/dist/extensions/pi.d.mts +2 -0
- package/dist/extensions/pi.mjs +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +1 -0
- package/dist/services/extensionService.cjs +2 -0
- package/dist/services/extensionService.cjs.map +1 -0
- package/dist/services/extensionService.d.cts +11 -0
- package/dist/services/extensionService.d.cts.map +1 -0
- package/dist/services/extensionService.d.mts +11 -0
- package/dist/services/extensionService.d.mts.map +1 -0
- package/dist/services/extensionService.mjs +2 -0
- package/dist/services/extensionService.mjs.map +1 -0
- package/dist/services/sessionController.cjs +2 -0
- package/dist/services/sessionController.cjs.map +1 -0
- package/dist/services/sessionController.d.cts +7 -0
- package/dist/services/sessionController.d.cts.map +1 -0
- package/dist/services/sessionController.d.mts +7 -0
- package/dist/services/sessionController.d.mts.map +1 -0
- package/dist/services/sessionController.mjs +2 -0
- package/dist/services/sessionController.mjs.map +1 -0
- package/dist/services/traceRedaction.cjs +2 -0
- package/dist/services/traceRedaction.cjs.map +1 -0
- package/dist/services/traceRedaction.d.cts +29 -0
- package/dist/services/traceRedaction.d.cts.map +1 -0
- package/dist/services/traceRedaction.d.mts +29 -0
- package/dist/services/traceRedaction.d.mts.map +1 -0
- package/dist/services/traceRedaction.mjs +2 -0
- package/dist/services/traceRedaction.mjs.map +1 -0
- package/dist/sessionApi.cjs +1 -0
- package/dist/sessionApi.d.cts +2 -0
- package/dist/sessionApi.d.mts +2 -0
- package/dist/sessionApi.mjs +1 -0
- package/dist/types/computerUse.d.cts +104 -0
- package/dist/types/computerUse.d.cts.map +1 -0
- package/dist/types/computerUse.d.mts +104 -0
- package/dist/types/computerUse.d.mts.map +1 -0
- package/dist/types/computerUseApi.cjs +2 -0
- package/dist/types/computerUseApi.cjs.map +1 -0
- package/dist/types/computerUseApi.d.cts +75 -0
- package/dist/types/computerUseApi.d.cts.map +1 -0
- package/dist/types/computerUseApi.d.mts +75 -0
- package/dist/types/computerUseApi.d.mts.map +1 -0
- package/dist/types/computerUseApi.mjs +2 -0
- package/dist/types/computerUseApi.mjs.map +1 -0
- package/dist/types/extension.d.cts +17 -0
- package/dist/types/extension.d.cts.map +1 -0
- package/dist/types/extension.d.mts +17 -0
- package/dist/types/extension.d.mts.map +1 -0
- package/dist/webHub.cjs +1 -0
- package/dist/webHub.d.cts +3 -0
- package/dist/webHub.d.mts +3 -0
- package/dist/webHub.mjs +1 -0
- package/llms.txt +11 -0
- package/package.json +124 -0
- package/src/exports/webClient.ts +2 -0
- package/src/prompts/doompi-use-computer-use/SKILL.md +20 -0
- package/src/types/computerUse.ts +117 -0
- package/src/types/computerUseApi.ts +78 -0
- package/src/web/ComputerActionToolCard.tsx +51 -0
- package/src/web/ComputerStateToolCard.tsx +51 -0
- package/src/web/ComputerUsePanel.tsx +136 -0
- package/src/web/computerActionToolRender.ts +20 -0
- package/src/web/computerStateToolRender.ts +18 -0
- package/src/web/computerUseStore.ts +26 -0
- package/src/web/index.ts +41 -0
- package/src/web/tsconfig.json +28 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const COMPUTER_USE_PHASES = [
|
|
2
|
+
'inactive',
|
|
3
|
+
'requesting',
|
|
4
|
+
'awaiting_confirmation',
|
|
5
|
+
'activating',
|
|
6
|
+
'active',
|
|
7
|
+
'stopping',
|
|
8
|
+
'failed',
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
export type ComputerUsePhase = (typeof COMPUTER_USE_PHASES)[number];
|
|
12
|
+
|
|
13
|
+
export interface ComputerUseSessionIdentity {
|
|
14
|
+
readonly sessionId: string;
|
|
15
|
+
readonly runtimeId: string;
|
|
16
|
+
readonly modeEpoch: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ComputerUseTarget {
|
|
20
|
+
readonly bundleId: string;
|
|
21
|
+
readonly applicationName: string;
|
|
22
|
+
readonly processId: number;
|
|
23
|
+
readonly windowId: string;
|
|
24
|
+
readonly windowTitle: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ComputerUseGrant {
|
|
28
|
+
readonly runId: string;
|
|
29
|
+
readonly grantId: string;
|
|
30
|
+
readonly hostGeneration: string;
|
|
31
|
+
readonly targetGeneration: string;
|
|
32
|
+
readonly expiresAt: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ComputerUseElement {
|
|
36
|
+
readonly ref: string;
|
|
37
|
+
readonly role: string;
|
|
38
|
+
readonly label?: string;
|
|
39
|
+
readonly enabled: boolean;
|
|
40
|
+
readonly secure: boolean;
|
|
41
|
+
readonly actions: readonly ComputerUseActionKind[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ComputerUseObservation {
|
|
45
|
+
readonly runId: string;
|
|
46
|
+
readonly snapshotId: string;
|
|
47
|
+
readonly targetGeneration: string;
|
|
48
|
+
readonly applicationName: string;
|
|
49
|
+
readonly bundleId: string;
|
|
50
|
+
readonly windowTitle: string;
|
|
51
|
+
readonly elements: readonly ComputerUseElement[];
|
|
52
|
+
readonly screenshot: {
|
|
53
|
+
readonly mimeType: 'image/png';
|
|
54
|
+
readonly data: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ComputerUseActionKind = 'press' | 'set_value' | 'scroll';
|
|
59
|
+
|
|
60
|
+
export type ComputerUseAction =
|
|
61
|
+
| {
|
|
62
|
+
readonly kind: 'press';
|
|
63
|
+
readonly snapshotId: string;
|
|
64
|
+
readonly elementRef: string;
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
readonly kind: 'set_value';
|
|
68
|
+
readonly snapshotId: string;
|
|
69
|
+
readonly elementRef: string;
|
|
70
|
+
readonly value: string;
|
|
71
|
+
}
|
|
72
|
+
| {
|
|
73
|
+
readonly kind: 'scroll';
|
|
74
|
+
readonly snapshotId: string;
|
|
75
|
+
readonly elementRef: string;
|
|
76
|
+
readonly direction: 'up' | 'down' | 'left' | 'right';
|
|
77
|
+
readonly amount: 'line' | 'page';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type ComputerUseFailureCode =
|
|
81
|
+
| 'busy_in_another_session'
|
|
82
|
+
| 'desktop_unavailable'
|
|
83
|
+
| 'permission_required_on_mac'
|
|
84
|
+
| 'confirmation_denied'
|
|
85
|
+
| 'confirmation_expired'
|
|
86
|
+
| 'target_invalid'
|
|
87
|
+
| 'target_lost'
|
|
88
|
+
| 'recording_failed'
|
|
89
|
+
| 'trace_failed'
|
|
90
|
+
| 'grant_expired'
|
|
91
|
+
| 'stale_request'
|
|
92
|
+
| 'outcome_uncertain'
|
|
93
|
+
| 'internal_error';
|
|
94
|
+
|
|
95
|
+
export interface ComputerUseState {
|
|
96
|
+
readonly revision: number;
|
|
97
|
+
readonly phase: ComputerUsePhase;
|
|
98
|
+
readonly identity: ComputerUseSessionIdentity;
|
|
99
|
+
readonly requestId?: string;
|
|
100
|
+
readonly target?: ComputerUseTarget;
|
|
101
|
+
readonly grant?: ComputerUseGrant;
|
|
102
|
+
readonly failure?: {
|
|
103
|
+
readonly code: ComputerUseFailureCode;
|
|
104
|
+
readonly message: string;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type ComputerUseStateEvent =
|
|
109
|
+
| { readonly type: 'request'; readonly requestId: string; readonly target: ComputerUseTarget }
|
|
110
|
+
| { readonly type: 'await_confirmation'; readonly requestId: string }
|
|
111
|
+
| { readonly type: 'confirm'; readonly requestId: string }
|
|
112
|
+
| { readonly type: 'deny'; readonly requestId: string }
|
|
113
|
+
| { readonly type: 'activated'; readonly requestId: string; readonly grant: ComputerUseGrant }
|
|
114
|
+
| { readonly type: 'stop' }
|
|
115
|
+
| { readonly type: 'stopped' }
|
|
116
|
+
| { readonly type: 'fail'; readonly code: ComputerUseFailureCode; readonly message: string }
|
|
117
|
+
| { readonly type: 'reset' };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { DoomApiCaller } from '@agimon-ai/doompi-extension-contracts/package-api';
|
|
2
|
+
|
|
3
|
+
export const API_BASE_PATH = 'computer-use';
|
|
4
|
+
export const computerUseChannelType = 'computer_use_state';
|
|
5
|
+
export const COMPUTER_USE_MODE_ID = 'computer-use';
|
|
6
|
+
export const COMPUTER_USE_STATUS_KEY = 'doom-computer-use';
|
|
7
|
+
export const COMPUTER_USE_MODE_STATUS_KEY = 'doom-computer-use-mode';
|
|
8
|
+
export const SESSION_QUERY_PARAM = 'session';
|
|
9
|
+
export const COMPUTER_USE_MAX_DURATION_MS = 1_800 * 1_000;
|
|
10
|
+
export const COMPUTER_USE_DEFAULT_DURATION_MS = 300 * 1_000;
|
|
11
|
+
export const COMPUTER_USE_CONFIRMATION_WINDOW_MS = 2 * 60 * 1_000;
|
|
12
|
+
|
|
13
|
+
export function activationUrl(sessionId: string): string {
|
|
14
|
+
return `/api/plugin/${API_BASE_PATH}/activate?${SESSION_QUERY_PARAM}=${encodeURIComponent(sessionId)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const COMPUTER_USE_ROUTES = {
|
|
18
|
+
activate: '/activate',
|
|
19
|
+
agentState: '/agent/state',
|
|
20
|
+
agentObserve: '/agent/observe',
|
|
21
|
+
agentAction: '/agent/action',
|
|
22
|
+
agentStop: '/agent/stop',
|
|
23
|
+
hubState: '/hub/state',
|
|
24
|
+
hubActivation: '/hub/activation',
|
|
25
|
+
hubAuthorization: '/hub/authorization',
|
|
26
|
+
hubNext: '/hub/next',
|
|
27
|
+
hubComplete: '/hub/complete',
|
|
28
|
+
hubStop: '/hub/stop',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export const COMPUTER_USE_WAKE_LIMIT = 1_000_000;
|
|
32
|
+
|
|
33
|
+
export interface ComputerUseArtifactView {
|
|
34
|
+
readonly artifactId: string;
|
|
35
|
+
readonly status: 'ready' | 'failed';
|
|
36
|
+
readonly downloadUrl?: string;
|
|
37
|
+
readonly previewUrl?: string;
|
|
38
|
+
readonly actionCount?: number;
|
|
39
|
+
readonly completedAt?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ComputerUseSessionView {
|
|
43
|
+
readonly sessionId: string;
|
|
44
|
+
readonly revision: number;
|
|
45
|
+
readonly wake: number;
|
|
46
|
+
readonly phase: 'inactive' | 'awaiting_confirmation' | 'activating' | 'active' | 'stopping' | 'failed';
|
|
47
|
+
readonly requestId?: string;
|
|
48
|
+
readonly target?: Record<string, unknown>;
|
|
49
|
+
readonly durationMs?: number;
|
|
50
|
+
readonly expiresAt?: number;
|
|
51
|
+
readonly failure?: { readonly code: string; readonly message: string };
|
|
52
|
+
readonly artifact?: ComputerUseArtifactView;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ComputerUseActivationRequest {
|
|
56
|
+
readonly requestId: string;
|
|
57
|
+
readonly target: Record<string, unknown>;
|
|
58
|
+
readonly durationSeconds: number;
|
|
59
|
+
readonly createdAt: number;
|
|
60
|
+
readonly confirmationExpiresAt: number;
|
|
61
|
+
readonly caller: DoomApiCaller;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ComputerUseBrokerRequest {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly operation: 'observe' | 'act';
|
|
67
|
+
readonly payload?: unknown;
|
|
68
|
+
readonly grantId: string;
|
|
69
|
+
readonly sequence?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ComputerUseChannelPayload {
|
|
73
|
+
readonly state: ComputerUseSessionView;
|
|
74
|
+
readonly targets: readonly Record<string, unknown>[];
|
|
75
|
+
readonly busy?: true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ComputerUseBrowserCommand = { readonly action: 'status' | 'targets' | 'stop' | 'artifacts' };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @scaffold-generated
|
|
2
|
+
import {
|
|
3
|
+
collapseLines,
|
|
4
|
+
MessageItem,
|
|
5
|
+
MessageItemBody,
|
|
6
|
+
MessageItemHeader,
|
|
7
|
+
MessageItemStatus,
|
|
8
|
+
MessageLines,
|
|
9
|
+
toolTone,
|
|
10
|
+
} from '@agimon-ai/doompi-web-components';
|
|
11
|
+
import type { ToolMessageRenderProps } from '@agimon-ai/doompi-web-contracts';
|
|
12
|
+
import {
|
|
13
|
+
computerActionCallSummary,
|
|
14
|
+
computerActionCollapsedLines,
|
|
15
|
+
computerActionResultLines,
|
|
16
|
+
} from './computerActionToolRender.ts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The computer_action tool's timeline item: Perform one constrained semantic action in the authorized application window. The shell owns
|
|
20
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
21
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
22
|
+
* hides any so the toggle appears only when it reveals something.
|
|
23
|
+
*/
|
|
24
|
+
export function ComputerActionToolCard({ toolName, args, result, running, isError }: ToolMessageRenderProps) {
|
|
25
|
+
const lines = computerActionResultLines(result);
|
|
26
|
+
const collapsed = collapseLines(lines, computerActionCollapsedLines, false);
|
|
27
|
+
return (
|
|
28
|
+
<MessageItem tone={toolTone({ running, isError })} expandable={collapsed.hidden > 0}>
|
|
29
|
+
{({ expanded }) => {
|
|
30
|
+
const view = expanded ? collapseLines(lines, computerActionCollapsedLines, true) : collapsed;
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<MessageItemHeader title={toolName}>
|
|
34
|
+
<span data-testid={`tool-call-${toolName}`} className="min-w-0 flex-1 truncate text-doom-text">
|
|
35
|
+
{computerActionCallSummary(args)}
|
|
36
|
+
</span>
|
|
37
|
+
</MessageItemHeader>
|
|
38
|
+
{view.shown.length > 0 || running || isError ? (
|
|
39
|
+
<MessageItemBody data-testid={`tool-result-${toolName}`} className="flex flex-col gap-1">
|
|
40
|
+
{view.shown.length > 0 ? <MessageLines lines={view.shown} /> : null}
|
|
41
|
+
{view.hidden > 0 ? <MessageItemStatus expands>{view.hidden} more line(s)</MessageItemStatus> : null}
|
|
42
|
+
{running ? <MessageItemStatus tone="running">running</MessageItemStatus> : null}
|
|
43
|
+
{isError ? <MessageItemStatus tone="error">failed</MessageItemStatus> : null}
|
|
44
|
+
</MessageItemBody>
|
|
45
|
+
) : null}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}}
|
|
49
|
+
</MessageItem>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @scaffold-generated
|
|
2
|
+
import {
|
|
3
|
+
collapseLines,
|
|
4
|
+
MessageItem,
|
|
5
|
+
MessageItemBody,
|
|
6
|
+
MessageItemHeader,
|
|
7
|
+
MessageItemStatus,
|
|
8
|
+
MessageLines,
|
|
9
|
+
toolTone,
|
|
10
|
+
} from '@agimon-ai/doompi-web-components';
|
|
11
|
+
import type { ToolMessageRenderProps } from '@agimon-ai/doompi-web-contracts';
|
|
12
|
+
import {
|
|
13
|
+
computerStateCallSummary,
|
|
14
|
+
computerStateCollapsedLines,
|
|
15
|
+
computerStateResultLines,
|
|
16
|
+
} from './computerStateToolRender.ts';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The computer_state tool's timeline item: Observe the authorized application window and semantic accessibility state. The shell owns
|
|
20
|
+
* the frame, the outcome tone, the status badge, and the expand toggle; this
|
|
21
|
+
* card supplies the header summary and the body lines, and says whether it
|
|
22
|
+
* hides any so the toggle appears only when it reveals something.
|
|
23
|
+
*/
|
|
24
|
+
export function ComputerStateToolCard({ toolName, args, result, running, isError }: ToolMessageRenderProps) {
|
|
25
|
+
const lines = computerStateResultLines(result);
|
|
26
|
+
const collapsed = collapseLines(lines, computerStateCollapsedLines, false);
|
|
27
|
+
return (
|
|
28
|
+
<MessageItem tone={toolTone({ running, isError })} expandable={collapsed.hidden > 0}>
|
|
29
|
+
{({ expanded }) => {
|
|
30
|
+
const view = expanded ? collapseLines(lines, computerStateCollapsedLines, true) : collapsed;
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<MessageItemHeader title={toolName}>
|
|
34
|
+
<span data-testid={`tool-call-${toolName}`} className="min-w-0 flex-1 truncate text-doom-text">
|
|
35
|
+
{computerStateCallSummary(args)}
|
|
36
|
+
</span>
|
|
37
|
+
</MessageItemHeader>
|
|
38
|
+
{view.shown.length > 0 || running || isError ? (
|
|
39
|
+
<MessageItemBody data-testid={`tool-result-${toolName}`} className="flex flex-col gap-1">
|
|
40
|
+
{view.shown.length > 0 ? <MessageLines lines={view.shown} /> : null}
|
|
41
|
+
{view.hidden > 0 ? <MessageItemStatus expands>{view.hidden} more line(s)</MessageItemStatus> : null}
|
|
42
|
+
{running ? <MessageItemStatus tone="running">running</MessageItemStatus> : null}
|
|
43
|
+
{isError ? <MessageItemStatus tone="error">failed</MessageItemStatus> : null}
|
|
44
|
+
</MessageItemBody>
|
|
45
|
+
) : null}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}}
|
|
49
|
+
</MessageItem>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import { useStore } from '@tanstack/react-store';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { activationUrl, COMPUTER_USE_DEFAULT_DURATION_MS, computerUseChannelType } from '../types/computerUseApi.ts';
|
|
5
|
+
import { computerUse } from './computerUseStore.ts';
|
|
6
|
+
|
|
7
|
+
function text(value: unknown, fallback: string): string {
|
|
8
|
+
return typeof value === 'string' || typeof value === 'number' ? String(value) : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ComputerUsePanel({ sessionId, sendSessionFrame }: WebPluginSlotProps) {
|
|
12
|
+
const session = useStore(computerUse.store, (state) => computerUse.select(state, sessionId));
|
|
13
|
+
const [selected, setSelected] = useState(0);
|
|
14
|
+
const [confirmationTarget, setConfirmationTarget] = useState<Record<string, unknown>>();
|
|
15
|
+
const [error, setError] = useState<string>();
|
|
16
|
+
const send = (payload: Record<string, unknown>) => {
|
|
17
|
+
if (sessionId !== null) sendSessionFrame(sessionId, { type: computerUseChannelType, payload });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (sessionId === null) return;
|
|
22
|
+
sendSessionFrame(sessionId, { type: computerUseChannelType, payload: { action: 'status' } });
|
|
23
|
+
sendSessionFrame(sessionId, { type: computerUseChannelType, payload: { action: 'targets' } });
|
|
24
|
+
}, [sendSessionFrame, sessionId]);
|
|
25
|
+
|
|
26
|
+
if (sessionId === null) return <p className="px-1 text-[10px] text-doom-faint">Select a session.</p>;
|
|
27
|
+
const state = session.state;
|
|
28
|
+
const target = session.targets[selected];
|
|
29
|
+
const artifact = state.artifact;
|
|
30
|
+
const confirm = async () => {
|
|
31
|
+
if (confirmationTarget === undefined) return;
|
|
32
|
+
setError(undefined);
|
|
33
|
+
const response = await fetch(activationUrl(sessionId), {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'content-type': 'application/json' },
|
|
36
|
+
body: JSON.stringify({ target: confirmationTarget, durationMs: COMPUTER_USE_DEFAULT_DURATION_MS }),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const value = (await response.json()) as { error?: unknown };
|
|
40
|
+
setError(typeof value.error === 'string' ? value.error : `Activation failed with HTTP ${response.status}.`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
setConfirmationTarget(undefined);
|
|
44
|
+
send({ action: 'status' });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div data-testid="computer-use-panel" className="flex flex-col gap-2 text-[10px] text-doom-text">
|
|
49
|
+
<header className="flex items-center justify-between gap-2">
|
|
50
|
+
<span className="text-doom-faint">{state.phase.replaceAll('_', ' ')}</span>
|
|
51
|
+
<button type="button" className="rounded bg-doom-panel px-2 py-1" onClick={() => send({ action: 'targets' })}>
|
|
52
|
+
Refresh targets
|
|
53
|
+
</button>
|
|
54
|
+
</header>
|
|
55
|
+
{session.busy ? (
|
|
56
|
+
<p className="rounded border border-doom-warning px-2 py-1">Computer use is busy in another session</p>
|
|
57
|
+
) : null}
|
|
58
|
+
{state.failure ? <p className="text-doom-error">{state.failure.message}</p> : null}
|
|
59
|
+
{error ? <p className="text-doom-error">{error}</p> : null}
|
|
60
|
+
|
|
61
|
+
{(state.phase === 'inactive' || state.phase === 'failed') && confirmationTarget === undefined ? (
|
|
62
|
+
<div className="flex flex-col gap-2">
|
|
63
|
+
<label htmlFor="computer-use-target">Application window</label>
|
|
64
|
+
<select
|
|
65
|
+
id="computer-use-target"
|
|
66
|
+
value={selected}
|
|
67
|
+
onChange={(event) => setSelected(Number(event.target.value))}
|
|
68
|
+
className="rounded bg-doom-panel px-2 py-1"
|
|
69
|
+
>
|
|
70
|
+
{session.targets.map((item, index) => (
|
|
71
|
+
<option key={text(item.windowId, String(index))} value={index}>
|
|
72
|
+
{text(item.applicationName, text(item.windowTitle, text(item.windowId, 'Window')))}
|
|
73
|
+
</option>
|
|
74
|
+
))}
|
|
75
|
+
</select>
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
disabled={!target || Boolean(session.busy)}
|
|
79
|
+
className="rounded bg-doom-accent px-2 py-1 disabled:opacity-50"
|
|
80
|
+
onClick={() => target !== undefined && setConfirmationTarget(Object.freeze({ ...target }))}
|
|
81
|
+
>
|
|
82
|
+
Request activation
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
) : null}
|
|
86
|
+
|
|
87
|
+
{confirmationTarget !== undefined ? (
|
|
88
|
+
<div className="rounded border border-doom-accent p-3">
|
|
89
|
+
<strong>Confirm computer control</strong>
|
|
90
|
+
<p className="my-2 text-doom-text">
|
|
91
|
+
{text(confirmationTarget.applicationName, 'Application')}:{' '}
|
|
92
|
+
{text(confirmationTarget.windowTitle, text(confirmationTarget.windowId, 'Window'))}
|
|
93
|
+
</p>
|
|
94
|
+
<p className="my-2 text-doom-faint">Confirm a 5 minute session for this exact application window.</p>
|
|
95
|
+
<div className="flex gap-2">
|
|
96
|
+
<button type="button" className="rounded bg-doom-accent px-2 py-1" onClick={() => void confirm()}>
|
|
97
|
+
Confirm
|
|
98
|
+
</button>
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
className="rounded bg-doom-panel px-2 py-1"
|
|
102
|
+
onClick={() => setConfirmationTarget(undefined)}
|
|
103
|
+
>
|
|
104
|
+
Cancel
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
) : null}
|
|
109
|
+
|
|
110
|
+
{state.phase === 'awaiting_confirmation' || state.phase === 'activating' ? (
|
|
111
|
+
<p>Desktop activation is pending.</p>
|
|
112
|
+
) : null}
|
|
113
|
+
{state.phase === 'active' || state.phase === 'stopping' ? (
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
disabled={state.phase === 'stopping'}
|
|
117
|
+
className="rounded bg-doom-panel px-2 py-1"
|
|
118
|
+
onClick={() => send({ action: 'stop' })}
|
|
119
|
+
>
|
|
120
|
+
Stop computer use
|
|
121
|
+
</button>
|
|
122
|
+
) : null}
|
|
123
|
+
{artifact ? (
|
|
124
|
+
<div data-testid="computer-use-artifact" className="rounded bg-doom-panel p-2">
|
|
125
|
+
<strong>Completed artifact</strong>
|
|
126
|
+
<p>ID: {artifact.artifactId}</p>
|
|
127
|
+
<p>Status: {artifact.status}</p>
|
|
128
|
+
{artifact.completedAt ? <p>Completed: {artifact.completedAt}</p> : null}
|
|
129
|
+
{artifact.actionCount !== undefined ? <p>Actions: {artifact.actionCount}</p> : null}
|
|
130
|
+
{artifact.previewUrl ? <a href={artifact.previewUrl}>Preview</a> : null}
|
|
131
|
+
{artifact.downloadUrl ? <a href={artifact.downloadUrl}>Download</a> : null}
|
|
132
|
+
</div>
|
|
133
|
+
) : null}
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MessageLine } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { type ToolResultView, toolResultTextLines } from '@agimon-ai/doompi-web-contracts';
|
|
3
|
+
|
|
4
|
+
export const computerActionToolName = 'computer_action' as const;
|
|
5
|
+
export const computerActionCollapsedLines = 8;
|
|
6
|
+
|
|
7
|
+
export function computerActionCallSummary(args: Readonly<Record<string, unknown>>): string {
|
|
8
|
+
const kind = typeof args.kind === 'string' ? args.kind : '';
|
|
9
|
+
const ref = typeof args.elementRef === 'string' ? args.elementRef : '';
|
|
10
|
+
return [kind, ref].filter(Boolean).join(' ');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
15
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
16
|
+
*/
|
|
17
|
+
export function computerActionResultLines(result: ToolResultView | null): MessageLine[] {
|
|
18
|
+
if (result === null) return [];
|
|
19
|
+
return toolResultTextLines(result.content).map((text) => ({ text, tone: 'dim' }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MessageLine } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import { type ToolResultView, toolResultTextLines } from '@agimon-ai/doompi-web-contracts';
|
|
3
|
+
|
|
4
|
+
export const computerStateToolName = 'computer_state' as const;
|
|
5
|
+
export const computerStateCollapsedLines = 8;
|
|
6
|
+
|
|
7
|
+
export function computerStateCallSummary(_args: Readonly<Record<string, unknown>>): string {
|
|
8
|
+
return 'authorized window';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The body as toned lines: what the result's details say, or its text. Pure,
|
|
13
|
+
* so a unit test can pin it without a DOM; the item decides how many to show.
|
|
14
|
+
*/
|
|
15
|
+
export function computerStateResultLines(result: ToolResultView | null): MessageLine[] {
|
|
16
|
+
if (result === null) return [];
|
|
17
|
+
return toolResultTextLines(result.content).map((text) => ({ text, tone: 'dim' }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineSessionStore } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import { computerUseChannelType } from '../types/computerUseApi.ts';
|
|
3
|
+
import type { ComputerUseChannelPayload } from '../types/computerUseApi.ts';
|
|
4
|
+
|
|
5
|
+
export interface ComputerUseSession extends ComputerUseChannelPayload {}
|
|
6
|
+
|
|
7
|
+
const initialState: ComputerUseSession = {
|
|
8
|
+
state: { sessionId: '', revision: 0, wake: 0, phase: 'inactive' },
|
|
9
|
+
targets: [],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const computerUse = defineSessionStore<ComputerUseSession>(initialState);
|
|
13
|
+
|
|
14
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const computerUseChannel = computerUse.channel<ComputerUseChannelPayload>({
|
|
19
|
+
channel: computerUseChannelType,
|
|
20
|
+
parse(input) {
|
|
21
|
+
if (!isRecord(input) || !isRecord(input.state) || !Array.isArray(input.targets)) return null;
|
|
22
|
+
if (typeof input.state.phase !== 'string' || typeof input.state.revision !== 'number') return null;
|
|
23
|
+
return input as unknown as ComputerUseChannelPayload;
|
|
24
|
+
},
|
|
25
|
+
reduce: (_current, payload) => payload,
|
|
26
|
+
});
|
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import {
|
|
3
|
+
COMPUTER_USE_MODE_ID,
|
|
4
|
+
COMPUTER_USE_MODE_STATUS_KEY,
|
|
5
|
+
COMPUTER_USE_STATUS_KEY,
|
|
6
|
+
} from '../types/computerUseApi.ts';
|
|
7
|
+
import { ComputerActionToolCard } from './ComputerActionToolCard.tsx';
|
|
8
|
+
import { computerActionToolName } from './computerActionToolRender.ts';
|
|
9
|
+
import { ComputerStateToolCard } from './ComputerStateToolCard.tsx';
|
|
10
|
+
import { computerStateToolName } from './computerStateToolRender.ts';
|
|
11
|
+
import { ComputerUsePanel } from './ComputerUsePanel.tsx';
|
|
12
|
+
import { computerUseChannel } from './computerUseStore.ts';
|
|
13
|
+
|
|
14
|
+
export const webPlugin = defineWebPlugin({
|
|
15
|
+
id: 'computer-use',
|
|
16
|
+
minorModes: [
|
|
17
|
+
{
|
|
18
|
+
name: 'computer use',
|
|
19
|
+
modeId: COMPUTER_USE_MODE_ID,
|
|
20
|
+
keys: 'c e',
|
|
21
|
+
statusKey: COMPUTER_USE_MODE_STATUS_KEY,
|
|
22
|
+
order: 70,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
activityGroups: [
|
|
26
|
+
{
|
|
27
|
+
name: 'computer-use',
|
|
28
|
+
keys: 'c e',
|
|
29
|
+
order: 70,
|
|
30
|
+
statusKey: COMPUTER_USE_STATUS_KEY,
|
|
31
|
+
hideWhenEmpty: true,
|
|
32
|
+
marksBackgroundWork: false,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
activitySections: [{ id: 'computer-use', component: ComputerUsePanel }],
|
|
36
|
+
channels: [computerUseChannel],
|
|
37
|
+
toolRenderers: [
|
|
38
|
+
{ tools: [computerStateToolName], message: ComputerStateToolCard },
|
|
39
|
+
{ tools: [computerActionToolName], message: ComputerActionToolCard },
|
|
40
|
+
],
|
|
41
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
".",
|
|
23
|
+
"../types/computerUse.ts",
|
|
24
|
+
"../types/computerUseApi.ts",
|
|
25
|
+
"../exports/webClient.ts",
|
|
26
|
+
"../../tests/web"
|
|
27
|
+
]
|
|
28
|
+
}
|