wyrd/tools
esc

    ↑↓ Move ↵ Open t3c results first Search by Pagefind
    Releases

    Files, repositories and server settings

    Files, repositories and server settings

    Use the typed routes and the server, repository and terminal methods.

    HTTP routes

    client.routes.call validates the input, fills the path and decodes the answer:

    const snapshot = await client.routes.call("orchestration.threadBoundedSnapshot", {
      params: { threadId },
      query: { deferWork: "1" },
    });
    const project = await client.routes.call("projects.mutate", { body: mutation });
    const ticket = await client.routes.call("auth.webSocketTicketFor", { params: { use: "media" } });

    Files

    Upload through a signed URL, and read through another. The token in a signed URL is the credential, so treat a relativeUrl as a secret until it expires:

    import { assetUrlParams, attachmentUploadUrlParams } from "@wyrd-company/t3code-lib";
    
    const upload = await client.rpc.call("attachments.createUploadUrl", {
      type: "file",
      name: "notes.txt",
      mimeType: "text/plain",
      sizeBytes: body.byteLength,
    });
    await client.routes.call("attachments.upload", {
      params: attachmentUploadUrlParams(upload.relativeUrl),
      body,
    });
    
    const { relativeUrl } = await client.rpc.call("assets.createUrl", {
      resource: { _tag: "attachment", attachmentId: upload.attachmentId },
    });
    const { bytes, contentType } = await client.routes.call("assets.read", {
      params: assetUrlParams(relativeUrl),
    });

    assets.read holds the whole file in memory. To read part of a large file, fetch relativeUrl yourself with a range header.

    Voice

    The voice.* routes transcribe on the server machine with a local speech model, or forward a recording to a cloud speech provider. voice.status says if the machine can transcribe and which model is selected. voice.models lists the catalog; voice.downloadModel and voice.selectModel need settings:write. When voice.status reports supportsModelLinking, voice.linkModels uses a model file, or the model files of a folder, from elsewhere on the server’s disk. It needs settings:write and filesystem:read, and a model’s linkedFrom names its file only to a session with filesystem:read. A transcription body is up to five minutes of 16 kHz mono Float32 PCM, framed with its recognition options:

    import {
      DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS,
      encodeSpeechPcmRequest,
    } from "@wyrd-company/t3code-lib";
    
    const status = await client.routes.call("voice.status");
    if (status.supported && status.state === "ready") {
      const { text } = await client.routes.call("voice.transcribe", {
        body: encodeSpeechPcmRequest(new Uint8Array(samples.buffer), {
          ...DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS,
          speechLanguage: "en",
        }),
      });
    }

    voice.transcribeCloud sends the recording to the provider in params. Set the provider’s key first with voice.setCloudKey, as a literal or a secret reference; key: null removes it. A cloud failure is a T3HttpError with status 422 and tag CloudSpeechError; schemas.speech.CloudSpeechError decodes its body. A local failure has reason invalid_audio, speech_unavailable or speech_busy.

    T3Client.openSpeechStream transcribes while the audio arrives, over its own socket. It needs orchestration:operate. Send at most one second of PCM in each sendAudio call; each call gets an update with the text so far. finish asks for the final text. An event type that this library does not know is an unknown variant:

    import { schemas } from "@wyrd-company/t3code-lib";
    
    const stream = await client.openSpeechStream({
      ...DEFAULT_SPEECH_TRANSCRIPTION_OPTIONS,
      speechLanguage: "en",
    });
    for (const chunk of chunks) stream.sendAudio(chunk);
    stream.finish();
    for await (const item of stream) {
      if (item.kind !== "item" || schemas.common.isUnknownVariant<"type">(item.value)) continue;
      if (item.value.type === "update") console.log(item.value.text?.committed);
      if (item.value.type === "finished") console.log(item.value.text);
      if (item.value.type === "error") console.error(item.value.message);
    }

    The server closes the socket after finished or error. stream.closed gives the close code: 1000 after finished, 1008 after error.

    Repositories and terminals

    git.runStackedAction streams progress until the action finishes or fails. review.* reads only inside the server’s workspace root or a known workspace:

    import { schemas } from "@wyrd-company/t3code-lib";
    
    for await (const item of client.rpc.stream("git.runStackedAction", {
      actionId,
      cwd,
      action: "commit_push",
      commitMessage: "Update the notes",
    })) {
      if (item.kind !== "item" || schemas.common.isUnknownVariant<"kind">(item.value)) continue;
      if (item.value.kind === "action_finished") console.log(item.value.result.push.status);
    }
    
    const { commits } = await client.rpc.call("review.getBranchCommits", { cwd, baseRef: "main" });
    const { sources } = await client.rpc.call("review.getDiffPreview", { cwd, baseRef: "main" });

    subscribeVcsBranch streams the checked-out branch of cwd as { refName }, null when HEAD is detached. It sends the current branch first and then each change. vcs.refreshBranch reads the branch again and publishes a change to the open streams. Both need orchestration:read.

    terminal.observe follows a terminal with only terminal:read, and it cannot start one. subscribeTerminalMetadata lists every terminal and then follows them. client.terminal.observe and client.terminal.watchMetadata wrap these streams. client.terminal.read returns one terminal’s snapshot with its retained history, and client.terminal.list returns every terminal; both close their stream after the first item.

    Settings

    server.updateSettings takes a patch of only the keys to change, and answers with the whole settings. It needs settings:write, providers:manage for the provider keys and providerInstanceMutation, and both for a patch that has both:

    await client.rpc.call("server.updateSettings", { patch: { idleCompaction: "off" } });
    
    await client.rpc.call("server.updateSettings", {
      patch: {},
      providerInstanceMutation: { operation: "remove", instanceId },
    });
    
    const { keybindings, issues } = await client.rpc.call("server.upsertKeybinding", {
      key: "mod+shift+u",
      command: "usage.open",
    });

    A patch’s lists are closed, so a mistyped value fails before the call. The answer keeps loose fields. server.updateProvider runs the provider’s installer on the server host. secrets.answerRequest saves or declines an agent’s secret request: the server keeps the value, and the thread learns only the status.

    A credential setting, such as a usage limit source’s managementKey or an EAS host’s accessToken, takes either the literal or a secret provider reference (schemas.providerInstance.SettingsSecretValue). The client tells the server it reads references, so settings show a reference as written:

    await client.rpc.call("server.updateSettings", {
      patch: {
        hiddenThreadTags: ["sample-tag"],
        hideTaggedThreads: true,
        usageLimitSources: {
          sample: {
            kind: "cliproxy",
            url: "http://127.0.0.1:8317",
            managementKey: { provider: "sample", reference: "op://vault/item/field" },
          },
        },
      },
    });

    Managed policy

    An administrator can put a policy file on the server’s machine. The server then sends managedPolicy in server.getConfig: the organization, the settings that users cannot change and the limits that the server enforces. schemas.managedPolicy holds its shapes. The summary carries no setting values.

    const { managedPolicy } = await client.server.getConfig();
    if (managedPolicy?.limits?.maxRuntimeMode) {
      // Create threads at or below this runtime mode.
    }

    A patch that changes a locked setting fails with a ServerSettingsError whose operation is write-managed-setting and whose managedSetting is the locked path. A provider that the policy does not allow fails with use-disallowed-provider. A scheduled task that the policy stops has a blockedByPolicy reason. When the policy does not allow remote clients, auth.createPairingCredential fails with a 403 EnvironmentHttpForbiddenError.

    Diagnostics

    The server reads its traces, its process tree and its host. Most of these reads need diagnostics:read. A failed read is part of the result: error is an Option record and the result keeps what the server could read.

    client.diagnostics wraps the reads: traces, processes, hostResources, resourceHistory and storageCleanupReport. The last one returns the first item of server.getStorageCleanupReport, null before the first cleanup, and closes the stream. The facade has no method that signals, cleans up or restarts.

    const { processes, error } = await client.diagnostics.processes();
    if (error._tag === "Some") console.warn(error.value.message);
    
    const history = await client.diagnostics.resourceHistory({
      windowMs: 60_000,
      bucketMs: 10_000,
    });
    
    for await (const item of client.rpc.stream("subscribeResourceTelemetry", {})) {
      if (item.kind === "item") console.log(item.value.groups.allT3.currentRssBytes);
    }

    client.localServers reads the local servers that the server’s preview discovery finds on its host: the listeners that it attributes to a thread terminal (terminal) or to the agent of a thread (agent), of every thread, plus each configured URL that answers as a web server. list returns one fresh scan and watch follows changes; both need orchestration:read. stop({ pid, port }) needs terminal:operate: the server scans again, then sends SIGTERM and later SIGKILL to the process group of a listener that it attributes to a thread, and fails with a PreviewStopLocalServerError otherwise.

    const { servers } = await client.localServers.list({
      configuredUrls: ["http://127.0.0.1:5173/"],
    });
    const own = servers.find((server) => server.agent?.threadId === "thread-1");
    if (own?.pid != null) await client.localServers.stop({ pid: own.pid, port: own.port });

    server.signalProcess sends SIGINT or SIGKILL to a process of server.getProcessDiagnostics, identified by its pid and startTimeMs. It needs environment:maintain. A refusal is a result: signaled is false and message says why. server.runStorageCleanup needs settings:write and removes what the storageCleanup settings allow on the server host. server.checkOtlpEndpoint also needs settings:write: the server sends an empty export to the URL, and the answer is Accepted, Rejected or Unreachable. server.retryResourceTelemetry needs diagnostics:read and environment:maintain.

    server.getDatabaseStorage reports canReclaim when a supervisor, such as the desktop app or the background service, can restart the server. Then server.reclaimDatabaseSpace (environment:maintain) restarts the server to return free database pages to disk, and the connection closes. Otherwise it fails with DatabaseReclaimError, whose reason names the offline command to run instead. A failed repair shows as reclaimFailure on the next status.

    server.listTextGeneratorModels (settings:write) lists the models of a text generator: a saved one by id, or unsaved settings in config. test: true also sends one short prompt and returns the testedModel. Both spend the API key. A failure is a TextGeneratorError with a detail.

    const { models } = await client.rpc.call("server.listTextGeneratorModels", {
      id: schemas.common.providerInstanceId("sample-generator"),
      config: { kind: "openaiCompatible", baseUrl: "https://models.example.invalid/v1" },
    });