ticlawk 0.1.15-dev.6 → 0.1.16-dev.1
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 +83 -212
- package/bin/ticlawk.mjs +107 -46
- package/package.json +2 -5
- package/src/adapters/ticlawk/api.mjs +93 -26
- package/src/adapters/ticlawk/credentials.mjs +1 -2
- package/src/adapters/ticlawk/index.mjs +78 -32
- package/src/cli/agent-commands.mjs +290 -0
- package/src/core/adapter-registry.mjs +12 -28
- package/src/core/agent-cli-handlers.mjs +291 -0
- package/src/core/config.mjs +0 -15
- package/src/core/http.mjs +90 -18
- package/src/core/runtime-env.mjs +41 -5
- package/src/core/runtime-support.mjs +16 -1
- package/src/core/ticlawk-control.mjs +4 -3
- package/src/runtimes/_shared/standing-prompt.mjs +89 -0
- package/src/runtimes/claude-code/index.mjs +26 -7
- package/src/runtimes/claude-code/session.mjs +15 -7
- package/src/runtimes/codex/index.mjs +18 -6
- package/src/runtimes/codex/session.mjs +9 -5
- package/src/runtimes/openclaw/index.mjs +22 -3
- package/src/runtimes/opencode/index.mjs +19 -6
- package/src/runtimes/opencode/session.mjs +11 -2
- package/src/runtimes/pi/index.mjs +19 -6
- package/src/runtimes/pi/session.mjs +8 -2
- package/ticlawk.mjs +6 -4
- package/assets/ticlawk-concept.svg +0 -137
- package/src/adapters/telegram/index.mjs +0 -359
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync } from 'node:fs';
|
|
11
11
|
import { basename } from 'node:path';
|
|
12
|
+
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
13
|
+
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
12
14
|
import {
|
|
13
15
|
createOpenCodeSession,
|
|
14
16
|
getOpenCodeRuntimeHealth,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
shouldStreamRuntime,
|
|
28
30
|
createDeltaAggregator,
|
|
29
31
|
sendAdapterMessage,
|
|
30
|
-
|
|
32
|
+
recordActivity,
|
|
31
33
|
reportSubprocessFailure,
|
|
32
34
|
terminalRuntimeFailure,
|
|
33
35
|
updateBindingRuntimeMeta,
|
|
@@ -40,23 +42,27 @@ export const openCodeRuntime = {
|
|
|
40
42
|
return createOpenCodeSession({ cwd, message: text, opencodePath });
|
|
41
43
|
},
|
|
42
44
|
|
|
43
|
-
runTurn({ sessionId, cwd, opencodePath }, text, opts = {}) {
|
|
45
|
+
runTurn({ sessionId, cwd, opencodePath, agentEnv }, text, opts = {}) {
|
|
44
46
|
return runOpenCodePrompt({
|
|
45
47
|
sessionId,
|
|
46
48
|
cwd,
|
|
47
49
|
message: text,
|
|
48
50
|
opencodePath,
|
|
51
|
+
agentEnv,
|
|
52
|
+
standingPrompt: opts.standingPrompt || null,
|
|
49
53
|
files: opts.files,
|
|
50
54
|
timeoutMs: opts.timeoutMs,
|
|
51
55
|
});
|
|
52
56
|
},
|
|
53
57
|
|
|
54
|
-
runTurnStream({ sessionId, cwd, opencodePath }, text, opts = {}) {
|
|
58
|
+
runTurnStream({ sessionId, cwd, opencodePath, agentEnv }, text, opts = {}) {
|
|
55
59
|
return streamOpenCodePrompt({
|
|
56
60
|
sessionId,
|
|
57
61
|
cwd,
|
|
58
62
|
message: text,
|
|
59
63
|
opencodePath,
|
|
64
|
+
agentEnv,
|
|
65
|
+
standingPrompt: opts.standingPrompt || null,
|
|
60
66
|
files: opts.files,
|
|
61
67
|
timeoutMs: opts.timeoutMs,
|
|
62
68
|
onEvent: opts.onEvent,
|
|
@@ -204,8 +210,15 @@ export const openCodeRuntime = {
|
|
|
204
210
|
try {
|
|
205
211
|
const opencodePath = requireOpenCodePath(runtimeOpenCodePath);
|
|
206
212
|
const opencodeVersion = getOpenCodeRuntimeHealth(opencodePath).version || meta.opencodeVersion || null;
|
|
213
|
+
const agentEnv = buildAgentRuntimeEnv({
|
|
214
|
+
agentId: binding.id,
|
|
215
|
+
sessionId: meta.sessionId,
|
|
216
|
+
hostId: binding.runtime_host_id,
|
|
217
|
+
});
|
|
218
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
207
219
|
const result = shouldStreamRuntime(this.name, this)
|
|
208
|
-
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, opencodePath }, message, {
|
|
220
|
+
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, opencodePath, agentEnv }, message, {
|
|
221
|
+
standingPrompt,
|
|
209
222
|
files,
|
|
210
223
|
onEvent: async (event) => {
|
|
211
224
|
if (event?.type === 'turn.started') {
|
|
@@ -230,7 +243,7 @@ export const openCodeRuntime = {
|
|
|
230
243
|
}
|
|
231
244
|
},
|
|
232
245
|
})
|
|
233
|
-
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, opencodePath }, message, { files });
|
|
246
|
+
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, opencodePath, agentEnv }, message, { files, standingPrompt });
|
|
234
247
|
|
|
235
248
|
await deltaAggregator.flush();
|
|
236
249
|
|
|
@@ -243,7 +256,7 @@ export const openCodeRuntime = {
|
|
|
243
256
|
rotatePending: false,
|
|
244
257
|
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
245
258
|
}, { status: 'connected' });
|
|
246
|
-
await
|
|
259
|
+
await recordActivity(adapter, nextBinding, inbound, {
|
|
247
260
|
...result,
|
|
248
261
|
media: normalizeOutboundMedia(result),
|
|
249
262
|
});
|
|
@@ -208,15 +208,24 @@ export function runOpenCodePrompt({
|
|
|
208
208
|
message,
|
|
209
209
|
files = [],
|
|
210
210
|
opencodePath = null,
|
|
211
|
+
agentEnv = null,
|
|
212
|
+
standingPrompt = null,
|
|
211
213
|
timeoutMs = Number(process.env.OPENCODE_RUN_TIMEOUT_MS || DEFAULT_OPENCODE_RUN_TIMEOUT_MS),
|
|
212
214
|
onEvent,
|
|
213
215
|
}) {
|
|
216
|
+
// opencode has no documented `--system` flag, so we prepend the
|
|
217
|
+
// standing prompt to the first-turn message body. Resumed sessions
|
|
218
|
+
// (sessionId set) skip injection because the model already saw it
|
|
219
|
+
// on the originating turn.
|
|
220
|
+
const finalMessage = standingPrompt && !sessionId
|
|
221
|
+
? `${standingPrompt}\n\n---\n\n${message}`
|
|
222
|
+
: message;
|
|
214
223
|
return new Promise((resolve, reject) => {
|
|
215
224
|
const startedAt = Date.now();
|
|
216
225
|
const opencodeCommand = requireOpenCodePath(opencodePath);
|
|
217
|
-
const child = spawn(opencodeCommand, buildOpenCodeRunArgs({ sessionId, message, files }), {
|
|
226
|
+
const child = spawn(opencodeCommand, buildOpenCodeRunArgs({ sessionId, message: finalMessage, files }), {
|
|
218
227
|
cwd,
|
|
219
|
-
env: buildRuntimeEnv(),
|
|
228
|
+
env: buildRuntimeEnv(agentEnv || {}),
|
|
220
229
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
221
230
|
});
|
|
222
231
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { basename } from 'node:path';
|
|
9
|
+
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
10
|
+
import { buildStandingPrompt } from '../_shared/standing-prompt.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
|
});
|
|
@@ -176,8 +182,15 @@ export const piRuntime = {
|
|
|
176
182
|
try {
|
|
177
183
|
const runtimePiPath = requirePiPath(meta.piPath || meta.runtimePath);
|
|
178
184
|
const runtimePiVersion = getPiRuntimeHealth(runtimePiPath).version || meta.piVersion || null;
|
|
185
|
+
const agentEnv = buildAgentRuntimeEnv({
|
|
186
|
+
agentId: binding.id,
|
|
187
|
+
sessionId: meta.sessionId,
|
|
188
|
+
hostId: binding.runtime_host_id,
|
|
189
|
+
});
|
|
190
|
+
const standingPrompt = buildStandingPrompt({ agentId: binding.id });
|
|
179
191
|
const result = shouldStreamRuntime(this.name, this)
|
|
180
|
-
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, piPath: runtimePiPath }, message, {
|
|
192
|
+
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, piPath: runtimePiPath, agentEnv }, message, {
|
|
193
|
+
standingPrompt,
|
|
181
194
|
images,
|
|
182
195
|
onEvent: async (event) => {
|
|
183
196
|
if (event?.type === 'turn.started') {
|
|
@@ -202,7 +215,7 @@ export const piRuntime = {
|
|
|
202
215
|
}
|
|
203
216
|
},
|
|
204
217
|
})
|
|
205
|
-
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, piPath: runtimePiPath }, message, { images });
|
|
218
|
+
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: meta.cwd, piPath: runtimePiPath, agentEnv }, message, { images, standingPrompt });
|
|
206
219
|
|
|
207
220
|
await deltaAggregator.flush();
|
|
208
221
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
@@ -215,7 +228,7 @@ export const piRuntime = {
|
|
|
215
228
|
rotatePending: false,
|
|
216
229
|
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
217
230
|
}, { status: 'connected' });
|
|
218
|
-
await
|
|
231
|
+
await recordActivity(adapter, nextBinding, inbound, result);
|
|
219
232
|
await emitWorkerEvent({
|
|
220
233
|
adapter,
|
|
221
234
|
binding: nextBinding,
|
|
@@ -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,7 +17,6 @@ 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';
|
|
@@ -225,7 +224,6 @@ export async function startTiclawk() {
|
|
|
225
224
|
|
|
226
225
|
const { runtimeList, runtimes } = await buildRuntimeContext();
|
|
227
226
|
const resolveRuntimeBinding = createResolveRuntimeBinding(runtimes);
|
|
228
|
-
const configuredAdapter = getConfiguredAdapter();
|
|
229
227
|
let adapter;
|
|
230
228
|
const cacheBinding = createCacheBinding(runtimes, () => adapter);
|
|
231
229
|
let baseRuntimeCtx;
|
|
@@ -237,7 +235,7 @@ export async function startTiclawk() {
|
|
|
237
235
|
};
|
|
238
236
|
baseRuntimeCtx = createBaseRuntimeCtx(runtimes, cacheBinding, (binding) => syncBinding(binding));
|
|
239
237
|
adapter = createAdapter(
|
|
240
|
-
|
|
238
|
+
'ticlawk',
|
|
241
239
|
createAdapterContext(baseRuntimeCtx, resolveRuntimeBinding)
|
|
242
240
|
);
|
|
243
241
|
syncBinding = createUpsertBindingWithSync(runtimes, adapter);
|
|
@@ -249,7 +247,11 @@ export async function startTiclawk() {
|
|
|
249
247
|
}
|
|
250
248
|
registerRuntimeHandlers(runtimeList, baseRuntimeCtx, adapter);
|
|
251
249
|
await replayBindings(runtimes, adapter);
|
|
252
|
-
startLocalHttpServer({
|
|
250
|
+
startLocalHttpServer({
|
|
251
|
+
port: HTTP_PORT,
|
|
252
|
+
adapter,
|
|
253
|
+
ctx: { listBindings, getBinding },
|
|
254
|
+
});
|
|
253
255
|
await recoverAllRuntimes(runtimeList, adapter);
|
|
254
256
|
await reconcileBindingsAfterRestart(runtimes, adapter);
|
|
255
257
|
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>
|