@astrosheep/square 0.3.91 → 0.3.92
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/dist/cli/context.js +2 -1
- package/dist/codex-address.d.ts +3 -0
- package/dist/codex-address.js +8 -0
- package/dist/codex-boundary-state.d.ts +6 -8
- package/dist/codex-boundary-state.js +8 -85
- package/dist/codex-hook.d.ts +1 -3
- package/dist/codex-hook.js +20 -76
- package/dist/codex-queue.d.ts +5 -17
- package/dist/codex-queue.js +21 -64
- package/dist/file-lock.d.ts +1 -17
- package/dist/file-lock.js +1 -64
- package/dist/mcp.js +2 -1
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +13 -0
- package/dist/packages/agent-delivery/src/codex-boundary.js +75 -0
- package/dist/packages/agent-delivery/src/codex-cli.d.ts +10 -0
- package/dist/packages/agent-delivery/src/codex-cli.js +69 -0
- package/dist/packages/agent-delivery/src/codex-endpoint.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex-endpoint.js +14 -0
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +26 -0
- package/dist/packages/agent-delivery/src/codex-hook.js +58 -0
- package/dist/packages/agent-delivery/src/codex-queue.d.ts +24 -0
- package/dist/packages/agent-delivery/src/codex-queue.js +58 -0
- package/dist/packages/agent-delivery/src/codex.d.ts +7 -0
- package/dist/packages/agent-delivery/src/codex.js +332 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +1 -1
- package/dist/packages/agent-delivery/src/errors.js +1 -0
- package/dist/packages/agent-delivery/src/file-lock.d.ts +17 -0
- package/dist/packages/agent-delivery/src/file-lock.js +64 -0
- package/dist/packages/agent-delivery/src/index.d.ts +17 -3
- package/dist/packages/agent-delivery/src/index.js +29 -4
- package/dist/packages/agent-delivery/src/types.d.ts +58 -3
- package/guides/codex-delivery.md +79 -0
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +3 -0
- package/plugins/paseo/client/markdown.tsx +16 -38
- package/plugins/paseo/client/palette.ts +62 -0
- package/plugins/paseo/client/square-message.tsx +75 -45
- package/plugins/paseo/shared/message.ts +16 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
5
|
+
export class FileLockError extends Error {
|
|
6
|
+
code = 'not_a_database';
|
|
7
|
+
constructor(lockPath, cause) {
|
|
8
|
+
super(`File lock is not a database: ${lockPath}`);
|
|
9
|
+
this.name = 'FileLockError';
|
|
10
|
+
if (cause !== undefined)
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isBusy(error) {
|
|
15
|
+
return error instanceof Error && /database is locked|SQLITE_BUSY/i.test(error.message);
|
|
16
|
+
}
|
|
17
|
+
function isNotADatabase(error) {
|
|
18
|
+
return error instanceof Error && /not a database|file is not a database/i.test(error.message);
|
|
19
|
+
}
|
|
20
|
+
export async function withFileLock(lockPath, options, fn) {
|
|
21
|
+
if (options.createParent !== false)
|
|
22
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
23
|
+
while (true) {
|
|
24
|
+
if (options.signal?.aborted)
|
|
25
|
+
throw options.signal.reason ?? new Error('File lock acquisition aborted');
|
|
26
|
+
let database;
|
|
27
|
+
try {
|
|
28
|
+
database = new DatabaseSync(lockPath);
|
|
29
|
+
database.exec(`PRAGMA busy_timeout = 0;
|
|
30
|
+
BEGIN EXCLUSIVE;
|
|
31
|
+
CREATE TABLE IF NOT EXISTS __agent_delivery_lock (id INTEGER PRIMARY KEY CHECK (id = 1));`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
try {
|
|
35
|
+
database?.close();
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
if (isBusy(error)) {
|
|
39
|
+
if (options.skipIfBusy)
|
|
40
|
+
return undefined;
|
|
41
|
+
await sleep(options.retryMs, undefined, { signal: options.signal });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (isNotADatabase(error))
|
|
45
|
+
throw new FileLockError(lockPath, error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const result = await fn();
|
|
50
|
+
database.exec('COMMIT;');
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
try {
|
|
55
|
+
database.exec('ROLLBACK;');
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
database.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DeliveryError } from './errors.js';
|
|
2
|
-
import type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions } from './types.js';
|
|
3
|
-
export type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions } from './types.js';
|
|
2
|
+
import type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
|
|
3
|
+
export type { OpenCodeConnectOptions, ClaudeConnectOptions, PiConnectOptions, PaseoConnectOptions, CodexConnectOptions, CodexAppServerConnectOptions, CodexCliConnectOptions } from './types.js';
|
|
4
4
|
export interface SendOptions {
|
|
5
5
|
readonly timeoutMs?: number;
|
|
6
6
|
readonly signal?: AbortSignal;
|
|
@@ -34,10 +34,24 @@ export type PaseoAgent = Steerable<KeyedSendOptions> & {
|
|
|
34
34
|
readonly harness: 'paseo';
|
|
35
35
|
readonly agentId: string;
|
|
36
36
|
};
|
|
37
|
-
export type
|
|
37
|
+
export type CodexAppServerAgent = Steerable & Queueable & {
|
|
38
|
+
readonly harness: 'codex';
|
|
39
|
+
readonly transport: 'app-server';
|
|
40
|
+
readonly sessionId: string;
|
|
41
|
+
};
|
|
42
|
+
export type CodexCliAgent = Queueable & {
|
|
43
|
+
readonly harness: 'codex';
|
|
44
|
+
readonly transport: 'cli';
|
|
45
|
+
readonly sessionId: string;
|
|
46
|
+
};
|
|
47
|
+
export type CodexAgent = CodexAppServerAgent | CodexCliAgent;
|
|
48
|
+
export type Agent = OpenCodeAgent | ClaudeAgent | PiAgent | PaseoAgent | CodexAgent;
|
|
38
49
|
export declare function connect(options: OpenCodeConnectOptions): Promise<OpenCodeAgent>;
|
|
39
50
|
export declare function connect(options: ClaudeConnectOptions): Promise<ClaudeAgent>;
|
|
40
51
|
export declare function connect(options: PiConnectOptions): Promise<PiAgent>;
|
|
41
52
|
export declare function connect(options: PaseoConnectOptions): Promise<PaseoAgent>;
|
|
53
|
+
export declare function connect(options: CodexCliConnectOptions): Promise<CodexCliAgent>;
|
|
54
|
+
export declare function connect(options: CodexAppServerConnectOptions): Promise<CodexAppServerAgent>;
|
|
55
|
+
export declare function connect(options: CodexConnectOptions): Promise<CodexAgent>;
|
|
42
56
|
export { DeliveryError };
|
|
43
57
|
export type { DeliveryErrorCode } from './errors.js';
|
|
@@ -25,11 +25,30 @@ function failure(error, phase, id) {
|
|
|
25
25
|
const code = attempt.state === 'unavailable'
|
|
26
26
|
? attempt.code === 'aborted' ? 'aborted' : attempt.code === 'timeout' ? 'timeout'
|
|
27
27
|
: attempt.code === 'authentication_failed' ? 'authentication_failed' : 'unavailable'
|
|
28
|
-
: attempt.state === 'rejected' ? attempt.code === 'not_found' ? 'session_not_found'
|
|
28
|
+
: attempt.state === 'rejected' ? attempt.code === 'not_found' ? 'session_not_found'
|
|
29
|
+
: attempt.code === 'unsupported_version' ? 'unsupported_version' : 'rejected'
|
|
29
30
|
: codes[attempt.code ?? 'transport'] ?? 'transport';
|
|
30
31
|
return new DeliveryError(code, attempt.state === 'unknown', id ?? attempt.inputId, attempt.status);
|
|
31
32
|
}
|
|
32
33
|
const harnesses = {
|
|
34
|
+
'codex-cli': {
|
|
35
|
+
harness: 'codex', transport: 'cli', proof: 'admitted', accepted: 'admitted', coordinate: 'sessionId', keyed: false, queue: true, steer: false,
|
|
36
|
+
id: (result) => result.inputId,
|
|
37
|
+
open: async (options) => {
|
|
38
|
+
const { connectCodexCli } = await import('./codex-cli.js');
|
|
39
|
+
const target = await connectCodexCli(options);
|
|
40
|
+
return { coordinate: target.coordinate, send: (text, value) => target.send(text, value) };
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
codex: {
|
|
44
|
+
harness: 'codex', transport: 'app-server', proof: 'admitted', accepted: 'admitted', coordinate: 'sessionId', keyed: false, queue: true,
|
|
45
|
+
id: (result) => result.inputId,
|
|
46
|
+
open: async (options) => {
|
|
47
|
+
const { connectCodex, sendCodexText } = await import('./codex.js');
|
|
48
|
+
const target = await connectCodex(options);
|
|
49
|
+
return { coordinate: target.sessionId, send: (text, value) => sendCodexText(target, text, value) };
|
|
50
|
+
},
|
|
51
|
+
},
|
|
33
52
|
opencode: {
|
|
34
53
|
harness: 'opencode', proof: 'admitted', accepted: 'accepted', coordinate: 'sessionId', keyed: true, queue: true,
|
|
35
54
|
id: (result) => result.inputId,
|
|
@@ -70,8 +89,9 @@ const harnesses = {
|
|
|
70
89
|
export async function connect(options) {
|
|
71
90
|
const harness = isRecord(options) && typeof options.harness === 'string' ? options.harness : undefined;
|
|
72
91
|
// An own-key check keeps Object.prototype names out of the transport table.
|
|
73
|
-
const
|
|
74
|
-
|
|
92
|
+
const key = harness === 'codex' && isRecord(options) && options.transport === 'cli' ? 'codex-cli' : harness;
|
|
93
|
+
const spec = key !== undefined && Object.hasOwn(harnesses, key) ? harnesses[key] : undefined;
|
|
94
|
+
if (spec === undefined || spec.harness !== harness)
|
|
75
95
|
throw new DeliveryError('invalid_arguments');
|
|
76
96
|
let opened;
|
|
77
97
|
try {
|
|
@@ -86,6 +106,8 @@ export async function connect(options) {
|
|
|
86
106
|
throw new DeliveryError('invalid_arguments');
|
|
87
107
|
// An unusable caller id is an argument precondition; the native sender rejects it.
|
|
88
108
|
const id = options.id;
|
|
109
|
+
if (!spec.keyed && id !== undefined)
|
|
110
|
+
throw new DeliveryError('invalid_arguments');
|
|
89
111
|
try {
|
|
90
112
|
const result = await opened.send(text, spec.keyed
|
|
91
113
|
? { delivery, inputId: id, timeoutMs: options.timeoutMs, signal: options.signal }
|
|
@@ -101,8 +123,11 @@ export async function connect(options) {
|
|
|
101
123
|
const agent = {
|
|
102
124
|
harness: spec.harness,
|
|
103
125
|
[spec.coordinate]: opened.coordinate,
|
|
104
|
-
steer: (text, value) => send(text, value, 'steer'),
|
|
105
126
|
};
|
|
127
|
+
if (spec.transport)
|
|
128
|
+
agent.transport = spec.transport;
|
|
129
|
+
if (spec.steer !== false)
|
|
130
|
+
agent.steer = (text, value) => send(text, value, 'steer');
|
|
106
131
|
if (spec.queue)
|
|
107
132
|
agent.queue = (text, value) => send(text, value, 'queue');
|
|
108
133
|
return Object.freeze(agent);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CodexBoundaryStore } from './codex-boundary.js';
|
|
1
2
|
declare const targetBrand: unique symbol;
|
|
2
3
|
export interface OpenCodeTarget {
|
|
3
4
|
readonly harness: 'opencode';
|
|
@@ -19,7 +20,12 @@ export interface PaseoTarget {
|
|
|
19
20
|
readonly agentId: string;
|
|
20
21
|
readonly [targetBrand]: true;
|
|
21
22
|
}
|
|
22
|
-
export
|
|
23
|
+
export interface CodexTarget {
|
|
24
|
+
readonly harness: 'codex';
|
|
25
|
+
readonly sessionId: string;
|
|
26
|
+
readonly [targetBrand]: true;
|
|
27
|
+
}
|
|
28
|
+
export type Target = OpenCodeTarget | ClaudeTarget | PiTarget | PaseoTarget | CodexTarget;
|
|
23
29
|
export type Delivery = 'steer' | 'queue';
|
|
24
30
|
export interface OpenCodeSendTextOptions {
|
|
25
31
|
readonly delivery?: Delivery;
|
|
@@ -45,7 +51,13 @@ export interface PaseoSendTextOptions {
|
|
|
45
51
|
readonly timeoutMs?: number;
|
|
46
52
|
readonly signal?: AbortSignal;
|
|
47
53
|
}
|
|
48
|
-
export
|
|
54
|
+
export interface CodexSendTextOptions {
|
|
55
|
+
readonly delivery?: Delivery;
|
|
56
|
+
readonly inputId?: never;
|
|
57
|
+
readonly timeoutMs?: number;
|
|
58
|
+
readonly signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
export type SendTextOptions = OpenCodeSendTextOptions | ClaudeSendTextOptions | PiSendTextOptions | PaseoSendTextOptions | CodexSendTextOptions;
|
|
49
61
|
export interface OpenCodeConnectOptions {
|
|
50
62
|
readonly harness: 'opencode';
|
|
51
63
|
readonly sessionId: string;
|
|
@@ -88,6 +100,32 @@ export interface PaseoConnectOptions {
|
|
|
88
100
|
readonly timeoutMs?: number;
|
|
89
101
|
readonly signal?: AbortSignal;
|
|
90
102
|
}
|
|
103
|
+
/** Connect only to an existing app-server; never starts a service or resumes a thread. */
|
|
104
|
+
export interface CodexAppServerConnectOptions {
|
|
105
|
+
readonly harness: 'codex';
|
|
106
|
+
readonly transport?: 'app-server';
|
|
107
|
+
readonly sessionId: string;
|
|
108
|
+
readonly endpoint?: string;
|
|
109
|
+
readonly token?: string;
|
|
110
|
+
readonly codexHome?: string;
|
|
111
|
+
readonly timeoutMs?: number;
|
|
112
|
+
readonly signal?: AbortSignal;
|
|
113
|
+
}
|
|
114
|
+
/** Bind CLI coordinates and the same lifecycle source used by the receiver's hooks. */
|
|
115
|
+
export interface CodexCliConnectOptions {
|
|
116
|
+
readonly harness: 'codex';
|
|
117
|
+
readonly transport: 'cli';
|
|
118
|
+
readonly sessionId: string;
|
|
119
|
+
readonly boundaries: Pick<CodexBoundaryStore, 'queueEligible'>;
|
|
120
|
+
readonly codexHome?: string;
|
|
121
|
+
readonly endpoint?: string;
|
|
122
|
+
readonly bin?: string;
|
|
123
|
+
readonly args?: readonly string[];
|
|
124
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
125
|
+
readonly timeoutMs?: number;
|
|
126
|
+
readonly signal?: AbortSignal;
|
|
127
|
+
}
|
|
128
|
+
export type CodexConnectOptions = CodexAppServerConnectOptions | CodexCliConnectOptions;
|
|
91
129
|
interface OpenCodeAttempt {
|
|
92
130
|
readonly harness: 'opencode';
|
|
93
131
|
readonly sessionId: string;
|
|
@@ -158,5 +196,22 @@ export type PaseoDeliveryResult = (PaseoAttempt & {
|
|
|
158
196
|
readonly state: 'unknown';
|
|
159
197
|
readonly code: 'timeout' | 'aborted' | 'transport';
|
|
160
198
|
});
|
|
161
|
-
|
|
199
|
+
interface CodexAttempt {
|
|
200
|
+
readonly harness: 'codex';
|
|
201
|
+
readonly sessionId: string;
|
|
202
|
+
readonly inputId: string;
|
|
203
|
+
}
|
|
204
|
+
export type CodexDeliveryResult = (CodexAttempt & {
|
|
205
|
+
readonly state: 'admitted';
|
|
206
|
+
}) | (CodexAttempt & {
|
|
207
|
+
readonly state: 'unknown';
|
|
208
|
+
readonly code: 'timeout' | 'aborted' | 'transport' | 'invalid_response';
|
|
209
|
+
}) | (CodexAttempt & {
|
|
210
|
+
readonly state: 'unavailable';
|
|
211
|
+
readonly code: 'timeout' | 'aborted' | 'transport' | 'authentication_failed';
|
|
212
|
+
}) | (CodexAttempt & {
|
|
213
|
+
readonly state: 'rejected';
|
|
214
|
+
readonly code: 'not_found' | 'refused' | 'unsupported_version';
|
|
215
|
+
});
|
|
216
|
+
export type DeliveryResult = OpenCodeDeliveryResult | ClaudeDeliveryResult | PiDeliveryResult | PaseoDeliveryResult | CodexDeliveryResult;
|
|
162
217
|
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Codex delivery
|
|
2
|
+
|
|
3
|
+
Square uses the shared Codex leaves in `packages/agent-delivery/src`, rather than
|
|
4
|
+
maintaining a second hook protocol, boundary store or CLI sender. The standalone
|
|
5
|
+
package's [README](../packages/agent-delivery/README.md) describes both its native
|
|
6
|
+
app-server capability and its host-owned hook integration.
|
|
7
|
+
|
|
8
|
+
## Square's hook path
|
|
9
|
+
|
|
10
|
+
`plugins/codex/hooks/hooks.json` invokes `square codex-hook`:
|
|
11
|
+
|
|
12
|
+
- `SessionStart`, `SessionResume` and `UserPromptSubmit` invalidate the previous
|
|
13
|
+
Stop observation. `SessionEnd` marks the session ended and ends Square's owned
|
|
14
|
+
memberships. Later Stop/tool callbacks cannot re-enable queue until a new
|
|
15
|
+
`SessionStart` or `SessionResume`.
|
|
16
|
+
- `PostToolUse` asks Square for current pending activity and returns the existing
|
|
17
|
+
bounded `additionalContext` presentation.
|
|
18
|
+
- `Stop` records queue eligibility and sweeps pending work, but does not itself
|
|
19
|
+
claim that a preview was presented.
|
|
20
|
+
|
|
21
|
+
The shared handler owns hook framing, lifecycle sequencing, its cancellation
|
|
22
|
+
budget and bounded sweeps. Square supplies activity selection, formatting,
|
|
23
|
+
artifact-owned presentation claims, cancellation, membership cleanup and
|
|
24
|
+
pending-work sweeps. The handler cooperatively awaits Square's pull: the signal
|
|
25
|
+
cancels work before presentation, but a successful presentation's `settleAs`
|
|
26
|
+
finishes independently of request cancellation. Even when that settlement crosses
|
|
27
|
+
the internal budget, the hook waits for it and returns the associated context;
|
|
28
|
+
it never abandons settlement that can mark activity seen without returning text.
|
|
29
|
+
After expiry, no sweep starts, and a sweep timeout cannot erase successful pull
|
|
30
|
+
output. Settlement must still finish within Codex's external hook timeout. Hook inbox lookup uses
|
|
31
|
+
an exact Body (`{ channel: 'codex', session }`), not a bare session id. The shared
|
|
32
|
+
boundary store uses Square's caller-selected `SQUARE_CODEX_BOUNDARIES` file,
|
|
33
|
+
defaulting to `~/.square/codex-boundaries.json`; its JSON observations are not
|
|
34
|
+
conversation storage and are not proof of current native execution or consumption.
|
|
35
|
+
Square's format5 artifact remains authoritative for seats and delivery evidence.
|
|
36
|
+
|
|
37
|
+
`CodexQueueTransport` implements the existing `WakeTransportPort` and uses the public
|
|
38
|
+
`connect({ harness: 'codex', transport: 'cli', sessionId, boundaries, ... })`
|
|
39
|
+
and `agent.queue(text)` interface, just like independent hosts. Square reads its
|
|
40
|
+
stored pending preview and finishes its currentness gate first; the bound client
|
|
41
|
+
checks boundary eligibility once inside `queue()`, then launches the CLI. No
|
|
42
|
+
caller-side lifecycle check is duplicated. Outcomes continue through the existing
|
|
43
|
+
artifact-owned delivery path; no host ledger or new route kind is introduced.
|
|
44
|
+
|
|
45
|
+
Both CLI coordinate creation and MCP call-local Codex coordinates capture the
|
|
46
|
+
recipient's `CODEX_HOME` (or home default), so later senders use the recipient's
|
|
47
|
+
CLI configuration rather than their own. Only an explicitly configured
|
|
48
|
+
`SQUARE_CODEX_ENDPOINT` adds a remote endpoint. `SQUARE_CODEX_BIN` remains
|
|
49
|
+
Square's executable selection; it is not an environment convention of the
|
|
50
|
+
independent package.
|
|
51
|
+
|
|
52
|
+
By default the sender runs `codex queue --thread ... --message ...`, allowing the
|
|
53
|
+
CLI's normal connection/startup behavior. Only an explicit endpoint adds
|
|
54
|
+
`--remote`. This does not make another host's private stdio pipe attachable.
|
|
55
|
+
After an observed `SessionEnd`, Square does not dispatch queue; the bound lifecycle
|
|
56
|
+
check also catches an end observed while the currentness gate was running.
|
|
57
|
+
It cannot retract a submission already launched or infer a missed end hook.
|
|
58
|
+
Queue success means native admission only; its receipt id is a local attempt
|
|
59
|
+
UUID, not a server message id. The child runs asynchronously and respects the
|
|
60
|
+
send's abort signal/deadline. Timeout, killed child and unexplained post-launch
|
|
61
|
+
failures remain unknown, not a safe reason to resend. A receiver with no tool
|
|
62
|
+
calls waits for Stop; this path is not instantaneous native steer.
|
|
63
|
+
|
|
64
|
+
## Optional native app-server capability
|
|
65
|
+
|
|
66
|
+
Independent consumers can use `connect({ harness: 'codex', sessionId, endpoint })`
|
|
67
|
+
from `@astrosheep/agent-delivery` for fenced busy steer and native queue.
|
|
68
|
+
`transport: 'app-server'` is the explicit spelling and the default when omitted;
|
|
69
|
+
it exposes both methods. `transport: 'cli'` exposes queue only and requires the
|
|
70
|
+
host's hook lifecycle source at connection time. There is no automatic fallback
|
|
71
|
+
between transports. The app-server transport never starts a service or calls
|
|
72
|
+
`thread/resume`. This does not replace Square's hook policy or add a new persisted
|
|
73
|
+
Square route kind. A loaded thread's owner remains its existing host, not the
|
|
74
|
+
external sender.
|
|
75
|
+
|
|
76
|
+
Neither path implements a read-receipt roundtrip. Admission and runtime
|
|
77
|
+
observations must not be presented as model comprehension or completion. An
|
|
78
|
+
unknown outcome stays unknown. Installing, restarting or changing a real Codex
|
|
79
|
+
server is outside this integration.
|
package/package.json
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"SessionEnd": [
|
|
10
10
|
{ "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 3 }] }
|
|
11
11
|
],
|
|
12
|
+
"UserPromptSubmit": [
|
|
13
|
+
{ "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5 }] }
|
|
14
|
+
],
|
|
12
15
|
"PostToolUse": [
|
|
13
16
|
{
|
|
14
17
|
"hooks": [
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { useMemo, type ReactNode } from "react";
|
|
2
|
-
import { Platform, Text, View, type TextStyle } from "react-native";
|
|
2
|
+
import { Platform, Text, View, type TextStyle, type ViewStyle } from "react-native";
|
|
3
3
|
import type { PluginTheme } from "@getpaseo/plugin";
|
|
4
4
|
import { Lexer, lexer, type Token, type Tokens } from "marked";
|
|
5
|
-
import { parseSquareBlocks } from "../shared/message.ts";
|
|
6
|
-
|
|
7
|
-
export { parseSquareBlocks };
|
|
8
5
|
|
|
9
6
|
export const MONO = Platform.select({ ios: "Menlo", android: "monospace", default: "ui-monospace, Menlo, monospace" });
|
|
10
7
|
|
|
@@ -12,30 +9,21 @@ function makeStyles(colors: PluginTheme["colors"]) {
|
|
|
12
9
|
return {
|
|
13
10
|
block: { gap: 6 },
|
|
14
11
|
text: { color: colors.foreground, fontSize: 13, lineHeight: 19 },
|
|
15
|
-
strong: { fontWeight: "
|
|
16
|
-
em: { fontStyle: "italic" as const },
|
|
17
|
-
del: { textDecorationLine: "line-through" as const },
|
|
12
|
+
strong: { fontWeight: "500" as const, color: colors.foreground },
|
|
13
|
+
em: { color: colors.foregroundMuted, fontStyle: "italic" as const },
|
|
14
|
+
del: { color: colors.foregroundMuted, textDecorationLine: "line-through" as const },
|
|
18
15
|
link: { color: colors.accent, textDecorationLine: "underline" as const },
|
|
19
|
-
inlineCode: { fontFamily: MONO
|
|
20
|
-
codeBlock: {
|
|
21
|
-
|
|
22
|
-
borderRadius: 8,
|
|
23
|
-
borderWidth: 1,
|
|
24
|
-
borderColor: colors.border,
|
|
25
|
-
paddingHorizontal: 9,
|
|
26
|
-
paddingVertical: 7,
|
|
27
|
-
},
|
|
28
|
-
codeText: { fontFamily: MONO, fontSize: 11.5, lineHeight: 17, color: colors.foreground },
|
|
16
|
+
inlineCode: { fontFamily: MONO },
|
|
17
|
+
codeBlock: { backgroundColor: colors.surface0, borderRadius: 4, paddingHorizontal: 8, paddingVertical: 6 },
|
|
18
|
+
codeText: { fontFamily: MONO, fontSize: 13, lineHeight: 19, color: colors.foreground },
|
|
29
19
|
quote: { borderLeftWidth: 2, borderLeftColor: colors.border, paddingLeft: 8, gap: 2 },
|
|
30
20
|
quoteText: { color: colors.foregroundMuted, fontStyle: "italic" as const },
|
|
31
|
-
meta: { color: colors.foregroundMuted, fontSize: 11, lineHeight: 15, fontFamily: MONO },
|
|
32
|
-
actor: { color: colors.accent, fontSize: 12, lineHeight: 17 },
|
|
33
21
|
separator: { height: 1, backgroundColor: colors.border, marginVertical: 4 },
|
|
34
22
|
listRow: { flexDirection: "row" as const, gap: 6 },
|
|
35
23
|
listItem: { flexShrink: 1, gap: 2 },
|
|
36
|
-
bullet: { color: colors.
|
|
24
|
+
bullet: { color: colors.foregroundMuted, fontSize: 13, lineHeight: 19 },
|
|
37
25
|
tableRow: { flexDirection: "row" as const, gap: 8 },
|
|
38
|
-
tableCell: { flex: 1
|
|
26
|
+
tableCell: { flex: 1 },
|
|
39
27
|
};
|
|
40
28
|
}
|
|
41
29
|
|
|
@@ -55,7 +43,7 @@ function renderInlines(tokens: Token[], styles: Styles, prefix: string, extra?:
|
|
|
55
43
|
case "em": return nested(styles.em);
|
|
56
44
|
case "del": return nested(styles.del);
|
|
57
45
|
case "link": return nested(styles.link);
|
|
58
|
-
case "codespan": return <Text key={key} style={[styles.text, extra, styles.inlineCode]}>{
|
|
46
|
+
case "codespan": return <Text key={key} style={[styles.text, extra, styles.inlineCode]}>{token.text}</Text>;
|
|
59
47
|
case "br": return "\n";
|
|
60
48
|
case "image": return token.text;
|
|
61
49
|
default: {
|
|
@@ -86,7 +74,7 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
86
74
|
);
|
|
87
75
|
case "heading":
|
|
88
76
|
return (
|
|
89
|
-
<Text key={key} style={[styles.text, textStyle, styles.strong
|
|
77
|
+
<Text key={key} style={[styles.text, textStyle, styles.strong]}>
|
|
90
78
|
{renderInlines(children(token), styles, key)}
|
|
91
79
|
</Text>
|
|
92
80
|
);
|
|
@@ -100,7 +88,7 @@ function renderBlocks(tokens: Token[], styles: Styles, prefix: string, textStyle
|
|
|
100
88
|
{list.items.map((item, itemIndex) => (
|
|
101
89
|
<View key={`${key}-${itemIndex}`} style={styles.listRow}>
|
|
102
90
|
<Text style={styles.bullet}>
|
|
103
|
-
{item.task ? (item.checked === true ? "☑" : "☐") : list.ordered ? `${start + itemIndex}.` : "
|
|
91
|
+
{item.task ? (item.checked === true ? "☑" : "☐") : list.ordered ? `${start + itemIndex}.` : "·"}
|
|
104
92
|
</Text>
|
|
105
93
|
<View style={styles.listItem}>{renderBlocks(children(item), styles, `${key}-${itemIndex}`, textStyle)}</View>
|
|
106
94
|
</View>
|
|
@@ -153,19 +141,9 @@ export function SquareInline({ text, theme, numberOfLines, style }: {
|
|
|
153
141
|
);
|
|
154
142
|
}
|
|
155
143
|
|
|
156
|
-
|
|
144
|
+
/** A body of Markdown: no envelope, no route line — the words only, indented by the card. */
|
|
145
|
+
export function SquareMarkdownBody({ text, theme, style }: { text: string; theme: PluginTheme; style?: ViewStyle }) {
|
|
157
146
|
const styles = useMemo(() => makeStyles(theme.colors), [theme.colors]);
|
|
158
|
-
const
|
|
159
|
-
return (
|
|
160
|
-
<View style={styles.block}>
|
|
161
|
-
{blocks.map((block, index) => (
|
|
162
|
-
<View key={index} style={styles.block}>
|
|
163
|
-
{index > 0 && <View style={styles.separator} />}
|
|
164
|
-
{block.meta !== undefined && <Text style={styles.meta}>{block.meta}</Text>}
|
|
165
|
-
{block.actor !== undefined && <Text style={styles.actor}>{block.actor}</Text>}
|
|
166
|
-
{renderBlocks(lexer(block.body), styles, `${index}`)}
|
|
167
|
-
</View>
|
|
168
|
-
))}
|
|
169
|
-
</View>
|
|
170
|
-
);
|
|
147
|
+
const tokens = useMemo(() => lexer(text), [text]);
|
|
148
|
+
return <View style={[styles.block, style]}>{renderBlocks(tokens, styles, "body")}</View>;
|
|
171
149
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { PluginTheme } from "@getpaseo/plugin";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Square's own courtyard palette: cool pewter slate and a cold blue voice,
|
|
5
|
+
* deliberately no relative of notesoup's warm parchment or pi-meantime's
|
|
6
|
+
* neutral carbon. Every token here is opaque and self-contained — the card and
|
|
7
|
+
* Markdown never borrow the host's surface ramp or accent, only its appearance
|
|
8
|
+
* (read from the host ink) and its accentForeground, which paints text on top
|
|
9
|
+
* of an accent fill.
|
|
10
|
+
*/
|
|
11
|
+
const DARK = {
|
|
12
|
+
surface0: "#121519", // inset / code
|
|
13
|
+
surface1: "#171b20", // card
|
|
14
|
+
surface2: "#121519", // inset / code
|
|
15
|
+
border: "#262d36",
|
|
16
|
+
foreground: "#e6ebf2",
|
|
17
|
+
foregroundMuted: "#7d8999",
|
|
18
|
+
accent: "#60a5fa", // plaza ■ / links
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
const LIGHT = {
|
|
22
|
+
surface0: "#e8edf3",
|
|
23
|
+
surface1: "#f3f6fa", // pewter slate, not notesoup's warm parchment
|
|
24
|
+
surface2: "#e8edf3",
|
|
25
|
+
border: "#d5dee8",
|
|
26
|
+
foreground: "#141b24",
|
|
27
|
+
foregroundMuted: "#64748b",
|
|
28
|
+
accent: "#2563eb",
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
const VOICE = { dark: "#93c5fd", light: "#3b82f6" } as const;
|
|
32
|
+
|
|
33
|
+
export interface SquarePalette {
|
|
34
|
+
/** Complete theme for the card and its Markdown surfaces. */
|
|
35
|
+
theme: PluginTheme;
|
|
36
|
+
/** The filled ● speaker dot; the hollow ○ keeps the muted ink instead. */
|
|
37
|
+
voice: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Paseo theme colors are hex (#RGB or #RRGGBB). */
|
|
41
|
+
function brightness(hex: string): number {
|
|
42
|
+
const rgb = hex.length === 4 ? hex.slice(1).replace(/./g, digit => digit + digit) : hex.slice(1, 7);
|
|
43
|
+
return 0.299 * parseInt(rgb.slice(0, 2), 16)
|
|
44
|
+
+ 0.587 * parseInt(rgb.slice(2, 4), 16)
|
|
45
|
+
+ 0.114 * parseInt(rgb.slice(4, 6), 16);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function squarePalette(theme: PluginTheme): SquarePalette {
|
|
49
|
+
// The SDK exposes colors, not appearance. Use the host theme, never the OS color scheme.
|
|
50
|
+
const dark = brightness(theme.colors.foreground) > brightness(theme.colors.surface0);
|
|
51
|
+
const palette = dark ? DARK : LIGHT;
|
|
52
|
+
return {
|
|
53
|
+
theme: { colors: {
|
|
54
|
+
...palette,
|
|
55
|
+
accentForeground: theme.colors.accentForeground,
|
|
56
|
+
statusSuccess: theme.colors.statusSuccess,
|
|
57
|
+
statusWarning: theme.colors.statusWarning,
|
|
58
|
+
statusDanger: theme.colors.statusDanger,
|
|
59
|
+
} },
|
|
60
|
+
voice: dark ? VOICE.dark : VOICE.light,
|
|
61
|
+
};
|
|
62
|
+
}
|