ticlawk 0.1.15 → 0.1.16-dev.2
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 +96 -212
- package/bin/ticlawk.mjs +228 -46
- package/package.json +2 -5
- package/src/adapters/ticlawk/api.mjs +303 -26
- package/src/adapters/ticlawk/credentials.mjs +1 -2
- package/src/adapters/ticlawk/index.mjs +273 -77
- package/src/cli/agent-commands.mjs +829 -0
- package/src/core/adapter-registry.mjs +12 -28
- package/src/core/agent-cli-handlers.mjs +704 -0
- package/src/core/agent-home.mjs +89 -0
- package/src/core/config.mjs +0 -15
- package/src/core/http.mjs +204 -18
- package/src/core/reminder-ticker.mjs +70 -0
- package/src/core/runtime-contract.mjs +1 -1
- package/src/core/runtime-env.mjs +41 -5
- package/src/core/runtime-support.mjs +47 -30
- package/src/core/ticlawk-control.mjs +7 -6
- package/src/migrate/write-initial-memory.mjs +101 -0
- package/src/runtimes/_shared/standing-prompt.mjs +290 -0
- package/src/runtimes/claude-code/index.mjs +34 -34
- package/src/runtimes/claude-code/session.mjs +15 -7
- package/src/runtimes/codex/index.mjs +31 -36
- package/src/runtimes/codex/session.mjs +9 -5
- package/src/runtimes/openclaw/index.mjs +56 -16
- package/src/runtimes/openclaw/target.mjs +0 -30
- package/src/runtimes/opencode/index.mjs +30 -35
- package/src/runtimes/opencode/session.mjs +11 -2
- package/src/runtimes/pi/index.mjs +30 -36
- package/src/runtimes/pi/session.mjs +8 -2
- package/ticlawk.mjs +37 -10
- package/assets/ticlawk-concept.svg +0 -137
- package/src/adapters/telegram/index.mjs +0 -359
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* Wraps the pi CLI RPC mode and exposes the ticlawk runtime contract.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { existsSync } from 'node:fs';
|
|
8
7
|
import { basename } from 'node:path';
|
|
8
|
+
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
9
|
+
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
10
|
+
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
9
11
|
import {
|
|
10
12
|
buildPiImagesFromInbound,
|
|
11
13
|
discoverPiSessions,
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
shouldStreamRuntime,
|
|
22
24
|
createDeltaAggregator,
|
|
23
25
|
sendAdapterMessage,
|
|
24
|
-
|
|
26
|
+
recordActivity,
|
|
25
27
|
reportSubprocessFailure,
|
|
26
28
|
terminalRuntimeFailure,
|
|
27
29
|
updateBindingRuntimeMeta,
|
|
@@ -30,24 +32,28 @@ import {
|
|
|
30
32
|
export const piRuntime = {
|
|
31
33
|
name: 'pi',
|
|
32
34
|
|
|
33
|
-
runTurn({ sessionId, cwd, piPath }, text, opts = {}) {
|
|
35
|
+
runTurn({ sessionId, cwd, piPath, agentEnv }, text, opts = {}) {
|
|
34
36
|
return runPiPrompt({
|
|
35
37
|
sessionId,
|
|
36
38
|
cwd,
|
|
37
39
|
message: text,
|
|
38
40
|
images: opts.images,
|
|
39
41
|
piPath,
|
|
42
|
+
agentEnv,
|
|
43
|
+
standingPrompt: opts.standingPrompt || null,
|
|
40
44
|
timeoutMs: opts.timeoutMs,
|
|
41
45
|
});
|
|
42
46
|
},
|
|
43
47
|
|
|
44
|
-
runTurnStream({ sessionId, cwd, piPath }, text, opts = {}) {
|
|
48
|
+
runTurnStream({ sessionId, cwd, piPath, agentEnv }, text, opts = {}) {
|
|
45
49
|
return runPiPrompt({
|
|
46
50
|
sessionId,
|
|
47
51
|
cwd,
|
|
48
52
|
message: text,
|
|
49
53
|
images: opts.images,
|
|
50
54
|
piPath,
|
|
55
|
+
agentEnv,
|
|
56
|
+
standingPrompt: opts.standingPrompt || null,
|
|
51
57
|
timeoutMs: opts.timeoutMs,
|
|
52
58
|
onEvent: opts.onEvent,
|
|
53
59
|
});
|
|
@@ -74,8 +80,6 @@ export const piRuntime = {
|
|
|
74
80
|
displayName: payload.name || basename(session.cwd || requestedCwd) || 'pi',
|
|
75
81
|
runtimeMeta: {
|
|
76
82
|
sessionId: session.sessionId,
|
|
77
|
-
workdir: session.cwd || requestedCwd,
|
|
78
|
-
cwd: session.cwd || requestedCwd,
|
|
79
83
|
path: session.path || null,
|
|
80
84
|
runtimePath: piPath,
|
|
81
85
|
piPath,
|
|
@@ -85,20 +89,11 @@ export const piRuntime = {
|
|
|
85
89
|
};
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
if (!requestedCwd) {
|
|
89
|
-
throw new Error('cwd or sessionId is required for pi binding');
|
|
90
|
-
}
|
|
91
|
-
if (!existsSync(requestedCwd)) {
|
|
92
|
-
throw new Error(`pi cwd not found locally: ${requestedCwd}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
92
|
return {
|
|
96
93
|
runtime: this.name,
|
|
97
|
-
displayName: payload.name || basename(requestedCwd)
|
|
94
|
+
displayName: payload.name || (requestedCwd ? basename(requestedCwd) : 'pi'),
|
|
98
95
|
runtimeMeta: {
|
|
99
96
|
sessionId: null,
|
|
100
|
-
workdir: requestedCwd,
|
|
101
|
-
cwd: requestedCwd,
|
|
102
97
|
path: null,
|
|
103
98
|
runtimePath: piPath,
|
|
104
99
|
piPath,
|
|
@@ -117,16 +112,9 @@ export const piRuntime = {
|
|
|
117
112
|
if (!binding) return false;
|
|
118
113
|
const adapter = ctx.adapter;
|
|
119
114
|
const meta = binding.runtimeMeta || {};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
type: 'assistant',
|
|
124
|
-
text: `⚠️ pi cwd not found: ${meta.cwd || '(missing)'}`,
|
|
125
|
-
media: [],
|
|
126
|
-
replyToMessageId: inbound.messageId || null,
|
|
127
|
-
});
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
115
|
+
const agentHome = ensureAgentHome(binding.id, {
|
|
116
|
+
displayName: binding.display_name || binding.name || null,
|
|
117
|
+
});
|
|
130
118
|
|
|
131
119
|
let images = [];
|
|
132
120
|
let message = inbound.text || '';
|
|
@@ -161,7 +149,7 @@ export const piRuntime = {
|
|
|
161
149
|
binding,
|
|
162
150
|
agent: this.name,
|
|
163
151
|
sessionId: sessionId || meta.sessionId || binding.id,
|
|
164
|
-
cwd: cwd ||
|
|
152
|
+
cwd: cwd || agentHome,
|
|
165
153
|
replyToMessageId: inbound.messageId || null,
|
|
166
154
|
event: {
|
|
167
155
|
hook_event_name: 'worker.message.delta',
|
|
@@ -176,8 +164,15 @@ export const piRuntime = {
|
|
|
176
164
|
try {
|
|
177
165
|
const runtimePiPath = requirePiPath(meta.piPath || meta.runtimePath);
|
|
178
166
|
const runtimePiVersion = getPiRuntimeHealth(runtimePiPath).version || meta.piVersion || null;
|
|
167
|
+
const agentEnv = buildAgentRuntimeEnv({
|
|
168
|
+
agentId: binding.id,
|
|
169
|
+
sessionId: meta.sessionId,
|
|
170
|
+
hostId: binding.runtime_host_id,
|
|
171
|
+
});
|
|
172
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
179
173
|
const result = shouldStreamRuntime(this.name, this)
|
|
180
|
-
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd:
|
|
174
|
+
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
|
|
175
|
+
standingPrompt,
|
|
181
176
|
images,
|
|
182
177
|
onEvent: async (event) => {
|
|
183
178
|
if (event?.type === 'turn.started') {
|
|
@@ -186,7 +181,7 @@ export const piRuntime = {
|
|
|
186
181
|
binding,
|
|
187
182
|
agent: this.name,
|
|
188
183
|
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
189
|
-
cwd:
|
|
184
|
+
cwd: agentHome,
|
|
190
185
|
replyToMessageId: inbound.messageId || null,
|
|
191
186
|
event: {
|
|
192
187
|
hook_event_name: 'worker.turn.start',
|
|
@@ -197,17 +192,16 @@ export const piRuntime = {
|
|
|
197
192
|
} else if (event?.type === 'message.delta' && event.text) {
|
|
198
193
|
deltaAggregator.push(event.text, {
|
|
199
194
|
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
200
|
-
cwd:
|
|
195
|
+
cwd: agentHome,
|
|
201
196
|
});
|
|
202
197
|
}
|
|
203
198
|
},
|
|
204
199
|
})
|
|
205
|
-
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd:
|
|
200
|
+
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt });
|
|
206
201
|
|
|
207
202
|
await deltaAggregator.flush();
|
|
208
203
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
209
204
|
sessionId: result?.sessionId || meta.sessionId,
|
|
210
|
-
cwd: result?.cwd || meta.cwd,
|
|
211
205
|
path: result?.path || meta.path || null,
|
|
212
206
|
runtimePath: runtimePiPath,
|
|
213
207
|
piPath: runtimePiPath,
|
|
@@ -215,13 +209,13 @@ export const piRuntime = {
|
|
|
215
209
|
rotatePending: false,
|
|
216
210
|
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
217
211
|
}, { status: 'connected' });
|
|
218
|
-
await
|
|
212
|
+
await recordActivity(adapter, nextBinding, inbound, result);
|
|
219
213
|
await emitWorkerEvent({
|
|
220
214
|
adapter,
|
|
221
215
|
binding: nextBinding,
|
|
222
216
|
agent: this.name,
|
|
223
217
|
sessionId: result?.sessionId || meta.sessionId || binding.id,
|
|
224
|
-
cwd: result?.cwd ||
|
|
218
|
+
cwd: result?.cwd || agentHome,
|
|
225
219
|
replyToMessageId: inbound.messageId || null,
|
|
226
220
|
event: {
|
|
227
221
|
hook_event_name: 'Stop',
|
|
@@ -237,7 +231,7 @@ export const piRuntime = {
|
|
|
237
231
|
binding,
|
|
238
232
|
agent: this.name,
|
|
239
233
|
sessionId: meta.sessionId || binding.id,
|
|
240
|
-
cwd:
|
|
234
|
+
cwd: agentHome,
|
|
241
235
|
replyToMessageId: inbound.messageId || null,
|
|
242
236
|
event: {
|
|
243
237
|
hook_event_name: 'worker.turn.error',
|
|
@@ -269,7 +263,7 @@ export const piRuntime = {
|
|
|
269
263
|
binding,
|
|
270
264
|
agent: this.name,
|
|
271
265
|
sessionId: meta.sessionId || binding.id,
|
|
272
|
-
cwd:
|
|
266
|
+
cwd: ensureAgentHome(binding.id) || '',
|
|
273
267
|
event: {
|
|
274
268
|
hook_event_name: 'Stop',
|
|
275
269
|
worker_event_name: 'worker.turn.complete',
|
|
@@ -195,15 +195,21 @@ export function runPiPrompt({
|
|
|
195
195
|
message,
|
|
196
196
|
images = [],
|
|
197
197
|
piPath = null,
|
|
198
|
+
agentEnv = null,
|
|
199
|
+
standingPrompt = null,
|
|
198
200
|
timeoutMs = Number(process.env.PI_RUN_TIMEOUT_MS || DEFAULT_PI_RUN_TIMEOUT_MS),
|
|
199
201
|
onEvent,
|
|
200
202
|
}) {
|
|
203
|
+
// pi has no documented system-prompt flag — prepend on first turn.
|
|
204
|
+
const finalMessage = standingPrompt && !sessionId
|
|
205
|
+
? `${standingPrompt}\n\n---\n\n${message}`
|
|
206
|
+
: message;
|
|
201
207
|
return new Promise((resolve, reject) => {
|
|
202
208
|
const startedAt = Date.now();
|
|
203
209
|
const piCommand = requirePiPath(piPath);
|
|
204
210
|
const child = spawn(piCommand, buildPiRpcArgs({ sessionId }), {
|
|
205
211
|
cwd,
|
|
206
|
-
env: buildRuntimeEnv(),
|
|
212
|
+
env: buildRuntimeEnv(agentEnv || {}),
|
|
207
213
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
208
214
|
});
|
|
209
215
|
|
|
@@ -349,7 +355,7 @@ export function runPiPrompt({
|
|
|
349
355
|
|
|
350
356
|
(async () => {
|
|
351
357
|
try {
|
|
352
|
-
await send({ type: 'prompt', message, images });
|
|
358
|
+
await send({ type: 'prompt', message: finalMessage, images });
|
|
353
359
|
await completion;
|
|
354
360
|
const state = await send({ type: 'get_state' }).catch(() => null);
|
|
355
361
|
const lastAssistant = await send({ type: 'get_last_assistant_text' }).catch(() => null);
|
package/ticlawk.mjs
CHANGED
|
@@ -17,11 +17,11 @@ import {
|
|
|
17
17
|
AF_CONFIG_PATH,
|
|
18
18
|
AF_LOG_PATH,
|
|
19
19
|
AF_CRASH_LOG_PATH,
|
|
20
|
-
getConfiguredAdapter,
|
|
21
20
|
loadPersistentConfig,
|
|
22
21
|
persistConfig,
|
|
23
22
|
} from './src/core/config.mjs';
|
|
24
23
|
import { startLocalHttpServer } from './src/core/http.mjs';
|
|
24
|
+
import { startReminderTicker } from './src/core/reminder-ticker.mjs';
|
|
25
25
|
import { installProcessDiagnostics } from './src/core/diagnostics.mjs';
|
|
26
26
|
import * as logger from './src/core/logger.mjs';
|
|
27
27
|
import { Bus } from './src/core/bus.mjs';
|
|
@@ -31,6 +31,28 @@ import { buildRuntimeContext, normalizeServiceType } from './src/core/runtime-re
|
|
|
31
31
|
import { belongsToRuntimeHost, getBindingRuntimeHostId, getHostId } from './src/core/host-id.mjs';
|
|
32
32
|
import { readPkgVersion } from './src/core/update.mjs';
|
|
33
33
|
import { buildImageMessageFromInbound } from './src/core/media/inbound.mjs';
|
|
34
|
+
import { existsSync as _fsExists, unlinkSync as _fsUnlink } from 'node:fs';
|
|
35
|
+
import { join as _pathJoin } from 'node:path';
|
|
36
|
+
import { getActiveProfile } from './src/core/profiles.mjs';
|
|
37
|
+
|
|
38
|
+
// Pre-profile builds wrote bindings to ~/.ticlawk/bindings.json. The
|
|
39
|
+
// profile flow now owns binding persistence under
|
|
40
|
+
// ~/.ticlawk/profiles/<adapter>/<userId>/bindings.json. The root file
|
|
41
|
+
// hasn't had a writer for a while but lingers as stale state. Prune
|
|
42
|
+
// once per daemon start so it doesn't confuse `jq` audits later.
|
|
43
|
+
function pruneLegacyRootBindings() {
|
|
44
|
+
if (!getActiveProfile()) return;
|
|
45
|
+
const rootPath = _pathJoin(AF_HOME, 'bindings.json');
|
|
46
|
+
if (!_fsExists(rootPath)) return;
|
|
47
|
+
try {
|
|
48
|
+
_fsUnlink(rootPath);
|
|
49
|
+
logger.debugLog?.('startup', 'legacy-root-bindings.pruned', { path: rootPath });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.debugError?.('startup', 'legacy-root-bindings.prune-failed', {
|
|
52
|
+
path: rootPath, error: err?.message || String(err),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
34
56
|
|
|
35
57
|
// Re-export the config-owned paths so local tooling can inspect the
|
|
36
58
|
// ticlawk home/config/log locations without reaching into src/.
|
|
@@ -81,7 +103,7 @@ function createUpsertBindingWithSync(runtimes, adapter) {
|
|
|
81
103
|
};
|
|
82
104
|
}
|
|
83
105
|
|
|
84
|
-
function
|
|
106
|
+
function createPersistBinding(runtimes, getAdapter) {
|
|
85
107
|
return async (binding) => {
|
|
86
108
|
const nextBinding = await upsertBinding(binding);
|
|
87
109
|
if (!belongsToRuntimeHost(nextBinding)) {
|
|
@@ -102,13 +124,13 @@ function createCacheBinding(runtimes, getAdapter) {
|
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
126
|
|
|
105
|
-
function createBaseRuntimeCtx(runtimes,
|
|
127
|
+
function createBaseRuntimeCtx(runtimes, persistBinding, upsertBindingWithSync) {
|
|
106
128
|
return {
|
|
107
129
|
runtimes,
|
|
108
130
|
getBinding,
|
|
109
131
|
listBindings,
|
|
110
132
|
deleteBinding,
|
|
111
|
-
|
|
133
|
+
persistBinding,
|
|
112
134
|
upsertBinding: upsertBindingWithSync,
|
|
113
135
|
buildImageMessageFromInbound,
|
|
114
136
|
logger,
|
|
@@ -222,12 +244,12 @@ export async function startTiclawk() {
|
|
|
222
244
|
if (started) return;
|
|
223
245
|
started = true;
|
|
224
246
|
installProcessDiagnostics();
|
|
247
|
+
pruneLegacyRootBindings();
|
|
225
248
|
|
|
226
249
|
const { runtimeList, runtimes } = await buildRuntimeContext();
|
|
227
250
|
const resolveRuntimeBinding = createResolveRuntimeBinding(runtimes);
|
|
228
|
-
const configuredAdapter = getConfiguredAdapter();
|
|
229
251
|
let adapter;
|
|
230
|
-
const
|
|
252
|
+
const persistBinding = createPersistBinding(runtimes, () => adapter);
|
|
231
253
|
let baseRuntimeCtx;
|
|
232
254
|
let syncBinding = async (binding) => {
|
|
233
255
|
if (!adapter) {
|
|
@@ -235,13 +257,13 @@ export async function startTiclawk() {
|
|
|
235
257
|
}
|
|
236
258
|
return upsertBinding(binding);
|
|
237
259
|
};
|
|
238
|
-
baseRuntimeCtx = createBaseRuntimeCtx(runtimes,
|
|
260
|
+
baseRuntimeCtx = createBaseRuntimeCtx(runtimes, persistBinding, (binding) => syncBinding(binding));
|
|
239
261
|
adapter = createAdapter(
|
|
240
|
-
|
|
262
|
+
'ticlawk',
|
|
241
263
|
createAdapterContext(baseRuntimeCtx, resolveRuntimeBinding)
|
|
242
264
|
);
|
|
243
265
|
syncBinding = createUpsertBindingWithSync(runtimes, adapter);
|
|
244
|
-
baseRuntimeCtx = createBaseRuntimeCtx(runtimes,
|
|
266
|
+
baseRuntimeCtx = createBaseRuntimeCtx(runtimes, persistBinding, (binding) => syncBinding(binding));
|
|
245
267
|
|
|
246
268
|
printBanner(adapter);
|
|
247
269
|
if (typeof adapter.refreshBindings === 'function') {
|
|
@@ -249,7 +271,12 @@ export async function startTiclawk() {
|
|
|
249
271
|
}
|
|
250
272
|
registerRuntimeHandlers(runtimeList, baseRuntimeCtx, adapter);
|
|
251
273
|
await replayBindings(runtimes, adapter);
|
|
252
|
-
startLocalHttpServer({
|
|
274
|
+
startLocalHttpServer({
|
|
275
|
+
port: HTTP_PORT,
|
|
276
|
+
adapter,
|
|
277
|
+
ctx: { listBindings, getBinding },
|
|
278
|
+
});
|
|
279
|
+
startReminderTicker();
|
|
253
280
|
await recoverAllRuntimes(runtimeList, adapter);
|
|
254
281
|
await reconcileBindingsAfterRestart(runtimes, adapter);
|
|
255
282
|
await adapter.start();
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
<svg width="960" height="520" viewBox="0 0 960 520" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
|
2
|
-
<title id="title">ticlawk connects any client to any agent</title>
|
|
3
|
-
<desc id="desc">A hub-and-spoke diagram with clients on the left, ticlawk in the center, and agent runtimes on the right.</desc>
|
|
4
|
-
|
|
5
|
-
<defs>
|
|
6
|
-
<linearGradient id="hub" x1="372" y1="162" x2="588" y2="378" gradientUnits="userSpaceOnUse">
|
|
7
|
-
<stop stop-color="#1D4ED8"/>
|
|
8
|
-
<stop offset="0.52" stop-color="#0F766E"/>
|
|
9
|
-
<stop offset="1" stop-color="#7C2D12"/>
|
|
10
|
-
</linearGradient>
|
|
11
|
-
<linearGradient id="clientLine" x1="180" y1="72" x2="460" y2="260" gradientUnits="userSpaceOnUse">
|
|
12
|
-
<stop stop-color="#2563EB"/>
|
|
13
|
-
<stop offset="1" stop-color="#0F766E"/>
|
|
14
|
-
</linearGradient>
|
|
15
|
-
<linearGradient id="agentLine" x1="500" y1="260" x2="780" y2="448" gradientUnits="userSpaceOnUse">
|
|
16
|
-
<stop stop-color="#0F766E"/>
|
|
17
|
-
<stop offset="1" stop-color="#C2410C"/>
|
|
18
|
-
</linearGradient>
|
|
19
|
-
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
|
20
|
-
<feDropShadow dx="0" dy="10" stdDeviation="16" flood-color="#0F172A" flood-opacity="0.12"/>
|
|
21
|
-
</filter>
|
|
22
|
-
<style>
|
|
23
|
-
.label { fill: #0f172a; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 700; font-size: 16px; }
|
|
24
|
-
.small { fill: #475569; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 600; font-size: 13px; }
|
|
25
|
-
.hubText { fill: white; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 800; font-size: 27px; }
|
|
26
|
-
.hubSub { fill: #E0F2FE; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 650; font-size: 13px; letter-spacing: 0.08em; }
|
|
27
|
-
.caption { fill: #334155; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 650; font-size: 14px; }
|
|
28
|
-
.node { fill: #FFFFFF; stroke: #CBD5E1; stroke-width: 1.4; }
|
|
29
|
-
.dot { fill: #F8FAFC; stroke: #CBD5E1; stroke-width: 1.4; }
|
|
30
|
-
</style>
|
|
31
|
-
</defs>
|
|
32
|
-
|
|
33
|
-
<rect width="960" height="520" rx="22" fill="#F8FAFC"/>
|
|
34
|
-
<rect x="24" y="24" width="912" height="472" rx="18" fill="#FFFFFF" stroke="#E2E8F0"/>
|
|
35
|
-
|
|
36
|
-
<text x="180" y="64" text-anchor="middle" class="caption">Any client surface</text>
|
|
37
|
-
<text x="780" y="64" text-anchor="middle" class="caption">Any agent runtime</text>
|
|
38
|
-
|
|
39
|
-
<path d="M230 116 C310 126 350 166 405 215" stroke="url(#clientLine)" stroke-width="4" stroke-linecap="round"/>
|
|
40
|
-
<path d="M230 188 C310 192 354 216 405 244" stroke="url(#clientLine)" stroke-width="4" stroke-linecap="round"/>
|
|
41
|
-
<path d="M230 260 C306 260 348 260 405 260" stroke="url(#clientLine)" stroke-width="4" stroke-linecap="round"/>
|
|
42
|
-
<path d="M230 332 C310 328 354 304 405 276" stroke="url(#clientLine)" stroke-width="4" stroke-linecap="round"/>
|
|
43
|
-
<path d="M230 404 C310 394 350 354 405 305" stroke="url(#clientLine)" stroke-width="4" stroke-linecap="round"/>
|
|
44
|
-
|
|
45
|
-
<path d="M555 215 C610 166 650 126 730 116" stroke="url(#agentLine)" stroke-width="4" stroke-linecap="round"/>
|
|
46
|
-
<path d="M555 244 C606 216 650 192 730 188" stroke="url(#agentLine)" stroke-width="4" stroke-linecap="round"/>
|
|
47
|
-
<path d="M555 260 C612 260 654 260 730 260" stroke="url(#agentLine)" stroke-width="4" stroke-linecap="round"/>
|
|
48
|
-
<path d="M555 276 C606 304 650 328 730 332" stroke="url(#agentLine)" stroke-width="4" stroke-linecap="round"/>
|
|
49
|
-
<path d="M555 305 C610 354 650 394 730 404" stroke="url(#agentLine)" stroke-width="4" stroke-linecap="round"/>
|
|
50
|
-
|
|
51
|
-
<g filter="url(#shadow)">
|
|
52
|
-
<rect x="70" y="88" width="160" height="56" rx="8" class="node"/>
|
|
53
|
-
<circle cx="100" cy="116" r="14" fill="#DBEAFE"/>
|
|
54
|
-
<path d="M94 116h12M100 110v12" stroke="#2563EB" stroke-width="2.4" stroke-linecap="round"/>
|
|
55
|
-
<text x="124" y="112" class="label">Telegram</text>
|
|
56
|
-
<text x="124" y="130" class="small">chat bot</text>
|
|
57
|
-
|
|
58
|
-
<rect x="70" y="160" width="160" height="56" rx="8" class="node"/>
|
|
59
|
-
<circle cx="100" cy="188" r="14" fill="#CCFBF1"/>
|
|
60
|
-
<rect x="94" y="180" width="12" height="16" rx="3" stroke="#0F766E" stroke-width="2.2"/>
|
|
61
|
-
<path d="M98 202h4" stroke="#0F766E" stroke-width="2.2" stroke-linecap="round"/>
|
|
62
|
-
<text x="124" y="184" class="label">ticlawk</text>
|
|
63
|
-
<text x="124" y="202" class="small">mobile app</text>
|
|
64
|
-
|
|
65
|
-
<rect x="70" y="232" width="160" height="56" rx="8" class="node"/>
|
|
66
|
-
<circle cx="100" cy="260" r="14" fill="#FDE68A"/>
|
|
67
|
-
<path d="M92 256h16M92 262h16M100 252v16" stroke="#A16207" stroke-width="2" stroke-linecap="round"/>
|
|
68
|
-
<text x="124" y="256" class="label">Web UI</text>
|
|
69
|
-
<text x="124" y="274" class="small">browser</text>
|
|
70
|
-
|
|
71
|
-
<rect x="70" y="304" width="160" height="56" rx="8" class="node"/>
|
|
72
|
-
<circle cx="100" cy="332" r="14" fill="#EDE9FE"/>
|
|
73
|
-
<path d="M94 326l6 6-6 6M102 338h6" stroke="#6D28D9" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
74
|
-
<text x="124" y="328" class="label">CLI</text>
|
|
75
|
-
<text x="124" y="346" class="small">terminal</text>
|
|
76
|
-
|
|
77
|
-
<rect x="70" y="376" width="160" height="56" rx="8" class="node"/>
|
|
78
|
-
<circle cx="100" cy="404" r="14" fill="#FFE4E6"/>
|
|
79
|
-
<path d="M94 398h12v12H94z" stroke="#BE123C" stroke-width="2.2" stroke-linejoin="round"/>
|
|
80
|
-
<path d="M97 395h6M97 413h6" stroke="#BE123C" stroke-width="2.2" stroke-linecap="round"/>
|
|
81
|
-
<text x="124" y="400" class="label">Custom app</text>
|
|
82
|
-
<text x="124" y="418" class="small">your surface</text>
|
|
83
|
-
</g>
|
|
84
|
-
|
|
85
|
-
<g filter="url(#shadow)">
|
|
86
|
-
<rect x="730" y="88" width="160" height="56" rx="8" class="node"/>
|
|
87
|
-
<circle cx="760" cy="116" r="14" fill="#FEF3C7"/>
|
|
88
|
-
<path d="M753 121l7-12 7 12M756 117h8" stroke="#B45309" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
89
|
-
<text x="784" y="112" class="label">Claude Code</text>
|
|
90
|
-
<text x="784" y="130" class="small">project agent</text>
|
|
91
|
-
|
|
92
|
-
<rect x="730" y="160" width="160" height="56" rx="8" class="node"/>
|
|
93
|
-
<circle cx="760" cy="188" r="14" fill="#DBEAFE"/>
|
|
94
|
-
<path d="M754 188a6 6 0 1 1 12 0a6 6 0 1 1-12 0" stroke="#1D4ED8" stroke-width="2.2"/>
|
|
95
|
-
<path d="M760 176v4M760 196v4M748 188h4M768 188h4" stroke="#1D4ED8" stroke-width="2.2" stroke-linecap="round"/>
|
|
96
|
-
<text x="784" y="184" class="label">Codex</text>
|
|
97
|
-
<text x="784" y="202" class="small">coding agent</text>
|
|
98
|
-
|
|
99
|
-
<rect x="730" y="232" width="160" height="56" rx="8" class="node"/>
|
|
100
|
-
<circle cx="760" cy="260" r="14" fill="#CCFBF1"/>
|
|
101
|
-
<path d="M752 260h16M760 252v16M755 255l10 10M765 255l-10 10" stroke="#0F766E" stroke-width="2" stroke-linecap="round"/>
|
|
102
|
-
<text x="784" y="256" class="label">OpenClaw</text>
|
|
103
|
-
<text x="784" y="274" class="small">local runtime</text>
|
|
104
|
-
|
|
105
|
-
<rect x="730" y="304" width="160" height="56" rx="8" class="node"/>
|
|
106
|
-
<circle cx="760" cy="332" r="14" fill="#FCE7F3"/>
|
|
107
|
-
<path d="M752 332c3-8 13-8 16 0c-3 8-13 8-16 0Z" stroke="#BE185D" stroke-width="2.2"/>
|
|
108
|
-
<circle cx="760" cy="332" r="3" fill="#BE185D"/>
|
|
109
|
-
<text x="784" y="328" class="label">opencode</text>
|
|
110
|
-
<text x="784" y="346" class="small">provider agnostic</text>
|
|
111
|
-
|
|
112
|
-
<rect x="730" y="376" width="160" height="56" rx="8" class="node"/>
|
|
113
|
-
<circle cx="760" cy="404" r="14" fill="#E0E7FF"/>
|
|
114
|
-
<path d="M754 398h12v12h-12zM751 401h3M766 401h3M751 407h3M766 407h3" stroke="#4338CA" stroke-width="2" stroke-linejoin="round"/>
|
|
115
|
-
<text x="784" y="400" class="label">Custom agent</text>
|
|
116
|
-
<text x="784" y="418" class="small">your harness</text>
|
|
117
|
-
</g>
|
|
118
|
-
|
|
119
|
-
<g filter="url(#shadow)">
|
|
120
|
-
<circle cx="480" cy="260" r="118" fill="url(#hub)"/>
|
|
121
|
-
<circle cx="480" cy="260" r="86" fill="#FFFFFF" fill-opacity="0.12" stroke="#FFFFFF" stroke-opacity="0.32" stroke-width="1.5"/>
|
|
122
|
-
<circle cx="480" cy="260" r="52" fill="#FFFFFF" fill-opacity="0.16" stroke="#FFFFFF" stroke-opacity="0.38" stroke-width="1.4"/>
|
|
123
|
-
|
|
124
|
-
<circle cx="480" cy="176" r="8" class="dot"/>
|
|
125
|
-
<circle cx="548" cy="212" r="8" class="dot"/>
|
|
126
|
-
<circle cx="548" cy="308" r="8" class="dot"/>
|
|
127
|
-
<circle cx="480" cy="344" r="8" class="dot"/>
|
|
128
|
-
<circle cx="412" cy="308" r="8" class="dot"/>
|
|
129
|
-
<circle cx="412" cy="212" r="8" class="dot"/>
|
|
130
|
-
<path d="M480 184v152M420 216l120 88M540 216l-120 88" stroke="#FFFFFF" stroke-opacity="0.65" stroke-width="2.6" stroke-linecap="round"/>
|
|
131
|
-
|
|
132
|
-
<text x="480" y="253" text-anchor="middle" class="hubText">ticlawk</text>
|
|
133
|
-
<text x="480" y="281" text-anchor="middle" class="hubSub">UNIVERSAL ROUTER</text>
|
|
134
|
-
</g>
|
|
135
|
-
|
|
136
|
-
<text x="480" y="465" text-anchor="middle" class="small">Bind sessions, relay streams, keep every side replaceable.</text>
|
|
137
|
-
</svg>
|